Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Free an Interface Object (Delphi 7)

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?

like image 623
Pablo Venturino Avatar asked Jan 27 '11 19:01

Pablo Venturino


4 Answers

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.

like image 184
Deltics Avatar answered Nov 03 '22 20:11

Deltics


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.
like image 39
Mikael Eriksson Avatar answered Nov 03 '22 20:11

Mikael Eriksson


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.

like image 4
Re0sless Avatar answered Nov 03 '22 20:11

Re0sless


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;
like image 3
DiGi Avatar answered Nov 03 '22 20:11

DiGi