C#에서 C/C++ 동적 라이브러리 호출 시 구조체 및 배열 매핑

구조체 전달 방식

C++ 코드

extern "C" __declspec(dllexport) bool GetVersionInfo(OSINFO* info);
extern "C" __declspec(dllexport) bool GetVersionRef(OSINFO& info);

C# 구조체 정의

[StructLayout(LayoutKind.Sequential)]
public struct OSINFO
{
    public int osVersion;
    public int majorVersion;
    public int minorVersion;
    public int buildNumber;
    public int platformId;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
    public string versionString;
}

방법 1: 참조 전달 (ref 키워드 사용)

[DllImport("jnalib.dll", EntryPoint = "GetVersionInfo")]
public static extern bool GetVersionInfo(ref OSINFO data);

[DllImport("jnalib.dll", EntryPoint = "GetVersionRef")]
public static extern bool GetVersionRef(ref OSINFO data);

방법 2: IntPtr를 통한 직접 메모리 접근

IntPtr buffer = Marshal.AllocHGlobal(Marshal.SizeOf<OSINFO>());
try
{
    if (GetVersionInfo(buffer))
    {
        Console.WriteLine("버전: {0}", Marshal.ReadInt32(buffer));
        Console.WriteLine("메이저: {0}", Marshal.ReadInt32(buffer, 4));
        Console.WriteLine("빌드 번호: {0}", Marshal.ReadInt32(buffer, 12));
        Console.WriteLine("버전 문자열: {0}", Marshal.PtrToStringAnsi(new IntPtr(buffer.ToInt64() + 20)));
    }
}
finally
{
    Marshal.FreeHGlobal(buffer);
}

구조체 배열 전달

C++ 함수 선언

extern "C" __declspec(dllexport) bool GetVersionList(OSINFO* list, int count);

C#에서의 처리

[DllImport("jnalib.dll", EntryPoint = "GetVersionList")]
public static extern bool GetVersionList(IntPtr buffer, int count);

OSINFO[] items = new OSINFO[2];
IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf<OSINFO>() * 2);

try
{
    GetVersionList(ptr, 2);

    for (int i = 0; i < 2; i++)
    {
        IntPtr itemPtr = new IntPtr(ptr.ToInt64() + i * Marshal.SizeOf<OSINFO>());
        items[i] = (OSINFO)Marshal.PtrToStructure(itemPtr, typeof(OSINFO));
        Console.WriteLine($"OS 버전: {items[i].osVersion}, 이름: {items[i].versionString}");
    }
}
finally
{
    Marshal.FreeHGlobal(ptr);
}

복합 구조체 및 배열 처리

C++ 복합 구조체 정의

struct Student
{
    char name[20];
    int age;
    double scores[30];
};

struct ClassRoom
{
    int classId;
    Student students[50];
};

extern "C" __declspec(dllexport) int PopulateClass(ClassRoom* classroom, int size);

C#에서의 매핑 및 사용

[StructLayout(LayoutKind.Sequential)]
public struct Student
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
    public string name;
    public int age;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 30)]
    public double[] scores;
}

[StructLayout(LayoutKind.Sequential)]
public struct ClassRoom
{
    public int classId;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 50)]
    public Student[] students;
}

[DllImport("jnalib.dll", EntryPoint = "PopulateClass")]
public static extern int PopulateClass(IntPtr buffer, int size);

// 사용 예시
int totalSize = Marshal.SizeOf<ClassRoom>() * 50;
IntPtr memoryBlock = Marshal.AllocHGlobal(totalSize);

try
{
    PopulateClass(memoryBlock, 50);

    ClassRoom[] classrooms = new ClassRoom[50];
    for (int i = 0; i < 50; i++)
    {
        IntPtr itemPtr = new IntPtr(memoryBlock.ToInt64() + i * Marshal.SizeOf<ClassRoom>());
        classrooms[i] = (ClassRoom)Marshal.PtrToStructure(itemPtr, typeof(ClassRoom));
    }
}
finally
{
    Marshal.FreeHGlobal(memoryBlock);
}

입력 파라미터로 구조체 설정

대규모 구조체를 입력으로 전달할 때는 Marshal.StructureToPtr를 사용해 메모리에 직접 쓰기 가능:

Student studentData = new Student();
// 값 설정...

IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf<Student>());
Marshal.StructureToPtr(studentData, ptr, false); // false: 복사 후 해제
// 사용 후 반드시 해제 필요
Marshal.FreeHGlobal(ptr);

태그: C# C++ P/Invoke StructLayout Marshal

7월 19일 16:55에 게시됨