在使用监测项时,一般都会加一个context,然后在回调函数里使用这个context,这就需要保证context的内存空间在执行回调函数时是有效的。往往有以下三种方法:
- 使用静态内存空间:使用static创建静态变量,然后把变量地址当做context,缺点是一个静态变量只能用于一个监测项,函数无法重用(重用会造成多个监测项使用同一个静态变量)
- 使用局部内存空间:保证局部变量一直有效就行,例如局部变量定义在main函数里,因为main函数一直在栈上,所以这个局部变量会一直有效。这个只能用于简单项目。
- 使用动态内存:在创建监测项时直接动态分配内存,比较简单,就是释放内存比较费劲
一般来说,都是使用第三个方法。本文先举例讲解第三个方法,最后讲述如何使用智能指针去解决释放问题。
一 例子
下面是个简单的监测项例子,server端添加一个变量,然后创建监测项去监测该变量,再添加一个定时任务去每隔2s把该变量的值加1,从而可以触发监测的回调函数,
// 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_Int32 myInteger = 1;
UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]);
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_INT32].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 dataChangeNotificationCallback(UA_Server *server, UA_UInt32 monitoredItemId,
void *monitoredItemContext, const UA_NodeId *nodeId,
void *nodeContext, UA_UInt32 attributeId,
const UA_DataValue *value)
{
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Received Notification");
UA_NodeId * targetNodeId = (UA_NodeId*)monitoredItemContext;
if (monitoredItemId == monid && UA_NodeId_equal(nodeId, targetNodeId))
{
UA_Int32 currentValue = *(UA_Int32*)(value->value.data);
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Current Value: %d\n", currentValue);
}
}
UA_UInt32 addMonitoredItemToVariable(UA_Server *server, UA_NodeId TargetNodeId)
{
UA_MonitoredItemCreateResult result;
UA_MonitoredItemCreateRequest monRequest = UA_MonitoredItemCreateRequest_default(TargetNodeId);
monRequest.requestedParameters.samplingInterval = 100.0; // 100 ms interval
// 使用动态内存
UA_NodeId * pContext = (UA_NodeId *)UA_malloc(sizeof(UA_NodeId));
UA_NodeId_copy(&TargetNodeId, pContext);
result = UA_Server_createDataChangeMonitoredItem(server, UA_TIMESTAMPSTORETURN_BOTH,
monRequest, (void*)pContext,
dataChangeNotificationCallback);
if (result.statusCode == UA_STATUSCODE_GOOD)
{
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Add monitored item for variable, OK.");
return result.monitoredItemId;
}
else
{
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Add monitored item for variable, Fail.");
return 0xFFFFFFFF;
}
}
void cycleCallback(UA_Server *server, void *data)
{
static UA_Int32 update = 2;
UA_NodeId *targetNodeId = static_cast<UA_NodeId*>(data);
UA_Variant myVar;
UA_Variant_init(&myVar);
UA_Variant_setScalar(&myVar, &update, &UA_TYPES[UA_TYPES_INT32]);
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);
monid = addMonitoredItemToVariable(server, targetNodeId);
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Monitored item id: %d\n", monid);
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;
}
PS: 所添加的变量,其nodeid是数字形式的,不能是string类型的
context的内存分配是在函数addMonitoredItemToVariable()里。
编译运行,一切OK,如下,回调函数也正常运行
但是,如果使用Valgrind去检查,就会发现有内存泄露,命令如下,demo是本人的可执行文件名字,
valgrind --tool=memcheck --leak-check=full ./demo
启动后,等回调函数执行之后按Ctrl+c结束,然后会给出分析结果,
可以看到有24个字节的内存泄露,UA_NodeId占用的大小就是24个字节。
根本原因是我们动态分配了内存之后,没有释放,虽然最终程序结束会释放内存,但如果在程序运行时,我们删除这个监测项而没有释放对应的内存,那么就会内存泄露。
二 使用智能指针
如果项目使用C++开发的,那么就可以使用shared_ptr来解决释放问题。重新编写addMonitoredItemToVariable(),如下,
std::shared_ptr<UA_NodeId> addMonitoredItemToVariable(UA_Server *server, UA_NodeId TargetNodeId, UA_UInt32& monitoredItemId)
{
UA_MonitoredItemCreateResult result;
UA_MonitoredItemCreateRequest monRequest = UA_MonitoredItemCreateRequest_default(TargetNodeId);
monRequest.requestedParameters.samplingInterval = 100.0; // 100 ms interval
// UA_NodeId * pContext = (UA_NodeId *)UA_malloc(sizeof(UA_NodeId));
// UA_NodeId_copy(&TargetNodeId, pContext);
std::shared_ptr<UA_NodeId> spContext = std::make_shared<UA_NodeId>();
UA_NodeId_copy(&TargetNodeId, spContext.get());
result = UA_Server_createDataChangeMonitoredItem(server, UA_TIMESTAMPSTORETURN_BOTH,
monRequest, (void*)spContext.get(),
dataChangeNotificationCallback);
if (result.statusCode == UA_STATUSCODE_GOOD)
{
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Add monitored item for variable, OK.");
monitoredItemId = result.monitoredItemId;
}
else
{
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Add monitored item for variable, Fail.");
monitoredItemId = 0xFFFFFFFF;
}
return spContext;
}
函数里使用spContext 来指向context内存,最后返回这个智能指针。
main函数如下,创建了一个holdSP来承接addMonitoredItemToVariable()的返回值,有了这个holdSP,那么智能指针指向的内存就不会被释放,
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);
std::shared_ptr<UA_NodeId> spContext;
monid = addMonitoredItemToVariable(server, targetNodeId, spContext);
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Monitored item id: %d\n", monid);
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,最后使用Valgrind来分析内存,
可以看出没有内存泄漏了。
三 总结
本文讲述如何使用智能指针来存放context内存,使用完后只要再创建一个智能指针存放一下,这样当程序结束时就会自动释放,不用手动去释放,这样就不会担心忘记内存泄漏了。