Delphi 如何解决接口混乱

Delphi 如何解决接口混乱,delphi,interface,delphi-2009,Delphi,Interface,Delphi 2009,我一直认为接口是一种为不同的无关类提供公共功能的方法。但是接口的属性——“当RefCOunt降为零时释放对象”不允许我按自己的意愿工作 例如:假设我有两个不同的类:TMyObject和tmyDifferentintObject。它们都支持此接口: const IID_MyInterface: TGUID = '{4D91C27F-510D-4673-8773-5D0569DFD168}'; type IMyInterface = Interface(IInterface) ['{4D

我一直认为接口是一种为不同的无关类提供公共功能的方法。但是接口的属性——“当RefCOunt降为零时释放对象”不允许我按自己的意愿工作

例如:假设我有两个不同的类:TMyObject和tmyDifferentintObject。它们都支持此接口:

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!
这是一个例子。一般来说,我希望将对象的实例传递给某个过程,并检查该对象是否支持接口,如果支持,我希望执行该接口的方法。但我不想在接口超出范围时完成该对象的工作。如何做到这一点


关于。

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

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;
这样做意味着创建后的引用计数为零。或者将对象指定给接口引用,只要需要

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在评论中的建议禁用refcounting。这本质上意味着声明您自己的“TInterfacedObject”并实现三种接口方法:

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

本质上是为AddRef和Release返回
-1
。正如David所说:看看TComponent是如何实现的。只要看看FVCLComObject为nil时它在做什么。

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

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;
然后,您可以从此类派生类

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


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

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

您可以禁用引用计数。看看IInterface是如何在TComponent中实现的。谢谢你的回答,Marjan,但我没有得到一件事。如果RefCount已降至0,则MyObject已自动释放。但是你在做MyObject.RefCount的时候却没有得到ACCES违例,为什么?编译器认识到您是在引用接口,而不是对象本身?其本质是不计算引用数。返回-1并不是禁用引用计数的原因。@Wodzu,访问已释放对象的字段不一定会导致程序崩溃。除非内存已返回到操作系统,否则读取该内存不可能导致访问冲突,因为操作系统认为它仍然属于您的进程,即使您的进程尚未将其分配给任何对象。谈论引用接口而不是对象本身是毫无意义的;没有对象,就没有接口。接口本身不包含任何数据。@Rob,因此Marjan示例中的最后一行代码是错误的(在接口为零后显示RefCount),它工作只是巧合?@Wodzu:可能吧。虽然有时编译器引入的临时变量会将refcount保持在零以上,并将实例保持在零附近,直到过程/函数退出。很棒的类名(
tinterfacedobjectwithoutlinefetimemanagement
)+这是一个非常简单的选择,在很多情况下,只要你稍后调用_release,引用计数达到零,并且对象最终被释放,并且没有泄漏,那么这个选择就是好的。