如何在C#中创建固定大小的字节数组用户类型?

11
我正在将一个旧的Visual BASIC程序转换为C#。它通过以太网向一些工业设备发送消息。为此,它从用户定义的固定大小块中组装字节流。
这些块中大多数都很小,在C#中,可以轻松地创建几个字节或整数的结构体,并使用StructLayout来控制它们的大小和布局,例如:
[StructLayout(LayoutKind.Sequential, Pack = 1)]

因此,当我们进入非托管空间执行按字节复制时,就不会出现字节顺序或填充问题。

但是,一些VB6结构是大型数组,例如,

Private Type SEND_MSG_BUFFER_320_BYTES
    bytes(0 To 319) As Byte  '320 bytes
End Type

我正在努力找到如何在C#中实现这一点。我可以在一个类中创建一个固定大小的数组,例如

  [StructLayout(LayoutKind.Sequential, Pack = 1)]
  public class SOME_BYTES
  {
      public byte[] b = new byte[320];
  } 

但是要进行按字节复制,我需要能在运行时发现其大小,而System. Runtime.InteropServices.Marshal.SizeOf返回4

如有建议将不胜感激。

3个回答

14

如果你愿意使用不安全代码并将你的类更改为结构体,你可以使用 固定大小缓冲区

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public unsafe struct SomeBytes
{
    public fixed byte MessageData[320];
}

就我个人而言,如果可能的话,我会试着避免所有这些问题。如果你只是在网络上发送数据,为什么需要“进入非托管空间”呢?你能否以某种方式消除这种要求?(也许这是基本的 - 但从你的问题中并不清楚。)


有超过50个不同的消息可以发送到机器,每个消息都是在运行时从不同类型的单个结构体组装而成。一个结构体可能有一个double、一个16位uint、几个字节等等。进入非托管空间使得按字节复制变得容易。更多细节请参见我之前的问题http://stackoverflow.com/questions/14485653/copying-different-structs-to-byte-arrays。 - user316117
@user316117:从我的经验来看,逐字节复制在短期内可能会使事情变得更简单,但在长期内很难迁移到不同的格式。我通常更喜欢一种更灵活的序列化方法,例如协议缓冲区。 - Jon Skeet
我查看了 Protocol Buffer 的文档,链接为 https://developers.google.com/protocol-buffers/docs/overview ,发现通信双方都需要知道该协议。我们正在与的工业设备不是个人电脑;没有办法编程让其接受新的或不同的格式。我已经与管理层讨论过,未来是否会想要迁移到新的或不同的格式,答案是明确的“不”。 - user316117
@user316117:我认为你在谈论将数据转换在客户机上。我仍然不会强制将内存布局与网络上的布局相同。当然,这是你的选择... 只是我不会做出这样的选择。 - Jon Skeet
鉴于网络布局已经确定,且.Net Socket.Send方法需要一个包含该数据的缓冲区,那么有什么替代方案呢? - user316117
1
@user316117:当您想要序列化时,构建字节数组,并且仅在该点使用BinaryWriter。在我看来,这使得格式更加清晰。 - Jon Skeet

11

您可以使用固定大小的数组:

unsafe struct SomeBytes {
    public fixed byte b[320];
}

1
嗯,这似乎可以工作。我不熟悉"unsafe"关键字,并发现要使用它,您必须在项目的构建属性中使用"/unsafe"进行构建,这会关闭整个程序的边界检查和其他一些东西。为我编写并完全了解的一些方法进入不安全模式是一回事,但我有点担心为近200K行代码的整个程序设置"/unsafe"。其他人怎么想? - user316117
5
@user316117 /unsafe并不会改变项目中的任何内容,只是允许使用不安全的代码。 - ghord

9
我认为你想要做的是这样的:
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public class SOME_BYTES
    {
        [MarshalAs(UnmanagedType.ByValArray, SizeConst=320)]
        public byte[] b;
    } 

您可以按照以下方式进行初始化:
SOME_BYTES data = new SOME_BYTES {b = new byte[320]};

然后,您可以填充data.b[]并使用序列化来获取要发送的数据。MarshalAs属性告诉编排器在编排数据时要使用什么固定大小的缓冲区。

您不需要使用不安全的fixed关键字来做这种事情,我强烈建议您避免使用它。


这个就是解决办法。我肯定得多了解一下整个InteropServices / MarshallAs服务范围,因为看起来在这个项目中我会深陷其中! - user316117

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接