【问题标题】:How do I make fixed-size byte array user type in C#?如何在 C# 中制作固定大小的字节数组用户类型?
【发布时间】:2013-01-31 23:25:30
【问题描述】:

我正在将旧的 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];
  } 

但要进行逐字节复制,我需要能够在运行时发现 this 的大小,并且 System.Runtime.InteropServices.Marshal.SizeOf 为此返回 4

任何关于如何做到这一点的建议将不胜感激。

【问题讨论】:

    标签: c#


    【解决方案1】:

    如果您可以使用不安全的代码并将您的类更改为结构,则可以使用fixed size buffers

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

    如果可能,我个人会尝试避免所有这些。如果您只是通过网络发送数据,为什么需要“进入非托管空间”?你能以某种方式删除这个要求吗? (也许这是基本的 - 但你的问题并不清楚。)

    【讨论】:

    • 有超过 50 种不同的消息可以发送到机器,每个消息在运行时由不同类型的单独结构组装而成。一个结构可能有一个双精度、一个 16 位 uint、几个字节等。进入非托管空间可以很容易地进行按字节复制。有关更多详细信息,请参阅我之前关于 stackoverflow.com/questions/14485653/… 的问题。
    • @user316117:根据我的经验,按字节复制往往会使事情在 短期 中变得更简单,但从长远来看,很难迁移到不同的格式.我通常更喜欢更灵活的序列化方法,例如协议缓冲区。
    • 我查看了developers.google.com/protocol-buffers/docs/overview 的协议缓冲区文档,看起来通信链路的两端都需要了解协议。我们正在谈论的工业设备不是PC,而是PC。没有办法对其进行编程以接受新的或不同的格式。我已经与管理层讨论过他们是否愿意在未来迁移到新的或不同的格式,答案是明确的“不”。
    • @user316117:我假设您在谈论在客户端计算机中转换数据。我仍然不会强制内存布局与网络布局相同。当然,您的选择……这不是我会做出的选择。
    • @user316117:当你想要序列化并且仅在那个时候构建字节数组,例如使用BinaryWriter。这使得格式更加清晰,IMO。
    【解决方案2】:

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

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

    【讨论】:

    • 嗯,这似乎确实有效。我不熟悉“unsafe”关键字,并发现要使用它,您必须使用“/unsafe”来构建项目的构建属性,这会关闭边界检查以及整个程序的其他内容。对于我编写并完全理解的一些方法进入不安全模式是一回事,但我对为近 200K 行代码的整个程序设置“/unsafe”有点紧张。其他人怎么看?
    • @user316117 /unsafe 不会改变项目中的任何内容,除非它允许使用不安全的代码。
    【解决方案3】:

    我想你想做这样的事情:

        [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 属性告诉编组器在编组数据时使用什么固定大小的缓冲区。

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

    【讨论】:

    • 这可以解决问题。我肯定需要阅读更多关于整个 InteropServices / MarshallAs 服务范围的内容,因为看起来我将在这个项目中全力以赴!
    猜你喜欢
    • 2022-11-02
    • 1970-01-01
    • 2017-09-29
    • 2020-04-15
    • 2020-08-14
    • 2018-03-31
    • 1970-01-01
    • 2011-07-11
    • 1970-01-01
    相关资源
    最近更新 更多