绕过(禁用)Delphi';s接口的引用计数
对于我正在处理的应用程序体系结构中的一个特定问题,接口似乎是一个很好的解决方案。具体来说,一些“业务对象”依赖于从实际应用程序中的数据库中提取的一组设置。让这些业务对象请求接口(通过控制反转),并让一个中央绕过(禁用)Delphi';s接口的引用计数,delphi,Delphi,对于我正在处理的应用程序体系结构中的一个特定问题,接口似乎是一个很好的解决方案。具体来说,一些“业务对象”依赖于从实际应用程序中的数据库中提取的一组设置。让这些业务对象请求接口(通过控制反转),并让一个中央TDatabaseSettings对象实现这些接口,可以实现更好的隔离,从而更容易进行单元测试 然而,在Delphi中,接口似乎附带了一个令人不快的好处:引用计数。这意味着如果我这样做: type IMySettings = interface function getMySettin
TDatabaseSettings
对象实现这些接口,可以实现更好的隔离,从而更容易进行单元测试
然而,在Delphi中,接口似乎附带了一个令人不快的好处:引用计数。这意味着如果我这样做:
type
IMySettings = interface
function getMySetting: String;
end;
TDatabaseSettings = class(..., IMySettings)
//...
end;
TMyBusinessObject = class(TInterfacedObject, IMySettings)
property Settings: IMySettings read FSettings write FSettings;
end;
var
DatabaseSettings: TDatabaseSettings;
// global object (normally placed in a controller somewhere)
//Now, in some function...
O := TMyBusinessObject.Create;
O.Settings := DatabaseSettings;
// ... do something with O
O.Free;
在最后一行(O.Free
),我的全局DatabaseSettings
对象现在也被释放,因为对它的最后一个接口引用(包含在O
中)丢失了
一种解决方案是使用接口存储“全局”数据库设置对象;另一个解决方案是覆盖TDatabaseSettings
类的引用计数机制,这样我可以继续将DatabaseSettings
作为普通对象进行管理(这与应用程序的其余部分更加一致)
总之,我的问题是:如何禁用特定类的接口引用计数机制?
我已经找到一些信息,建议覆盖类的接口方法\u AddRef
和\u Release
(TDatabaseSettings
);有人这样做过吗
或者你会说我不应该这样做(混淆?只是一个坏主意?),并找到一个不同的解决方案来解决架构问题
非常感谢 \u AddRef
、\u Release
和\u QueryInterface
实际上是您想要覆盖的内容。但是,您应该非常清楚自己在做什么,因为这可能会导致内存泄漏或奇怪的、难以发现的bug
不要从TInterfacedObject
开始,而是从TObject
开始,实现返回1的前两个方法的您自己的版本。好的,您可以绕过它,但问题是您是否真的想要它。
如果您想使用接口,最好完全使用它们。所以,正如您所经历的,如果混合使用类和接口变量,您会遇到问题
var
// DatabaseSettings: TDatabaseSettings;
DatabaseSettings : IMySettings;
//Now, in some function...
O := TMyBusinessObject.Create;
O.Settings := DatabaseSettings;
// ... do something with O
O.Free;
现在您有了对接口的第二个引用,丢失第一个引用不会释放对象
还可以尽可能保留类和对象:
var
DatabaseSettings: TDatabaseSettings;
DatabaseSettingsInt : IMySettings;
确保在创建对象后立即设置接口
如果您真的想禁用引用计数,您只需要创建一个实现IInterface的ToObject的新子体。我已经在D2009中测试了下面的示例,它是有效的:
// Query Interface can stay the same because it does not depend on reference counting.
function TMyInterfacedObject.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
if GetInterface(IID, Obj) then
Result := 0
else
Result := E_NOINTERFACE;
end;
constructor TMyInterfacedObject.Create;
begin
FRefCount := 1;
end;
procedure TMyInterfacedObject.FreeRef;
begin
if Self = nil then
Exit;
if InterlockedDecrement(FRefCount) = 0 then
Destroy;
end;
function TMyInterfacedObject._AddRef: Integer;
begin
Result := InterlockedIncrement(FRefCount);
end;
function TMyInterfacedObject._Release: Integer;
begin
Result := InterlockedDecrement(FRefCount);
if Result = 0 then
Destroy;
end;
FreeRef只是降低refcount,就像释放一样。您可以在正常情况下免费使用它。禁用此类问题的引用计数闻起来很糟糕。
一个更好的体系结构解决方案是使用某种“单例”模式。
实现这一点的最简单方法如下所示:
interface
type
TDatabaseSettings = class(..., IMySettings)
end;
function DatabaseSettings: IMySettings;
implementation
var
GDatabaseSettings: IMySettings;
function DatabaseSettings: IMySettings;
begin
if GDatabaseSettings = nil then GDatabaseSettings := TDatabaseSettings.Create;
Result := GDatabaseSettings;
end;
O := TMyBusinessObject.Create;
O.Settings := DatabaseSettings;
O.Free;
顺便说一下:使用接口时:始终使用接口变量!不要混合使用两个class en接口变量(使用“var设置:IMySettings”而不是“var设置:TDatabaseSettings”)。否则引用计数将妨碍您(自动销毁、无效指针操作等)。
在上面的解决方案中,GDatabaseSettings也是“IMySettings”类型,因此它会获得一个正确的引用计数,并将持续到程序终止。要禁用引用计数,AddRef和Release只能返回-1
function TMyInterfacedObject._AddRef: Integer;
begin
Result := -1;
end;
function TMyInterfacedObject._Release: Integer;
begin
Result := -1;
end;
在没有引用计数的接口中有很多实用程序。如果使用引用计数,则不能混合对象引用和接口引用,因为会发生不好的事情。通过禁用引用计数,您可以愉快地混合接口和对象引用,而无需担心对象突然自动销毁。或者只需使用以下代码:
var
I: IMyInterface;
begin
I := ...;
...
Do whatever you want in a scope;
Initialize(I); //- this will clear the interface variable without calling the _release.
end.
变量
I:IMyInterface;
开始
I:=。。。;
...
在一个范围内做任何你想做的事;
初始化(I);//-这将在不调用_release的情况下清除接口变量。
结束。
不要从标准装置下降,而是从标准装置下降
- TSingletonImplementation是需要基本接口实现的简单类的基础,引用计数被禁用
- TSingletonImplementation是支持接口的Delphi类的线程安全基类。与TInterfacedObject不同,TSingletonImplementation不实现引用计数
真是太快了。。。非常感谢!你认为我使用接口的方式有意义吗?让我感到奇怪的是,整个引用计数都出现了,真的——为什么不把它们作为解耦类的好方法呢?引用计数实际上非常灵活。您不需要释放对象;当分配给它的变量不在范围内时,Delphi会帮你做的。是的,这是真的,它很光滑(尽管我还没有找到它的用途)。但是能够禁用它也很好,因为如果与“传统”对象管理混合使用,它确实会使事情变得不一致:)返回-1只是一种约定,返回值本身并不重要,只要它不是0(这将导致实现对象的破坏)。@Mghie:实际上,因为调用Destroy(即在TInterfacedObject上)的是_Release方法本身,而不是一些外部代码,所以您甚至不必担心返回0。不过,最好返回-1,让最终用户知道这不是一个参考计数界面,以防他们在意。非常感谢您的广泛回复,非常感谢!是的,在我离开之前我应该多想想