VTK Activiz 를 활용하면서 함수의 매개변수 정보가 Array 값을 갖을 때 각index 값을 따로 입력하는 방식과 IntPtr 를 전달하는 방식으로 API를 제공하고 있다.
나는 index별 매개변수를 전달하는 것을 매우 귀찮아 하기 때문에 IntPtr 로 전달하려고 했고..
그런 부분을 해결하기 위해 구성한 코드를 정리한다.
1. GCHandle based ToIntPtr
GCHandle 을 이용하여 입력받은 array 값의 메모리를 Pinned 로 지정하여 해당 메모리의 값에 대한 GCHandle 을 생성한다. Handle 은 Target의 변수 값과 메모리 값을 관리하게 되며, 필요한 IntPtr 값을 얻은 뒤에는 Handle을 free 메모리해제하여 사용하면 된다.
public static IntPtr ToIntPtr<TInput>(this TInput[] array)
{
//https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.gchandle.fromintptr?view=netframework-4.8
GCHandle handle = GCHandle.Alloc(array, GCHandleType.Pinned);
//https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.gchandle.tointptr?view=netframework-4.8
var intPtr = handle.AddrOfPinnedObject();
handle.Free();
return intPtr;
}
2. Marshal based ToIntPtr
Marshal 에서 제공하는 방식은 GCHandle 과 동일하게 내부 unmanaged api 를 이용하여 해당 array 값에 Pinned 메모리 값을 전달하고 있다.
하지만 GCHandle 과 다른 점은 두가지가 있다.
- Array index 메모리 접근이 가능하다.
- Array 의 type이 unmanaged 데이터만 사용이 가능하다.
/// <summary>
/// Marshal 의 Anmanaged Array 값을 얻어온다.
/// https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/unmanaged-types
/// </summary>
/// <param name="array"></param>
/// <returns></returns>
public static IntPtr ToIntPtrUnmanaged<TInput>(this TInput[] array, int offset) where TInput : unmanaged
{
return Marshal.UnsafeAddrOfPinnedArrayElement(array, offset);
}
3. Marshal based New Array IntPtr
Marshal 의 AllocHGlobal을 이용하여 새로운 동적 메모리를 생성하고 그에 대한 IntPtr를 바로 전달할 수 있다.
하지만 전달된 IntPtr 에 대한 객체를 컨트롤 할 수 없기 때문에 메모리 해제를 별도로 해줘야 한다.
/// <summary>
/// 입력받은 Array 크기의 메모리를 할당한 IntPtr를 전달한다.
/// 사용자가 직접 메모리 해제를 해야한다. Marshal.FreeHGlobal
/// </summary>
/// <param name="size"></param>
/// <returns></returns>
public static IntPtr AllocInPtr<T>(int arrSize)
{
var memSize = Marshal.SizeOf(typeof(T))* arrSize;
var intptr = Marshal.AllocHGlobal(memSize);
return intptr;
}
4. NewArray based New Array IntPtr
새로운 메모리를 생성하고 해당 메모리의 IntPtr를 전달하는 방식이다. 위의 Marshal 방식과 다르게 new array 를 통해서 생성한 메모리에 대한 IntPtr를 전달하기 때문에 GC에서 알아서 메모리를 해제 해 줄 수 있다.
/// <summary>
/// 새로운 메모리를 생성하고 IntPtr 를 전달한다.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="size"></param>
/// <returns></returns>
public static IntPtr NewArrayToInPtr<T>(int size) where T : unmanaged
{
T[] data = new T[size];
return data.ToIntPtr();
}
5. IntPtr to Data Array
데이터를 갖고 있는 IntPtr handle의 값을 해당 타입의 array 로 전달하는 것 또한 유용한 기능이다.
이를 위해서는 handle이 갖고 있는 데이터 타입과 array 의 크기를 전달하여 managed array 로 전환하여 전달하도록 한다.
/// <summary>
/// handle 객체 메모리의 값을 새로운 메모리의 Array 에 복사하여 전달한다.
/// </summary>
/// <param name="handle"></param>
/// <param name="arrSize"></param>
/// <returns></returns>
public static TOutput[] ToArray<TOutput>(this IntPtr handle, int arrSize)
{
TOutput[] outArr = new TOutput[arrSize];
var outPtr = outArr.ToIntPtr();
var memSize = Marshal.SizeOf(typeof(TOutput)) * arrSize;
CopyMemory(outPtr, handle, (uint)memSize);
return outArr;
}