Утечка памяти с 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} };

};   

.h Файл слишком длинный для тела вопроса ( полный код здесь), но метод 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?

1 ответ

Решение

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