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

使用open62541读取Omron NX102 PLC OPCUA数组结构遇类型转换问题

问题:open62541读取Omron NX102 PLC OPC UA服务器结构体数据异常

我用open62541读取Omron NX102 PLC的OPC UA服务器,PLC内存在一个包含STRING类型字段和长度为4的DOUBLE数组的数据结构。最初将结构体中describe字段定义为UA_String类型时,代码和UA Expert都能正常读取所有数据;但当我将describe字段类型改为UA_UInt16后,arSize无法被填充,也无法读取Extension Object,而UA Expert仍能正常读取该数据。


初始可正常运行的代码

结构体与类型定义

typedef struct {
    UA_String describe;
    size_t arSize;
    UA_Double* arguments;
} dArgument;

static UA_DataTypeMember dArgument_members[2] = {
            {
        UA_TYPENAME("describe") /* .memberName */
        & UA_TYPES[UA_TYPES_STRING],            /* .memberType */
        0,                                     /* .padding */
        false,                                 /* .isArray */
        false                                  /* .isOptional */
    },
    {
        UA_TYPENAME("arguments")            /* .memberName */
        & UA_TYPES[UA_TYPES_DOUBLE],             /* .memberType */
        0,                                     /* .padding */
        true,                                  /* .isArray */
        false                                  /* .isOptional */
    }
};

static const UA_DataType dArgumentType = {
    UA_TYPENAME("dArgument")              /* .typeName */
    {4, UA_NODEIDTYPE_NUMERIC, {5001}},     /* .typeId */
    {4, UA_NODEIDTYPE_NUMERIC, {5002}}, /* .binaryEncodingId, the numeric
                            identifier used on the wire (the
                            namespaceindex is from .typeId) */
    sizeof(dArgument),                   /* .memSize */
    UA_DATATYPEKIND_STRUCTURE,              /* .typeKind */
    false,                                  /* .pointerFree */
    false,                                  /* .overlayable (depends on endianness and
                                the absence of padding) */
    2,                                      /* .membersSize */
    dArgument_members
};

对应main()打印逻辑

UA_ReadRequest request;
UA_ReadRequest_init(&request);
UA_ReadValueId ids[3];

UA_ReadValueId_init(&ids[0]);
ids[0].attributeId = UA_ATTRIBUTEID_VALUE;
ids[0].nodeId = UA_NODEID_STRING(4, (char*)"moveAbsoluteArgument");

request.nodesToRead = ids;
request.nodesToReadSize = 1;
UA_DataValue reading[3];

UA_ReadResponse response = UA_Client_Service_read(client, request);
reading[0] = *response.results;

dArgument* arg = (dArgument*)reading[0].value.data;
for (int i = 0; i < arg->arSize; i++)
    printf(" args[%d]: %f Describe: %.*s\n", i, arg->arguments[i], 
    (int)arg->describe.length, arg->describe.data);

修改类型后出现问题的代码

结构体与类型定义

typedef struct {
    UA_UInt16 describe;
    size_t arSize;
    UA_Double* arguments;
} dArgument;

static UA_DataTypeMember dArgument_members[2] = {
            {
        UA_TYPENAME("describe") /* .memberName */
        & UA_TYPES[UA_TYPES_UINT16],            /* .memberType */
        0,                                     /* .padding */
        false,                                 /* .isArray */
        false                                  /* .isOptional */
    },
    {
        UA_TYPENAME("arguments")            /* .memberName */
        & UA_TYPES[UA_TYPES_DOUBLE],             /* .memberType */
        0,                                     /* .padding */
        true,                                  /* .isArray */
        false                                  /* .isOptional */
    }
};

static const UA_DataType dArgumentType = {
    UA_TYPENAME("dArgument")              /* .typeName */
    {4, UA_NODEIDTYPE_NUMERIC, {5001}},     /* .typeId */
    {4, UA_NODEIDTYPE_NUMERIC, {5002}}, /* .binaryEncodingId, the numeric
                            identifier used on the wire (the
                            namespaceindex is from .typeId) */
    sizeof(dArgument),                   /* .memSize */
    UA_DATATYPEKIND_STRUCTURE,              /* .typeKind */
    false,                                  /* .pointerFree */
    false,                                  /* .overlayable (depends on endianness and
                                the absence of padding) */
    2,                                      /* .membersSize */
    dArgument_members
};

对应main()打印逻辑

UA_ReadRequest request;
UA_ReadRequest_init(&request);
UA_ReadValueId ids[3];


UA_ReadValueId_init(&ids[0]);
ids[0].attributeId = UA_ATTRIBUTEID_VALUE;
ids[0].nodeId = UA_NODEID_STRING(4, (char*)"moveAbsoluteArgument");

// set here the nodes you want to read
request.nodesToRead = ids;
request.nodesToReadSize = 1;
UA_DataValue reading[3];

UA_ReadResponse response = UA_Client_Service_read(client, request);
reading[0] = *response.results;

dArgument* arg = (dArgument*)reading[0].value.data;
for (int i = 0; i < arg->arSize; i++)
    printf(" args[%d]: %f Describe: %d\n", i, arg->arguments[i], arg->describe);

问题排查与解决

核心原因:结构体内存对齐与padding配置错误

当把describe从UA_String(64位系统下占16字节的复合结构体)改成UA_UInt16(仅2字节)后,结构体的内存布局发生变化:编译器会自动插入填充字节,让后续的size_t(64位系统下8字节)和指针字段满足对齐要求。但你硬编码的.padding值为0,导致open62541解析二进制数据时位置偏移,从而读错arSize和arguments字段。

修复步骤

  1. 计算实际padding值
    用编译器宏自动计算describe字段后的填充字节数,避免手动计算出错:

    #include <stddef.h>
    // 自动计算describe到arSize之间的填充字节
    size_t describe_padding = offsetof(dArgument, arSize) - sizeof(UA_UInt16);
    
  2. 修正UA_DataTypeMember的padding配置
    将第一个成员的.padding替换为计算出的值:

    static UA_DataTypeMember dArgument_members[2] = {
        {
            UA_TYPENAME("describe"),
            &UA_TYPES[UA_TYPES_UINT16],
            describe_padding, // 使用自动计算的padding值
            false,
            false
        },
        // 第二个成员配置保持不变
        {
            UA_TYPENAME("arguments"),
            &UA_TYPES[UA_TYPES_DOUBLE],
            0,
            true,
            false
        }
    };
    
  3. 验证并重新注册类型
    确保修改后的dArgumentType被正确注册到open62541客户端,保证和PLC端的结构体内存布局完全匹配。

额外说明

UA Expert能正常读取是因为它会从PLC服务器获取结构体的元数据(包括对齐规则、padding等),而你的代码是硬编码类型定义,必须和服务器端的布局完全一致才能正确解析。


内容的提问来源于stack exchange,提问作者Gaurav K

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 21:50:38