How to Free an Interface Object (Delphi 7)

Pablo Venturino picture Pablo Venturino · Jan 27, 2011 · Viewed 17.6k times · Source

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?

Answer

Deltics picture Deltics · Jan 27, 2011

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.