콘텐츠로 이동

C# P/Invoke와 Native Memory Marshaling 완전 가이드

P/Invoke(Platform Invocation)는 C#에서 C/C++ 네이티브 DLL 함수를 호출하는 메커니즘입니다. 게임 엔진 플러그인, OS API 호출, 고성능 네이티브 라이브러리 통합에 필수적입니다.


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);
}

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);

// 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 (리틀 엔디안)

// 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); // 반드시 해제
}

// 델리게이트를 함수 포인터로 전달
[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);
};

// 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() 자동 호출

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/InvokeO
SafeHandle자동 핸들 해제O
NativeLibrary동적 DLL 로드O
Marshal메모리 변환O