커스텀 마샬링 기법

.NET에서는 기본적으로 다양한 데이터 타입을 네이티브 코드로 마샬링하는 기본 제공 방식이 있지만, 복잡한 데이터 구조나 사용자 정의 타입을 마샬링해야 할 때는 커스텀 마샬링이 필요할 수 있습니다. 커스텀 마샬링을 통해 개발자는 특정 요구 사항에 맞게 데이터를 변환하거나 성능 최적화를 할 수 있습니다.

ICustomMarshaler 인터페이스

.NET에서는 ICustomMarshaler 인터페이스를 제공하여 사용자가 직접 마샬링 과정을 제어할 수 있습니다. 이 인터페이스는 네이티브 코드와 매니지드 코드 간의 데이터를 변환하는 커스텀 로직을 작성하는 데 사용됩니다.

ICustomMarshaler의 주요 메서드

  • MarshalManagedToNative: 매니지드 코드를 네이티브 코드로 변환하는 로직을 정의합니다.
  • MarshalNativeToManaged: 네이티브 코드를 매니지드 코드로 변환하는 로직을 정의합니다.
  • CleanUpManagedData: 매니지드 데이터를 정리하는 로직을 정의합니다.
  • CleanUpNativeData: 네이티브 데이터를 정리하는 로직을 정의합니다.
  • GetNativeDataSize: 네이티브 데이터의 크기를 반환합니다.

이 인터페이스를 구현하면 복잡한 데이터 구조를 맞춤형으로 처리할 수 있습니다.

커스텀 마샬러 구현

다음은 C++의 복잡한 구조체를 C#에서 커스텀 마샬링을 사용해 처리하는 예시입니다.

C++ 코드

// NativeLibrary.cpp
struct ComplexStruct {
    int id;
    float value;
    char description[50];
};

extern "C" __declspec(dllexport) void ProcessComplexStruct(const ComplexStruct* cs) {
    printf("ID: %d\n", cs->id);
    printf("Value: %f\n", cs->value);
    printf("Description: %s\n", cs->description);
}

C# 코드

using System;
using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct ComplexStruct
{
    public int id;
    public float value;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
    public string description;
}

public class ComplexStructMarshaler : ICustomMarshaler
{
    public static ICustomMarshaler GetInstance(string cookie)
    {
        return new ComplexStructMarshaler();
    }

    public IntPtr MarshalManagedToNative(object managedObj)
    {
        if (!(managedObj is ComplexStruct complexStruct))
            throw new ArgumentException("Invalid type");

        int size = Marshal.SizeOf(complexStruct);
        IntPtr ptr = Marshal.AllocHGlobal(size);
        Marshal.StructureToPtr(complexStruct, ptr, false);
        return ptr;
    }

    public object MarshalNativeToManaged(IntPtr pNativeData)
    {
        return Marshal.PtrToStructure(pNativeData, typeof(ComplexStruct));
    }

    public void CleanUpManagedData(object managedObj)
    {
        // No cleanup needed for managed data
    }

    public void CleanUpNativeData(IntPtr pNativeData)
    {
        Marshal.FreeHGlobal(pNativeData);
    }

    public int GetNativeDataSize()
    {
        return -1; // Dynamic size
    }
}

class Program
{
    [DllImport("NativeLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern void ProcessComplexStruct([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(ComplexStructMarshaler))] ComplexStruct cs);

    static void Main()
    {
        ComplexStruct cs = new ComplexStruct
        {
            id = 1,
            value = 42.5f,
            description = "Custom marshaling example"
        };

        ProcessComplexStruct(cs);
    }
}

이 예제에서 ComplexStructMarshaler 클래스는 ICustomMarshaler 인터페이스를 구현하여 ComplexStruct 구조체를 마샬링하는 방식을 정의합니다. MarshalManagedToNative 메서드는 매니지드 데이터를 네이티브 코드로 변환하고, MarshalNativeToManaged 메서드는 네이티브 데이터를 매니지드 코드로 변환합니다.

커스텀 마샬링의 사용 사례

커스텀 마샬링은 다음과 같은 경우에 유용합니다:

  1. 복잡한 데이터 구조: 기본 제공되는 마샬링 방식으로 처리할 수 없는 복잡한 데이터 구조를 네이티브 코드로 전달해야 할 때.
  2. 성능 최적화: 특정 데이터 타입의 변환 과정을 최적화하기 위해 커스텀 마샬링을 사용하여 불필요한 메모리 복사를 피하고 성능을 향상시킬 수 있습니다.
  3. 네이티브 API와의 상호운용성: 네이티브 라이브러리가 고유한 데이터 구조나 포맷을 사용할 때 이를 매니지드 코드와 맞춰 처리할 수 있습니다.

결론

커스텀 마샬링은 .NET에서 제공하는 표준 마샬링 방식으로 처리하기 어려운 복잡한 데이터 구조를 네이티브 코드로 전달할 때 매우 유용합니다. ICustomMarshaler 인터페이스를 사용하여 사용자가 원하는 방식으로 데이터를 변환하고 관리할 수 있으며, 이를 통해 성능을 최적화하거나 네이티브 API와의 상호운용성을 개선할 수 있습니다.