如何通过TCP连接将Windows端C#结构体发送至VxWorks的C服务端
实现方案
你当前的C#结构体定义存在类型不匹配、内存布局不兼容的问题,直接序列化发送会导致C端完全无法解析,需要按以下步骤调整:
核心逻辑说明
跨语言传输原生C结构体的核心是保证两端内存布局完全一致,不需要用通用序列化框架(Json、Protobuf、BinaryFormatter等),这类框架输出的格式和C原生内存布局不兼容,无法对接不可修改的C服务端。你需要对齐以下几个维度:
- 基础类型长度完全匹配
- 字段顺序完全一致
- 结构体对齐/填充字节完全一致
- 字节序(大小端)完全一致
- 定长数组、字符串的内存格式完全匹配
现有代码的错误点
- C#的
long是8字节有符号整数,而32位VxWorks(工业场景绝大多数GPIB设备运行的环境)下ANSI C的long是4字节有符号整数,类型长度直接不匹配 - C#的
string是引用类型,内存布局和C端定长char[100]完全不同 - 没有显式指定结构体的内存布局、对齐规则,C#默认的结构体布局可能和C端编译器生成的布局存在差异
- 没有显式指定枚举的基础类型长度,可能和C端enum默认的4字节int长度不匹配
正确实现代码
首先引入必须的命名空间:
using System; using System.Runtime.InteropServices; using System.Text;
第一步:定义和C端完全匹配的结构体
// 显式指定枚举基础类型为4字节int,和C端enum默认长度一致 public enum TYPE_OP : int { START_INITIALIZE = 0, STOP_INITIALIZE = 1, IDENTIFY = 2, GET_VALUE = 3, SET_VALUE = 4, SET_RSE_MODE = 5, GET_VALUE_DOUBLE = 6, SET_VALUE_DOUBLE = 7, SET_STRING = 8, GET_VALUE_FLOAT = 9, SET_VALUE_FLOAT = 10, SET_TARGET = 11, GET_TARGET = 12, GET_TASK_STATUS = 13 } // 注意:如果你的VxWorks是64位版本,C端long为8字节,将结构体中两个int字段替换为long即可 [StructLayout(LayoutKind.Sequential, Pack = 8)] public struct DESCR_DATA { public int addr; // 对应C端long addr public int value; // 对应C端long value public double dvalue; } [StructLayout(LayoutKind.Sequential, Pack = 8, CharSet = CharSet.Ansi)] public struct VAR_DATA { public int len; public TYPE_OP type; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)] // 对应C端定长char[100] public byte[] name; public DESCR_DATA data; }
说明:
Pack = 8对应C编译器默认的自然对齐规则,如果你的C端代码用#pragma pack修改了对齐值,将Pack改为对应数值即可(比如1字节对齐就设为Pack=1)。
第二步:实现结构体转二进制字节数组的方法
public byte[] StructToBytes<T>(T obj) where T : struct { int structSize = Marshal.SizeOf<T>(); byte[] result = new byte[structSize]; IntPtr unmanagedPtr = IntPtr.Zero; try { unmanagedPtr = Marshal.AllocHGlobal(structSize); Marshal.StructureToPtr(obj, unmanagedPtr, false); Marshal.Copy(unmanagedPtr, result, 0, structSize); } finally { if (unmanagedPtr != IntPtr.Zero) Marshal.FreeHGlobal(unmanagedPtr); } // 注意:如果你的VxWorks运行在PowerPC等大端架构上,需要在这里对所有多字节类型(int、enum、double、long)做字节序翻转 // Windows默认是小端序,和x86/ARM架构的VxWorks字节序一致,不需要转换 return result; }
第三步:结构体赋值与发送
// 初始化结构体,定长数组必须提前分配空间 VAR_DATA vardata = new VAR_DATA(); vardata.name = new byte[100]; // 按业务需求赋值 vardata.len = Marshal.SizeOf<VAR_DATA>(); // len字段一般填结构体总长度,按实际协议要求调整 vardata.type = TYPE_OP.GET_VALUE; // 字符串转ASCII字节拷贝到定长数组,最长不超过99字节,保留最后一位的0终止符 byte[] nameBytes = Encoding.ASCII.GetBytes("GPIB_Device_01"); Array.Copy(nameBytes, vardata.name, Math.Min(nameBytes.Length, 99)); vardata.data.addr = 1; vardata.data.value = 0; vardata.data.dvalue = 3.1415926; // 转成可直接发送的二进制数组 byte[] sendBuffer = StructToBytes(vardata); // 调用你的Socket/串口发送逻辑,把sendBuffer发给VxWorks服务端即可
调通前必须验证的点
- 对比两端结构体大小:C#端调用
Marshal.SizeOf<VAR_DATA>()拿到的长度,必须和VxWorks端调用sizeof(struct VAR_DATA)拿到的长度完全相等 - 对比字段偏移:C#端用
Marshal.OffsetOf<VAR_DATA>(nameof(字段名))拿到的每个字段偏移,必须和VxWorks端用offsetof(struct VAR_DATA, 字段名)拿到的偏移完全相等 - 确认字节序:如果VxWorks是大端架构,必须对每个多字节字段做字节序翻转,否则数值会完全错误
- 字符串拷贝长度不要超过99字节,避免覆盖后续字段的内存
内容的提问来源于stack exchange,提问作者Massimo Costanzo
相关产品推荐
相关产品推荐

