當“C#默認封送 ≠ 你想要的 C++ 內存佈局”時,需要在結構體設置[MarshalAs]特性
【不需要】MarshalAs 的類型(⭐最安全)
這些是 blittable types,CLR 可以直接 memcpy
基礎數值類型
|
C#
|
C++
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public int x;
public long ticks;
public double angle;
結構體(滿足條件)
✔ 字段都是 blittable
✔ 順序一致
✔ Pack 一致
[StructLayout(LayoutKind.Sequential)]
struct A
{
public int x;
public double y;
}
為什麼結構體要設置[StructLayout(LayoutKind.Sequential)]
如果不寫 StructLayout
//結構體
public struct Test
{
public byte a;
public int b;
}
CLR 可能按以下方式排
a | padding | padding | padding | b
或者
b | a | padding...
所以 C# 必須顯式告訴 CLR:不要亂排
LayoutKind 三個枚舉的含義
LayoutKind.Sequential
字段按照“聲明順序”依次排列,CLR 只會插入必要的 padding
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct A
{
public byte a;
public int b;
}
Pack=1時
內存:
a | b
LayoutKind.Explicit
手動指定每個字段的內存偏移
[StructLayout(LayoutKind.Explicit)]
struct A
{
[FieldOffset(0)] public byte a;
[FieldOffset(4)] public int b;
}
內存:
a | pad | pad | pad | b
LayoutKind.Auto
CLR 自己決定字段順序和對齊
[StructLayout(LayoutKind.Auto)]
struct A
{
public byte a;
public int b;
}
JIT 可以隨時改變佈局, 僅限純託管代碼使用。
|
LayoutKind
|
順序可控
|
可用於 C++ 互操作
|
使用頻率
|
|
Sequential
|
✅
|
✅
|
⭐⭐⭐⭐⭐
|
|
Explicit
|
✅(手動)
|
✅
|
⭐⭐
|
|
Auto
|
❌
|
❌
|
❌
|
Pack 和 LayoutKind 配套使用
Pack 控制“對齊粒度”
|
Pack
|
對齊粒度
|
常見用途
|
|
1
|
1 字節
|
SDK / 網絡 / 文件 / PInvoke
|
|
2
|
2 字節
|
老協議
|
|
4
|
4 字節
|
Win32 API
|
|
8
|
8 字節
|
CLR 默認
|
|
16
|
16 字節
|
SIMD(少見)
|
Pack 必須和 C++ 的 #pragma pack
一個你很可能會遇到的真實例子
C++
#pragma pack(push, 1)
struct Data
{
uint8_t flag;
int32_t value;
};
#pragma pack(pop)
C# 錯誤
[StructLayout(LayoutKind.Sequential)]
struct Data
{
public byte flag;
public int value;
}
value 會被對齊到 4 字節,錯 3 個字節
正確:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct Data
{
public byte flag;
public int value;
}
Pack = 1 = “按字節對齊,不補空隙”
【必須】MarshalAs 的類型(⭐重點)
數組(最常見)
固定數組(結構體字段)
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public int[] values;
C++:
int32_t values[8];
沒有 MarshalAs → 變成指針
字符數組(C 風格字符串)
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string name;
C++:
char name[32];
string(非常重要)
ANSI 字符串
[MarshalAs(UnmanagedType.LPStr)]
string text;
Unicode 字符串
[MarshalAs(UnmanagedType.LPWStr)]
string text;
不寫的話默認行為經常不符合 C++ 期望
bool(極易出錯)
錯誤直覺
public bool flag; // ❌
正確(C++ bool = 1 byte)
[MarshalAs(UnmanagedType.I1)]
public bool flag;
C++:
bool flag; // 1 byte
enum(強烈建議指定)
public enum Status : int
{
OK = 0,
NG = 1
}
明確指定底層類型,不用 MarshalAs,但 必須指定 : int
一個完整正確示例
|
類型
|
是否 MarshalAs
|
|
|
❌
|
|
|
❌
|
|
|
✅ ByValArray
|
|
|
✅ I1
|
|
|
✅ LPStr / LPWStr
|
|
|
❌(但指定底層類型)
|
C#
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct CameraParam
{
public int width;
public int height;
[MarshalAs(UnmanagedType.I1)]
public bool enable;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public ROIStruct_t[] rois;
}
C++
#pragma pack(push, 1)
struct CameraParam
{
int32_t width;
int32_t height;
bool enable;
ROIStruct_t rois[8];
};
#pragma pack(pop)