3

我仍在尝试使用接口。我实现它们的唯一目的是与在 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访问这些导出的方法。

为什么我会得到这个,我该如何解决?

4

2 回答 2

6

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

在这种情况下,您要释放的对象已经被销毁。在析构函数中放一个断点,自己看看。

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

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

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

一旦对象被销毁,你的全局变量就是一个悬空引用。它拥有一个不再存在的对象的地址。当你调用Free它时,析构函数将地址传递给内存管理器,但内存管理器知道它在那个地址(不再)没有任何东西,所以它引发了一个异常。

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

一旦你开始通过接口引用访问一个对象,最好不要再通过普通的对象引用来使用它。风险太大,以至于您会在该对象已被破坏后意外使用该对象。

于 2013-09-12T04:06:38.317 回答
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;
于 2013-09-12T04:54:31.820 回答