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

onn*_*odb 10 delphi

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

然而,在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;
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

非常感谢!

Too*_*the 13

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

var
  // DatabaseSettings: TDatabaseSettings; 
  DatabaseSettings : IMySettings;

//Now, in some function...
O := TMyBusinessObject.Create;
O.Settings := DatabaseSettings; 
// ... do something with O
O.Free;
Run Code Online (Sandbox Code Playgroud)

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

它也可以保持类和对象:

var
  DatabaseSettings: TDatabaseSettings; 
  DatabaseSettingsInt : IMySettings;
Run Code Online (Sandbox Code Playgroud)

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

如果你真的想要禁用引用计数,你只需要创建一个实现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;
Run Code Online (Sandbox Code Playgroud)

FreeRef只是像_Release那样降低引用数量.您可以在通常使用Free的地方使用它.


Tim*_*van 7

_AddRef,_Release并且_QueryInterface,事实上,要覆盖什么.但是,你应该非常清楚你正在做什么,因为这会导致内存泄漏或奇怪的,难以发现的错误.

不要从中返回TInterfacedObject,而是从其中继承TObject并实现返回1的前两个方法的自己版本.

  • 返回-1只是一个约定,返回值本身并不重要,只要它不是0(这将导致实现对象的破坏). (2认同)

tz.*_*tz. 7

不要从TInterfacedObject继承,而是从标准System.Generics.Defaults单元的TSingletonImplementation继承.

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