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?
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.