닷넷 7.0 이전 버전에서는 대부분 외부 DLL 파일을 Import 하거나 P/Invoke1를 활용하여 Native DLL(Unmanaged DLL)에 있는 함수를 호출하기 위해 DllImport
를 사용해 왔다. 8.0버전부터는 LibraryImport
의 사용을 권장한다.
LibraryImport가 새로 생겨난 이유는 DllImport가 마샬링을 런타임에서 수행해서 IL 코드를 emit 한다고 하는데 NativeAOT 등 동적으로 IL 코드를 생성할 수 없는 환경에서 쓸 수 없으므로 LibraryImport의 소스 생성기 기능을 이용해 컴파일 시점에서 마샬링 코드를 삽입한다.2
기존에 작성했던 DllImport에서 LibraryImport 스타일로 변경할 때 몇 가지 변경 사항이 있는데 이 부분을 간단한 예제를 통하여 정리하였다. ~.csproj
를 열어 <PropertyGroup>
사이에<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
를 추가한다.
[LibraryImport("user32.DLL", EntryPoint = "ReleaseCapture", StringMarshalling = StringMarshalling.Utf16)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvStdcall)])]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool ReleaseCapture();
[LibraryImport("user32.DLL", EntryPoint = "SendMessageW", StringMarshalling = StringMarshalling.Utf16)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvStdcall)])]
[return: MarshalAs(UnmanagedType.SysInt)]
private static partial IntPtr SendMessageW(IntPtr hWnd, uint wMsg, IntPtr wParam, IntPtr lParam);
[LibraryImport("USER32.DLL", EntryPoint = "FindWindowW", StringMarshalling = StringMarshalling.Utf16)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvStdcall)])]
[return: MarshalAs(UnmanagedType.SysInt)]
private static partial IntPtr FindWindowW(string? lpClassName, string? lpWindowName);
public static void DragMoveWindow(string windowTitle)
{
// MainWindow? mainWindow = Application.Current.MainWindow as MainWindow;
// _ = ReleaseCapture();
// _ = SendMessageW(mainWindow == null ? Process.GetCurrentProcess().MainWindowHandle : FindWindowW(null, mainWindow.Title),
// 0x112, 0xf012, 0);
_ = ReleaseCapture();
_ = SendMessageW(FindWindowW(null, windowTitle), 0x112, 0xf012, 0);
}
C#(.NET)으로 만든 앱을 네이티브 AOT로 게시하면 자체 포함 배포처럼 네이티브 코드로 AOT(ahead-of-time) 컴파일된 앱이 생성된다. 즉 IL(Intermediate language)을 네이티브 코드로 컴파일한다.1
System.Text.Json에서 원본을 생성하는 방법은 몇 가지 추가 및 제약 사항이 있는데 아래의 예제는 이를 설명하는 소스이다.2 테스트를 위해 생성한 파일은 Program.cs, TestModel.cs, JsonHelper.cs
이다.
테스트를 위하여 콘솔 프로젝트를 만들고 프로젝트 설정(~.csproj)을 아래와 같이 변경하고 터미널에서 dotnet publish -c Release -r win-x64
빌드한다. 정상적으로 빌드가 완료되면 하위 폴더 Native
에 실행파일이 생성된다.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>
</Project>
C#과 같은 일반적인 프로그래밍 언어에서는 class, struct(구조체) 안에 property(속성)와 method(메서드)를 정의하여 활용할 수 있다. rust는 구조체를 지원하지만 해당 구조체 안에 속성만을 기술할 수 있지 메서드는 정의할 수 없고 impl 키워드를 사용하여 외부에 정의한다.
impl 키워드와 더불어 사용할 수 있는 trait는 타입에 대해 공통된 동작을 표시한다. 약간의 차이는 있지만 다른 프로그래밍 언어에서 말하는 interface와 비슷한 개념이다. 아래의 소스는 C#에서의 interface 기능을 간략하게 살펴보고 이를 rust를 이용하여 구현하고 비교해 본 것이다.
using System;
namespace ConsoleTest;
internal class Program
{
private static void Main()
{
Test1();
Console.WriteLine("------------------------------");
Test2();
/*
Truck can drive : 1111 : 1111
Truck can drive : 2222 : 1111
Truck can drive : 2222
Sedan can drive : 3333
Sedan can drive : 4444 : 1111
------------------------------
Truck can drive : 1111 : 1111
Truck can drive : 2222 : 2222
Truck can drive : 2222
Sedan can drive : 3333
Sedan can drive : 4444 : 2222
*/
}
콜백은 일반적으로 delegate를 통하여 구현하나 인터페이스를 사용하여 콜백을 구현할 수도 있다. 또한 모델 클래스를 만들 때 클래스에서 지원하지 않는 다중상속을 구현가능하게 해 준다. 아래는 전체 소스이다.