2

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

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

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

在问题发生时编辑Callstack:

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(???)
4

4 回答 4

7

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

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

如果这是您的原始代码中的问题,您应该能够通过激活所有检查的 FastMM4 内存管理器在您的 DLL 的 Delphi 5 版本中检测到它。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 的进程。创建表单后,一个消息框将显示监视器实例的地址。记下地址,然后单击“确定”。调试器将出现,您在对该指针的写访问设置一个断点,如下所示:

巴 w4 A32D00

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

于 2010-01-14T15:22:45.323 回答
3

无效的指针操作意味着您的程序试图释放一个指针,但它存在以下三个问题之一:

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

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

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

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

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

于 2010-01-14T15:18:38.247 回答
1

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

PClass(TForm)^ := TMyOwnClass 

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

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

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

于 2010-01-18T18:37:17.947 回答
0

Delphi中有两个TMonitor:

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

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

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

于 2010-01-14T14:00:01.483 回答