Delphi [弱]引用属性产生"无效类类型转换"当实现在多个库中时

时间:2016-09-20 13:45:29

标签: delphi com delphi-10.1-berlin

Delphi Berlin 10.1增加了[弱]引用。 Marco Cantu's Blog有一些基础知识。

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

以下两个方案已经过测试并正常工作(弱引用设置为null并释放内存):

  • 包含接口和CoClasses的单个COM库。
  • 两个COM库,一个带有接口,另一个带有CoClasses

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

这是第一个库.ridl文件,它定义了接口和CoClass 容器:

[
  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.

为什么在拆分实现时出错?我该如何缓解这个问题呢?

2 个答案:

答案 0 :(得分:16)

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

原因是COM接口实现无法通过Delphi类实现,无法正确处理[弱]引用。此外,您拥有的COM库不共享基础TObject的相同实现。你可以 使用共享的rtl包来构建一切,但即便如此......你在地雷上跳舞。

答案 1 :(得分:12)

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

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

Pointer

只要您需要使用其方法/属性,就必须将type TMyContent = class(TAutoObject, IMyContent) private FContainer : Pointer{IMyContainer}; ... end; 强制转换为FContainer,例如:

IMyContainer

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

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

正如Marco的博客中提到的那样:

Weak and Unsafe Interface References in Delphi 10.1 Berlin

  

如果对象具有标准引用计数实现并且您希望创建一个保持在引用总数之外的接口引用,该怎么办?您现在可以通过将[unsafe]属性添加到接口变量声明,将上面的代码更改为:

来实现此目的
type
  TMyContent = class(TAutoObject, IMyContent)
  private
    [Unsafe] FContainer : IMyContainer;
    ...
  end;
     

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

     

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