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

如何使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.02 09:18:27