Delphi中的"Supports"会增加对[weak]或[unsafe]接口的引用计数

11
当我在使用 Delphi Berlin 10.1 的 [weak] (和 [unsafe]) 引用时,"Supports" 函数和 "QueryInterface" 都会在给定标记有 "weak" 属性的接口变量时增加引用计数(与 "unsafe" 属性的行为相同)。
program WeakReferences;

{$APPTYPE CONSOLE}

{$R *.res}

uses System.SysUtils;

type
   IAnInterfacedObject = interface
   ['{351DFDA3-42CA-4A1D-8488-494CA454FD9C}']
   end;

   TAnInterfacedObject = class(TInterfacedObject, IAnInterfacedObject)
     protected

      function  GetTheReferenceCount : integer;

     public
      constructor Create;
      destructor  Destroy; override;
      property    TheReferenceCount : integer read GetTheReferenceCount;
   end;

   constructor TAnInterfacedObject.Create;

   begin
      inherited Create;
      writeln('(create AIO instance)');
   end;

   destructor TAnInterfacedObject.Destroy;

   begin
      writeln('(destroy AIO instance)');

      inherited Destroy;
   end;

   function TAnInterfacedObject.GetTheReferenceCount : integer;

   begin
      Result := FRefCount;
   end;

   procedure WithoutSupports;

   var
      AIOinstance : TAnInterfacedObject;

      AIOinterfaced : IAnInterfacedObject;

      [Weak]
      WeakAIOinterfaced : IAnInterfacedObject;

   begin
      AIOinstance := TAnInterfacedObject.Create;
      writeln('created AIO instance; refcount: '+AIOinstance.TheReferenceCount.ToString);

      AIOinterfaced := AIOinstance;
      writeln('create AIO interfaced; refcount: '+AIOinstance.TheReferenceCount.ToString);

      WeakAIOinterfaced := AIOinstance;
      writeln('create WEAK AIO interfaced; refcount: '+AIOinstance.TheReferenceCount.ToString);
   end;

   procedure WithSupports_Weak;

   var
      AIOinstance : TAnInterfacedObject;

      AIOinterfaced : IAnInterfacedObject;

      [Weak]
      WeakAIOinterfaced : IAnInterfacedObject;

   begin
      AIOinstance := TAnInterfacedObject.Create;
      writeln('created AIO instance; refcount: '+AIOinstance.TheReferenceCount.ToString);

      AIOinterfaced := AIOinstance;
      writeln('create AIO interfaced; refcount: '+AIOinstance.TheReferenceCount.ToString);

      Supports(AIOinstance, IAnInterfacedObject, WeakAIOinterfaced);
      writeln('create WEAK AIO interfaced with SUPPORTS; refcount: '+AIOinstance.TheReferenceCount.ToString);
   end;

   procedure WithSupports_Unsafe;

   var
      AIOinstance : TAnInterfacedObject;

      AIOinterfaced : IAnInterfacedObject;

      [Unsafe]
      UnsafeAIOinterfaced : IAnInterfacedObject;

   begin
      AIOinstance := TAnInterfacedObject.Create;
      writeln('created AIO instance; refcount: '+AIOinstance.TheReferenceCount.ToString);

      AIOinterfaced := AIOinstance;
      writeln('create AIO interfaced; refcount: '+AIOinstance.TheReferenceCount.ToString);

      Supports(AIOinstance, IAnInterfacedObject, UnsafeAIOinterfaced);
      writeln('create UNSAFE AIO interfaced with SUPPORTS; refcount: '+AIOinstance.TheReferenceCount.ToString);
   end;

   procedure WithQueryInterface_Weak;

   var
      AIOinstance : TAnInterfacedObject;

      AIOinterfaced : IAnInterfacedObject;

      [Weak]
      WeakAIOinterfaced : IAnInterfacedObject;

   begin
      AIOinstance := TAnInterfacedObject.Create;
      writeln('created AIO instance; refcount: '+AIOinstance.TheReferenceCount.ToString);

      AIOinterfaced := AIOinstance;
      writeln('create AIO interfaced; refcount: '+AIOinstance.TheReferenceCount.ToString);

      AIOinterfaced.QueryInterface(IAnInterfacedObject, WeakAIOinterfaced);
      writeln('create WEAK AIO interfaced with QUERYINTERFACE; refcount: '+AIOinstance.TheReferenceCount.ToString);
   end;

begin
  try
     writeln('--Without "Supports"-------------------');
     WithoutSupports;
     writeln;
     writeln('--With "Supports" - weak-------------------');
     WithSupports_Weak;
     writeln;
     writeln('--With "Supports" - unsafe-------------------');
     WithSupports_Unsafe;
     writeln;
     writeln('--With "QueryInterface" - weak-------------------');
     WithQueryInterface_Weak;

  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;

  readln;
end.

我在这里错过了什么?是否有一个“WeakSupports”函数?这是一个 bug 还是新的“弱”接口特性的缺陷?

1个回答

9

Delphi中的Supports(其中一个重载 - 但所有其他内容都考虑out参数相同)函数声明如下:

function Supports(const Instance: IInterface; const IID: TGUID; out Intf): Boolean;

并使用 QueryInterface 作为

function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;

两个out参数均未标记为[weak]。这意味着您不能将[weak][unsafe]接口引用传递给它们。您只能传递强引用到这样的参数中以保持引用计数有序。
根据文档Weak References

注意:只有将[Weak]变量传递给也标记为[Weak]的var或out参数才可以。您无法将常规强引用传递给[Weak] var或out参数。

注意:只有将[Unsafe]变量传递给也标记为[Unsafe]的var或out参数才可以。您无法将常规强引用传递给[Unsafe] var或out参数。

此外,您的测试案例还存在另一个问题。除非您使用完全支持ARC的Delphi编译器(目前为Android和iOS),否则您不能将引用计数对象存储到对象引用中,否则会破坏引用计数。更精确地说,在非ARC编译器下,对象引用不会对引用计数做出贡献,您只能将它们用作临时访问点来执行一些无法通过声明的接口访问的对象上的操作。您应该始终至少有一个接口引用到该对象实例中,以保持引用计数有序,并防止过早的对象销毁或内存泄漏。
替换为:
 var
   AIOinstance : TAnInterfacedObject;
   ...
   AIOinstance := TAnInterfacedObject.Create;

你应该始终使用

var
  AIOinstance : IAnInterfacedObject;
  ...
  AIOinstance := TAnInterfacedObject.Create;

1
@ArnaudBouchez我不确定你的意思。混合对象引用和接口引用问题正是ARC编译器解决的问题。当然,对于跨平台代码,您仍需要遵循非ARC编译器规则,永远不要混合这两者。 - Dalija Prasnikar
1
Dalija,有合理的理由创建一个具有本地对象实例的对象,然后稍后将其分配给接口。我们经常创建和初始化对象,然后再将其分配给接口。 - Graymatter
1
我并不是在说要保留这个对象,而是使用它来创建和设置对象。例如,begin MyObj := TMyObject.Create; MyObj.Init(xxxx); Result := MyObj; end; 这里函数返回 IMyObject - 你最后的评论“你应该总是使用”并不一定切实可行。当然,你可以创建一个接口来设置你的对象,但那是不必要的。 - Graymatter
2
我似乎漏掉了什么。你如何获得错误的引用计数?这是工厂模式中非常常见的一种情况。http://pastebin.com/nYWgdfSe - Graymatter
1
@DalijaPrasnikar 这种 ARC 和非 ARC 之间的不一致行为在实践中使得与之工作非常困难,特别是如果你还需要维护一些非 ARC 代码。还有 Free/DisposeOf 技巧。以及需要显式 [weak] 引用,包括函数参数。我发现相比于 Delphi 显式内存管理,通过 Free 或 TComponent 拥有权或接口引用计数(包括短生命周期类实例的智能指针技巧),它更冗长和难以调试。但这是非常个人的看法。 - Arnaud Bouchez
显示剩余11条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接