OPC UA ExtensionObject序列化问题:动态对象回写PLC标签遇字节差异
解决OPC UA ExtensionObject序列化与原始读取字节不匹配的问题
我基于OPCFoundation.NetStandard.Opc.Ua开发PLC标签读写包装库,标量及标量数组读写功能正常,但ExtensionObject类型写入时遇到序列化格式不匹配问题:
- 读取时可将ExtensionObject转为
ExpandoObject:dynamic tagValue = await plc.ReadAsync<dynamic>(someTag); - 修改动态对象后回写失败,对比原始读取字节与自序列化字节,发现格式差异:
原始读取十六进制:
00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-01-00-00-00-00-00-09-00-00-00-4E-6F-20-41-6C-61-72-6D-73-00
自序列化十六进制:
00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-01-00-00-00-00-00-4E-6F-20-41-6C-61-72-6D-73-00
额外字节的来源分析
原始字节中的09-00-00-00是UA字符串类型的长度前缀(小端序UInt32),对应后续字符串"No Alarms"的UTF-8字节数(9字节)。你的自序列化结果丢失了这个长度前缀,导致格式不匹配。
结合你描述的父ExtensionObject结构(3个子对象:8个bool、10个bool、1个bool+2个int+1个string),原始字节的完整结构符合UA规范:
- ExtensionObject头部:16字节Null NodeId(表示使用默认类型)+ 1字节编码标志(
01表示Body为字节数组) - Body内容:
- 8个bool:打包为1字节(UA布尔数组按位压缩)
- 10个bool:打包为2字节(8位+2位,补0对齐)
- 第三个子对象:1字节bool + 8字节(2个int) + 4字节长度前缀 + 9字节字符串 + 1字节终止符
解决方案
核心问题
ExpandoObject丢失了UA结构体的类型元数据(字段顺序、编码规则、类型定义),无法还原原始序列化逻辑。
推荐方案:使用强类型结构体
- 生成强类型类:通过UA服务器的DataType节点信息,用OPC UA代码生成工具(如官方的
ModelCompiler)生成对应结构体的C#类。 - 读取解析:直接读取为
ExtensionObject,再通过UA的BinaryDecoder解析为强类型对象:var extensionObj = await plc.ReadAsync<ExtensionObject>(someTag); var decoder = new BinaryDecoder(extensionObj.Body, plc.Session.MessageContext); var myStruct = decoder.ReadStructure<MyCustomStructure>(); - 修改并重序列化:修改强类型对象属性后,用
BinaryEncoder重新序列化为ExtensionObject:myStruct.AlarmText = "Updated Alarm"; var encoder = new BinaryEncoder(plc.Session.MessageContext); encoder.WriteStructure(myStruct); var newBody = encoder.CloseAndReturnBuffer(); var newExtensionObj = new ExtensionObject(extensionObj.TypeId, newBody); await plc.WriteAsync(someTag, newExtensionObj);
临时方案:手动实现序列化规则
若无法生成强类型,需手动按照UA规范处理每个字段:
- 布尔数组:按位打包,8个bool占1字节,不足补0
// 序列化8个bool bool[] bools = new bool[8]; byte serializedByte = 0; for (int i = 0; i < 8; i++) { if (bools[i]) serializedByte |= (byte)(1 << i); } - 字符串:先写小端序UInt32长度前缀,再写UTF-8字节
string text = "No Alarms"; byte[] textBytes = Encoding.UTF8.GetBytes(text); using (var ms = new MemoryStream()) { var writer = new BinaryWriter(ms); writer.Write((uint)textBytes.Length); // 长度前缀 writer.Write(textBytes); byte[] serializedString = ms.ToArray(); }
内容的提问来源于stack exchange,提问作者Logan K
相关产品推荐
相关产品推荐

