绕过(禁用)Delphi对接口的引用计数

时间:2009-04-20 17:41:18

标签: delphi

对于我正在研究的应用程序架构中的一个特定问题,接口似乎是一个很好的解决方案。具体来说,一些“业务对象”依赖于从实际应用程序中的数据库中提取的一堆设置。让这些业务对象请求接口(通过控制反转),让中心TDatabaseSettings对象实现这些接口,可以更好地隔离,从而更容易进行单元测试。 / p>

然而,在Delphi中,接口似乎带有一个令人不愉快的奖励:引用计数。这意味着如果我做这样的事情:

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;

在最后一行(O.Free)上,我的全局DatabaseSettings对象现在也被释放,因为它的最后一个接口引用(包含在O中)丢失了! / p>

一种解决方案是使用接口存储“全局”DatabaseSettings对象;另一种解决方案是覆盖TDatabaseSettings类的引用计数机制,因此我可以继续将DatabaseSettings作为普通对象进行管理(这与应用程序的其余部分更加一致)。 / p>

因此,总而言之,我的问题是:如何禁用特定类的接口引用计数机制?

我已经能够找到一些建议覆盖IInterface方法_AddRef_Release的类(示例中为TDatabaseSettings)的信息;有没有人这样做过?

或者你会说我不应该这样做(令人困惑?只是一个坏主意?),并找到一个不同的解决方案来解决这个问题?

非常感谢!

6 个答案:

答案 0 :(得分:13)

好的,你可以绕过它,但问题是你是否真的想要那样。 如果要使用接口,最好完全使用它们。因此,当您体验过它时,如果混合使用类和接口变量,就会遇到问题。

var
  // DatabaseSettings: TDatabaseSettings; 
  DatabaseSettings : IMySettings;

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

您现在有第二个对该界面的引用,并且丢失第一个将不会释放该对象。

同样可以保留类和对象:

var
  DatabaseSettings: TDatabaseSettings; 
  DatabaseSettingsInt : IMySettings;

确保在创建对象后立即设置界面。

如果你真的想要禁用引用计数,你只需要创建一个实现IInterface的TObject的新后代。我在D2009测试了下面的例子,它可以工作:

// 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只是像_Release那样降低引用计数。您可以在通常使用Free的地方使用它。

答案 1 :(得分:7)

事实上,

_AddRef_Release_QueryInterface是您要覆盖的内容。但是,您应该非常清楚自己正在做什么,因为这会导致内存泄漏或奇怪的,难以发现的错误。

不要从TInterfacedObject下降,而是从TObject下降,并实现返回1的前两个方法的自己版本。

答案 2 :(得分:7)

不要从TInterfacedObject下降,而是从标准TSingletonImplementation单元的System.Generics.Defaults下降。

  • TSingletonImplementation 是需要基本IInterface实现的简单类的基础,禁用引用计数。
  • TSingletonImplementation 是支持接口的Delphi类的线程安全基类。与TInterfacedObject不同,TSingletonImplementation不实现引用计数。

答案 3 :(得分:4)

要禁用引用计数,AddRef和Release除了返回-1

之外什么都不做
function TMyInterfacedObject._AddRef: Integer;
begin
  Result := -1;
end;

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

在没有引用计数的接口中有很多实用程序。如果使用引用计数,则不能混合对象和接口引用,因为会发生错误。通过禁用引用计数,您可以愉快地混合接口和对象引用,而不必担心您的对象突然被自动销毁。

答案 4 :(得分:3)

禁用此类问题的引用计数闻起来很糟糕。 更好的和架构解决方案是使用某种“单例”模式。 实现这一目标的最简单方法如下:

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;

顺便说一下:当你使用接口时:总是使用接口变量!不要混合使用类en接口变量(使用“var Settings:IMySettings”而不是“var Settings:TDatabaseSettings”)。否则引用计数将阻塞(自动销毁,无效指针操作等)。 在上面的解决方案中,GDatabaseSettings也是“IMySettings”类型,因此它获得了正确的引用计数,并将持续到程序终止。

答案 5 :(得分:0)

或者只使用以下代码:

    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.