在 C# 中序列化这些值以在 C++ 中作为已知结构正确读取时遇到问题

Posted

技术标签:

【中文标题】在 C# 中序列化这些值以在 C++ 中作为已知结构正确读取时遇到问题【英文标题】:Having trouble serializing these values in C# to be properly read in C++ as a known struct 【发布时间】:2019-07-18 18:09:35 【问题描述】:

另一个组织为我们提供了一个程序,该程序从多播源收集数据并整理和保存该数据。它需要一个格式如下的 C++ 结构:

#define SP_PACKET_SIZE 200
#define NAME_SIZE 64
struct spPacketStruct

    int Size;
    char Name[SP_PACKET_SIZE][NAME_SIZE];
    double Value[SP_PACKET_SIZE];
;

显然我不能在 C# 中使用这个结构,因为一个结构不能有预初始化的数组,所以我想创建单独的位并序列化它们。所以现在我在 C# 中有这个:

int SpPacketSize;
char[,] SpNames = new char[SP_PACKET_SIZE, NAME_SIZE];
double[] SpValues = new double[SP_PACKET_SIZE];

我之前的经验是使用 BinaryWriter...我不需要在 C# 中反序列化,我只需要将它放到 C++ 程序中。我的测试序列化代码如下:

System.IO.MemoryStream outputstream = new System.IO.MemoryStream();

BinaryFormatter serializer = new BinaryFormatter();
serializer.TypeFormat = System.Runtime.Serialization.Formatters.FormatterTypeStyle.TypesWhenNeeded;

serializer.Serialize(outputstream, SpPacketSize);
serializer.Serialize(outputstream, SpNames);
serializer.Serialize(outputstream, SpValues);

byte[] buffer = outputstream.GetBuffer();

udpclient.Send(buffer, buffer.Length, remoteep);

我得到一个二进制数据包,但长度不正确,因为它仍然包括类型格式。当我在 Wireshark 中查看这个数据包时,我在它的开头看到了一个 System.Int32 符号。这使得数据包比预期的大,因此在 C++ 端没有正确反序列化。

我添加了 TypesWhenNeeded TypeFormat 以为我可以最小化它,但它没有改变……我注意到没有不使用 TypeFormat 的选项,除非我在某个地方错过了它。

是否有人对如何在没有额外信息的情况下正确序列化此数据有任何提示?

【问题讨论】:

***.com/questions/13853129/… 是您正在寻找的... 这似乎并没有帮助,不......问题不在于我需要在二进制输出中添加一些东西,问题是我需要 TypeFormat 不存在。 int 的第一个 dat 点是数据包大小计数,它告诉 C++ 程序尝试从数据包中读取多少个名称/双精度对。 注意:使用限制作为数据协议可能会很麻烦。例如,int 的大小或字节顺序并不总是相同。填充可以注入不同数量的死区。读取数据、解析并放入结构中,这很酷,但直接读取结构是获得令人讨厌的惊喜的好方法。 在双方建立并实施您自己的协议。仅当帮助类完全按照您的要求进行时才使用它们。您是否考虑过使用像 Protocol Buffers 这样的工具来为您完成繁重的工作? 我对接收方没有任何控制权。我没有那个代码。他们确实向我发送了一个 c++ 类,用于在 c++ 中发送给它,它在 C++ 中运行良好,但我需要能够从 C# 发送给它。我现在只是在玩Sharpserializer,但它实际上使数据包变得更大并且炸毁了我的套接字缓冲区。我觉得这不应该这么难...我只需要按正确的顺序获取 1 和 0...如果有帮助,我可以分享用于填充 C++ 版本中的结构的代码? 【参考方案1】:

虽然您不能预初始化结构字段,也不能直接将大小放入 ByValue 二维数组的 MarshalAs 属性中,但您可以做一些变通方法。您可以像这样定义两个结构:

const short SP_PACKET_SIZE = 200;
const short NAME_SIZE = 64;

struct spPacketStruct

  public int Size;
  [MarshalAs(UnmanagedType.ByValArray, SizeConst = SP_PACKET_SIZE)]
  private fixedString[] names;
  public fixedString[] Names  get  return names ?? (names = new fixedString[SP_PACKET_SIZE]);  
  [MarshalAs(UnmanagedType.ByValArray, SizeConst = SP_PACKET_SIZE)]
  private double[] value;
  public double[] Value  get  return value ?? (value = new double[SP_PACKET_SIZE]);  


struct fixedString

  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = NAME_SIZE)]
  public string Name;

通过将附加结构作为原始结构的成员,您可以通过将原始结构中的SizeConst 设置为第一个维度并将其设置为新结构中的第二个维度来指定两个维度的长度.将字段设为私有并为其创建属性只是为了方便,因此您不必在创建结构时自己分配数组。 然后你可以像这样序列化/反序列化结构(来自这个答案的代码:https://***.com/a/35717498/9748260):

public static byte[] GetBytes<T>(T str)

  int size = Marshal.SizeOf(str);
  byte[] arr = new byte[size];
  GCHandle h = default;

  try
  
    h = GCHandle.Alloc(arr, GCHandleType.Pinned);
    Marshal.StructureToPtr(str, h.AddrOfPinnedObject(), false);
  
  finally
  
    if (h.IsAllocated)
    
      h.Free();
    
  

  return arr;


public static T FromBytes<T>(byte[] arr) where T : struct

  T str = default;
  GCHandle h = default;

  try
  
    h = GCHandle.Alloc(arr, GCHandleType.Pinned);
    str = Marshal.PtrToStructure<T>(h.AddrOfPinnedObject());
  
  finally
  
    if (h.IsAllocated)
    
      h.Free();
    
  

  return str;

最后一件事是在尝试像这样序列化/反序列化结构时,请注意结构对齐,因为它可能会弄乱结构大小

【讨论】:

我会在早上到办公室时试一试...非常有希望,谢谢!已投赞成票,我会在测试后标记为已接受。 这可能是一个愚蠢的问题,但我如何正确填充结构的值?我是否继续填充我的非结构变量并以某种方式将它们复制到结构中?另外,string 和 char 数组的序列化会有区别吗? m_spPacket.Name[0].Name = "你好"; m_spPacket.Value[0] = 12345; @RobbieP。是的,就像这样,你在m_spPacket.Name's'[0] 错过了一个“s”。我假设 char 数组表示一个字符串,因为它包含名称,所以我继续这样做。如果你真的需要它是 char 数组,那么将第二个结构内容更改为:[MarshalAs(UnmanagedType.ByValArray, SizeConst = NAME_SIZE)] public char[] Name; 您会很高兴知道我得到了您的建议!我不得不在默认初始化程序之后放置一个 (GCHandle),但除此之外,它运行良好,我要回家享受我的周末,不再为这个问题感到压力!

以上是关于在 C# 中序列化这些值以在 C++ 中作为已知结构正确读取时遇到问题的主要内容,如果未能解决你的问题,请参考以下文章

从 localStorage 获取值以在 Sencha Touch AJAX 代理中使用

在 C# 程序中嵌入外部可执行文件

在 C# 程序中嵌入外部可执行文件

c# - 如何调用分配输出缓冲区以在c#中返回数据的非托管c++函数?

索引空值以在 DB2 上快速搜索

如何导出 C# dll 方法/函数以在 C++ 中使用它 [重复]