【问题标题】:Invalid pointer operation in TMonitor.DestroyTMonitor.Destroy 中的无效指针操作
【发布时间】:2011-01-05 01:53:34
【问题描述】:

我目前正在将现有的 Delphi 5 应用程序移植到 Delphi 2010。

这是一个加载到 Outlook 中的多线程 DLL(其中线程由 Outlook 生成)。当通过 Delphi 2010 编译时,每当我关闭一个表单时,我都会在 TMonitor.Destroy 中遇到“无效指针操作”……就是 system.pas 中的那个。

由于这是一个现有的并且有点复杂的应用程序,我有很多 的方向要研究,而 delphi 帮助甚至没有记录几乎没有记录这个特定的TMonitor 类开始(我将其追溯到一些带有附加信息的 Allen Bauer 帖子)......所以我想我先问问是否有人以前遇到过这个问题,或者对可能导致这个问题的原因有任何建议。 作为记录:我没有在我的代码中明确使用 TMonitor 功能,我们在这里谈论的是 Delphi 5 代码的直接移植。

编辑问题发生时的调用栈:

System.TMonitor.Destroy
System.TObject.Free
Forms.TCustomForm.CMRelease(???)
Controls.TControl.WndProc(???)
Controls.TWinControl.WndProc((45089, 0, 0, 0, 0, 0, 0, 0, 0, 0))
Forms.TCustomForm.WndProc(???)
Controls.TWinControl.MainWndProc(???)
Classes.StdWndProc(15992630,45089,0,0)
Forms.TApplication.ProcessMessage(???)

【问题讨论】:

标签: delphi delphi-2010 tmonitor


【解决方案1】:

Delphi中有两个TMonitor:

  1. System.TM 监视器;这是一条记录,用于线程同步。
  2. Forms.TM 监视器;这是一个表示连接的监视器(显示设备)的类。

System.TMonitor 自 Delphi 2009 起被添加到 Delphi;因此,如果您从 Delphi 5 移植代码,您的代码使用的是 Forms.TMonitor,而不是 System.TMonitor。

我认为在您的代码中引用的类名没有单元名,这会造成混淆。

【讨论】:

  • 我不这么认为。任何提到 Forms 类的单元都必须在其 uses 子句中包含 Forms。无论发生这种情况,Forms 中的所有内容都比 System 中的任何内容都具有更近的范围——在名称解析期间首先找到最近使用的单元的内容。任何使用裸 TMonitor 标识符来引用 Forms.TMonitor 的旧代码将继续引用该类。只有不使用 Forms 的代码才会引用 System.TMonitor,但在 Delphi 5 中,这样的代码永远不会编译。问题出在其他地方。
  • 我没有使用 Forms.TMonitor。我知道 Forms.TMonitor 做了什么,我对新 System.TMonitor 做了什么有了基本的了解,我只是不知道它为什么会在我面前爆炸(我的代码没有明确使用它)。
【解决方案2】:

一个无效的指针操作意味着你的程序试图释放一个指针,但它有以下三个错误之一:

  • 它是由其他一些内存管理器分配的。
  • 之前已经释放过一次。
  • 它从来没有被任何东西分配过。

您不太可能有多个内存管理器分配TMonitor 记录,所以我认为我们可以排除第一种可能性。

至于第二种可能性,如果您的程序中有一个类没有自定义析构函数或没有释放其析构函数中的任何内存,那么该对象的第一个实际内存释放可能在 TObject ,它释放对象的监视器。如果您有该类的一个实例并且您尝试释放它两次,则该问题可能会以 TMonitor 中的异常形式出现。在你的程序中寻找双重释放错误。 debugging options in FastMM 可以帮助您。此外,当您遇到该异常时,请使用call stack 了解您是如何进入 TMonitor 的析构函数的。

如果第三种可能是原因,那么你有内存损坏。如果您的代码对对象的大小进行了假设,那么这可能就是原因。 TObject 比 Delphi 2009 大四个字节。始终使用InstanceSize 方法来获取对象的大小;不要只是将所有字段的大小相加或使用幻数。

您说线程是由 Outlook 创建的。您是否设置了IsMultithread 全局变量?您的程序通常在创建线程时将其设置为 True,但如果您不是创建线程的人,它将保持其默认 False 值,这会影响内存管理器在分配和释放期间是否费心保护其全局数据结构.在 DPR 文件的主程序块中将其设置为 True。

【讨论】:

  • 好的通用指针,谢谢。 IsMultithread 肯定是开启的,Delphi 5 也非常需要它。来自 TMonitor 析构函数的调用堆栈不是那么有用(与析构函数调用堆栈一样),我将其添加到问题中。 FastMM 是我名单上的下一个。
  • FastMM 的完全调试模式,通常是我在这种情况下最好的朋友,但什么也没产生。
【解决方案3】:

指向每个对象的System.Monitor 实例的指针存储在所有数据字段之后。如果向对象的最后一个字段写入过多数据,则可能会在监视器的地址中写入虚假值,当对象的析构函数试图破坏虚假监视器时,这很可能会导致崩溃。您可以在表单的BeforeDestruction 方法中检查此地址是否为nil,对于直接的Delphi 5 端口,不应分配任何监视器。类似的东西

procedure TForm1.BeforeDestruction;
var
  MonitorPtr: PPMonitor;
begin
  MonitorPtr := PPMonitor(Integer(Self) + InstanceSize - hfFieldSize + hfMonitorOffset);
  Assert(MonitorPtr^ = nil);
  inherited;
end;

如果这是您的原始代码中的问题,您应该能够在您的 DLL 的 Delphi 5 版本中检测到它,方法是使用 FastMM4 内存管理器并激活所有检查。 OTOH 这也可能是由于 Unicode 构建中字符数据的大小增加造成的,在这种情况下,它只会在使用 Delphi 2009 或 2010 的 DLL 构建中表现出来。使用最新的 FastMM4 进行所有检查仍然是个好主意。

编辑:

从您的堆栈跟踪看来,监视器确实已分配。找出我为什么要使用数据断点。我无法让它们与 Delphi 2009 一起工作,但你可以使用 WinDbg 轻松完成。

在表单的 OnCreate 处理程序中输入以下内容:

var
  MonitorPtr: PPMonitor;
begin
  MonitorPtr := PPMonitor(Integer(Self) + InstanceSize - hfFieldSize + hfMonitorOffset);
  MessageDlg(Format('MonitorPtr: %p', [pointer(MonitorPtr)]), mtInformation,
    [mbOK], 0);
  DebugBreak;
  // ...

现在加载 WinDbg 并打开并运行调用您的 DLL 的进程。创建表单后,一个消息框将显示监视器实例的地址。记下地址,然后单击“确定”。调试器会出现,并且您在对该指针的写访问设置一个断点,如下所示:

ba w4 A32D00

用消息框中的正确地址替换A32D00。继续执行,调试器应该在分配监视器时命中断点。使用各种调试器视图(模块、线程、堆栈),您可以获得有关写入该地址的代码的重要信息。

【讨论】:

  • 好提示!设法在 DLL 之外重现问题,这有助于让 IDE 在出现问题时真正停止。
【解决方案4】:

经过大量挖掘,事实证明我做得很好(阅读:可怕,但它在我们的 delphi 5 应用程序中已经正常工作了很长时间

PClass(TForm)^ := TMyOwnClass 

在我们应用程序框架的深处。显然,Delphi 2010 有一些类初始化来初始化现在没有发生的“监视器字段”,导致 RTL 在表单销毁时尝试“释放同步对象”,因为 getFieldAddress 返回了一个非零值。呃。

为什么我们首先做这个 hack 的原因是因为我想自动更改所有表单实例上的 createParams,以实现无图标可调整大小的表单。我将提出一个关于如何在没有破坏 rtl 的黑客攻击的情况下做到这一点的新问题(现在只需在表单中添加一个漂亮的闪亮图标)。

我会将 Mghie 的建议标记为答案,因为它为我(以及阅读此主题的任何人)提供了非常丰富的见解。感谢大家的贡献!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-04-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多