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

Delphi7,传递对象';s接口-释放对象时导致无效指针操作,delphi,pointers,interface,Delphi,Pointers,Interface,我有一个实现接口的类,该接口可用于插件。 类的声明非常简单。对于整个应用程序,该类只有一个实例。当调用返回接口的函数时,它会在检索到的接口上调用_AddRef,然后将其作为结果传回。不幸的是,在我尝试释放对象之前,它一直工作(请参阅“终结”部分)——它报告无效的指针操作。如果我把它注释掉,它可以正常工作(但是FastMM报告内存泄漏,所以对象没有被释放) 下面是函数中返回接口的部分代码(事实上,它是我的“ServicesManager”类的重写查询接口) 以及ConfigManager类的代码

我有一个实现接口的类,该接口可用于插件。 类的声明非常简单。对于整个应用程序,该类只有一个实例。当调用返回接口的函数时,它会在检索到的接口上调用_AddRef,然后将其作为结果传回。不幸的是,在我尝试释放对象之前,它一直工作(请参阅“终结”部分)——它报告无效的指针操作。如果我把它注释掉,它可以正常工作(但是FastMM报告内存泄漏,所以对象没有被释放)

下面是函数中返回接口的部分代码(事实上,它是我的“ServicesManager”类的重写查询接口)

以及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
类型
TConfigManager=class(TInterfacedObject,ISDK_ConfigManager)
私有的
... 
结束;
变量
ConfigManager:TConfigManager;
实施
...
初始化
ConfigManager:=TConfigManager.Create();
定稿
如果ConfigManager为零,则
FreeAndNil(配置管理器)//如果我把它注释掉,它会泄漏内存,但没有无效的Ptr。作品.加薪
我做错了什么?
我需要传递一个对ConfigManager实例的引用。

你说这是一个插件系统?您是否将插件作为BPL加载?事实上,我上周遇到了这个问题。您不能依赖于终结来清除接口引用。您需要确保在卸载插件之前清除它们,否则其内存空间将无效


编辑:所谓“清除接口引用”,我的意思是对它们调用_Release,或者手动将其设置为nil,或者让引用超出范围。如果您的接口管理器持有对插件的接口引用,那么当接口管理器被销毁时,它们将被清除。

在处理接口时,您将听到的第一条建议是切勿将接口引用与对象引用混在一起。这意味着,一旦开始通过接口引用引用对象,就不再通过对象引用引用它。永远

原因是第一次分配接口变量时,对象的引用计数将变为1。当该变量超出范围或被赋予新值时,引用计数变为零,对象释放自身。这一切都没有对原始对象引用变量进行任何修改,因此当您稍后尝试使用该变量时,它不是空指针,但它引用的对象已不存在-它是一个悬空引用。当您试图释放不存在的内容时,会出现无效指针操作异常

ConfigManager
变量声明为接口。不要自己释放它。一旦这样做,就可以将
TConfigManager
的整个声明移动到实现部分,因为该单元之外的任何代码都不会引用它


此外,很少有理由提供自己的
QueryInterface
实现。(您说过您已经覆盖了它,但这是不可能的,因为它不是虚拟的。)
TInterfacedObject
提供的一个就足够了。你所提供的那个实际上导致了内存泄漏,因为你在不应该增加引用计数的时候增加了引用计数
GetInterface
已经调用了
\u AddRef
(通过执行接口赋值),因此您返回的对象引用计数过高。

我完全同意Rob的观点

最有可能的帮助是重写初始化代码,如下所示

现在
ConfigManager
属于
ISDK\u ConfigManager
类型,通过为其分配nil,引用计数将减少。 当引用计数变为零时,它将自动释放

type
  TConfigManager = class(TInterfacedObject, ISDK_ConfigManager)
  private
  ...
  end;

var
  ConfigManager: ISDK_ConfigManager;
implementation

...

initialization
  ConfigManager:= TConfigManager.Create();
finalization
  ConfigManager := nil;
end;

--jeroen

TConfigManager类是否有声明为“已发布”的方法?

没有,插件是DLL,但它们都(应用程序和插件)使用共享BPL。嗯,你能告诉我你说的清除ifaces参考资料是什么意思吗?顺便问一下,您是如何编辑我的qestion以突出显示语法的P我在StackOverflow上没有这个编辑器,它不能为我处理
标记…StackOverflow不能用
标记查看代码。它是通过特殊的缩进来实现的,如果在段落中嵌入代码,则使用反勾号。我通过删除标记、突出显示代码并按下编辑器上方的“010 101”按钮(自动设置格式)修复了此问题。TConfigManager.Destroy做了什么?不会导致错误。当然,我在这里发布之前也对它进行了注释,这对我没有帮助……好吧,我找到了一个解决方法,而不是修复方法。我不是给FreeAndNil打电话,而是给你打电话。。。但仍然在寻找答案!!公元1年。好的,如果我希望我的主窗体实现其中一个接口,然后在插件请求时传递它,该怎么办?顺便说一句,我用同样的方法实现了它(Form.GetInterface…+\u AddRef),它在那里工作!ad 2.对,并没有真正覆盖它,只是使用“自己的”版本;)为什么?因为我要传递给插件的是“服务”的接口,这是插件唯一可以访问的东西,而且非常有限,不公开任何功能。不过,稍后它可以用来访问各种接口,代码类似于`(应用程序作为ISDK_ConfigManager)`在表单中,
\u AddRef
实际上什么都不做。我不明白你为什么要自己实现
QueryInterface
。所有接口都提供了
QueryInterface
,甚至您所说的“服务”接口也没有公开任何功能。(如果它没有任何功能,那么它为什么存在呢?)它的存在是为了为所有可用的服务提供接口,如给定的示例所示;)哦,我找到了。在一些论坛上,他们说:
type
  TConfigManager = class(TInterfacedObject, ISDK_ConfigManager)
  private
  ...
  end;

var
  ConfigManager: ISDK_ConfigManager;
implementation

...

initialization
  ConfigManager:= TConfigManager.Create();
finalization
  ConfigManager := nil;
end;