跳到主要内容

结构体解码 (StructDecoder)

概述

OPC UA 1.04+ 把每个自定义结构体的字段定义放到 DataType 节点的 DataTypeDefinition 属性 (AttributeId=27, Part 5 §6.4.2)。客户端 Read 该属性 → 拿 StructureDefinition (Fields[]) → 缓存; 拿到 ExtensionObject 二进制后, 按字段顺序用 UA Binary 编码 (Part 6 §5.2) 反序列化为 字段名 → 值 字典。

StructDecoder 实现了完整的协议层:

  • 全部基本类型 (Boolean / Int / Float / Double / String / DateTime / Guid / ByteString / NodeId / ExpandedNodeId / StatusCode / QualifiedName / LocalizedText / XmlElement)
  • 嵌套结构体 (字段 DataType 指向另一个自定义结构, 自动递归)
  • 枚举 (按 Int32 解码)
  • 数组 (ValueRank ≥ 1)
  • Union (1-based selector)
  • Optional 字段 (UInt32 EncodingMask)

API

成员类别读写说明
new StructDecoder(ua)构造绑定到会话, 共享字段表缓存
decoder.Decode(Variant ext)方法解码 ExtensionObject, 返回 DynamicExtensionObject
decoder.DecodeBody(byte[] body, string dataTypeId)方法直接给字节流 + DataType 解码
decoder.GetDefinition(string dataTypeId)方法获取 / 缓存 StructDefinition
decoder.PrefetchDefinitions(IEnumerable<string>)方法预热缓存 (一次性 Read 多个 DataTypeDefinition)
DynamicExtensionObject[fieldName]索引器取字段值 (object)
DynamicExtensionObject.Fields属性字段名 → 值 字典
DynamicExtensionObject.Definition属性关联的 StructDefinition

相关结构:

public class StructDefinition
{
public string Name { get; } // 结构体名
public string DataTypeId { get; } // DataType NodeId
public string BaseDataType { get; } // 父类 NodeId
public StructureKind Kind { get; } // StructureKind 枚举
public IList<StructField> Fields { get; }// StructField 列表
public string DefaultEncodingId { get; } // Binary Encoding 节点
public bool IsUnion { get; } // 是否 Union
}

public enum StructureKind
{
Structure = 0, // 普通, 顺序固定
StructureWithSubtypedValues = 1, // 含子类型字段 (编码同 Structure)
StructureWithOptionalFields = 2, // 含可选字段, 前置 UInt32 EncodingMask
Union = 3, // Union, 前置 UInt32 selector (1-based, 0=null)
UnionWithSubtypedValues = 4 // Union 含子类型 (编码同 Union)
}

代码示例

using DarraOpcUa_Client;

using var ua = new DarraOpcUa("opc.tcp://localhost:4840");
ua.Connect();

var dec = new StructDecoder(ua);

// 1) 读包含自定义结构体的节点 (例如 Robot.CurrentPose : PoseType)
using var dv = ua.Read("ns=2;s=Robot.CurrentPose");
var pose = dec.Decode(dv.Value);

Console.WriteLine($"x = {pose["X"]}"); // 100.5
Console.WriteLine($"y = {pose["Y"]}"); // 200.3
Console.WriteLine($"q = {pose["Quaternion"]}"); // 嵌套结构 → 又一个 DynamicExtensionObject

// 2) 全字段遍历
foreach (var (name, value) in pose.Fields)
Console.WriteLine($"{name} = {value}");

// 3) 嵌套结构 — 递归取
var quat = pose["Quaternion"] as DynamicExtensionObject;
Console.WriteLine($"qw = {quat["W"]}");

// 4) 数组字段
var arr = (object[])pose["JointAngles"];
foreach (var a in arr) Console.WriteLine(a);

// 5) Union (Variant-like)
using var uv = ua.Read("ns=2;s=AnyValue");
var u = dec.Decode(uv.Value);
Console.WriteLine($"selector={u.Definition.Fields[0].Name}, value={u.Fields.Values.First()}");

// 6) 预热缓存 (启动时一次性拉所有自定义类型)
dec.PrefetchDefinitions(new[] {
"ns=2;i=3001", // PoseType
"ns=2;i=3002", // QuaternionType
"ns=2;i=3003", // JointStateType
});

实现限制

  • 当前 native 层 (DLL.cs) 暂未导出 ExtensionObject 的 TypeId / Body 提取接口, Decode(Variant) 在拿不到字节流时返回带 _status 字段的部分结果, 提示后续需接通 native
  • 协议层 (二进制解析 / 字段表缓存 / Union / Optional 编码掩码) 已全部实现, native 接通后立刻可用 — 对应 DecodeBody(byte[], string) 重载
  • BuiltinType.DiagnosticInfo 跳过 (生产代码极少用)

最佳实践

  • 共享一个 decoder 实例 — 字段表缓存是实例字段, 每个会话保持一个
  • PrefetchDefinitions 启动时调 — 启动一次性拉, 后续 Decode 零 RPC
  • 嵌套结构 cast 后再用pose["Quaternion"] as DynamicExtensionObject, 不要 (object)
  • 数组字段返回 object[] — 元素类型可能是 primitive 或嵌套结构
  • Union 看 selectorselector 决定哪个字段有值, 其他字段为 null

跨语言对照

C#PythonJavaC++RustC
StructDecoderStructDecoderStructDecoderStructDecoderStructDecoderDarraUa_StructDecoder
DecodedecodedecodeDecodedecodeDarraUa_StructDecoder_Decode
DynamicExtensionObjectDynamicExtensionObjectDynamicExtensionObjectDynamicExtensionObjectDynamicExtensionObjectDarraUa_DynamicExtensionObject

下一步