Reputation: 5318
In some part of my application, I have the situation where I receive an interface which I know to be an object, albeit I don't know the exact class. I have to store that object in an interface-type variable.
Eventually, I might receive another instance of that type, and the first one must be discarded and replaced with the new one. For that, I need to free the memory that interfaced object uses (my interface provides an AsObject method so I can use the TObject methods on it). My problem is, when I want to assign "nil" to that variable again, I get an access violation.
I wrote a small program that reproduces my situation. I post it here to clarify the situation.
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils, Classes;
type
ISomeInterface = interface
function SomeFunction : String;
function AsObject : TObject;
end;
TSomeClass = class(TComponent, ISomeInterface)
public
called : Integer;
function SomeFunction : String;
function AsObject : TObject;
end;
var
SomeInterface : ISomeInterface;
i : Integer;
function TSomeClass.SomeFunction : String;
begin
Result := 'SomeFunction called!';
end;
function TSomeClass.AsObject : TObject;
begin
Result := Self;
end;
begin
try
SomeInterface := nil;
for i := 1 to 10 do
begin
if SomeInterface <> nil then
begin
SomeInterface.AsObject.Free;
SomeInterface := nil; // <-- Access Violation occurs here
end;
SomeInterface := TSomeClass.Create(nil);
SomeInterface.SomeFunction; // <-- if commented, Access
// Violation does not occur
end;
except on e : Exception do
WriteLn(e.Message);
end;
end.
So the question is: how can I free that object correctly?
Upvotes: 16
Views: 20699
Reputation: 23056
Assuming that you have a legitimate reason for doing this (and using TComponent it is quite possible that you do - see end of answer for why), then the problem occurs as a result of changing the reference of an interface variable after you have destroyed the object it currently references.
Any change to an interface reference generates code like this:
intfA := intfB;
becomes (in simple terms):
if Assigned(intfA) then
intfA.Release;
intfA := intfB;
if Assigned(intfA) then
intfA.AddRef;
If you relate that to your code, you should see the problem:
SomeInterface.AsObject.Free;
SomeInterface := nil;
becomes:
SomeInterface.AsObject.Free;
if Assigned(SomeInterface) then
SomeInterface.Release;
SomeInterface := nil;
if Assigned(SomeInterface) then
SomeInterface.AddRef;
So you can see that it is the generated call to Release() that results from assigning NIL to the interface that causes your access violation.
You should also quickly see that there is a simple way to avoid this, simply defer the freeing of the object until after you have NIL'd the interface reference:
obj := SomeInterface.AsObject;
SomeInterface := NIL;
obj.Free;
BUT
The key question here is why are you explicitly free'ing an object that is interfaced (and presumably reference counted).
When you change the code to cache the object reference and NIL the interface before explicitly Free'ing the object, you may find that obj.Free will then cause an access violation as the NIL'ing of the interface reference might itself result in the object being free'd.
The ONLY way to be sure that explicitly free'ing the interfaced object is safe is:
1) That the interfaced object has overridden/reimplemented IUnknown and eliminated the reference counted lifetime management
AND
2) That you have no other interfaced references to that object elsewhere in your code.
If the first of these conditions doesn't make much sense to you then, without wishing to be patronising, this is probably a good sign that you shouldn't be explicitly freeing the object as it is almost certainly being managed by reference count.
Having said that, since you are using an interfaced TComponent class then as long as your TComponent class does not encapsulate a COM Object, then TComponent meets condition #1, so all that then remains is that you ensure the rest of your code meets condition #2.
Upvotes: 31
Reputation: 139000
You should not use TComponent as base class for your interfaced objects, you should use TInterfacedObject instead. TInerfacedObject has implemented the necessary functions to handle lifetime management for interfaces in Delphi. You should also never mix accessing your interface as interface and object. Here is a modification of your code that works just fine with no memory leaks.
program Project2;
{$APPTYPE CONSOLE}
uses
SysUtils, Classes;
type
ISomeInterface = interface
function SomeFunction: string;
end;
TSomeClass = class(TInterfacedObject, ISomeInterface)
public
called: Integer;
function SomeFunction: string;
end;
var
SomeInterface: ISomeInterface;
i: Integer;
function TSomeClass.SomeFunction: string;
begin
Result := 'SomeFunction called!';
end;
begin
try
SomeInterface := nil;
for i := 1 to 10 do
begin
if SomeInterface <> nil then
begin
SomeInterface := nil;
end;
SomeInterface := TSomeClass.Create;
SomeInterface.SomeFunction;
end;
except
on e: Exception do
WriteLn(e.message);
end;
end.
Upvotes: 6
Reputation: 2558
You're mixing it. All depends on _AddRef
and _Release
methods. Check how TInterfacedObject
in system.pas
is declared.
Delphi just calls _AddRef and _Release methods during using Interafaces, calling Free depends on how object implements _Relase method.
TComponent
is not automatically destroyed (except Com object components).
var
o: TSomeClass;
begin
..
..
begin
o := SomeInterface.AsObject as TSomeClass;
SomeInterface := nil; // now we decrease reference counter, but it will not free anything unless you rewrite _AddRef and _Release methods
o.Free; // just free object by your own
end;
Upvotes: 3
Reputation: 10886
When you have an interface variable such as your ISomeInterface var, you don't need to free it, as it is reference counted and will free it's self when it drops out of scope.
Read Rob Kennedy's answer to this question: Delphi7, passing object's interface - causes Invalid Pointer Operation when freeing the object
From http://delphi.about.com/od/beginners/l/aa113004a.htm
As soon as the interface goes out of scope, Delphi will actually free the interface for you automatically! Interface's declared within a procedure or function will naturally fall out of scope when the procedure ends. Interface's declared within a class or are declared globally will naturally fall out of scope when the object is freed or the program ends.
If in doubt try using FastMM memory manager and tuning on the memory leak detection, to see if the object is leaked.
Upvotes: 4