我目前正致力于将现有的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(???)
答案 0 :(得分: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的进程。创建表单时,消息框将显示监视器实例的地址。记下地址,然后单击“确定”。调试器将出现,并在对该指针的写访问时设置断点,如下所示:
ba w4 A32D00
用消息框中的正确地址替换A32D00
。继续执行,调试器应该在分配监视器时命中断点。使用各种调试器视图(模块,线程,堆栈),您可以获得有关写入该地址的代码的重要信息。
答案 1 :(得分:3)
无效的指针操作意味着你的程序试图释放一个指针,但它有三个错误之一:
你不太可能有多个内存管理器分配TMonitor
个记录,所以我认为我们可以排除第一种可能性。
至于第二种可能性,如果程序中有一个类没有自定义析构函数或者没有释放其析构函数中的任何内存,那么该对象的第一个实际内存释放可以在TObject中,它释放对象的监视器。如果您有该类的实例并尝试将其释放两次,则该问题可能以TMonitor中的异常形式出现。在程序中查找双重免费错误。 debugging options in FastMM可以帮助您。此外,当您获得该异常时,请使用call stack了解如何使用TMonitor的析构函数。
如果第三种可能性是原因,那么你就有内存损坏。如果你有代码来假设一个对象的大小,那么这可能是原因。从Delphi 2009开始,TObject大四个字节。总是使用InstanceSize
方法获取对象的大小;不要只是添加其所有字段的大小或使用幻数。
你说线程是由Outlook创建的。您是否设置了IsMultithread
全局变量?您的程序通常在创建线程时将其设置为True,但如果您不是创建线程的那个,它将保持其默认的False值,这会影响内存管理器是否在分配和释放期间无法保护其全局数据结构。在DPR文件的主程序块中将其设置为True。
答案 2 :(得分:1)
经过大量挖掘后发现我做得很好(阅读:恐怖,但它已经在我们的delphi 5应用程序中正常工作了很长时间)
PClass(TForm)^ := TMyOwnClass
在我们的应用程序框架内部的某个地方。显然Delphi 2010有一些类初始化来初始化现在没有发生的“监视器字段”,导致RTL在表单销毁时尝试“释放syncobject”,因为getFieldAddress返回了一个非零值。啊。
为什么我们首先要做这个黑客的原因是因为我想在所有表单实例上自动更改createParams,以实现无图标的可调整大小的表单。我将打开一个关于如何在没有破坏黑客攻击的情况下执行此操作的新问题(现在只需在表单中添加一个漂亮的闪亮图标)。
我会将Mghie的建议作为答案,因为它为我(以及任何阅读此主题的人)提供了非常多的见解。谢谢大家的贡献!
答案 3 :(得分:0)
Delphi中有两个TMonitor:
自Delphi 2009以来,System.TMonitor已添加到Delphi中;因此,如果您从Delphi 5移植代码,那么您的代码使用的是Forms.TMonitor,而不是System.TMonitor。
我认为在您的代码中引用的类名没有单元名,这就引起了混淆。