如何解决界面乱七八糟的问题

时间:2011-10-28 06:51:02

标签: delphi interface delphi-2009

我一直在考虑将接口作为一种为不同的不相关类提供通用功能的方法。但接口的属性 - “当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!

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

问候。

3 个答案:

答案 0 :(得分:8)

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

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在评论中所建议的那样。这实际上意味着声明自己的“TInterfacedObject”并实现三个IInterface方法:

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

本质上是为_AddRef和_Release返回-1。正如大卫所说:看看TComponent是如何做到的。当FVCLComObject为零时,只需要做它正在做的事情。

答案 1 :(得分:3)

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

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,并且在对象上调用方法之后调用方法是错误的销毁。这是一种特别讨厌的错误类型,因为在您的代码在最重要的客户端计算机上运行之前,它通常不会表现为运行时故障!换句话说,这种错误可能是间歇性的。

答案 2 :(得分:3)

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