문자열 마샬링

.NET과 C++ 네이티브 코드 간의 상호작용에서 문자열을 주고받는 것은 자주 발생하는 작업입니다. 그러나 문자열은 메모리 레이아웃이 다르고, 네이티브 코드와 매니지드 코드 간에 인코딩 방식도 다를 수 있기 때문에 올바르게 마샬링하기 위해서는 주의가 필요합니다. .NET에서는 문자열 마샬링 시 ANSI유니코드(UTF-16) 두 가지 주요 인코딩 방식을 사용하며, 이를 명시적으로 처리해야 합니다.

문자열 마샬링 방법

.NET에서는 기본적으로 string 타입을 사용하여 문자열을 처리하지만, C++에서는 char* 또는 wchar_t* 타입을 사용하여 문자열을 처리합니다. 마샬링 과정에서 .NET의 문자열을 네이티브 코드에서 처리할 수 있는 형식으로 변환해야 하며, 이를 위해 마샬링 특성이나 Marshal 클래스를 사용할 수 있습니다.

기본적인 ANSI 문자열 마샬링

ANSI 문자열은 ASCII와 유사한 인코딩 방식으로, 네이티브 C++에서 일반적으로 **char***를 통해 처리됩니다. C#에서는 CharSet.Ansi 특성을 사용하여 문자열을 ANSI로 마샬링할 수 있습니다.

예시: ANSI 문자열 마샬링

다음은 C++에서 ANSI 문자열을 받아 출력하는 함수와 이를 C#에서 호출하는 예시입니다.

C++ 코드

// NativeLibrary.cpp
extern "C" __declspec(dllexport) void PrintAnsiString(const char* str) {
    printf("Received ANSI string: %s\n", str);
}

C# 코드

using System;
using System.Runtime.InteropServices;

class Program
{
    [DllImport("NativeLibrary.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
    public static extern void PrintAnsiString(string str);

    static void Main()
    {
        PrintAnsiString("Hello, ANSI!");
    }
}

이 예제에서는 C#의 string 타입이 ANSI 인코딩으로 C++ 네이티브 함수에 전달됩니다. CharSet.Ansi 특성을 통해 문자열이 ANSI 형식으로 마샬링되며, C++에서 이를 올바르게 처리할 수 있습니다.

유니코드(UTF-16) 문자열 마샬링

유니코드 문자열은 UTF-16 인코딩을 사용하여 다국어를 지원하는 문자열 처리 방식입니다. .NET의 기본 문자열 인코딩은 유니코드이므로, 별도의 특성을 지정하지 않으면 기본적으로 유니코드로 마샬링됩니다. C++에서는 **wchar_t***를 사용하여 유니코드 문자열을 처리할 수 있습니다.

예시: 유니코드 문자열 마샬링

다음은 C++에서 유니코드 문자열을 받아 출력하는 함수와 이를 C#에서 호출하는 예시입니다.

C++ 코드

// NativeLibrary.cpp
#include <wchar.h>

extern "C" __declspec(dllexport) void PrintUnicodeString(const wchar_t* str) {
    wprintf(L"Received Unicode string: %ls\n", str);
}

C# 코드

using System;
using System.Runtime.InteropServices;

class Program
{
    [DllImport("NativeLibrary.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
    public static extern void PrintUnicodeString(string str);

    static void Main()
    {
        PrintUnicodeString("Hello, Unicode!");
    }
}

이 예제에서는 C#의 string 타입이 **유니코드(UTF-16)**로 마샬링되며, C++에서 **wchar_t***로 받아들여집니다. 기본적으로 .NET에서는 유니코드를 지원하므로, CharSet.Unicode를 사용하여 네이티브 코드로 전달할 수 있습니다.

문자열을 포인터로 마샬링

때때로 네이티브 코드에서 문자열을 처리하는 동안, 문자열의 포인터를 직접 전달받아야 할 수 있습니다. 이때, C#에서는 IntPtr을 사용하여 문자열 포인터를 처리할 수 있으며, Marshal.StringToHGlobalAnsi 또는 Marshal.StringToHGlobalUni를 사용하여 문자열을 마샬링한 후, 해당 메모리 주소를 네이티브 코드로 전달합니다.

예시: 문자열을 포인터로 마샬링

다음은 C++에서 문자열 포인터를 받아 처리하는 예시입니다.

C++ 코드

// NativeLibrary.cpp
extern "C" __declspec(dllexport) void PrintStringPointer(const char* str) {
    printf("Received string pointer: %s\n", str);
}

C# 코드

using System;
using System.Runtime.InteropServices;

class Program
{
    [DllImport("NativeLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern void PrintStringPointer(IntPtr str);

    static void Main()
    {
        string message = "Hello, from pointer!";
        IntPtr strPtr = Marshal.StringToHGlobalAnsi(message);
        PrintStringPointer(strPtr);
        Marshal.FreeHGlobal(strPtr);
    }
}

이 예제에서는 Marshal.StringToHGlobalAnsi 메서드를 사용하여 C# 문자열을 ANSI 문자열로 변환한 후, 해당 문자열 포인터를 네이티브 C++ 함수에 전달합니다. 사용 후에는 **Marshal.FreeHGlobal**을 호출하여 메모리를 해제합니다.

문자열 배열 마샬링

때때로 네이티브 코드에서 문자열 배열을 전달해야 할 경우가 있습니다. .NET에서는 배열을 전달할 수 있지만, 문자열 배열은 각각의 문자열 포인터가 필요하기 때문에 별도의 처리 과정이 필요합니다.

예시: 문자열 배열 마샬링

다음은 C++에서 문자열 배열을 받아 출력하는 예시입니다.

C++ 코드

// NativeLibrary.cpp
extern "C" __declspec(dllexport) void PrintStringArray(const char** strArray, int count) {
    for (int i = 0; i < count; i++) {
        printf("String %d: %s\n", i, strArray[i]);
    }
}

C# 코드

using System;
using System.Runtime.InteropServices;

class Program
{
    [DllImport("NativeLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern void PrintStringArray(IntPtr[] strArray, int count);

    static void Main()
    {
        string[] messages = { "Hello", "World", "From C#" };
        IntPtr[] ptrArray = new IntPtr[messages.Length];

        for (int i = 0; i < messages.Length; i++)
        {
            ptrArray[i] = Marshal.StringToHGlobalAnsi(messages[i]);
        }

        PrintStringArray(ptrArray, messages.Length);

        // 메모리 해제
        for (int i = 0; i < messages.Length; i++)
        {
            Marshal.FreeHGlobal(ptrArray[i]);
        }
    }
}

이 예제에서는 문자열 배열을 각각의 포인터로 마샬링한 후 네이티브 코드로 전달합니다. 배열의 각 문자열은 포인터로 변환된 후 전달되며, 마샬링 후에는 반드시 메모리를 해제해야 합니다.

결론

문자열 마샬링은 .NET과 C++ 간의 상호작용에서 중요한 부분을 차지합니다. ANSI와 유니코드 문자열을 올바르게 마샬링하기 위해서는 CharSet 특성을 활용하고, 문자열을 포인터로 마샬링할 때는 Marshal.StringToHGlobal을 사용하여 적절하게 처리해야 합니다. 또한 문자열 배열을 처리할 때는 각각의 문자열을 포인터로 변환하여 네이티브 코드로 전달하고, 사용 후 메모리 해제를 잊지 말아야 합니다.