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)

 

posted @ 2025-12-25 08:16  ZHIZRL  阅读(3)  评论(0)    收藏  举报