Reputation: 5869
To provide as much information as I can, here's a very basic example of what I'm doing
type
IMyInterface = interface
[THE_GUID_HERE]
// some methods
end;
TMyInterfaceArray = Array of IMyInterface;
TMyInterfacedObject = class(TInterfacedObject, IMyInterface)
// implementation of the Interface etc. here
end;
TContainingObject = class
private
FIObjectArray: TMyInterfaceArray;
public
constructor Create;
destructor Destroy; override;
procedure NewInstanceOfInterfacedObject;
end;
implementation
constructor TContainingObject.Create;
begin
inherited;
// Just to illustrate that an Instance is being created...
NewInstanceOfInterfacedObject;
end;
destructor TContainingObject.Destroy;
var
I: Integer;
begin
for I := Low(FIObjectArray) to High(FIObjectArray) do
FIObjectArray[I] := nil;
SetLength(FIObjectArray, 0); // Array collapsed
inherited;
end;
procedure TContainingObject.NewInstanceOfInterfacedObject;
var
LIndex: Integer;
begin
LIndex := Length(FIObjectArray);
SetLength(FIObjectArray, LIndex + 1);
FIObjectArray[LIndex] := TMyInterfacedObject.Create;
end;
Okay, so an instance of TContainingObject
is created, and in turn creates an instance of TMyInterfacedObject
, stored in an Array of IMyInterface
.
When TContainingObject
's destructor
is called, it nil's the reference and collapses the Array.
The issue I have is that, with no other references anywhere, TMyInterfacedObject
's destructor
is never called, and thus memory leaks.
Am I doing something wrong, or is Delphi's reference counting system not able to cope with the simple concept of Interfaced Objects being held in an Array of the Interface Type?
Thanks for any advice!
MORE INFORMATION
TContainingObject
provides an Array property to access individual instances of IMyInterface
contained within the Array.
In my actual code, there are circular references between multiple Interface types.
Let's suggest that IMyInterface
contains a function GetSomething: IAnotherInterface
, and IAnotherInterface
contains GetMyInterface: IMyInterface
(a circular reference).
Could this be causing my problem? If so, the circular reference is absolutely required, so what would a solution be with that in mind?
Upvotes: 1
Views: 611
Reputation: 596948
If the implementation for IMyInterface
contains an IAnotherInterface
member, and the implementation for IAnotherInterface
contains an IMyInterface
member, and they refer to each other, then their reference counts will never be able to fall to 0 unless you clear one of the references, which likely means adding methods to your interfaces to do that, eg:
type
IAnotherInterface = interface;
IMyInterface = interface
['{guid}']
function GetAnotherInterface: IAnotherInterface;
procedure SetAnotherInterface(Value: IAnotherInterface);
property AnotherInterface: IAnotherInterface read GetAnotherInterface write SetAnotherInterface;
end;
IAnotherInterface = interface
['{guid}']
function GetMyInterface: IMyInterface;
procedure SetMyInterface(Value: IMyInterface);
property MyInterface: IMyInterface read GetMyInterface write SetMyInterface;
end;
.
type
TMyInterface = class(TInterfacedObject, IMyInterface)
private
FAnotherInterface: IAnotherInterface;
public
function GetAnotherInterface: IAnotherInterface;
procedure SetAnotherInterface(Value: IAnotherInterface);
end;
TAnotherInterface = class(TInterfacedObject, IAnotherInterface)
private
FMyInterface: IMyInterface;
public
function GetMyInterface: IMyInterface;
procedure SetMyInterface(Value: IMyInterface);
end;
function TMyInterface.GetAnotherInterface;
begin
Result := FAnotherInterface;
end;
procedure TMyInterface.SetAnotherInterface(Value: IAnotherInterface);
begin
if FAnotherInterface <> Value then
begin
if FAnotherInterface <> nil then FAnotherInterface.SetMyInterface(nil);
FAnotherInterface := Value;
if FAnotherInterface <> nil then FAnotherInterface.SetMyInterface(Self);
end;
end;
function TAnotherInterface.GetMyInterface: IMyInterface;
begin
Result := FMyInterface;
end;
procedure TAnotherInterface.SetMyInterface(Value: IMyInterface);
begin
if FMyInterface <> Value then
begin
if FMyInterface <> nil then FMyInterface.SetAnotherInterface(nil);
FMyInterface := Value;
if FMyInterface <> nil then FMyInterface.SetAnotherInterface(Self);
end;
end;
Now watch the reference counts when you don't explicitally free one of the references:
var
I: IMyInterface;
J: IAnotherInterface;
begin
I := TMyInterface.Create; // I.RefCnt becomes 1
J := TAnotherInterface.Create; // J.RefCnt becomes 1
I.AnotherInterface := J; // I.RefCnt becomes 2, J.RefCnt becomes 2
...
{
// implicit when scope is cleared:
I := nil; // I.RefCnt becomes 1, I is NOT freed
J := nil; // J.RefCnt becomes 1, J is NOT freed
}
end;
Now add an explicit release to one of the references:
var
I: IMyInterface;
J: IAnotherInterface;
begin
I := TMyInterface.Create; // I.RefCnt becomes 1
J := TAnotherInterface.Create; // J.RefCnt becomes 1
I.AnotherInterface := J; // I.RefCnt becomes 2, J.RefCnt becomes 2
...
I.AnotherInterface := nil; // I.RefCnt becomes 1, J.RefCnt becomes 1
{
// implicit when scope is cleared:
I := nil; // I.RefCnt becomes 0, I is freed
J := nil; // J.RefCnt becomes 0, J is freed
}
end;
.
var
I: IMyInterface;
J: IAnotherInterface;
begin
I := TMyInterface.Create; // I.RefCnt becomes 1
J := TAnotherInterface.Create; // J.RefCnt becomes 1
I.AnotherInterface := J; // I.RefCnt becomes 2, J.RefCnt becomes 2
J := nil; // I.RefCnt still 2, J.RefCnt becomes 1, J is NOT freed yet
...
I.AnotherInterface := nil; // I.RefCnt becomes 1, J.RefCnt becomes 0, J is freed
{
// implicit when scope is cleared:
I := nil; // I.RefCnt becomes 0, I is freed
}
end;
Upvotes: 5
Reputation: 596948
When I run your code example in Delphi XE2 (after fixing a small typo - see edit), it runs just fine for me and calls the TMyInterfacedObject
destructor as expected.
Upvotes: 0