C#如何将自定义类转为字节数组以写入PLC缓冲区?
C# 类转PLC字节流的序列化问题解决方案
问题背景
我正在用C#开发软件,需要将由基本类型组成的泛型类转换为字节流发送至PLC缓冲区。尝试了三种方案都有问题:
- BinaryFormatter:序列化后可还原原类,但生成的字节流包含大量元数据,找不到目标数据,且该类已被微软标记为弃用;
- BinaryWriter:功能正常,但因存在大量不同类声明,需通过反射动态处理,实现过于复杂,暂作为备选;
- TypeConverter:完全无效,运行时提示“TypeConverter无法转换X类”。
测试代码:
TYPE_S_TESTA test = new TYPE_S_TESTA(); test.b_tipo = 54; using (MemoryStream ms = new MemoryStream()) { BinaryFormatter bf = new BinaryFormatter(); // BinaryFormatter is deprecated bf.Serialize(ms, test); ms.Seek(0, SeekOrigin.Begin); byte[] test3 = ms.ToArray(); }
待序列化类定义:
[Serializable] public class TYPE_S_TESTA { public short b_tipo; public char b_modo; public char b_area; public char b_sorgente; public char b_destinatario; public short w_lunghezza; public short w_contatore; public short w_turno; public short w_tempo; }
预期得到包含值54的14字节数组,且序列化顺序与类定义一致,但实际得到512字节的缓冲区且未找到目标数据。
最优解决方案:StructLayout + Marshal 直接转换
针对PLC的字节流需求,最适合的方式是使用StructLayout特性强制成员的内存布局,再通过Marshal类直接将结构体(或类)转换为字节数组,既能保证顺序和大小精确,又无需复杂反射。
步骤1:调整数据结构与特性
由于C#默认的char是2字节UTF-16类型,而你预期的14字节总大小对应单字节的字符类型,因此需要通过MarshalAs特性指定字符按单字节处理,或直接将char改为byte类型。同时用StructLayout强制顺序和紧密排列(取消字节对齐)。
推荐将类改为结构体(更贴合PLC的C风格结构体场景):
using System.Runtime.InteropServices; [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct TYPE_S_TESTA { public short b_tipo; // 指定char按单字节ASCII处理,对应PLC的1字节字符 [MarshalAs(UnmanagedType.U1)] public char b_modo; [MarshalAs(UnmanagedType.U1)] public char b_area; [MarshalAs(UnmanagedType.U1)] public char b_sorgente; [MarshalAs(UnmanagedType.U1)] public char b_destinatario; public short w_lunghezza; public short w_contatore; public short w_turno; public short w_tempo; }
LayoutKind.Sequential:强制成员按定义顺序排列,保证和PLC缓冲区顺序一致;Pack = 1:取消字节对齐,避免自动添加填充字节,确保总大小为成员字节之和(25 + 14 = 14字节);MarshalAs(UnmanagedType.U1):将C#的双字节char转为单字节,匹配PLC的字符类型。
步骤2:转换为字节数组
使用Marshal类的方法直接将结构体转换为字节数组:
TYPE_S_TESTA test = new TYPE_S_TESTA(); test.b_tipo = 54; // 获取结构体的字节大小 int bufferSize = Marshal.SizeOf(typeof(TYPE_S_TESTA)); byte[] plcBuffer = new byte[bufferSize]; // 分配非托管内存并复制结构体数据 IntPtr unmanagedPtr = Marshal.AllocHGlobal(bufferSize); try { Marshal.StructureToPtr(test, unmanagedPtr, false); Marshal.Copy(unmanagedPtr, plcBuffer, 0, bufferSize); } finally { // 释放非托管内存 Marshal.FreeHGlobal(unmanagedPtr); } // plcBuffer即为14字节的目标字节流,顺序与结构体定义一致,包含54的二进制值
方案优势
- 精确控制:完全匹配PLC的字节顺序和大小要求,无额外元数据;
- 高效简洁:无需反射或复杂的手动序列化逻辑,适合大量不同结构的场景;
- 安全可靠:避免使用已弃用的
BinaryFormatter,不存在安全风险。
其他方案问题说明
- BinaryFormatter:它序列化的是.NET完整对象,包含类型元数据、版本信息等额外内容,因此生成的字节流远大于实际数据大小,且无法直接用于PLC缓冲区;
- TypeConverter:仅用于类型间的逻辑转换(如字符串转数值),不适合二进制序列化场景;
- BinaryWriter:若后续需要使用,可结合反射封装通用序列化方法,但复杂度远高于本方案。
内容的提问来源于stack exchange,提问作者Samuele Stefanoni
相关产品推荐
相关产品推荐

