释放DLL中的接口对象时无效的指针操作

时间:2013-09-12 02:58:17

标签: delphi dll interface delphi-xe2 destructor

我仍在尝试使用Interfaces。我实现它们的唯一目的是与在DLL中实例化的对象进行交互。当我使用它时,一切正常,所有方法都按预期工作,等等。问题是清理该界面背后的对象。

我有一个像这样的简单界面

IMyInterface = interface
  ['{d52b14f3-156b-4df8-aa16-cb353193d27c}']
  procedure Foo;
end;

和它的对象

TMyObject = class(TInterfacedObject, IMyInterface)
private
  procedure Foo;
end;

在DLL内部,我有一个这个对象的全局变量,以及两个导出函数来创建和销毁这个实例

var
  _MyObject: TMyObject;

function CreateMyObject: IMyInterface; stdcall;
begin
  _MyObject:= TMyObject.Create;
  Result:= IMyInterface(_MyObject);
end;

function DestroyMyObject: Integer; stdcall;
begin
  _MyObject.Free; //   <--   Invalid Pointer Operation
end;

对象的析构函数实际上什么也没做,只是inherited而且我仍然有这个问题。但我在Invalid Pointer Operation上获得_MyObject.Free

我使用LoadLibraryGetProcAddress来访问这些导出的方法。

为什么我得到这个以及如何修复它?

2 个答案:

答案 0 :(得分:6)

无效的指针操作意味着您释放了未分配的内容。

在这种情况下,您正在释放的对象已被销毁。在析构函数中放置一个断点并亲自查看。

接口有与它们相关的引用计数代码,这就是为什么你读过的关于接口的所有建议都说不要将它们与没有这种引用计数的对象引用混合在一起。

当您实例化对象并将其分配给全局变量时,对象的引用计数为零,并且尚未涉及接口。将其分配给功能结果时,引用计数变为1。您可以观察如果启用调试DCU并使用调试器逐步执行该语句,会发生这种情况。 (顺便说一下,类型转换是不必要的;编译器已经知道该对象实现了目标接口,并且允许自己进行简单的赋值。)

在其他地方,在此DLL的消费方面,保存对象的最后接口引用的变量被清除。引用计数变为零,对象将自行销毁。

一旦对象被销毁,您的全局变量就是悬空参考。它保存不再存在的对象的地址。当你在它上面调用Free时,析构函数会将地址传递给内存管理器,但内存管理器知道它在该地址上没有任何内容(因此它会引发异常)。

要解决此问题,请将该全局变量的类型更改为接口类型,然后删除Free调用;将其替换为为变量分配nil的语句。通过这些更改,创建对象并在变量中存储接口引用会将对象的引用计数设置为1,并将其返回给调用者将其设置为2。当消费者清除其引用时,计数将降为1,新的nil赋值将其设置为零,使对象在适当的时间自由释放。

一旦通过接口引用开始访问对象,最好不再通过普通对象引用来使用它。风险太大,以至于在物体被摧毁后会意外地使用它。

答案 1 :(得分:2)

您不应该在基于TInterfacedObject的类上调用.Free。当最后一个带接口的引用为零时,它会自动释放。

您的代码示例应如下所示:

var
  _MyObject: IUnknown;

function CreateMyObject: IMyInterface; stdcall;
begin
  // unified interface
  _MyObject:= TMyObject.Create as IUnknown;
  // cast to IMyInterface
  Result:= _MyObject as IMyInterface;
end;

function DestroyMyObject: Integer; stdcall;
begin
  _MyObject := nil; 
end;