Delphi7,传递对象接口 - 释放对象时导致无效指针操作

3
我有一个实现接口的类,该类可供插件使用。 类的声明非常简单。整个应用程序只有一个此类的实例。当调用返回接口的函数时,它会在传递结果之前对检索到的接口调用_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 实例。


TConfigManager.Destroy是做什么用的? - Ozan
在编程方面的相关内容翻译成中文。仅返回已翻译的文本:不包含任何可能导致错误的内容。在发布前,我已经将其注释掉了,但没有帮助我... - migajek
我发现了一个“变通方法”,不是“解决办法”。 我改为调用._release而不是FreeAndNil。 但仍在寻找答案!! - migajek
4个回答

10
你会在处理接口时听到的最重要建议之一是永远不要混合使用接口引用与对象引用。这意味着一旦你开始通过接口引用引用一个对象,你就停止通过对象引用引用它。因为第一次分配接口变量时,对象的引用计数会变成1。当该变量超出范围或被赋予新值时,引用计数变为0,对象自己释放。当你稍后尝试使用那个变量时,它不是空指针,但它所引用的对象已经消失了——它是一个悬挂引用。当你尝试释放不存在的东西时,会得到一个无效指针操作异常。
将你的ConfigManager变量声明为一个接口。不要自己释放它。一旦你这样做了,你可以将整个TConfigManager声明移动到实现部分,因为没有任何代码在该单元外部会引用它。
此外,很少有理由提供自己的QueryInterface实现。(你说你覆盖了它,但这是不可能的,因为它不是虚拟的。)由TInterfacedObject提供的那个应该足够了。你提供的那个实际上正导致了一个内存泄漏,因为你在不该增加引用计数的时候增加了它。GetInterface已经调用了_AddRef(通过执行接口分配),所以你返回的对象具有膨胀的引用计数。

广告1:好的,如果我想让我的主窗体实现其中一个接口,然后按照插件请求传递它怎么办? 顺便说一句,我已经以相同的方式实现了它(Form.GetInterface... + _AddRef),在那里它起作用了!广告2:没错,不是真正覆盖它,只是使用“自己”的版本;) 为什么? 因为我传递给插件的是“服务”接口,这是插件可以访问的唯一内容,并且非常有限,不公开任何功能。 但是稍后可以使用类似以下代码的代码来访问各种接口(FApplication as ISDK_ConfigManager).DoSomething ... - migajek
在这个表单中,_AddRef实际上并没有实现任何功能。而且我不理解为什么你要有自己的QueryInterface实现。所有接口都提供QueryInterface,即使是你所说不公开任何函数的"服务"接口。(如果它没有任何函数,那么它存在的目的是什么?) - Rob Kennedy
它的存在是为了提供所有可用服务的接口,就像给出的例子一样 ;) - migajek
哦,我找到了。在一些讨论论坛上,他们说TComponent(和它的子类)不是引用计数的。我应该使用一些不计算引用计数的类作为基类吗?... - migajek
我认为我已经清楚地表达了我认为你应该做什么。请参考Jeroen的答案获取代码示例。我还更新了我的答案,以解释为什么你的自定义“QueryInterface”实现是错误的。 - Rob Kennedy

2
你说这是一个插件系统?你是将插件加载为BPL吗?实际上我上周遇到了这个问题。不能依赖终结来清除接口引用。在卸载插件之前,你需要确保清除它们,否则它的内存空间将变得无效。
编辑:通过“清除接口引用”,我指的是调用_Release,要么手动将其设置为nil,要么让引用超出作用域。如果你的接口管理器持有插件的接口引用,当接口管理器被销毁时,它们将被清除。

不,插件是dll文件,但是应用程序和插件都使用共享的BPL文件。嗯,请问清除ifaces引用是什么意思?顺便问一下,你是怎么编辑我的问题以突出语法的?:P 我在StackOverflow上用不了这个编辑器,它不能为我处理<code>标签... - migajek
StackOverflow不使用<code>标签进行代码查看,而是通过特殊缩进或在段落中嵌入代码时使用反引号。我通过删除标签、突出显示代码并按编辑器上方的“010 101”按钮来自动设置格式,从而解决了这个问题。 - Mason Wheeler

1

我完全同意Rob的观点。

最有可能有帮助的是像下面这样重写你的初始化代码。

现在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


0
TConfigManager类中是否声明了任何“published”方法?

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接