.NET 7에 LibraryImport 특성이 추가되었습니다. 기존 DllImport 특성으로 다음의 DllImport 장식을
[DllImport("Kernel32.dll")]
public extern static bool Beep(uint freq, uint duration);
다음의 LibraryImport 특성으로 장식할 수 있습니다.
[LibraryImport("Kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool Beep(uint freq, uint duration);
LibraryImport가 새로 생겨난 이유는 DllImport가 마샬링을 런타임에서 수행해서 IL 코드를 emit한다고 하는데 NativeAOT 등 동적으로 IL 코드를 생성할 수 없는 환경에서 쓸 수 없기 때문입니다.
LibraryImport는 소스 생성기 기능을 이용해 컴파일 시점에서 마샬링 코드를 삽입합니다.
obj/Debug/net7.0/generated/Microsoft.Interop.LibraryImportGenerator/Microsoft.Interop.LibraryImportGenerator/LibraryImports.g.cs
(csproj에 EmitCompilerGeneratedFiles 설정을 true로 줘야 해당 경로에 파일로 생성되어 확인할 수 있습니다.)
// <auto-generated/>
static unsafe partial class ExternDll
{
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Interop.LibraryImportGenerator", "7.0.6.42610")]
[System.Runtime.CompilerServices.SkipLocalsInitAttribute]
public static partial bool Beep(uint freq, uint duration)
{
bool __retVal;
int __retVal_native;
{
__retVal_native = __PInvoke(freq, duration);
}
// Unmarshal - Convert native data to managed data.
__retVal = __retVal_native != 0;
return __retVal;
// Local P/Invoke
[System.Runtime.InteropServices.DllImportAttribute("Kernel32.dll", EntryPoint = "Beep", ExactSpelling = true)]
static extern unsafe int __PInvoke(uint freq, uint duration);
}
}
뭔가 코드가 삽입됐고 심지어 최종 다시 DllImport를 사용하는군요? 결국에 LibraryImport 특성으로 인해 생성된 코드는 마샬링 관련 코드를 삽입하는 역할만 딱 하고 기존 DllImport를 사용합니다.
// Unmarshal - Convert native data to managed data.
__retVal = __retVal_native != 0;
정말로 마샬링 처리를 하는지 다른 예제로 확인해 봤습니다. LZ4 DLL을 다음과 같이 LibraryImport 특성을 사용해
static partial class ExternDll
{
[LibraryImport("msys-lz4-1.dll")]
public static partial int LZ4_compressBound(int inputSize);
[LibraryImport("msys-lz4-1.dll")]
public static partial int LZ4_compress_default(byte[] src, byte[] dst, int srcSize, int dstCapacity);
[LibraryImport("msys-lz4-1.dll")]
public static partial int LZ4_decompress_safe(byte[] src, byte[] dst, int compressedSize, int dstCapacity);
}
byte[] 은 관리 힙 메모리에 위치하므로 비관리 모듈에 그대로 전달하면 안됩니다. LibraryImport 장식에 의해 생성된 코드를 보면,
static unsafe partial class ExternDll
{
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Interop.LibraryImportGenerator", "7.0.6.42610")]
[System.Runtime.CompilerServices.SkipLocalsInitAttribute]
public static partial int LZ4_compress_default(byte[] src, byte[] dst, int srcSize, int dstCapacity)
{
int __retVal;
// Pin - Pin data in preparation for calling the P/Invoke.
fixed (void* __src_native = &global::System.Runtime.InteropServices.Marshalling.ArrayMarshaller<byte, byte>.ManagedToUnmanagedIn.GetPinnableReference(src))
fixed (void* __dst_native = &global::System.Runtime.InteropServices.Marshalling.ArrayMarshaller<byte, byte>.ManagedToUnmanagedIn.GetPinnableReference(dst))
{
__retVal = __PInvoke((byte*)__src_native, (byte*)__dst_native, srcSize, dstCapacity);
}
return __retVal;
// Local P/Invoke
[System.Runtime.InteropServices.DllImportAttribute("msys-lz4-1.dll", EntryPoint = "LZ4_compress_default", ExactSpelling = true)]
static extern unsafe int __PInvoke(byte* src, byte* dst, int srcSize, int dstCapacity);
}
}
위와 같이 마샬링을 하는 코드가 삽입됨을 볼 수 있습니다.
간단하게 테스트 코드를 만든 후,
using System.Runtime.InteropServices;
using System.Text;
// TestBeep()
TestLZ4Compress();
static void TestBeep()
{
for (uint i = 0; i < 100; i++)
{
ExternDll.Beep(i * 100, 50);
}
}
static void TestLZ4Compress()
{
var sourceText = """
닷넷 코어는 ASP.NET Core 웹 응용 프로그램, 명령줄 응용 프로그램, 라이브러리 및 유니버셜 윈도우 플랫폼 앱, 응용 프로그램 등 총 4가지로 크로스 플랫폼 시나리오를 지원한다. 다만, 현재 윈도우의 데스크톱 소프트웨어용 표준 GUI를 렌더링하는 윈도우 폼 또는 WPF는 구현되어 있지 않다.[3][4] 이에 마이크로소프트는 닷넷 코어3에서 윈도우 폼, WPF을 유니버셜 윈도우 플랫폼 앱과 함께 지원할 방침이다.[5] 여기에 닷넷 코어는 NuGet 패키지의 사용을 지원한다. 윈도우 버전의 닷넷 프레임워크와는 달리 업데이트는 윈도우 업데이트에서만 주로 이루어지만, 닷넷 코어는 업데이트를 패키지 관리자 형식으로 업데이트를 하는 장점이 있다.[3][4]
닷넷 코어는 공통 언어 런타임(CLR)의 완전한 런타임환경을 구현시킨 CoreCLR로 구성되어있다. 이 런타임은 닷넷 프로그램 실행 관리를 위한 가상 컴퓨터로 마이크로소프트에서 시작하여, RyuJIT라는 JIT 컴파일을 포함한다.[6] 또한, AOT 컴파일 된 원시 바이너리에 통합되도록 최적화 된 닷넷 원시 런타임인 CoreRT를 포함한다.
닷넷 코어는 닷넷 프레임워크의 표준 라이브러리의 일부 포크인 CoreFX도 포함되어 있으며,[7] 닷넷 코어의 API의 일부분은 닷넷 프레임워크의 API과 동일한 부분도 있으나, 닷넷 프레임워크와는 전혀 다른 전용 API을 사용한다. 그리고 닷넷 코어의 라이브러리를 변형시켜 UWP의 개발에 활용할 수 있다.[8]
닷넷 코어의 명령 줄 인터페이스는 운영 체제에 대한 실행 진입 점을 제공하고 컴파일 및 패키지 관리와 같은 개발자 서비스를 제공한다.[9]
""";
Console.WriteLine(sourceText);
Console.WriteLine("------");
var source = Encoding.Default.GetBytes(sourceText);
var maxDstSize = ExternDll.LZ4_compressBound(source.Length);
//Console.WriteLine(maxDstSize);
var target = new byte[maxDstSize];
var compressedSize = ExternDll.LZ4_compress_default(source, target, source.Length, maxDstSize);
Console.WriteLine($"압축 (LZ4 기본)");
Console.WriteLine($"소스 크기 = {source.Length}");
Console.WriteLine($"압축된 크기 = {compressedSize}");
Console.WriteLine($"압축(%) = {(1 - (float)compressedSize / source.Length) * 100} %");
Console.WriteLine("------");
var decompressedTarget = new byte[source.Length];
var decompressedSize = ExternDll.LZ4_decompress_safe(target, decompressedTarget, compressedSize, decompressedTarget.Length);
Console.WriteLine($"압축 해제 (LZ4 기본)");
Console.WriteLine($"압축 해제 크기 = {decompressedSize}");
Console.WriteLine("------");
var decompressedText = Encoding.Default.GetString(decompressedTarget);
Console.WriteLine(decompressedText);
}
NativeAOT로 다음의 프로젝트 설정으로 게시하여,
...
<PublishAot>true</PublishAot>
<InvariantGlobalization>true</InvariantGlobalization>
<UseSystemResourceKeys>true</UseSystemResourceKeys>
<IlcOptimizationPreference>Size</IlcOptimizationPreference>
<IlcGenerateStackTraceData>false</IlcGenerateStackTraceData>
<DebuggerSupport>false</DebuggerSupport>
<EnableUnsafeBinaryFormatterSerialization>false</EnableUnsafeBinaryFormatterSerialization>
<EventSourceSupport>false</EventSourceSupport>
<HttpActivityPropagationSupport>false</HttpActivityPropagationSupport>
<MetadataUpdaterSupport>false</MetadataUpdaterSupport>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
...
2M 사이즈 .NET 런타임 의존성 없는 실행파일과 500K 사이즈의 LZ4 DLL을 볼 수 있고,
$ .\No19.LibraryImportAttributeTest.exe
닷넷 코어는 ASP.NET Core 웹 응용 프로그램, 명령줄 응용 프로그램...
------
압축 (LZ4 기본)
소스 크기 = 1857
압축된 크기 = 1216
압축(%) = 34.51804 %
------
압축 해제 (LZ4 기본)
압축 해제 크기 = 1857
------
닷넷 코어는 ASP.NET Core 웹 응용 프로그램, 명령줄 응용 프로그램...
잘 동작하는 것을 확인할 수 있었습니다.
https://github.com/dimohy/csharp-check/tree/main/No19.LibraryImportAttributeTest
