Утечка памяти с ActiveX в C ++ Builder

Я столкнулся с утечкой памяти при использовании компонента 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?

0

Решение

У вас утечка памяти при звонке 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);
1

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]