我有一个实现接口的类,可用于插件。 课堂宣言很简单。整个应用程序只有一个此类的实例。当调用返回接口的函数时,它会在检索到的接口上调用_AddRef,然后再将其作为结果传回。不幸的是,它一直有效,直到我尝试释放对象(参见“终结”部分) - 它报告无效的指针操作。如果我注释掉它,它可以正常工作(但是FastMM会报告内存泄漏,因此对象没有被释放)。
以下是函数中返回接口的部分代码(实际上它是我的“ServicesManager”类的重写的QueryInterface)。
if ConfigManager.GetInterface(IID, obj) then
begin
ISDK_ConfigManager(obj)._AddRef;
result:= 0;
end
和ConfigManager类的代码......
type
TConfigManager = class(TInterfacedObject, ISDK_ConfigManager)
private
...
end;
var
ConfigManager: TConfigManager;
implementation
...
initialization
ConfigManager:= TConfigManager.Create();
finalization
if ConfigManager <> nil then
FreeAndNil(ConfigManager); //if I comment it out, it leaks the memory but no Invalid Ptr. Op. raises
我做错了什么? 我需要传递一个对这个 ConfigManager实例的引用。
答案 0 :(得分:10)
在处理界面时,您会听到的第一条建议是永远不要将界面引用与对象引用混合。这意味着一旦您通过接口引用开始引用对象,您就不再通过对象引用来引用它。如初。
原因是第一次分配接口变量时,对象的引用计数将变为1.当该变量超出范围或被赋予新值时,引用计数变为零,并且对象释放本身。这一切都没有对原始对象引用变量进行任何修改,因此当您稍后尝试使用该变量时,它不是空指针,但它引用的对象已经消失 - 它是悬空引用 。当您尝试释放不存在的内容时,会出现无效指针操作异常。
将您的ConfigManager
变量声明为接口。不要自己解脱。完成后,您可以将TConfigManager
的整个声明移动到实现部分中,因为该单元外的代码不会引用它。
此外,很少有任何理由提供您自己的QueryInterface
实施。 (你说你覆盖了它,但这是不可能的,因为它不是虚拟的。)TInterfacedObject
提供的那个应该足够了。您提供的那个实际上是导致内存泄漏,因为您不应该增加引用计数。 GetInterface
已调用_AddRef
(通过执行接口分配),因此您将返回带有夸大引用计数的对象。
答案 1 :(得分:2)
你说这是一个插件系统?您是否将插件作为BPL加载?实际上,上周我遇到了这个问题。您不能依赖 finalization 来清除您的界面引用。在卸载插件之前,您需要确保清除它们,否则它的内存空间将无效。
编辑:通过“清除接口引用”,我的意思是通过手动将其设置为nil或通过让引用超出范围来调用它们的_Release。如果您的接口管理器持有对插件的接口引用,那么当接口管理器被破坏时它们将被清除。
答案 2 :(得分:1)
我完全赞同罗布。
最有可能帮助的是重写您的初始化代码,如下所示。
现在ConfigManager
的类型为ISDK_ConfigManager
,并且通过为其指定nil,引用计数将减少。
当引用计数变为零时,它将自动被释放。
type
TConfigManager = class(TInterfacedObject, ISDK_ConfigManager)
private
...
end;
var
ConfigManager: ISDK_ConfigManager;
implementation
...
initialization
ConfigManager:= TConfigManager.Create();
finalization
ConfigManager := nil;
end;
- 的Jeroen
答案 3 :(得分:0)
TConfigManager类是否有任何声明为“已发布”的方法?