포인터와 핸들 마샬링
.NET과 C++ 네이티브 코드 간의 상호작용에서는 포인터와 핸들을 주고받는 경우가 많습니다. 포인터는 특정 메모리 위치를 가리키며, 핸들은 주로 리소스나 객체를 참조하는 데 사용됩니다. 이러한 포인터와 핸들은 네이티브 코드와 매니지드 코드 간에 다른 메모리 관리 방식을 가지고 있기 때문에, 마샬링 과정에서 주의가 필요합니다.
포인터 마샬링
포인터는 .NET의 매니지드 메모리와 네이티브 코드의 언매니지드 메모리 간의 상호작용을 위해 자주 사용됩니다. .NET에서 포인터를 사용하려면 unsafe 코드 블록을 사용해야 하며, 이는 개발자가 명시적으로 메모리 관리에 신경 써야 함을 의미합니다.
예시: 포인터 마샬링
다음은 C++에서 정수를 가리키는 포인터를 반환하는 함수와 이를 C#에서 호출하는 예시입니다.
C++ 코드
// NativeLibrary.cpp
extern "C" __declspec(dllexport) int* GetPointerToInt(int value) {
int* ptr = new int;
*ptr = value;
return ptr;
}
extern "C" __declspec(dllexport) void FreePointer(int* ptr) {
delete ptr;
}
C# 코드
using System;
using System.Runtime.InteropServices;
class Program
{
[DllImport("NativeLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr GetPointerToInt(int value);
[DllImport("NativeLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void FreePointer(IntPtr ptr);
static void Main()
{
IntPtr intPtr = GetPointerToInt(10);
int value = Marshal.ReadInt32(intPtr);
Console.WriteLine($"Value: {value}");
FreePointer(intPtr);
}
}
이 예제에서는 IntPtr을 사용하여 C++에서 반환된 포인터를 C#에서 처리합니다. Marshal.ReadInt32를 사용해 해당 포인터에서 값을 읽고, 사용이 끝난 후에는 FreePointer를 호출하여 메모리를 해제합니다. 포인터를 네이티브 코드로 전달할 때는 메모리 해제를 명확하게 처리해야 하므로, 메모리 누수를 방지하는 코드가 필수적입니다.
함수 포인터 마샬링
함수 포인터는 C++에서 사용되는 콜백 함수에 해당하는 포인터로, 네이티브 코드와 매니지드 코드 간의 상호작용에서 자주 사용됩니다. 함수 포인터를 .NET에서 사용하기 위해서는 **델리게이트(delegate)**를 함수 포인터로 마샬링하여 네이티브 코드에서 사용할 수 있도록 해야 합니다.
예시: 함수 포인터 마샬링
다음은 C++에서 콜백 함수를 받는 예제와 이를 C#에서 함수 포인터로 전달하는 방법입니다.
C++ 코드
// NativeLibrary.cpp
typedef void (*CallbackFunction)(int);
extern "C" __declspec(dllexport) void RegisterCallback(CallbackFunction callback) {
for (int i = 0; i < 5; i++) {
callback(i);
}
}
C# 코드
using System;
using System.Runtime.InteropServices;
class Program
{
delegate void CallbackDelegate(int value);
[DllImport("NativeLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void RegisterCallback(CallbackDelegate callback);
static void MyCallback(int value)
{
Console.WriteLine($"Callback received: {value}");
}
static void Main()
{
CallbackDelegate callback = new CallbackDelegate(MyCallback);
RegisterCallback(callback);
}
}
이 예제에서는 C++ 함수에서 콜백 함수를 받기 위해 CallbackDelegate를 함수 포인터로 마샬링합니다. C++에서는 함수 포인터를 통해 콜백을 호출하며, C#에서는 해당 콜백이 실행됩니다.
핸들 마샬링
핸들은 주로 리소스나 시스템 객체(파일, 스레드, 윈도우 핸들 등)를 참조하는 데 사용됩니다. .NET에서는 네이티브 핸들을 IntPtr로 처리하며, 이러한 핸들을 네이티브 코드로 전달할 수 있습니다. 핸들 마샬링에서 주의해야 할 점은, 리소스를 적절하게 해제하지 않으면 리소스 누수가 발생할 수 있다는 것입니다.
예시: 파일 핸들 마샬링
다음 예제는 C++에서 파일 핸들을 반환하는 함수와 이를 C#에서 사용하는 방식입니다.
C++ 코드
// NativeLibrary.cpp
#include <Windows.h>
extern "C" __declspec(dllexport) HANDLE GetFileHandle(const char* fileName) {
HANDLE hFile = CreateFileA(fileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
return hFile;
}
extern "C" __declspec(dllexport) void CloseFileHandle(HANDLE hFile) {
CloseHandle(hFile);
}
C# 코드
using System;
using System.Runtime.InteropServices;
class Program
{
[DllImport("NativeLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr GetFileHandle(string fileName);
[DllImport("NativeLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void CloseFileHandle(IntPtr hFile);
static void Main()
{
IntPtr fileHandle = GetFileHandle("example.txt");
if (fileHandle != IntPtr.Zero)
{
Console.WriteLine("File handle obtained.");
CloseFileHandle(fileHandle);
}
else
{
Console.WriteLine("Failed to obtain file handle.");
}
}
}
이 예제에서는 C++에서 파일 핸들을 반환받아 C#에서 IntPtr로 처리합니다. 파일 핸들은 사용 후 반드시 CloseFileHandle을 호출하여 닫아야 리소스 누수를 방지할 수 있습니다.
결론
포인터와 핸들을 C++과 .NET 간에 마샬링할 때는 메모리 관리와 리소스 해제에 주의해야 합니다. 포인터는 IntPtr로, 함수 포인터는 델리게이트로 마샬링할 수 있으며, 핸들은 주로 IntPtr로 처리됩니다. 메모리나 리소스를 적절히 해제하지 않으면 리소스 누수와 메모리 문제가 발생할 수 있으므로, 이러한 부분을 신경 써야 합니다.