【问题标题】:How to solve an interface mess如何解决界面混乱
【发布时间】:2011-10-28 06:51:02
【问题描述】:

我一直认为接口是一种为不同的不相关类提供通用功能的方法。但是接口的属性 - “当 RefCOunt 降至零时释放对象”不允许我按我的意愿工作。

例如:假设我有两个不同的类:TMyObject 和 TMyDifferentObject。他们都支持这个接口:

const
  IID_MyInterface: TGUID = '{4D91C27F-510D-4673-8773-5D0569DFD168}';

type
 IMyInterface = Interface(IInterface)
  ['{4D91C27F-510D-4673-8773-5D0569DFD168}']
  function GetID : Integer;
 end;

type
  TMyObject = class(TInterfacedObject, IMyInterface)
    function GetID: Integer;
  end;

function TMyObject.GetID: Integer;
begin
  Result := 1;
end;


type
  TMyDifferentObject = class(TInterfacedObject, IMyInterface)
    function GetID: Integer;
  end;

function TMyDifferentObject.GetID: Integer;
begin
  Result := 2;
end;

现在,我想在我的程序中创建这些类的实例,然后将这些实例传递给这个方法:

procedure ShowObjectID(AObject: TObject);
var
  MyInterface: IMyInterface;
begin
  if Supports(AObject, IID_MyInterface, MyInterface) then
  begin
    ShowMessage(IntToStr(MyInterface.GetID));
  end;
end;  //Interface goes out of scope and AObject is freed but I still want to work with that object!

这是一个例子。一般来说,我想将对象的实例传递给某个过程并检查该对象是否支持接口,如果是,我想执行该接口的方法。但是当接口超出范围时,我不想完成该对象的工作。如何做到这一点?

问候。

【问题讨论】:

  • 您可以禁用引用计数。看看 IInterface 在 TComponent 中是如何实现的。

标签: delphi interface delphi-2009


【解决方案1】:

您的问题可能源于您使用对象引用创建对象:

var
  MyObject: TObject;
begin
  MyObject := TMyObject.Create;
  ShowMessage('Before ShowObjectID MyObject RefCount: ' + IntToStr(MyObject.RefCount));
  ShowObjectID(MyObject);
  ShowMessage('After ShowObjectID MyObject RefCount: ' + IntToStr(MyObject.RefCount));
end;

这样做意味着创建后的 RefCount 为零。只要您需要,也可以将您的对象分配给接口引用,

var
  MyObject: TMyObject;
  MyIntf: IMyInterface;
begin
  MyObject := TMyObject.Create;
  MyIntf := MyObject;
  ShowMessage('Before ShowObjectID MyObject RefCount: ' + IntToStr(MyObject.RefCount));
  ShowObjectID(MyObject);
  ShowMessage('After ShowObjectID MyObject RefCount: ' + IntToStr(MyObject.RefCount));
  MyIntf := nil;
  ShowMessage('After nilling the interface MyObject RefCount: ' + IntToStr(MyObject.RefCount));
end;

或者按照 David 在 cmets 中的建议禁用引用计数。这实质上意味着声明您自己的“TInterfacedObject”并实现三个 IInterface 方法:

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

本质是为_AddRef 和_Release 都返回-1。正如大卫所说:看看 TComponent 是如何做到的。当 FVCLComObject 为 nil 时,就拿它在做什么。

【讨论】:

  • 感谢 Marjan 的回答,但我一无所获。如果 RefCount 已降至 0,则 MyObject 已自动释放。但是然后您执行 MyObject.RefCount 并且您没有获得 AccesViolation,怎么会?编译器识别出您指的是接口而不是对象本身?
  • 本质是不计算引用。返回 -1 并不是禁用引用计数的原因。
  • @Wodzu,访问已释放对象的字段并不一定会导致程序崩溃。除非内存已返回给操作系统,否则读取该内存是不可能的访问冲突,因为操作系统认为它仍然属于您的进程,即使您的进程尚未将其分配给任何对象。说是指接口而不是指对象本身是无稽之谈;没有对象,就没有接口。接口本身不包含任何数据。
  • @Rob 所以 Marjan 示例中的最后一行代码是错误的(在 nilling 接口后显示 RefCount),它起作用只是巧合?
  • @Wodzu:可能。尽管有时编译器引入的临时变量会使引用计数保持在零以上,并且实例保持不变,直到过程/函数退出。
【解决方案2】:

解决您的问题的一种方法是更改​​您的代码,以便您只通过接口引用来引用对象。换句话说,而不是

var
  obj: TMyObject;
...
obj := TMyObject.Create;
try
  obj.DoStuff;
  //etc. etc.
finally
  obj.Free;
end;

你写

var
  obj: IMyObject;//NOTE: interface variable
...
obj := TMyObject.Create;
obj.DoStuff;
//etc. etc.
obj := nil;//or let it go out of scope and release that way

这可能不方便,因此禁用自动生命周期管理会更方便。您需要为您的实现对象执行此操作:

type
  TInterfacedObjectWithoutLifetimeManagement = class(TObject, IInterface)
  private
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
  end;

function TInterfacedObjectWithoutLifetimeManagement.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
  if GetInterface(IID, Obj) then
    Result := 0
  else
    Result := E_NOINTERFACE;
end;

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

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

然后你可以从这个类派生你的类。

这种方法有一个非常重要的警告。假设您在变量(本地、全局、类成员)中保存了由派生自TInterfacedObjectWithoutLifetimeManagement 的类实现的任何接口。所有此类接口变量都必须在您在实现对象上调用Free 之前完成。

如果你不遵循这个规则,你会发现当这些接口变量超出范围时,编译器仍然会发出代码来调用_Release,并且在对象被销毁后调用它的方法是错误的。这是一种特别讨厌的错误,因为在您的代码在最重要的客户端机器上运行之前,它通常不会出现运行时故障!换句话说,此类错误可能是间歇性的。

【讨论】:

  • 很棒的类名 (TInterfacedObjectWithoutLifetimeManagement)。 +1 使其易于理解。
【解决方案3】:

到目前为止没有人提到的另一个选项是在对象实例上显式调用 _AddRef 以使其在需要时保持活动状态,然后调用 _Release

【讨论】:

  • 确实是一个非常简单的选择,在很多情况下,很好的'nuff,只要你稍后调用_release并且引用计数确实达到零并且对象最终被释放,而不是泄漏。
猜你喜欢
  • 1970-01-01
  • 2012-07-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多