On the reference counting bugs in Delphi

Delphi allows to access an object instance via object reference and interface reference at the same time. Consider this code:

program RefCounts;

{$APPTYPE CONSOLE}

uses
  SysUtils;

procedure Test;
var
  Obj: TInterfacedObject;
  II: IInterface;

begin
  Obj:= TInterfacedObject.Create;
  Writeln(Obj.RefCount);             // 0
  II:= Obj;
  Writeln(Obj.RefCount);             // 1
  II:= nil;
// here Obj is already destroyed and we can't reference it anymore
//  Writeln(Obj.RefCount);
end;

begin
  Test;
  Readln;
end.

Nasty thing is that a reference-counted object is created with zero reference counter and can be destroyed by means of an interface reference. Needless to say that combining object and interface references is bad practice and should be avoided. Unfortunately the reference counting (with zero counters allowed) appeared to be a difficult problem for the compiler too and resulted in compiler bugs even when the code compiled does not use object references (see example here). The bugs were getting fixed with newer Delphi versions but nobody can say which Delphi version is free from them.

Advertisements

8 thoughts on “On the reference counting bugs in Delphi

  1. Yes. Mixing of interface and object references is bad practice, unfortunately, because it makes it impossible to introduce interfaces to legacy code. Consider the following:

    procedure PaintObjects(APaintableObjects : array of IPaintable; AColor : TColor);
    var
    Obj : IPaintable;
    begin
    for Obj in APaintableObjects do
    APaintableObject.SetColor(AColor);
    end;

    This would allow me to do the following:
    var
    MyCar : TCar;
    MyAppartement : TMyAppartement;
    begin
    MyCar := TCar.Create;
    MyAppartement := TAppartement.Create;
    PaintObjects([MyCar,MyAppartement], cl_Blue);
    end;

    But the problem is that when the parameter APaintableObjects gets out of scope it counts down the reference count, it will reach zero and the objects will be destroyed. That sucks. I would like to be able to disable reference counting on interfaces altogether.

  2. While you cannot disable reference counting for interfaces, you can implement it in a way that an object with zero references is still kept in memory. TComponent is a good example of that. At that point, you need to use standard memory management techniques.
    Under ARC, mixing interface references and object references becomes trivial, as the mechanism is the same and the references of the two types gets added and work fine out of the box.

  3. This is not a bug, but a “feature”.

    I mean, this is by design: if you want proper reference counting, you should NOT assign your instance to a class variable, but with an interface.

    As SOLID principles state: you should rely on abstraction, so only use interfaces in your code, in such cases.

    Either you use reference counting, AND interface; either you use manual memory allocation, AND class instances.

  4. You should never publicly expose a class if an interface and factory method will do. That way you avoid this issue.

  5. If you comment out that RefCount code, it will work in this case. Reason: the memory referred to by `Obj` is not overwritten.

    That is the real danger of many memory management issues, and the main reason you should run any interface based code with `FastMM4` and make sure you enable both the conditional defines `FullDebugMode` and `CatchUseOfFreedInterfaces` in your project settings.

    I’ve explained that in http://stackoverflow.com/questions/3139344/how-to-find-a-dangling-interface-that-causes-an-av-in-delphi/3140228#3140228

  6. It is a feature, not a bug. In fact this behaviour is absolutely indispensable for me because it allows you to access methods and properties that are not part of the interface, e.g. for initialization purposes:

    Function SomeClassFactory : iSomeInterface;
    var t:tMyInterfacedObject;
    Begin
    t:=tMyInterfacedObject.Create;
    t.SomePropertyNotPartOfTheInterface=123;
    t.CallSomeMethodNotPartOfTheInterface;
    Result:=T;
    End;

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s