You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 23:06:24