呼叫非託管 C++ DLL 時,若匯出函式參數為結構體陣列(如 extern "C" __declspec(dllexport) int GetClass(Class pClass[50])),直接對應 C# 陣列參數往往會導致記憶體存取違規或資料錯誤。本文詳細說明正確的封送策略。
typedef struct Student {
char name[20];
int age;
double scores[32];
} Student;
typedef struct Class {
int number;
Student students[126];
} Class;
extern "C" __declspec(dllexport) int GetClass(Class pClass[50]) {
for (int i = 0; i < 50; i++) {
pClass[i].number = i;
for (int j = 0; j < 126; j++) {
memset(pClass[i].students[j].name, 0, 20);
sprintf(pClass[i].students[j].name, "name_%d_%d", i, j);
pClass[i].students[j].age = (j % 2 == 0) ? 15 : 20;
}
}
return 0;
}
關鍵在於使用 StructLayout(LayoutKind.Sequential) 並精確標註陣列大小:
[StructLayout(LayoutKind.Sequential)]
struct Student {
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
public string name;
public int age;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
public double[] scores;
}
[StructLayout(LayoutKind.Sequential)]
struct Class {
public int number;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 126)]
public Student[] students;
}
Class myClass = new Class();
IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Class)));
GetClass(ptr); // 參數型別不符,且只傳遞單一結構體
Marshal.FreeHGlobal(ptr);
Class[] myClass = new Class[50];
IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Class)) * 50);
GetClass(ptr); // 托管陣列在記憶體中非連續,且包含嵌套結構體無法直接封送
Marshal.FreeHGlobal(ptr);
[DllImport("CppDll.dll", CallingConvention = CallingConvention.Cdecl)]
static extern int GetClass(IntPtr pClass);
static void Main() {
int structSize = Marshal.SizeOf(typeof(Class));
int totalSize = structSize * 50;
// 1. 配置非託管記憶體塊
IntPtr pBuff = Marshal.AllocHGlobal(totalSize);
Class[] pClass = new Class[50];
// 2. 呼叫非託管函式填充記憶體
GetClass(pBuff);
// 3. 逐項將非託管記憶體封送回托管結構體
for (int i = 0; i < 50; i++) {
IntPtr pItem = new IntPtr(pBuff.ToInt64() + structSize * i);
pClass[i] = (Class)Marshal.PtrToStructure(pItem, typeof(Class));
}
// 4. 釋放非託管記憶體
Marshal.FreeHGlobal(pBuff);
// 驗證結果
foreach (var c in pClass) {
Console.WriteLine($"Class {c.number}: Student0={c.students[0].name}, Age={c.students[0].age}");
}
Console.ReadLine();
}
LayoutKind.Sequential 確保欄位順序與 C++ 一致;ByValArray / ByValTStr 將陣列/字串內嵌在結構體內,而非指標參考。AllocHGlobal 分配連續記憶體,模擬 C++ 陣列在記憶體中的連續分佈。pBuff + i * structSize 計算每個元素起始位址,再用 PtrToStructure 逐一封送。Student 陣列內嵌於 Class 中,PtrToStructure 會遞歸處理內層陣列,前提是大小與佈局完全匹配。unsafe 指標直接存取以減少封送開銷。finally 區塊或 using 模式中釋放 AllocHGlobal 記憶體,避免洩漏。#pragma pack 改變對齊,C# 需對應 [StructLayout(LayoutKind.Sequential, Pack = n)]。