将 C++ 结构转换为 C# 以用于 UDP 通信

Posted

技术标签:

【中文标题】将 C++ 结构转换为 C# 以用于 UDP 通信【英文标题】:Converting C++ struct to C# for use with UDP communication 【发布时间】:2015-11-17 13:06:15 【问题描述】:

我正在编写一个通过 UDP 与 C++ 程序通信的程序。另一个程序已经写好了(不是我写的)。我得到了一个 .h 文件,它定义了两个用于数据的结构。

编辑:这意味着我无法更改数据格式。我需要能够根据 C++ .h 文件结构进行读写!

我将如何在 C# 中执行此操作?我将以这种格式发送和接收数据。

struct mdata

  uint32_t  mark_kupnr;
  uint16_t  mark_provnr;
  uint16_t  markriktning;
  uint16_t  xpos;
  uint16_t  ypos;
;

typedef struct  

  uint32_t      kupnr;
  uint16_t      lngd;
  uint16_t      bredd;
  uint16_t      tjocklek;
  char          slagkraft;
  uint8_t       antal;
  struct mdata  mark[10];
 markdata;

编辑:我尝试在 C# 中创建相应的结构,但它不起作用

顺便说一句,我在 Windows 上运行,而 C++ 程序在 Linux 上运行。在“规范”中说数据应该是小尾数。

struct mdata

  UInt32    mark_kupnr;
  UInt16    mark_provnr;
  UInt16    markriktning;
  UInt16    xpos;
  UInt16    ypos;
;

typedef struct  

  UInt32        kupnr;
  UInt16        lngd;
  UInt16        bredd;
  UInt16        tjocklek;
  char          slagkraft;
  byte          antal;
  // here I have some trouble
  mdata[]   mark[10]; //???
 markdata;

【问题讨论】:

C++ char 是 C# sbyte OP 还应该检查 C++ 中结构打包的有效设置,并在必要时使用 [StructLayout(Pack=)] 在 C# 中匹配它。 @dxiv:虽然这通常是一个问题,但无论包装设置如何,这个问题中的结构都不需要填充。元素已经自然对齐。 我已经更新了我的答案。 【参考方案1】:

听起来好像您正在寻找fixed keyword。 另外C#中的char的大小是C++中char的两倍,所以你需要使用正确的对应类型sbyte

这是你的第二个结构应该定义的方式:

struct markdata

  UInt32        kupnr;
  UInt16        lngd;
  UInt16        bredd;
  UInt16        tjocklek;
  sbyte         slagkraft;
  byte          antal;
  fixed   mdata mark[10];

如果您得到错误的值,请检查它们是否被字节交换。但根据使用的系统和数据格式规范,默认情况下字节序很可能是正确的。

【讨论】:

不幸的是,我收到此错误:错误 1 ​​固定大小缓冲区类型必须是以下之一:bool、byte、short、int、long、char、sbyte、ushort、uint、ulong、float 或 double .【参考方案2】:

如果您将结构序列化为与平台无关的格式,则可以避免所有容易出错和低级的数据对齐问题。

考虑使用Google Protocol Buffers 或Apache Thrift 自动生成序列化/反序列化代码。这些在多种语言和数据类型中巧妙地工作,被广泛使用(经过测试和调试)。这些库中的每一个都要求您以类似于Microsoft IDL 的格式保留结构的主要蓝图。然后您可以轻松地为任何语言生成互操作代码。

就我个人而言,对于这两个库,我在生成的 C++ 代码对 STL 的依赖方面遇到了问题(尽管对于大多数项目来说这不是问题),所以我选择将数据序列化为 JSON format并通过网络以 0 结尾的字符串形式发送/接收 JSON。我使用了只有标头的小型 RapidJSON C++ 库,并使用 RapidJSON 手动实现了序列化和反序列化代码。

【讨论】:

如上所述,我们必须连接到现有的数据格式,所以 JSON 和 XML 不是答案。 您可能是对的,但是“其他程序”可以/将被更改的可能性很小。但是,是的,看起来我已经回答了一个不同的问题。【参考方案3】:

如果您的 C++ 程序主机是 IBM-PC 兼容机器,您必须在 C# 客户端上执行字节交换。如果不是,那么你一定不要。

要与 C# 程序通信,您必须反转您读取的大于 1 字节的数据,因为 C# 使用大端字节序而不是原生 C++ 的小端字节序。要交换 WORD(16 位)和 DWORD(32 位),您可以使用此宏

// BYTE SWAPING IN WORD and DWORD

#define WORD_SWAP(x)    ((((x) >> 8) & 0x00ff) | (((x) << 8) & 0xff00))

#define DWORD_SWAP(x)   ((((x) >> 24) & 0x000000ff) | \
                         (((x) >> 8) & 0x0000ff00) | \
                         (((x) << 8) & 0x00ff0000) | \
                         (((x) << 24) & 0xff000000))

或 C# 中的类似函数。仅适用于单独的结构字段。

PS:不要忘记编译器打包功能以纠正双方的数据含义。

欲了解更多信息,请访问Endianness、Data structure alignment

所以人们要求我在 C# 中做同样的事情

// Swaps uint16_t
ushort Swap(ushort x)

     return (ushort)((((x) >> 8) & 0x00ff) | (((x) << 8) & 0xff00));


// Swaps uint32_t
uint Swap(uint x)

     return ((((x) >> 24) & 0x000000ff) |
             (((x) >> 8) & 0x0000ff00) |
             (((x) << 8) & 0x00ff0000) |
             (((x) << 24) & 0xff000000));

使用 C# 结构 client 表示 UDPClient 对象。这一切都必须以低级方式执行。

struct mdata

  UInt32  mark_kupnr;
  UInt16  mark_provnr;
  UInt16  markriktning;
  UInt16  xpos;
  UInt16  ypos;


// example of mdata read with sockets
mbata data;
var bytes = client.Receive()

data.mark_kupnr = Swap(BitConverter.ToUInt32(bytes, 0));
data.mark_provnr = Swap(BitConverter.ToUInt16(bytes, 4));
data.markriktning = Swap(BitConverter.ToUInt16(bytes, 6));
data.xpos = Swap(BitConverter.ToUInt16(bytes, 8));
data.ypos = Swap(BitConverter.ToUInt16(bytes, 10));

您必须将表单输出数据从UInt32UInt16 反转为字节数组。比使用List&lt;byte&gt;.AddRange(variable_byte_array); 以正确的顺序合并所有这些而不是将此形成的列表转换为字节数组并使用UDPClient.Send(formed_array);

C++ 中的数组以 [0][1][2][3][4][5][6][7][8][9] 的顺序放置在内存中。 所以你必须从 0 到 9 依次读取 mdata 结构 10 次。

// read first data
typedef struct  

  UInt32        kupnr;
  UInt16        lngd;
  UInt16        bredd;
  UInt16        tjocklek;
  sbyte          slagkraft;
  byte          antal;
  // now read mkdata 10 times
  struct mdata  mark[10];
 markdata; // we have done with markdata

没有人要求你使用类而不是结构。考虑到我们完全使用字节(二进制)序列化,处理数组会更简单。比为每个类添加适当的方法(例如)

mdata 类或结构方法

void Receive(UDPClient client)

    var bytes = client.Receive()

    mark_kupnr = Swap(BitConverter.ToUInt32(bytes, 0));
    mark_provnr = Swap(BitConverter.ToUInt16(bytes, 4));
    markriktning = Swap(BitConverter.ToUInt16(bytes, 6));
    xpos = Swap(BitConverter.ToUInt16(bytes, 8));
    ypos = Swap(BitConverter.ToUInt16(bytes, 10));


void Send(UDPClient client)

    var listOfBytes = new List<byte>();

    listOfBytes.AddRange(BitConverter.GetBytes(Swap(mark_kupnr)));
    listOfBytes.AddRange(BitConverter.GetBytes(Swap(mark_provnr)));
    listOfBytes.AddRange(BitConverter.GetBytes(Swap(markriktning)));
    listOfBytes.AddRange(BitConverter.GetBytes(Swap(xpos)));
    listOfBytes.AddRange(BitConverter.GetBytes(Swap(ypos)));

    client.Send(listOfBytes.ToArray(), listOfBytes.Count);


void Receive(byte[] bytes)
      
    mark_kupnr = Swap(BitConverter.ToUInt32(bytes, 0));
    mark_provnr = Swap(BitConverter.ToUInt16(bytes, 4));
    markriktning = Swap(BitConverter.ToUInt16(bytes, 6));
    xpos = Swap(BitConverter.ToUInt16(bytes, 8));
    ypos = Swap(BitConverter.ToUInt16(bytes, 10));


void Send(out byte[] bytes)

    var listOfBytes = new List<byte>();

    listOfBytes.AddRange(BitConverter.GetBytes(Swap(mark_kupnr)));
    listOfBytes.AddRange(BitConverter.GetBytes(Swap(mark_provnr)));
    listOfBytes.AddRange(BitConverter.GetBytes(Swap(markriktning)));
    listOfBytes.AddRange(BitConverter.GetBytes(Swap(xpos)));
    listOfBytes.AddRange(BitConverter.GetBytes(Swap(ypos)));

    bytes = listOfBytes.ToArray();

markdata 类中的数组不应该是固定的,因此您只需在类中创建所需数量的元素。所有脏活都会交给 send 和 receive 方法。

要处理数组,您可以添加字节移位值以逐步遍历mdata 数组。

【讨论】:

little-endian of native C++ C++ 没有字节序,它只依赖于下面的平台(CPU ...)。根据其余部分,它可以是任何字节序。 已经为 OP 定义了 C++ 端。他需要处理 C# 方面,所以你的回答有点无关紧要 @Peter M:交换字节在双方都有效意味着S(S(M)) = M @Peter M:C# 的工作方式与 c++ 宏的使用函数 instread 完全相同。 当 OP 正在寻找 C# 解决方案时,您已经提出了交换字节的 C++ 解决方案。他正在寻找的不仅仅是字节交换。当涉及到它时,您应该使用标准宏而不是自己滚动

以上是关于将 C++ 结构转换为 C# 以用于 UDP 通信的主要内容,如果未能解决你的问题,请参考以下文章

如何将结构从 C++ 迁移到 C#

如何将 C++ 结构转换为 C# 结构

通过 UDP 的 C++ 类在 C# 中使用,都有哪些选项?

将项目从 C++ 转换为 C#

将 C# 方法转换为 C++ 方法

将结构列表从 c# 转换为 c++