C# P/Invoke와 Native Memory Marshaling 완전 가이드
P/Invoke(Platform Invocation)는 C#에서 C/C++ 네이티브 DLL 함수를 호출하는 메커니즘입니다. 게임 엔진 플러그인, OS API 호출, 고성능 네이티브 라이브러리 통합에 필수적입니다.
1. DllImport (전통적 방식)
섹션 제목: “1. DllImport (전통적 방식)”using System.Runtime.InteropServices;
// Windows API[DllImport("kernel32.dll", SetLastError = true)]private static extern bool CreateDirectory(string lpPathName, IntPtr lpSecurityAttributes);
// C 표준 라이브러리[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]private static extern int puts(string str);
// 사용bool created = CreateDirectory("C:\\NewDir", IntPtr.Zero);if (!created){ int error = Marshal.GetLastWin32Error(); throw new System.ComponentModel.Win32Exception(error);}2. LibraryImport (.NET 7+ 권장)
섹션 제목: “2. LibraryImport (.NET 7+ 권장)”Source Generator 기반으로 AOT 호환, 더 나은 성능.
// [DllImport] 대신 [LibraryImport] 사용[LibraryImport("kernel32.dll", SetLastError = true)][return: MarshalAs(UnmanagedType.Bool)]private static partial bool CreateDirectory( [MarshalAs(UnmanagedType.LPWStr)] string lpPathName, IntPtr lpSecurityAttributes);
// 문자열을 UTF-8로 전달[LibraryImport("mylib.so", StringMarshalling = StringMarshalling.Utf8)]private static partial int ProcessString(string input);3. 구조체 마샬링
섹션 제목: “3. 구조체 마샬링”// C 구조체와 메모리 레이아웃 일치[StructLayout(LayoutKind.Sequential)]public struct RECT{ public int Left; public int Top; public int Right; public int Bottom;}
[DllImport("user32.dll")]private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
RECT rect;GetWindowRect(hwnd, out rect);Console.WriteLine($"창 크기: {rect.Right - rect.Left}x{rect.Bottom - rect.Top}");3.1 명시적 레이아웃 (Explicit Layout)
섹션 제목: “3.1 명시적 레이아웃 (Explicit Layout)”// C 유니온 시뮬레이션[StructLayout(LayoutKind.Explicit)]public struct Union{ [FieldOffset(0)] public int IntValue; [FieldOffset(0)] public float FloatValue; [FieldOffset(0)] public byte Byte0; [FieldOffset(1)] public byte Byte1; [FieldOffset(2)] public byte Byte2; [FieldOffset(3)] public byte Byte3;}
var u = new Union { IntValue = 0x12345678 };Console.WriteLine($"Byte0: 0x{u.Byte0:X2}"); // 78 (리틀 엔디안)4. 문자열 마샬링
섹션 제목: “4. 문자열 마샬링”// ANSI 문자열 (char*)[DllImport("mylib.dll", CharSet = CharSet.Ansi)]private static extern int ProcessAnsi(string input);
// Unicode 문자열 (wchar_t*)[DllImport("mylib.dll", CharSet = CharSet.Unicode)]private static extern int ProcessUnicode(string input);
// 수동 마샬링IntPtr ptr = Marshal.StringToHGlobalAnsi("Hello");try{ NativeFunction(ptr);}finally{ Marshal.FreeHGlobal(ptr); // 반드시 해제}5. 콜백 함수 (함수 포인터)
섹션 제목: “5. 콜백 함수 (함수 포인터)”// 델리게이트를 함수 포인터로 전달[UnmanagedFunctionPointer(CallingConvention.Cdecl)]delegate int CompareCallback(IntPtr a, IntPtr b);
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]private static extern void qsort( IntPtr arr, int num, int size, CompareCallback compare);
// GC가 델리게이트를 수집하지 않도록 유지private static CompareCallback _compare = (a, b) =>{ return Marshal.ReadInt32(a) - Marshal.ReadInt32(b);};6. SafeHandle — 안전한 핸들 관리
섹션 제목: “6. SafeHandle — 안전한 핸들 관리”// SafeHandle 상속으로 자동 해제public class FileHandle : SafeHandle{ public FileHandle() : base(IntPtr.Zero, ownsHandle: true) { }
public override bool IsInvalid => handle == IntPtr.Zero;
protected override bool ReleaseHandle() { CloseHandle(handle); return true; }
[DllImport("kernel32.dll")] private static extern bool CloseHandle(IntPtr hObject);}
[DllImport("kernel32.dll", SetLastError = true)]private static extern FileHandle CreateFile(/* ... */);
using var file = CreateFile(/* ... */);// using 블록 종료 시 ReleaseHandle() 자동 호출7. 포인터와 직접 메모리 접근
섹션 제목: “7. 포인터와 직접 메모리 접근”unsafe{ // 네이티브 메모리 직접 접근 void* nativeMem = NativeLibrary.Alloc(1024);
try { byte* ptr = (byte*)nativeMem; for (int i = 0; i < 1024; i++) ptr[i] = (byte)(i % 256); } finally { NativeLibrary.Free(nativeMem); }}
// Marshal 기반 안전한 접근IntPtr buffer = Marshal.AllocHGlobal(1024);try{ Marshal.WriteByte(buffer, 0, 42); int value = Marshal.ReadInt32(buffer, 4);}finally{ Marshal.FreeHGlobal(buffer);}8. 동적 라이브러리 로딩 (.NET 5+)
섹션 제목: “8. 동적 라이브러리 로딩 (.NET 5+)”using System.Runtime.InteropServices;
// 라이브러리 동적 로드IntPtr lib = NativeLibrary.Load("libopencv_core.so");
// 함수 포인터 가져오기IntPtr fnPtr = NativeLibrary.GetExport(lib, "cv_version");var getVersion = Marshal.GetDelegateForFunctionPointer<Func<string>>(fnPtr);
Console.WriteLine(getVersion());
NativeLibrary.Free(lib);9. 게임 개발 활용 — 네이티브 물리 엔진 바인딩
섹션 제목: “9. 게임 개발 활용 — 네이티브 물리 엔진 바인딩”[StructLayout(LayoutKind.Sequential)]public struct Vec3{ public float X, Y, Z;}
[StructLayout(LayoutKind.Sequential)]public struct RigidBodyDesc{ public Vec3 Position; public Vec3 Velocity; public float Mass; [MarshalAs(UnmanagedType.Bool)] public bool IsStatic;}
public class PhysicsWorld : IDisposable{ private IntPtr _handle;
[DllImport("physics.dll")] private static extern IntPtr PhysicsWorld_Create();
[DllImport("physics.dll")] private static extern void PhysicsWorld_Destroy(IntPtr world);
[DllImport("physics.dll")] private static extern int PhysicsWorld_AddBody(IntPtr world, in RigidBodyDesc desc);
public PhysicsWorld() => _handle = PhysicsWorld_Create();
public int AddBody(in RigidBodyDesc desc) => PhysicsWorld_AddBody(_handle, in desc);
public void Dispose() => PhysicsWorld_Destroy(_handle);}| 방법 | 용도 | AOT 호환 |
|---|---|---|
[DllImport] | 전통적 P/Invoke | 부분적 |
[LibraryImport] | 현대 P/Invoke | O |
SafeHandle | 자동 핸들 해제 | O |
NativeLibrary | 동적 DLL 로드 | O |
Marshal | 메모리 변환 | O |