You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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规范:

  1. ExtensionObject头部:16字节Null NodeId(表示使用默认类型)+ 1字节编码标志(01表示Body为字节数组)
  2. Body内容:
    • 8个bool:打包为1字节(UA布尔数组按位压缩)
    • 10个bool:打包为2字节(8位+2位,补0对齐)
    • 第三个子对象:1字节bool + 8字节(2个int) + 4字节长度前缀 + 9字节字符串 + 1字节终止符

解决方案

核心问题

ExpandoObject丢失了UA结构体的类型元数据(字段顺序、编码规则、类型定义),无法还原原始序列化逻辑。

推荐方案:使用强类型结构体

  1. 生成强类型类:通过UA服务器的DataType节点信息,用OPC UA代码生成工具(如官方的ModelCompiler)生成对应结构体的C#类。
  2. 读取解析:直接读取为ExtensionObject,再通过UA的BinaryDecoder解析为强类型对象:
    var extensionObj = await plc.ReadAsync<ExtensionObject>(someTag);
    var decoder = new BinaryDecoder(extensionObj.Body, plc.Session.MessageContext);
    var myStruct = decoder.ReadStructure<MyCustomStructure>();
    
  3. 修改并重序列化:修改强类型对象属性后,用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 22:23:24