tags:

views:

776

answers:

4

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!

+5  A: 

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

Tim Sullivan
That's really fast... thanks a bunch! Do you think that the way I want to use interfaces makes sense? It strikes me as odd that the whole reference counting comes in at all, really --- why not just use them as a really nice way to decouple classes?
onnodb
Reference counting is actually really slick. You don't need to free the object; Delphi will do it for you when the variable it's assigned to falls out of scope.
Tim Sullivan
Yes, that's true, it *is* slick (though I haven't found a use for it yet). But it's also nice to be able to disable it, since it does make things inconsistent if mixed with 'traditional' object management :)
onnodb
Hm. Shouldn't _AddRef and _Release return -1 (minus 1) instead of 1? (See Classes.pas, TComponent._AddRef)
onnodb
I've not got Delphi at hand atm, but IIRC they return the current (or new) count of instances of the interface. If you're only ever going to have one, you should return that.
Tim Sullivan
Returning -1 is just a convention, the returned value itself doesn't matter, as long as it is not 0 (which would lead to the destruction of the implementing object).
mghie
@mghie: ah, thanks for the clarification!
onnodb
@Mghie: Actually, since it's the _Release method itself that calls Destroy (on TInterfacedObject, that is), and not some external code, you don't even have to worry about returning 0. It's still better to return -1, though, to let end-users know that this is not a reference-counted interface, in case they care.
Mason Wheeler
@Mason: Indeed, you're right, my comment above wasn't really thought through. Regarding your second point I'm not so sure - since Delphi doesn't really have the concept of interfaces that aren't reference-counted I think using interfaces without it in code accessible by end-users shouldn't be done, at all. It violates their expectations, and it is impossible to code around the fact.
mghie
+8  A: 

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

Gamecat
Thanks a lot for the very extensive reply, it's much appreciated! Yes, I should probably think a bit more before I go down the dark path of disabling reference counting.
onnodb
Ok, I tested it in D2009 and it worked like a charm ;-).
Gamecat
Am I right that your example doesn't actually completely disable reference counting, but instead makes the reference count start off at 1? I guess you could also accomplish that by explicitly calling "TMyInterfacedObject._AddRef" right after object creation, and then doing a "_Release" where you would normally call "Free"?
onnodb
No, it treats FreeRef just like any _Release. So it does not mind which happens first. If all interfaces are out of scope and FreeRef is called, the object is freed.
Gamecat
+3  A: 

Disabling reference counting for this kind of problem smells bad. A much nicer and architectural solution would be to use some kind of "singleton" pattern. The easiest way to implement this would look like:

interface 

type

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

function DatabaseSettings: IMySettings;

implementation

var
  GDatabaseSettings: IMySettings; 

function DatabaseSettings: IMySettings;
begin
 if GDatabaseSettings = nil then GDatabaseSettings := TDatabaseSettings.Create;
 Result := GDatabaseSettings;
end;

O := TMyBusinessObject.Create;
O.Settings := DatabaseSettings; 
O.Free;

By the way: when you use interfaces: always use interface variables! Do not mix both class en interface vars (use "var Settings: IMySettings" instead of "var Settings: TDatabaseSettings"). Otherwise reference counting will get in your way (auto destroy, invalid pointer operations, etc). In the above solution, GDatabaseSettings is also of type "IMySettings", so it gets a proper reference count, and will last till your program terminates.

André
Yes, it does smell, and everyone here seems to agree on that. Which is too bad, since interfaces seem such a nice way to decouple objects (by only using them as a "contract"). Thanks for your input!
onnodb
@onnodb: Interfaces are indeed a good help for properly decoupled design. Unless you need to have circular references the ref-counting isn't bad at all - you just need to be careful when mixing interfaces with references to the implementing objects. Don't do this, and you will be happy. Since classes can implement multiple interfaces there is generally no need to use the object references directly, just implement and use a more specialized secondary interface instead.
mghie
Another tip: Don't use the technique in this answer, all the negative sides of singletons apply here as well - if you try to eliminate globals, don't go for such disguised globals instead. Having a interface reference in a place you control is just as good as above technique, with the added benefit that the implementing object will actually be destroyed once all references to it are reset, instead of it staying in memory until the app exits. That would be little better than a memory leak.
mghie
@mghie: thanks for the great input!
onnodb
+3  A: 

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.

SeanX
One caveat with this approach: make absolutely sure that you have cleared all interface references to an object before the object is Free'd, otherwise you will get weird errors.
Alistair Ward