我一直在考虑将接口作为一种为不同的不相关类提供通用功能的方法。但接口的属性 - “当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!
这是一个例子。一般情况下,我想将对象的实例传递给某个过程并检查该对象是否支持接口,如果是,我想执行该接口的方法。但是当界面超出范围时,我不想完成该对象的工作。怎么做?
问候。
答案 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
。