使用open62541读取Omron NX102 PLC OPCUA数组结构遇类型转换问题
我用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字段。
修复步骤
计算实际padding值
用编译器宏自动计算describe字段后的填充字节数,避免手动计算出错:#include <stddef.h> // 自动计算describe到arSize之间的填充字节 size_t describe_padding = offsetof(dArgument, arSize) - sizeof(UA_UInt16);修正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 } };验证并重新注册类型
确保修改后的dArgumentType被正确注册到open62541客户端,保证和PLC端的结构体内存布局完全匹配。
额外说明
UA Expert能正常读取是因为它会从PLC服务器获取结构体的元数据(包括对齐规则、padding等),而你的代码是硬编码类型定义,必须和服务器端的布局完全一致才能正确解析。
内容的提问来源于stack exchange,提问作者Gaurav K

