【问题标题】:Invalid Pointer Operation while freeing an Interfaced Object in a DLL释放 DLL 中的接口对象时指针操作无效
【发布时间】:2013-09-16 06:27:27
【问题描述】:

我仍在尝试使用接口。我实现它们的唯一目的是与在 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,我仍然有这个问题。但我在_MyObject.Free 上得到Invalid Pointer Operation

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

为什么会出现这种情况以及如何解决?

【问题讨论】:

  • 调用者两次调用CreateMyObject会发生什么?为什么你需要一个全局变量?您所需要的只是一个返回由新创建的对象实现的接口的函数。
  • @David 实际上,我上面的代码是一个更复杂的基础设施的重新设计版本。我实际上保留了这些实例的列表,但要发布我如何管理这个列表将是很多不必要的代码,所以我几乎淡化了它,只展示了事情如何工作的核心基础知识。

标签: delphi dll interface delphi-xe2 destructor


【解决方案1】:

一个无效的指针操作意味着你释放了一些没有分配的东西。

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

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

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

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

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

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

一旦你开始通过接口引用访问一个对象,最好不要再通过普通的对象引用来使用它。风险太大了,你会在它已经被破坏后不小心使用它。

【讨论】:

  • 另一个选项是通过实现 AddRef 和 Release 方法来删​​除引用计数,使其始终具有(比如说)1 的值;然后,您可以像处理非引用对象引用一样手动释放该对象。
  • @David,返回值与删除引用计数无关。要删除引用计数,您只需将它们实现为不计算引用。不过,这并不能解决所有问题。如果你有一个悬空的接口引用,你可能会遇到访问冲突或其他未定义的行为。
  • 天啊,现在我不喜欢接口[实际上我不喜欢引用计数]。永远不会想到我不应该释放它。
  • @RobKennedy,我不明白你的评论,抱歉。我读了你的答案,因为它已经被释放,因为当引用计数达到 0 时接口释放了自己,并且混合了对象和接口引用。因此,一种常见的解决方案是手动禁用引用计数和释放接口对象。我在您的评论或关于“返回值”的问题中遗漏了什么?
  • @David,你说去除引用计数的方法是实现AddRefRelease返回1。返回值无关; no 生产代码曾经使用过该值。要禁用引用计数,您可以在这些方法中做任何您想做的事情,但不要计算引用。但是,它并不能解决太多问题,因为即使您不计算接口引用,这并不意味着仍然不存在任何接口引用。如果有,并且您手动释放该对象,您的程序可能仍会崩溃。如果有的话,继续计数引用,但禁用自我释放行为。
【解决方案2】:

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

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

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;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-11-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-06
    • 1970-01-01
    • 2021-10-07
    • 1970-01-01
    相关资源
    最近更新 更多