C++ Builder 中 ActiveX 的内存泄漏

Memory leak with ActiveX in C++ Builder

我在项目中使用 activeX 组件时遇到内存泄漏。 我正在使用 Embarcadero Rad Studio 10.2 开发一个 C++ 工业程序,该程序需要与同一台机器上的 Codesys 软 PLC 进行通信。

所以,我有一个ActiveX组件可以处理我的程序和软PLC之间的通信部分。

我导入了 ActiveX,一切似乎都正常,但我发现内存泄漏,每小时约 20MB... 要导入库,我遵循了官方指南: http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Registering_a_COM_Object

我做了很多测试,我意识到每次我使用涉及变体的 ActiveX 方法时都会发生内存泄漏。看起来该程序无法释放组件使用的某种临时变体。

我已经测试了 Visual Studio 个示例并且一切正常,所以我认为问题是由 Rad Studio 在我导入 activeX 组件时生成的类型库引起的。 ActiveX 开发人员还声称一切都适用于 Visual Studio.

我还使用了 Dr. Memory 和其他确认泄漏存在的工具,但无法提供详细信息,因为我认为 ActiveX 未针对调试进行编译。

知道这种行为的原因吗?

RAD studio 中的 ActiveX 可能不兼容?

提前致谢


编辑

显示 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

并根据要求在导入 ActiveX 时由 RAD Studio 生成 TLB

.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 文件 对题体来说太长了(Full code here),但是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 方法returns 一个 VARIANT,它实际上包含一个字节数组和请求的变量值。

Variant varReceived 存储返回的 VARIANT,但在完成时用 VarClear 释放。

知道什么会导致内存泄漏吗?

我的感觉是MSyncReadVarsFromPlc返回的VARIANT在方法执行后没有被释放。但我看不出有什么方法可以解决这个问题,同样是因为 Visual Studio 示例中的相同用法工作正常。

ActiveX 可以在 Visual Studio 而不是在 RAD Studio 中正常工作吗?

调用 MSyncReadVarFromPlc() 时发生内存泄漏。它 return 是一个 OLE VARIANT, which you are assigning to an RTL Variant. That assignment copies the data, and then the leak occurs because you are not calling VariantClear() on the original VARIANT

A VARIANT is just a struct with data fields. Assigning a VARIANT directly to another VARIANT without using VariantCopy() 只是复制字段值 as-is。动态分配的数据,如字符串和数组,不是re-allocated,指针被复制as-is。

一个Variant, on the other hand, is a class wrapper that has copy semantics. Assigning a VARIANT (or another Variant) to a Variant allocates a new copy of dynamic data, preserving the original. The original and copy need to be cleared separately. You are leaking because you are not clearing the original VARIANT, only the copied Variant.

更改对 MSyncReadVarFromPlc() 的调用,以便在使用完后将 return 值保存到 VARIANT instead of a Variant, and then call VariantClear()

VARIANT varReceived;
...
varReceived = PLCHandler->MSyncReadVarFromPlc(iHandle, &iResult, name, 2);
...
VariantClear(&varReceived);