9

Delphi Berlin 10.1 添加了 [弱] 引用。Marco Cantu 的博客有一些基础知识。

对于我的测试,我创建了两个包含两种自动化对象类型的 COM 库。容器对象持有内容对象的列表,而内容对象持有对其容器的弱引用。

以下两种情况经过测试并正常工作(弱引用设置为空并释放内存):

  • 具有接口和 CoClass 的单个 COM 库。
  • 两个 COM 库,一个带有接口,另一个带有 CoClass

但是,当我将 coclass 放在两个单独的库中时,代码会产生“无效的类类型转换”,删除 [weak] 属性时错误就会消失。请原谅奇怪的样本,其目的只是为了使问题最小化,不应被视为标准编码实践

这是第一个为容器定义接口和 CoClass 的库 .ridl 文件:

[
  uuid(E1EE3651-A400-49BF-B5C5-006D9943B9C0),
  version(1.0)

]
library DelphiIntfComLib
{

  importlib("stdole2.tlb");

  interface IMyContainer;
  interface IMyContent;
  coclass MyContainer;


  [
    uuid(A7EF86F7-40CD-41EE-9DA1-4D9B7B24F06B),
    helpstring("Dispatch interface for MyContainer Object"),
    dual,
    oleautomation
  ]
  interface IMyContainer: IDispatch
  {
    [id(0x000000C9)]
    HRESULT _stdcall Add([in] IMyContent* AMyContent);
  };

  [
    uuid(BFD6D976-8CEF-4264-B95A-B5DA7817F6B3),
    helpstring("Dispatch interface for MyContent Object"),
    dual,
    oleautomation
  ]
  interface IMyContent: IDispatch
  {
    [id(0x000000C9)]
    HRESULT _stdcall SetWeakReferenceToContainer([in] IMyContainer* AContainer);
  };

  [
    uuid(1F56198B-B1BE-4E11-BC78-0E6FF8E55214)
  ]
  coclass MyContainer
  {
    [default] interface IMyContainer;
  };

};

这是我的容器实现

unit Unit1;

{$WARN SYMBOL_PLATFORM OFF}

interface

uses
  ComObj, ActiveX, DelphiIntfComLib_TLB, StdVcl, Generics.Collections;

type
  TMyContainer = class(TAutoObject, IMyContainer)
  private
     FList: TList<IMyContent>;
  protected
    procedure Add(const AMyContent: IMyContent); safecall;
  public
    Destructor Destroy; override;

    procedure Initialize; override;

  end;

implementation

uses ComServ;

procedure TMyContainer.Add(const AMyContent: IMyContent);
begin
  FList.Add(AMyContent);
  AMyContent.SetWeakReferenceToContainer(self);
end;

destructor TMyContainer.Destroy;
begin
  FList.Free;
  inherited;
end;

procedure TMyContainer.Initialize;
begin
  inherited;
  FList := TList<IMyContent>.create;
end;

initialization
  TAutoObjectFactory.Create(ComServer, TMyContainer, Class_MyContainer,
    ciMultiInstance, tmApartment);
end.

我的第二个库引用了第一个并且仅包含我的内容接口的 CoClass

[
  uuid(65659EE4-1949-4112-88CA-F2D5B5D8DA2C),
  version(1.0)

]
library DelphiImplComLib
{

  importlib("stdole2.tlb");
  importlib("DelphiIntfComLib.dll");

  coclass MyContent;


  [
    uuid(79D1669A-8EB6-4AE6-8F4B-91137E6E6DC1)
  ]
  coclass MyContent
  {
    [default] interface IMyContent;
  };

及其使用弱引用的实现

unit Unit2;

{$WARN SYMBOL_PLATFORM OFF}

interface

uses
  ComObj, ActiveX, DelphiImplComLib_TLB, StdVcl, DelphiIntfComLib_TLB;

type
  TMyContent = class(TAutoObject, IMyContent)
  private
   [Weak] //If included will cause "invalid class typecast" error
    FContainer : IMyContainer;
  protected
    procedure SetWeakReferenceToContainer(const AContainer: IMyContainer); safecall;
  end;

implementation

uses ComServ;

procedure TMyContent.SetWeakReferenceToContainer(const AContainer: IMyContainer);
begin
  FContainer := AContainer;
end;

initialization
  TAutoObjectFactory.Create(ComServer, TMyContent, Class_MyContent,
    ciMultiInstance, tmApartment);
end.

我测试如下

program Project13;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  DelphiImplComLib_TLB in 'impl\DelphiImplComLib_TLB.pas',
  DelphiIntfComLib_TLB in 'Intf\DelphiIntfComLib_TLB.pas';

var
  GMyContainer : IMyContainer;
  GMyContent : IMyContent;
begin
  GMyContainer := CoMyContainer.Create;
  GMyContent := CoMyContent.Create;
  GMyContainer.Add(GMyContent);
end.

为什么在拆分实现时会出现错误?我怎样才能缓解这个问题?

4

2 回答 2

17

请勿将 [Weak] 用于 COM 接口。它不适用于 COM。[Weak] 应仅用于内部非导出 COM 接口。

原因是 COM 接口实现(甚至可能没有由 Delphi 类实现)无法正确处理 [弱] 引用。此外,您拥有的 COM 库不共享基本 TObject 的相同实现。您可能会使用共享的 rtl 包构建所有内容,但即便如此……您还是在地雷上跳舞。

于 2016-09-20T16:15:20.673 回答
12

正如 Allen Bauer 在他的回答中解释的那样,[weak]它不适用于 COM 接口,因为它们不能保证得到 DelphiTObject派生类的支持,这对于[weak]在释放对象时自动取消引用是必需的。RTL 在运行时跟踪弱引用,但不能跨库跟踪弱引用,除非在它们之间共享 RTL 库的单个实例(即,如果您在启用运行时包的情况下编译库,然后使用可执行文件部署 RTL BPL )。

但是,只要您不需要使用 的 auto-nil 功能[weak],您就可以使用 untypedPointer代替:

type
  TMyContent = class(TAutoObject, IMyContent)
  private
    FContainer : Pointer{IMyContainer};
    ...
  end;

您只需在需要使用其方法/属性时进行类型转换,例如FContainerIMyContainer

IMyContainer(FContainer).Add(...);

在 10.1 Berlin 及更高版本中,您可以改用该[unsafe]属性:

type
  TMyContent = class(TAutoObject, IMyContent)
  private
    [Unsafe] FContainer : IMyContainer;
    ...
  end;

正如 Marco 的博客所述:

Delphi 10.1 Berlin 中的弱和不安全的接口引用

如果对象有一个标准的引用计数实现,并且您想创建一个不包含在引用总数中的接口引用怎么办?您现在可以通过将 [unsafe] 属性添加到接口变量声明来实现此目的,将上面的代码更改为:

procedure TForm3.Button2Click(Sender: TObject);
var
  [unsafe]
  one: ISimpleInterface;
begin
  one := TObjectOne.Create;
  one.DoSomething;
end;

这并不是一个好主意,因为上面的代码会导致内存泄漏。通过禁用引用计数,当变量超出范围时,什么也不会发生。在某些情况下这是有益的,因为您仍然可以使用接口而不触发额外的引用。换句话说,一个不安全的引用被视为...一个指针,没有额外的编译器支持。

现在,在您考虑使用 unsafe 属性来获得引用而不增加计数之前,请考虑在大多数情况下还有另一个更好的选择,即使用弱引用。弱引用也避免增加引用计数,但它们是受管理的。这意味着系统会跟踪弱引用,如果实际对象被删除,它会将弱引用设置为 nil。相反,使用不安全引用,您无法知道目标对象的状态(称为悬空引用的场景)。

于 2016-09-20T16:49:40.007 回答