Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bypassing (disabling) Delphi's reference counting for interfaces

Tags:

delphi

For one particular issue in the architecture of an application I'm working on, interfaces seem to be a nice solution. Specifically, some "business objects" depend on a bunch of settings that are pulled from the database in the actual app. Letting those business objects ask for an interface (through Inversion of Control), and letting a central TDatabaseSettings object implement those interfaces, allows for better isolation, and thus for much easier unit testing.

However, in Delphi, interfaces seem to come with an, in this case, unpleasant bonus: reference counting. This means that if I do something like this:

type
IMySettings = interface
    function getMySetting: String;
end;

TDatabaseSettings = class(..., IMySettings)
    //...
end;

TMyBusinessObject = class(TInterfacedObject, IMySettings)
    property Settings: IMySettings read FSettings write FSettings;
end;

var
  DatabaseSettings: TDatabaseSettings; 
    // global object (normally placed in a controller somewhere)

//Now, in some function...
O := TMyBusinessObject.Create;
O.Settings := DatabaseSettings; 
// ... do something with O
O.Free;

On the last line (O.Free), my global DatabaseSettings object is now also freed, since the last interface reference to it (which was contained in O) is lost!

One solution would be to store the 'global' DatabaseSettings object with an interface; another solution would be to override the reference counting mechanism for the TDatabaseSettings class, so I can continue to manage the DatabaseSettings as a normal object (which is much more consistent with the rest of the app).

So, in summary, my question is: how do I disable the interface reference counting mechanism for a particular class?

I've been able to find some info that suggests overriding the IInterface methods _AddRef and _Release for the class (TDatabaseSettings in the example); has anyone ever done that?

Or would you say I shouldn't do this (confusing? just a bad idea?), and find a different solution to the architectural problem?

Thanks a lot!

like image 678
onnodb Avatar asked Apr 20 '09 17:04

onnodb


5 Answers

Or just use the code below:

    var
      I: IMyInterface;
    begin
      I := ...;
      ...
      Do whatever you want in a scope;
      Initialize(I); //- this will clear the interface variable without calling the _release.
    end.
like image 109
Zigmund Avatar answered Nov 03 '22 05:11

Zigmund


Don't descend from TInterfacedObject, instead descend from TSingletonImplementation from standard System.Generics.Defaults unit.

  • TSingletonImplementation is a base for simple classes that need a basic IInterface implementation, with reference counting disabled.
  • TSingletonImplementation is a thread-safe base class for Delphi classes that support interfaces. Unlike TInterfacedObject, TSingletonImplementation does not implement reference counting.
like image 27
tz. Avatar answered Nov 20 '22 02:11

tz.


_AddRef, _Release and _QueryInterface are, in fact, what you want to override. You should be very clear about what you're doing, however, as this can cause memory leaks or strange, hard-to-find bugs.

Don't descend from TInterfacedObject, instead descend from TObject, and implement your own versions of the first two of those methods that return 1.

like image 40
Tim Sullivan Avatar answered Nov 20 '22 03:11

Tim Sullivan


Ok, you can bypass it, but the question is if you really want that. If you want to use interfaces, you better use them completely. So as you have experienced it, you get problems if you mix class and interface variables.

var
  // DatabaseSettings: TDatabaseSettings; 
  DatabaseSettings : IMySettings;

//Now, in some function...
O := TMyBusinessObject.Create;
O.Settings := DatabaseSettings; 
// ... do something with O
O.Free;

You now have a second reference to the interface and losing the first will not free the object.

It as also possible to keep both the class and the object:

var
  DatabaseSettings: TDatabaseSettings; 
  DatabaseSettingsInt : IMySettings;

Be sure to set the interface right after the object has been created.

If you really want to disable reference counting, you just have to create a new descendant of TObject that implements IInterface. I have tested the example below in D2009 and it works:

// Query Interface can stay the same because it does not depend on reference counting.
function TMyInterfacedObject.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
  if GetInterface(IID, Obj) then
    Result := 0
  else
    Result := E_NOINTERFACE;
end;

constructor TMyInterfacedObject.Create;
begin
  FRefCount := 1;
end;

procedure TMyInterfacedObject.FreeRef;
begin
  if Self = nil then
    Exit;
  if InterlockedDecrement(FRefCount) = 0 then
    Destroy;    
end;

function TMyInterfacedObject._AddRef: Integer;
begin
  Result := InterlockedIncrement(FRefCount);
end;

function TMyInterfacedObject._Release: Integer;
begin
  Result := InterlockedDecrement(FRefCount);
  if Result = 0 then
    Destroy;
end;

FreeRef just lowers the refcount just like _Release. You can use it where you normally use Free.

like image 13
Toon Krijthe Avatar answered Nov 20 '22 02:11

Toon Krijthe


To disable reference counting, AddRef and Release should do nothing but return -1

function TMyInterfacedObject._AddRef: Integer;
begin
  Result := -1;
end;

function TMyInterfacedObject._Release: Integer;
begin
  Result := -1;
end;

There is quite a lot of utility in interfaces without reference counting. If you use reference counting, then you cannot mix object and interface references as bad things will happen. By disabling ref counts, you can happily mix interface and object references without worrying about your objects suddenly getting auto destroyed.

like image 4
SeanX Avatar answered Nov 20 '22 02:11

SeanX