对于String类型的节点,其值的类型是UA_String,在这篇文章中我们解释了UA_String的生成方法。
当我们修改String类型节点的值时,会事先准备一个UA_String变量,这时就会遇到一个选择:是否需要动态分配内存,即是否需要使用UA_STRING_ALLOC,因为动态分配内存比较消耗资源,如果修改比较频繁,那么就会影响系统性能。
本文讲解一下这个注意点。
一 简单例子
首先添加一个String类型的变量,
UA_NodeId addTheAnswerVariable(UA_Server *server)
{
/* Define the attribute of the myInteger variable node */
UA_VariableAttributes attr = UA_VariableAttributes_default;
UA_String initStrVal = UA_STRING((char*)"hello 0");
UA_Variant_setScalar(&attr.value, &initStrVal, &UA_TYPES[UA_TYPES_STRING]);
attr.description = UA_LOCALIZEDTEXT((char*)"en-US", (char*)"the answer");
attr.displayName = UA_LOCALIZEDTEXT((char*)"en-US", (char*)"the answer");
attr.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
/* Add the variable node to the information model */
UA_NodeId theAnswerNodeId = UA_NODEID_NUMERIC(1, 62541);
UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, (char*)"the answer");
UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
UA_Server_addVariableNode(server, theAnswerNodeId, parentNodeId,
parentReferenceNodeId, myIntegerName,
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), attr, NULL, NULL);
return theAnswerNodeId;
}
注意,这里设置初始值时使用的是UA_STRING(),没有二次分配内存,内容是“hello 0”。
然后是修改变量值,
在main函数里添加一个定时任务,每隔2s去调用一次cycleCallback
UA_UInt64 callbackId = 0;
UA_Server_addRepeatedCallback(server, cycleCallback, &targetNodeId, 2000, &callbackId); // call every 2s
cycleCallback函数内容如下,
void cycleCallback(UA_Server *server, void *data)
{
static UA_Int32 update = 1;
UA_NodeId *targetNodeId = static_cast<UA_NodeId*>(data);
char strData[32];
snprintf(strData, 32, "hello %d", update);
UA_String tempStr = UA_STRING(strData);
UA_Variant myVar;
UA_Variant_init(&myVar);
UA_Variant_setScalar(&myVar, &tempStr, &UA_TYPES[UA_TYPES_STRING]);
UA_Server_writeValue(server, *targetNodeId, myVar);
if (update++ == 1000)
{
update = 1;
}
}
不断累加变量update,然后传给strData里,接着使用UA_STRING去构造UA_String变量,最后以此修改节点值,可见这样也没有动态分配内存。
完整代码如下,
// server.cpp
#include <memory>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include "open62541.h"
UA_Boolean running = true;
void stopHandler(int sign) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
running = false;
}
UA_UInt32 monid = 0;
UA_NodeId addTheAnswerVariable(UA_Server *server)
{
/* Define the attribute of the myInteger variable node */
UA_VariableAttributes attr = UA_VariableAttributes_default;
UA_String initStrVal = UA_STRING((char*)"hello 0");
UA_Variant_setScalar(&attr.value, &initStrVal, &UA_TYPES[UA_TYPES_STRING]);
attr.description = UA_LOCALIZEDTEXT((char*)"en-US", (char*)"the answer");
attr.displayName = UA_LOCALIZEDTEXT((char*)"en-US", (char*)"the answer");
attr.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
/* Add the variable node to the information model */
UA_NodeId theAnswerNodeId = UA_NODEID_NUMERIC(1, 62541);
UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, (char*)"the answer");
UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
UA_Server_addVariableNode(server, theAnswerNodeId, parentNodeId,
parentReferenceNodeId, myIntegerName,
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), attr, NULL, NULL);
return theAnswerNodeId;
}
void cycleCallback(UA_Server *server, void *data)
{
static UA_Int32 update = 1;
UA_NodeId *targetNodeId = static_cast<UA_NodeId*>(data);
char strData[32];
snprintf(strData, 32, "hello %d", update);
UA_String tempStr = UA_STRING(strData);
UA_Variant myVar;
UA_Variant_init(&myVar);
UA_Variant_setScalar(&myVar, &tempStr, &UA_TYPES[UA_TYPES_STRING]);
UA_Server_writeValue(server, *targetNodeId, myVar);
if (update++ == 1000)
{
update = 1;
}
}
int main(void)
{
signal(SIGINT, stopHandler);
signal(SIGTERM, stopHandler);
UA_Server *server = UA_Server_new();
UA_ServerConfig_setDefault(UA_Server_getConfig(server));
UA_NodeId targetNodeId = addTheAnswerVariable(server);
UA_UInt64 callbackId = 0;
UA_Server_addRepeatedCallback(server, cycleCallback, &targetNodeId, 2000, &callbackId); // call every 2s
UA_StatusCode retval = UA_Server_run(server, &running);
UA_Server_delete(server);
return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}
编译运行发现一切OK。
二 疑问和解答
可能会疑问:不使用动态内存,那么更新节点值之后,就会离开函数作用域,这样内存是否还有效呢?
答案是:出了函数作用域之后内存会失效,但是在写的过程中内部会重新拷贝一份内存,所以不会影响节点值
这里可以分析一下UA_Server_writeValue()的调用过程就可以知道答案了。其调用过程如下,
UA_Server_writeValue调用__UA_Server_write,
__UA_Server_write调用writeAttribute,
writeAttribute调用Operation_Write,
Operation_Write调用UA_Server_editNode,注意这里有个函数指针copyAttributeIntoNode,接下来会调用它,
在copyAttributeIntoNode中,会调用writeNodeValueAttribute ,因为我们是修改节点值,
在writeNodeValueAttribute()里,会调用writeValueAttributeWithoutRange ,
在writeValueAttributeWithoutRange里就会调用UA_DataValue_copy,会把string的内容拷贝一份过来
这样最外部的UA_String参数即使失效也不会影响节点值。
简化的调用流程如下,
UA_Server_writeValue -> __UA_Server_write -> writeAttribute -> Operation_Write -> UA_Server_editNode -> copyAttributeIntoNode -> writeNodeValueAttribute -> writeValueAttributeWithoutRange -> UA_DataValue_copy
三 结论
通过以上分析可以得知,当我们创建String类型节点并设置初值,以及后续更新节点值时,都不需要使用UA_STRING_ALLOC,这样就避免了动态分配内存,提高了性能。