I have a COM server written in C # and a COM client written in Delphi. I implemented a callback mechanism that is simple and elegant, and it works like a charm. However, FastMM4 reports that my Delphi client is creating a memory leak. I distilled the application to the essence of where the leak came from. My leak is caused by the way the object is counted by reference (it never vanishes, therefore it is never destroyed), so I’m trying to understand why reference counting works the way it is, and this is due to what I am doing wrong in its implementation.
I have reduced the code to the extent I can, but it still seems to me what needs to be included in the question. But I really don't know how else to explain what I'm doing. I have two projects (C # and Delphi) wrapped nice and neat in a zip file, but it seems like I can't attach it anywhere.
I declare two interfaces on the C # side ( ICOMCallbackContainerand ICOMCallbackTestServer) and implement one of them there ( COMCallbackTestServer). I implement a different interface on the Delphi ( TCOMCallbackContainer) side and pass the Delphi class to the C # class.
This is a C # COM server:
namespace COMCallbackTest
{
[ComVisible(true)]
[Guid("2AB7E954-0AAF-4CFE-844C-756E50FE6360")]
public interface ICOMCallbackContainer
{
void Callback(string message);
}
[ComVisible(true)]
[Guid("7717D7AE-B763-48BC-BA0B-0F3525BEE8A4")]
public interface ICOMCallbackTestServer
{
ICOMCallbackContainer CallbackContainer { get; set; }
void RunCOMProcess();
void Dispose();
}
[ComVisible(true)]
[Guid("CF33E3A7-0886-4A0D-A740-537D0640C641")]
public class COMCallbackTestServer : ICOMCallbackTestServer
{
ICOMCallbackContainer _callbackContainer;
ICOMCallbackContainer ICOMCallbackTestServer.CallbackContainer
{
get { return _callbackContainer; }
set { _callbackContainer = value; }
}
void ICOMCallbackTestServer.RunCOMProcess()
{
if (_callbackContainer != null)
{
_callbackContainer.Callback("Step One");
_callbackContainer.Callback("Step Two");
_callbackContainer.Callback("Step Three");
}
}
void ICOMCallbackTestServer.Dispose()
{
if (_callbackContainer != null)
_callbackContainer.Callback("Done");
}
}
}
This is the Delphi CallbackContainer:
type
TCOMCallbackMethod = reference to procedure(AMessage: string);
TCOMCallbackContainer = class(TAutoIntfObject, ICOMCallbackContainer)
private
FCallbackMethod: TCOMCallbackMethod;
procedure Callback(const message: WideString); safecall;
public
constructor Create(ACallbackMethod: TCOMCallbackMethod);
destructor Destroy; override;
end;
// ...
constructor TCOMCallbackContainer.Create(ACallbackMethod: TCOMCallbackMethod);
var
typeLib: ITypeLib;
begin
OleCheck(LoadRegTypeLib(LIBID_COMCallbackTestServer,
COMCallbackTestServerMajorVersion,
COMCallbackTestServerMinorVersion,
0,
{out} typeLib));
inherited Create(typeLib, ICOMCallbackContainer);
FCallbackMethod := ACallbackMethod;
end;
destructor TCOMCallbackContainer.Destroy;
begin
FCallbackMethod := nil;
inherited Destroy;
end;
procedure TCOMCallbackContainer.Callback(const message: WideString);
begin
if Assigned(FCallbackMethod) then
FCallbackMethod(message);
end;
TCOMCallbackContainer inherits from TAutoIntfObject, so it implements IDispatch. I don't know what I'm doing right in the constructor. I am not so familiar with how to use IDispatch as I would like.
This is the COM Delphi client:
procedure TfrmMain.FormCreate(Sender: TObject);
begin
FServer := CoCOMCallbackTestServer_.Create as ICOMCallbackTestServer;
// Increments RefCount by 2, expected 1
FServer.CallbackContainer := TCOMCallbackContainer.Create(Process_Callback);
end;
procedure TfrmMain.FormDestroy(Sender: TObject);
begin
// Decrements RefCount by 0, expected 1
FServer.CallbackContainer := nil;
FServer.Dispose;
FServer := nil;
end;
procedure TfrmMain.btnBeginProcessClick(Sender: TObject);
begin
FServer.RunCOMProcess;
end;
procedure TfrmMain.Process_Callback(AMessage: string);
begin
mmoProcessMessages.Lines.Add(AMessage);
end;
TCOMCallbackContainer , RefCount 2.
, : COM , nil COM ?
TMyInterfacedObject ( TInterfacedObject) TCOMCallbackContainer. TMyInterfacedObject. ( ). , RefCount, RefCount. QueryInterface IID ( Google) .
TfrmMain.FormCreate -> TCOMCallbackContainer.Create -> TInterfacedObject.NewInstance: 1
TfrmMain.FormCreate -> TCOMCallbackContainer.Create -> TInterfacedObject.AfterConstruction: 0
CLR -> TInterfacedObject.QueryInterface("00000000-0000-0000-C000-000000000046" {IUnknown}): S_OK
CLR -> TInterfacedObject.QueryInterface -> TObject.GetInterface -> _AddRef: 1
CLR -> TInterfacedObject.QueryInterface("C3FCC19E-A970-11D2-8B5A-00A0C9B7C9C4" {IManagedObject}): E_NOINTERFACE
CLR -> TInterfacedObject.QueryInterface("B196B283-BAB4-101A-B69C-00AA00341D07" {IProvideClassInfo}): E_NOINTERFACE
CLR -> TInterfacedObject._AddRef: 2
CLR -> TInterfacedObject.QueryInterface("ECC8691B-C1DB-4DC0-855E-65F6C551AF49" {INoMarshal}): E_NOINTERFACE
CLR -> TInterfacedObject.QueryInterface("94EA2B94-E9CC-49E0-C0FF-EE64CA8F5B90" {IAgileObject}): E_NOINTERFACE
CLR -> TInterfacedObject.QueryInterface("00000003-0000-0000-C000-000000000046" {IMarshal}): E_NOINTERFACE
CLR -> TInterfacedObject.QueryInterface("00000144-0000-0000-C000-000000000046" {IRpcOptions}): E_NOINTERFACE
CLR -> TInterfacedObject._Release: 1
CLR -> TInterfacedObject.QueryInterface("2AB7E954-0AAF-4CFE-844C-756E50FE6360" {ICOMCallbackContainer}): S_OK
CLR -> TInterfacedObject.QueryInterface -> TObject.GetInterface -> _AddRef: 2
CLR -> TInterfacedObject._AddRef: 3
CLR -> TInterfacedObject._Release: 2
FServer.CallbackContainer := TCOMCallbackContainer.Create(Process_Callback); TfrmMain.Create. Destroy, FServer.CallbackContainer := nil;, .
, , , , COM , FServer.CallbackContainer := nil; . .
, QueryInterface, , Delphi, ICOMCallbackContainer #, ( , ).
2
INoMarshal IAgileObject, , . , , . , . , CLR INoMarshal, IAgileObject IMarshal, INoMarshal, IAgileObject, IMarshal. ( , , , .)
INoMarshal TCOMCallbackContainer:
...
CLR -> TInterfacedObject._AddRef: 2
CLR -> TInterfacedObject.QueryInterface(INoMarshal): S_OK
CLR -> TInterfacedObject.QueryInterface -> TObject.GetInterface -> _AddRef: 3
CLR -> TInterfacedObject._Release: 2
CLR -> TInterfacedObject.QueryInterface(IRpcOptions): E_NOINTERFACE
CLR -> TInterfacedObject._Release: 1
...
IAgileObject TCOMCallbackContainer:
...
CLR -> TInterfacedObject._AddRef: 2
CLR -> TInterfacedObject.QueryInterface(INoMarshal): E_NOINTERFACE
CLR -> TInterfacedObject.QueryInterface(IAgileObject): S_OK
CLR -> TInterfacedObject.QueryInterface -> TObject.GetInterface -> _AddRef: 3
CLR -> TInterfacedObject._Release: 2
CLR -> TInterfacedObject.QueryInterface(IRpcOptions): E_NOINTERFACE
CLR -> TInterfacedObject._Release: 1
...