2011-10-28 66 views
2

我一直在想接口作为一种方式来给不同的不相关的类一个共同的功能。但接口的属性 - “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! 

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

问候。

+1

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

回答

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; 

否则像这意味着引用计数后创建为零。或者将对象分配给接口引用以及只要您需要,

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; 

或者禁用refcounting,如David在评论中所建议的那样。这实际上意味着,宣布自己的“TInterfacedObject”和实现三个IInterface方法:

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

本质是返回-1两个_AddRef和_Release。正如大卫所说:看看TComponent如何做到这一点。只要FVCLComObject为零时就可以做它正在做的事情。

+0

感谢Marjan的回答,但我没有得到一件事。如果RefCount已经降到0,那么MyObject已被自动释放。但是,然后你做MyObject.RefCount,你没有得到AccesViolation,怎么回事?编译器认识到你正在尝试接口而不是对象本身? – Wodzu

+0

*本质*不包括引用。返回-1不是禁用引用计数的原因。 –

+0

@Wodzu,访问释放对象的字段不一定会导致程序崩溃。除非内存已经返回到操作系统,否则即使您的进程没有将其分配给任何对象,也不可能因为操作系统认为它仍属于您的进程而从该内存中读取访问冲突。谈论提及界面而不是谈论物体本身是无稽之谈;没有对象,那里*是*没有界面。该接口不包含任何自己的数据。 –

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

+1

伟大的班级名称('TInterfacedObjectWithoutLifetimeManagement')。 +1使其可以理解。 –

3

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

+0

确实是一个非常简单的选项,在很多情况下,只要稍后确实调用_release并且引用计数达到零,并且该对象最终被释放并且不泄漏,那么确实是很好的“nuff”。 –