如何使用OPC UA .NET库读取服务端含自定义类型的结构体变量
根因说明
你手动实例化BinaryDecoder直接读取原始字节的方式,默认仅支持OPC UA内置基础类型解码,既没有加载服务端下发的自定义类型元数据,也没有注册嵌套类型的编解码规则,遇到WorkOrderStatusType对应的二进制段时,无法识别其字段长度、类型映射关系,自然无法正确解析。
推荐实现方案(实体类自动映射)
这是OPC Foundation官方库原生支持的用法,无需手动逐字段读取二进制内容,一次配置后可直接拿到强类型实体对象,步骤如下:
- 加载服务端自定义类型元数据
会话建立完成后,先拉取服务端地址空间内的所有自定义结构体、枚举类型元数据,并注册到客户端编解码工厂:
// 拉取命名空间表 session.FetchNamespaceTables(); // 加载服务端自定义类型定义 var typeTable = TypeTable.Load( session, session.NamespaceUris, new TypeTable.LoadOptions { LoadComplexTypes = true, LoadEnumerations = true, ThrowOnError = false }); // 将加载到的类型注册到全局上下文,供后续解码器自动识别 ServiceMessageContext.GlobalContext.Factory.AddEncodeableTypes(typeTable.GetEncodeableTypes());
- 定义强类型实体类
按照服务端结构体的字段顺序定义C#类,实现IEncodeable接口,嵌套类型单独定义后直接作为属性引用即可。类上需要标记EncodeableTypeId特性,值为对应自定义类型在服务端的NodeId(可通过UaExpert等客户端浏览类型节点获取):
// 嵌套自定义类型 WorkOrderStatusType [EncodeableTypeId("nsu=你的服务端对应命名空间;i=该类型的数值ID")] public class WorkOrderStatusType : IEncodeable { // 字段顺序必须和服务端定义完全一致 public int StatusCode { get; set; } public string StatusText { get; set; } public DateTime LastChangeTime { get; set; } public ExpandedNodeId TypeId => ExpandedNodeId.Parse(GetType().GetCustomAttribute<EncodeableTypeIdAttribute>().TypeId); public ExpandedNodeId BinaryEncodingId => TypeId; public ExpandedNodeId XmlEncodingId => TypeId; public void Encode(IEncoder encoder) { encoder.WriteInt32(nameof(StatusCode), StatusCode); encoder.WriteString(nameof(StatusText), StatusText); encoder.WriteDateTime(nameof(LastChangeTime), LastChangeTime); } public void Decode(IDecoder decoder) { StatusCode = decoder.ReadInt32(nameof(StatusCode)); StatusText = decoder.ReadString(nameof(StatusText)); LastChangeTime = decoder.ReadDateTime(nameof(LastChangeTime)); } public object Clone() => MemberwiseClone(); public void SetObject(IEncodeable value) => MemberwiseClone(); } // 外层结构体 WorkOrderVariable [EncodeableTypeId("nsu=你的服务端对应命名空间;i=外层结构体的数值ID")] public class WorkOrderVariable : IEncodeable { public Guid ID { get; set; } public long WorkOrderId { get; set; } public string WorkOrderName { get; set; } // 嵌套类型直接使用自定义类作为属性类型 public WorkOrderStatusType Status { get; set; } public ExpandedNodeId TypeId => ExpandedNodeId.Parse(GetType().GetCustomAttribute<EncodeableTypeIdAttribute>().TypeId); public ExpandedNodeId BinaryEncodingId => TypeId; public ExpandedNodeId XmlEncodingId => TypeId; public void Encode(IEncoder encoder) { encoder.WriteGuid(nameof(ID), ID); encoder.WriteInt64(nameof(WorkOrderId), WorkOrderId); encoder.WriteString(nameof(WorkOrderName), WorkOrderName); encoder.WriteEncodeable(nameof(Status), Status, typeof(WorkOrderStatusType)); } public void Decode(IDecoder decoder) { ID = decoder.ReadGuid(nameof(ID)); WorkOrderId = decoder.ReadInt64(nameof(WorkOrderId)); WorkOrderName = decoder.ReadString(nameof(WorkOrderName)); // 嵌套类型直接读取,解码器会自动调用已注册的类型规则解析 Status = (WorkOrderStatusType)decoder.ReadEncodeable(nameof(Status), typeof(WorkOrderStatusType)); } public object Clone() => MemberwiseClone(); public void SetObject(IEncodeable value) => MemberwiseClone(); }
- 读取节点值自动映射
类型注册、实体类定义完成后,无需手动操作BinaryDecoder,读取到ExtensionObject后直接调用As<T>()方法即可反序列化为强类型对象,嵌套类型字段会自动完成解析:
var readResult = session.ReadValue(NodeId.Parse("ns=2;s=Demo.WorkOrder.WorkOrderVariable")); if (readResult is ExtensionObject extObj) { WorkOrderVariable workOrder = extObj.As<WorkOrderVariable>(); // 直接访问嵌套类型属性 var currentStatus = workOrder.Status.StatusText; }
注意:实体类的属性定义顺序必须和服务端结构体的字段顺序完全一致,否则二进制解码会出现字节偏移,导致值解析错误。如果不清楚字段顺序,可以通过标准OPC UA客户端连接服务端,浏览对应数据类型的字段列表按顺序定义即可。
如果不想手动编写实体类代码,加载完类型元数据后可以调用动态类型生成接口,运行时自动生成对应类型,直接用dynamic或ExpandoObject访问字段即可:
var dynamicTypes = typeTable.CreateDynamicTypes(ServiceMessageContext.GlobalContext); ServiceMessageContext.GlobalContext.Factory.AddEncodeableTypes(dynamicTypes);
内容的提问来源于stack exchange,提问作者Bob
相关产品推荐
相关产品推荐

