Я столкнулся с утечкой памяти при использовании компонента ActiveX в моем проекте.
Я работаю с Embarcadero Rad Studio 10.2 и разрабатываю промышленную программу на C ++, которая должна взаимодействовать с программным ПЛК Codesys на той же машине.
Итак, у меня есть компонент ActiveX, который может обрабатывать связь между моей программой и программным ПЛК.
Я импортировал ActiveX, и все выглядело нормально, но я обнаружил утечку памяти, которая заполняет около 20 МБ в час …
Чтобы импортировать библиотеку, я следовал официальному руководству:
http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Registering_a_COM_Object
Я провел много тестов и понял, что утечка памяти происходит каждый раз, когда я работаю с методами ActiveX с задействованными вариантами. Похоже, программа не может освободить какие-то временные варианты, используемые компонентом.
Я протестировал примеры Visual Studio, и все работает нормально, поэтому я думаю, что проблемы генерируются библиотекой типов, которую Rad Studio создает при импорте компонента ActiveX.
Также разработчик ActiveX утверждает, что все работает с Visual Studio.
Я также использовал Dr. Memory и другие инструменты, которые подтверждают наличие утечки, но не могут предоставить подробности, потому что я думаю, что ActiveX не скомпилирован для отладки.
Есть идеи о причине такого поведения?
Есть некоторая возможная несовместимость для ActiveX в RAD studio?
заранее спасибо
редактировать
Пример, который показывает использование ActiveX.
unit1.cpp
#include <vcl.h>
#pragma hdrstop
#pragma package(smart_init)
#pragma resource "*.dfm"#include <System.IOUtils.hpp>
#include "Unit1.h"
TForm1 *Form1;
// ---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
counter = 0;
// Setting the path for communication setting files required for later connection
if (TFile::Exists("PLCHandler.ini"))
{
iniPath = (wchar_t*)L"PLCHandler.ini";
logPath = (wchar_t*)L"Log.txt";
}
iResult = PLCHandler->MCreate(&iHandle);
try
{
// Creating the component and retrieving the handle for other methods
iResult = PLCHandler->MCreate(&iHandle);
if (iResult == 0)
{
iResult = PLCHandler->MConnect(iHandle, 0, iniPath, logPath);
if (iResult == 0)
{
connected = true;
LabeledEdit1->Text = "CONNECTED";
long int numeroSimboli = 0;
PLCHandler->MGetNumberOfSymbols(iHandle, &numeroSimboli);
LabeledEdit2->Text = numeroSimboli;
PLCHandler->MGetPlcStatus(iHandle, &iPLCStatus);
LabeledEdit3->Text = iPLCStatus;
}
}
else
{
LabeledEdit2->Text = "ERROR: " + (String)iResult;
}
}
catch (...)
{
LabeledEdit2->Text = "ERROR";
}
}
// ---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
// Timers for testing purposes, they launch the next method every ms. Changing timing only delays the problem
Timer1->Enabled = !Timer1->Enabled;
Timer2->Enabled = !Timer2->Enabled;
}
// ---------------------------------------------------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
// Asking to the PLC Handler the value of a PLC variable, identified by name
Variant varReceived;
BSTR name = SysAllocString(L"Test.GVL.Test_INT");
try
{
counter++;
LabeledEdit1->Text = counter;
// This is where i suppose the memory leak happens; the problem vanishes commenting the next line
varReceived = PLCHandler->MSyncReadVarFromPlc(iHandle, &iResult, name, 2);
LabeledEdit3->Text = varReceived.GetElement(0);
SysFreeString(name);
VarClear(varReceived);
}
catch (...)
{
VarClear(varReceived);
SysFreeString(name);
}
}
// ---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
Timer1Timer(this);
}
// ---------------------------------------------------------------------------
void __fastcall TForm1::Button3Click(TObject *Sender)
{
// Other test: destroy the component and recreates it: the memory usages remains the same, no deallocation happens
try
{
PLCHandler->MDelete(&iHandle);
iResult = PLCHandler->MCreate(&iHandle);
if (iResult == 0)
{
iResult = PLCHandler->MConnect(iHandle, 0, iniPath, logPath);
if (iResult == 0)
{
connected = true;
LabeledEdit1->Text = "CONNECTED";
long int numeroSimboli = 0;
PLCHandler->MGetNumberOfSymbols(iHandle, &numeroSimboli);
LabeledEdit2->Text = numeroSimboli;
PLCHandler->MGetPlcStatus(iHandle, &iPLCStatus);
LabeledEdit3->Text = iPLCStatus;
}
}
else
{
LabeledEdit2->Text = "ERROR: " + (String)iResult;
}
}
catch (...)
{
LabeledEdit2->Text = "ERROR";
}
}
// ---------------------------------------------------------------------------
Unit1.h
#ifndef Unit1H
#define Unit1H
// ---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <Vcl.Controls.hpp>
#include <Vcl.StdCtrls.hpp>
#include <Vcl.Forms.hpp>
#include <Vcl.ExtCtrls.hpp>
#include <Vcl.OleCtrls.hpp>
#include "PLCHANDLERXLib_OCX.h"
// ---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published: // IDE-managed Components
TTimer * Timer1;
TButton * Button1;
TLabeledEdit *LabeledEdit1;
TTimer * Timer2;
TLabeledEdit *LabeledEdit2;
TButton * Button3;
TPLCHandlerX *PLCHandler;
TLabeledEdit *LabeledEdit3;
void __fastcall Button1Click(TObject *Sender);
void __fastcall Timer1Timer(TObject *Sender);
void __fastcall Button2Click(TObject *Sender);
void __fastcall Button3Click(TObject *Sender);
private:
// User declarations
public: // User declarations
long int counter;
wchar_t* iniPath;
wchar_t* logPath;
long int iPLCStatus;
long int iHandle;
long int readSize;
long int writeSize;
long int iResult;
Byte unbyte;
bool connected;
__fastcall TForm1(TComponent* Owner);
};
// ---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
// ---------------------------------------------------------------------------
#endif
И в соответствии с запросом TLB, сгенерированный RAD Studio при импорте ActiveX
.файл cpp
// ************************************************************************ //
// WARNING
// -------
// The types declared in this file were generated from data read from a
// Type Library. If this type library is explicitly or indirectly (via
// another type library referring to this type library) re-imported, or the
// 'Refresh' command of the Type Library Editor activated while editing the
// Type Library, the contents of this file will be regenerated and all
// manual modifications will be lost.
// ************************************************************************ //
// $Rev: 87174 $
// File generated on 14/03/2018 11:22:13 from Type Library described below.
// ************************************************************************ //
// Type Lib: C:\PLCHandler_SDK_Windows_v16\bin\Windows\PLCHandlerX.ocx (1)
// LIBID: {BB4C0C2B-D94B-4F5C-A774-4DF59A2227FF}
// LCID: 0
// Helpfile: C:\PLCHandler_SDK_Windows_v16\bin\Windows\PLCHandlerX.hlp
// HelpString: PLCHandlerX ActiveX Control module
// DepndLst:
// (1) v2.0 stdole, (C:\Windows\SysWOW64\stdole2.tlb)
// SYS_KIND: SYS_WIN32
// ************************************************************************ //
#include <vcl.h>
#pragma hdrstop
#include "PLCHANDLERXLib_TLB.h"
#if !defined(__PRAGMA_PACKAGE_SMART_INIT)
#define __PRAGMA_PACKAGE_SMART_INIT
#pragma package(smart_init)
#endif
namespace Plchandlerxlib_tlb
{// *********************************************************************//
// GUIDS declared in the TypeLibrary
// *********************************************************************//
const GUID LIBID_PLCHANDLERXLib = {0xBB4C0C2B, 0xD94B, 0x4F5C,{ 0xA7, 0x74, 0x4D,0xF5, 0x9A, 0x22,0x27, 0xFF} };
const GUID DIID__DPLCHandlerX = {0xA51B6208, 0x4C76, 0x4E79,{ 0xAC, 0x93, 0xB4,0x15, 0x7D, 0x6D,0x97, 0xC5} };
const GUID DIID__DPLCHandlerXEvents = {0xF2CC045D, 0x93E1, 0x4FE1,{ 0xA1, 0x5F, 0xE6,0x48, 0x18, 0x85,0x35, 0x5A} };
const GUID CLSID_PLCHandlerX = {0x99036BDD, 0x9A94, 0x4ED2,{ 0x89, 0x61, 0x42,0x0C, 0x74, 0xDD,0x51, 0xCE} };
};
.ч файл слишком долго для тела вопроса (Полный код здесь), но метод MSyncReadVarsFromPlc
VARIANT __fastcall MSyncReadVarsFromPlc(long lHandle, long* plResult, BSTR pszSymbols, VARIANT SizeList, long lNumOfVars)
{
_TDispID _dispid(/* MSyncReadVarsFromPlc */ DISPID(45));
TAutoArgs<5> _args;
_args[1] = lHandle /*[VT_I4:0]*/;
_args[2] = plResult /*[VT_I4:1]*/;
_args[3] = pszSymbols /*[VT_BSTR:0]*/;
_args[4] = SizeList /*[VT_VARIANT:0]*/;
_args[5] = lNumOfVars /*[VT_I4:0]*/;
OleFunction(_dispid, _args);
return _args.GetRetVariant();
}
Как вы можете видеть в TLB, метод MSyncReadVars возвращает VARIANT, который фактически содержит массив байтов с запрошенными значениями переменных.
Variant varReceived хранит возвращенный VARIANT, но после завершения освобождается с помощью VarClear.
Любая идея о том, что может привести к утечке памяти?
Мне кажется, что возвращенный VARIANT из MSyncReadVarsFromPlc не освобождается после выполнения метода. Но я не вижу способа решить эту проблему, в том числе и потому, что такое же использование в примере с Visual Studio работает нормально.
Может ли ActiveX нормально работать в Visual Studio, а не в RAD Studio?
У вас утечка памяти при звонке MSyncReadVarFromPlc()
, Возвращает OLE VARIANT
, который вы назначаете на RTL Variant
. Это назначение копирует данные, а затем происходит утечка, потому что вы не звоните VariantClear()
на оригинале VARIANT
.
VARIANT
это просто структура с полями данных. Назначение VARIANT
прямо к другому VARIANT
без использования VariantCopy()
просто копирует значения полей как есть. Динамически распределяемые данные, такие как строки и массивы, не перераспределяются, указатели копируются как есть.
Variant
, с другой стороны, это оболочка класса, которая имеет семантику копирования. Назначение VARIANT
(или другой Variant
) к Variant
выделяет новую копию динамических данных, сохраняя оригинал. Оригинал и копия должны быть очищены отдельно. Вы протекаете, потому что вы не очищаете оригинал VARIANT
, только скопированный Variant
.
Измените свой звонок на MSyncReadVarFromPlc()
чтобы сохранить возвращаемое значение в VARIANT
вместо Variant
, а затем позвоните VariantClear()
когда вы закончите, используя его:
VARIANT varReceived;
...
varReceived = PLCHandler->MSyncReadVarFromPlc(iHandle, &iResult, name, 2);
...
VariantClear(&varReceived);
Других решений пока нет …