LibraryImport와 소스 생성 P/Invoke
[LibraryImport](C# 11+)를 사용해 AOT와 호환되고 DllImport보다 뛰어난 성능을 내는 소스 생성 마샬링을 구현합니다.
LibraryImport와 소스 생성 P/Invoke은(는) CoddyKit의 무료 C# Academy 강의입니다. 이것은 4개 중 2번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 C# Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. C# Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
LibraryImport를 사용하는 이유
[LibraryImport]는 .NET 7(C# 11)에서 소스 생성 방식이며 AOT와 호환되는 [DllImport]의 대체 수단으로 도입되었습니다. 기존의 DllImport는 리플렉션을 통한 런타임 마샬링에 의존하므로 Native AOT에 문제가 될 수 있습니다. LibraryImport는 모든 마샬링 코드를 컴파일 시점에 생성합니다.
LibraryImport 메서드 선언하기
static partial 메서드에 [LibraryImport]를 지정합니다. 소스 생성기가 구현을 채웁니다. 메서드를 포함하는 클래스에도 partial을 지정해야 합니다.
using System.Runtime.InteropServices;
internal static partial class NativeMethods
{
// Source generator creates the P/Invoke body at compile time
[LibraryImport("mylib", EntryPoint = "add_integers")]
internal static partial int AddIntegers(int a, int b);
// String marshaling must be explicit in LibraryImport
[LibraryImport("mylib", StringMarshalling = StringMarshalling.Utf8)]
internal static partial int ProcessString(string text);
}LibraryImport에서 문자열 마샬링하기
DllImport와 달리 LibraryImport에서는 문자열 마샬링을 명시적으로 지정해야 합니다. StringMarshalling 열거형 또는 MarshalAs 특성을 사용하면 마샬링 비용을 명확히 확인하고 제어할 수 있습니다.
// Option 1: StringMarshalling enum (all strings in method)
[LibraryImport("kernel32", StringMarshalling = StringMarshalling.Utf16)]
static partial bool CreateDirectory(string lpPathName, nint lpSecurityAttributes);
// Option 2: Per-parameter MarshalAs
[LibraryImport("libc")]
static partial int Open(
[MarshalAs(UnmanagedType.LPUTF8Str)] string path,
int flags);
// Option 3: MarshalUsing for custom marshalers
[LibraryImport("mylib")]
static partial void Process(
[MarshalUsing(typeof(Utf8StringMarshaller))] string name);구조체 마샬링
구조체의 경우 [NativeMarshalling]을 추가하여 관리형 형식을 네이티브 표현으로 매핑하는 방법을 정의합니다. 소스 생성기는 마샬러 형식을 사용하여 안전하면서 할당을 최소화하는 코드를 생성합니다.
[NativeMarshalling(typeof(PointMarshaller))]
public struct Point
{
public int X;
public int Y;
}
[CustomMarshaller(typeof(Point), MarshalMode.Default, typeof(PointMarshaller))]
public static class PointMarshaller
{
public static Point ConvertToManaged(NativePoint native)
=> new Point { X = native.X, Y = native.Y };
public static NativePoint ConvertToUnmanaged(Point managed)
=> new NativePoint { X = managed.X, Y = managed.Y };
[StructLayout(LayoutKind.Sequential)]
public struct NativePoint { public int X, Y; }
}DllImport와 LibraryImport 비교
DllImport는 런타임에 해석되므로 시작이 느리고 리플렉션을 기반으로 하며 AOT 트리밍과 호환되지 않습니다. LibraryImport는 빌드 시점에 최적화된 C# 코드를 생성하므로 런타임 리플렉션이 필요 없고 트리밍에 안전하며 벤치마크에서 측정할 수 있을 정도로 더 빠릅니다.
// Old — DllImport (still works, but avoid for new AOT code)
[DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool MoveFile(string src, string dst);
// New — LibraryImport (AOT-safe, source-generated)
[LibraryImport("kernel32", StringMarshalling = StringMarshalling.Utf16,
SetLastError = true)]
static partial bool MoveFile(string src, string dst);
// The compiler generates the actual P/Invoke wrapper bodySetLastError 및 오류 처리
[LibraryImport]에서 SetLastError = true로 설정하여 OS 오류 코드를 캡처합니다. 호출 후 Marshal.GetLastPInvokeError()를 사용하는 것이 권장되며, Marshal.GetLastWin32Error()를 사용해도 오류 코드를 가져올 수 있습니다.
[LibraryImport("kernel32", StringMarshalling = StringMarshalling.Utf16,
SetLastError = true)]
static partial bool CreateDirectory(string path, nint secAttr);
bool ok = CreateDirectory(@"C:\Temp\NewDir", 0);
if (!ok)
{
int err = Marshal.GetLastPInvokeError();
// err is ERROR_ALREADY_EXISTS (183) if folder exists
throw new Win32Exception(err);
}Span<T> 및 메모리 마샬링
소스 생성 P/Invoke의 장점 중 하나는 Span<T>를 일급으로 지원한다는 점입니다. ReadOnlySpan<byte>를 전달하면 배열을 사용하는 DllImport에서 필요한 고정과 할당을 피할 수 있습니다.
[LibraryImport("mylib")]
static partial int ProcessBuffer(
ReadOnlySpan<byte> data,
int length);
// Usage — no fixed or GCHandle needed
byte[] buffer = Encoding.UTF8.GetBytes("hello");
int result = ProcessBuffer(buffer, buffer.Length);
// For output buffers use Span<byte>
[LibraryImport("mylib")]
static partial int FillBuffer(Span<byte> output, int maxLen);소스 생성 활성화하기
.NET 7 이상을 대상으로 하는 프로젝트에서 System.Runtime.InteropServices 네임스페이스를 참조하면 소스 생성이 자동으로 활성화됩니다. 이전 대상 프레임워크에서는 Microsoft.Interop.SourceGeneration 분석기 패키지가 필요합니다.
<!-- In your .csproj — no extra package needed on .NET 7+ -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<!-- Enable Roslyn analyzers and source generators -->
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<!-- AllowUnsafeBlocks may be needed for some marshalers -->
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
</Project>생성된 코드 확인하기
csproj에 <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>를 추가하면 생성된 파일이 obj/에 기록됩니다. 이를 통해 소스 생성기가 정확히 무엇을 생성하는지 확인할 수 있어 훌륭한 학습 연습이 됩니다.
// The generator emits something like this for a LibraryImport method:
// (simplified view of generated stub)
internal static partial int AddIntegers(int a, int b)
{
// Direct call — no reflection, no boxing, fully inlineable
return __PInvoke(a, b);
[System.Runtime.InteropServices.DllImportAttribute(
"mylib",
EntryPoint = "add_integers",
ExactSpelling = true)]
static extern int __PInvoke(int a, int b);
}실전: C 라이브러리 래핑하기
일반적인 패턴은 모든 LibraryImport 선언을 포함하는 정적 래퍼 클래스를 정의한 다음, 그 위에 높은 수준의 안전한 API를 노출하는 것입니다. partial 선언은 내부 또는 비공개로 유지하고, 안전한 래퍼만 공개적으로 노출하십시오.
internal static partial class LibSodiumNative
{
private const string LibName = "libsodium";
[LibraryImport(LibName, EntryPoint = "crypto_secretbox_keybytes")]
internal static partial nuint KeyBytes();
[LibraryImport(LibName, EntryPoint = "crypto_secretbox_easy")]
internal static partial int SecretBoxEasy(
Span<byte> ciphertext,
ReadOnlySpan<byte> message,
ulong mlen,
ReadOnlySpan<byte> nonce,
ReadOnlySpan<byte> key);
}
// Public safe wrapper hides the native signature
public static byte[] Encrypt(byte[] message, byte[] key, byte[] nonce)
{
var cipher = new byte[message.Length + 16];
LibSodiumNative.SecretBoxEasy(cipher, message, (ulong)message.Length, nonce, key);
return cipher;
}빠른 확인
[DllImport]보다 [LibraryImport]가 갖는 가장 큰 장점은 무엇인가요?
복습: LibraryImport 및 소스 생성 P/Invoke
핵심 요점:
- AOT에 안전한 P/Invoke를 위해
[LibraryImport]가[DllImport]를 대체합니다 - 마샬링이 컴파일 시점에 생성되므로 런타임 리플렉션이 필요하지 않습니다
static partial메서드와partial클래스가 필요합니다- 문자열 마샬링은
StringMarshalling또는MarshalAs를 통해 명시적으로 지정해야 합니다 - 고정에 따른 오버헤드 없이
Span<T>를 일급으로 지원합니다 EmitCompilerGeneratedFiles를 사용하여 생성된 코드를 확인할 수 있습니다
자주 묻는 질문
“LibraryImport와 소스 생성 P/Invoke” 강의는 무료인가요?
네 — “LibraryImport와 소스 생성 P/Invoke” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 C# Academy 강의 전체를 잠금 해제할 수 있습니다. C# Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
“LibraryImport와 소스 생성 P/Invoke”에서 뭘 배우나요?
[LibraryImport](C# 11+)를 사용해 AOT와 호환되고 DllImport보다 뛰어난 성능을 내는 소스 생성 마샬링을 구현합니다. 브라우저에서 직접 실행하는 실습 코드로 C# Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
C# Academy을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 C# Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 2번째 강의입니다.
“LibraryImport와 소스 생성 P/Invoke” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 C# Academy 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 C# Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- P/Invoke 기초
- LibraryImport와 소스 생성 P/Invoke
- 안전하지 않은 코드, 포인터 및 고정 버퍼
- COM 상호 운용 및 런타임 호출 가능 래퍼