C#封送结构体数据给C++
当“C#默认封送 ≠ 你想要的 C++ 内存布局”时,需要在结构体设置[MarshalAs]特性
【不需要】MarshalAs 的类型(⭐最安全)
这些是 blittable types,CLR 可以直接 memcpy
基础数值类型
| C# | C++ |
|---|---|
int |
int32_t |
long |
int64_t |
short |
int16_t |
byte |
uint8_t |
float |
float |
double |
double |
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 |
|---|---|
long |
❌ |
double |
❌ |
ROIStruct_t[] |
✅ ByValArray |
bool |
✅ I1 |
string |
✅ LPStr / LPWStr |
enum |
❌(但指定底层类型) |
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)
浙公网安备 33010602011771号