代码之家  ›  专栏  ›  技术社区  ›  onnodb

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

  •  10
  • onnodb  · 技术社区  · 16 年前

    对于我正在研究的应用程序体系结构中的一个特定问题,接口似乎是一个很好的解决方案。具体来说,一些“业务对象”依赖于从实际应用程序的数据库中提取的一系列设置。让这些业务对象请求接口(通过 控制反转 ,并让一个中心 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;
    

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

    一种解决方案是存储“全局” 数据库设置 具有接口的对象;另一个解决方案是重写 t数据库设置 类,以便我可以继续管理 数据库设置 作为一个普通的对象(与应用程序的其他部分更一致)。

    所以,总的来说,我的问题是:如何禁用特定类的接口引用计数机制?

    我已经找到了一些建议覆盖 IInterface 方法 _AddRef _Release 为班级( t数据库设置 在这个例子中);有人做过吗?

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

    谢谢!

    6 回复  |  直到 16 年前
        1
  •  13
  •   dummzeuch Stijn Sanders    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;
    

    确保在创建对象后立即设置接口。

    如果您真的想禁用引用计数,只需创建一个新的Tobject子代来实现iInterface。我在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只是像释放一样降低refCount。你可以在你通常免费使用的地方使用它。

        2
  •  7
  •   Tim Sullivan    13 年前

    _AddRef , _Release _QueryInterface 实际上,是您想要覆盖的内容。但是,您应该非常清楚自己在做什么,因为这可能导致内存泄漏或奇怪的、难以发现的错误。

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

        3
  •  7
  •   tz.    12 年前

    不要从 TInterfacedObject ,而不是从 TSingletonImplementation 从标准 System.Generics.Defaults 单位。

    • t单体实施 是需要基本IInterface实现的简单类的基础,禁用引用计数。
    • t单体实施 是支持接口的Delphi类的线程安全基类。与TinterfacedObject不同,TsingletionImplementation不实现引用计数。
        4
  •  4
  •   Tim Sullivan    16 年前

    要禁用引用计数,addref和release只应返回-1。

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

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

        5
  •  3
  •   André    16 年前

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

    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设置:imysettings”而不是“var设置:tdatabasesettings”)。否则,引用计数将妨碍您(自动销毁、指针操作无效等)。 在上面的解决方案中,gdatabasesettings也是“imysettings”类型,因此它得到了一个适当的引用计数,并将持续到程序终止。

        6
  •  0
  •   Zigmund    12 年前

    或者使用下面的代码:

        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.