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

Jas*_*out 9 delphi com delphi-10.1-berlin

德尔福柏林10.1增加了[弱]参考.Marco Cantu的博客有一些基础知识.

对于我的测试,我创建了两个包含两个自动化对象类型的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;
  };

};
Run Code Online (Sandbox Code Playgroud)

这是我的容器实现

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.
Run Code Online (Sandbox Code Playgroud)

我的第二个库引用第一个库,仅包含我的内容接口的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;
  };
Run Code Online (Sandbox Code Playgroud)

以及弱引用的实现

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.
Run Code Online (Sandbox Code Playgroud)

我测试如下

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.
Run Code Online (Sandbox Code Playgroud)

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

All*_*uer 16

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

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


Rem*_*eau 12

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

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

type
  TMyContent = class(TAutoObject, IMyContent)
  private
    FContainer : Pointer{IMyContainer};
    ...
  end;
Run Code Online (Sandbox Code Playgroud)

你只需要强制转换FContainerIMyContainer,当您需要使用它的方法/属性,如:

IMyContainer(FContainer).Add(...);
Run Code Online (Sandbox Code Playgroud)

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

type
  TMyContent = class(TAutoObject, IMyContent)
  private
    [Unsafe] FContainer : IMyContainer;
    ...
  end;
Run Code Online (Sandbox Code Playgroud)

如Marco的博客中所述:

Delphi 10.1 Berlin中的弱和不安全接口参考

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

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

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

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