结构体解码 (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 看 selector —
selector决定哪个字段有值, 其他字段为 null
跨语言对照
| C# | Python | Java | C++ | Rust | C |
|---|---|---|---|---|---|
| StructDecoder | StructDecoder | StructDecoder | StructDecoder | StructDecoder | DarraUa_StructDecoder |
| Decode | decode | decode | Decode | decode | DarraUa_StructDecoder_Decode |
| DynamicExtensionObject | DynamicExtensionObject | DynamicExtensionObject | DynamicExtensionObject | DynamicExtensionObject | DarraUa_DynamicExtensionObject |