TMonitor.Destroy 中的无效指针操作
我目前正在将现有的 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(???)
I'm currently working on porting an existing Delphi 5 application to Delphi 2010.
It's a multithreaded DLL (where the threads are spawned by Outlook) that loads into Outlook. When compiled through Delphi 2010, whenever I close a form I run into an "invalid pointer operation" inside TMonitor.Destroy... the one in system.pas, that is.
As this is an existing and kinda complex application, I have a lot of directions to look into, and the delphi help doesn't even document barely documents this particular TMonitor class to begin with (I traced it to some Allen Bauer posts with additional information) ... so I figured I'd first ask around if anyone had encountered this before or had any suggestions on what could cause this problem.
For the record: I am not using the TMonitor functionality explicitly in my code, we are talking a straight port of Delphi 5 code here.
Edit Callstack at the moment the problem occurs:
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(???)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
指向每个对象的 System.Monitor 实例的指针存储在所有数据字段之后。如果向对象的最后一个字段写入太多数据,则可能会向监视器的地址写入虚假值,当对象的析构函数尝试销毁虚假监视器时,这很可能会导致崩溃。您可以在表单的
BeforeDestruction
方法中检查此地址是否为nil
,对于直接的 Delphi 5 端口,不应分配任何监视器。 如果这是您原始代码中的问题,您应该能够通过使用 FastMM4 内存管理器并激活所有检查,在 Delphi 5 版本的 DLL 中检测到它。 OTOH 这也可能是由于 Unicode 构建中字符数据大小的增加造成的,在这种情况下,它只会在使用 Delphi 2009 或 2010 的 DLL 构建中体现出来。使用最新的 FastMM4 进行所有检查仍然是一个好主意。
编辑:
从堆栈跟踪来看,监视器确实已分配。为了找出为什么我会使用数据断点。我无法让它们与 Delphi 2009 一起工作,但您可以使用 WinDbg 轻松完成。
在表单的
OnCreate
处理程序中输入以下内容:现在加载 WinDbg 并打开并运行调用 DLL 的进程。创建表单后,消息框将显示监视器实例的地址。记下地址,然后单击“确定”。调试器将出现,并且您可以在对该指针的写访问上设置断点,如下所示:
将
A32D00
替换为消息框中的正确地址。继续执行,当监视器被分配时,调试器应该命中断点。使用各种调试器视图(模块、线程、堆栈),您可以获得有关写入该地址的代码的重要信息。The pointer to the
System.Monitor
instance of each object is stored after all the data fields. If you write too much data to the last field of an object it could happen that you write a bogus value to the address of the monitor, which would most probably lead to a crash when the destructor of the object attempts to destroy the bogus monitor. You could check for this address beingnil
in theBeforeDestruction
method of your forms, for a straight Delphi 5 port there shouldn't be any monitors assigned. Something likeIf this is a problem in your original code you should be able to detect it in the Delphi 5 version of your DLL by using the FastMM4 memory manager with all checks activated. OTOH this could also be caused by the size increase of character data in Unicode builds, and in that case it would only manifest in DLL builds using Delphi 2009 or 2010. It would still be a good idea to use the latest FastMM4 with all checks.
Edit:
From your stack trace it looks like the monitor is indeed assigned. To find out why I would use a data breakpoint. I haven't been able to make them work with Delphi 2009, but you can do it easily with WinDbg.
In the
OnCreate
handler of your form put the following:Now load WinDbg and open and run the process that calls your DLL. When the form is created a message box will show you the address of the monitor instance. Write down the address, and click OK. The debugger will come up, and you set a breakpoint on write access to that pointer, like so:
replacing
A32D00
with the correct address from the message box. Continue the execution, and the debugger should hit the breakpoint when the monitor gets assigned. Using the various debugger views (modules, threads, stack) you may get important information about the code that writes to that address.无效的指针操作意味着您的程序尝试释放指针,但存在以下三个错误之一:
您不太可能有多个内存管理器分配
TMonitor
< /a> 记录,所以我认为可以排除第一种可能。至于第二种可能性,如果程序中有一个类没有自定义析构函数,或者在其析构函数中没有释放任何内存,那么该对象的第一个实际内存释放可能在 TObject 中,其中它释放对象的监视器。如果您有该类的实例并尝试释放它两次,则该问题可能会在 TMonitor 中以异常的形式出现。查找程序中的双重释放错误。 FastMM 中的调试选项可以帮助您。另外,当您遇到该异常时,请使用调用堆栈 了解如何到达 TMonitor 的析构函数。
如果第三种可能性是原因,那么你就出现了内存损坏。如果您的代码对对象的大小做出假设,那么这可能就是原因。从 Delphi 2009 开始,TObject 增大了 4 个字节。始终使用
InstanceSize< /code>
获取对象大小的方法;不要只是将其所有字段的大小相加或使用幻数。
您说线程是由 Outlook 创建的。您是否设置了
IsMultithread
全局变量?你的程序通常在创建线程时将其设置为 True,但如果你不是创建线程的人,它将保持默认的 False 值,这会影响内存管理器在分配和释放期间是否费心保护其全局数据结构。在 DPR 文件的主程序块中将其设置为 True。An invalid pointer operation means your program attempted to free a pointer, but there was one of three things wrong with it:
It's unlikely that you'd have multiple memory managers allocating
TMonitor
records, so I think we can rule out the first possibility.As for the second possibility, if there's a class in your program that either doesn't have a custom destructor or that doesn't free any memory in its destructor, then the first actual memory deallocation for that object could be in TObject, where it frees the object's monitor. If you have an instance of that class and you attempt to free it twice, that problem could appear in the form of an exception in TMonitor. Look for double-free errors in your program. The debugging options in FastMM can help you with that. Also, when you get that exception, use the call stack to find out how you got to TMonitor's destructor.
If the third possibility is the cause, then you have memory corruption. If you have code that makes assumptions about the size of an object, then that could be the cause. TObject is four bytes larger as of Delphi 2009. Always use the
InstanceSize
method to get an object's size; don't just add up the size of all its fields or use a magic number.You say the threads are created by Outlook. Have you set the
IsMultithread
global variable? Your program normally sets it to True when it creates a thread, but if you're not the one creating threads, it will remain at its default False value, which affects whether the memory manager bothers to protects its global data structures during allocation and deallocation. Set it to True in your DPR file's main program block.经过大量挖掘后,结果证明我
在应用程序内部深处的某个地方 做得很好(读:令人恐惧,但它在我们的 delphi 5 应用程序中已经正常工作了很长时间)框架。显然,Delphi 2010 有一些类初始化来初始化“监视字段”,但现在没有发生,导致 RTL 在表单销毁时尝试“释放同步对象”,因为 getFieldAddress 返回了一个非零值。啊。
我们首先进行此黑客攻击的原因是因为我想自动更改所有表单实例上的 createParams,以实现无图标的可调整大小的表单。我将提出一个新问题,关于如何在不破坏 RTL 的情况下做到这一点(现在将简单地在表单中添加一个漂亮的闪亮图标)。
我将 Mghie 的建议标记为答案,因为它为我(以及阅读这篇文章的任何人)提供了非常多的见解。感谢大家的贡献!
After a lot of digging it turns out I was doing a nice (read: horrifying, but it has been properly doing its job in our delphi 5 apps for ages)
somewhere deep down in the bowels of our application framework. Apparently Delphi 2010 has some class initialization to initialize the "monitor field" that now didn't happen, causing the RTL to try and "free the syncobject" upon form destruction because getFieldAddress returned a non-nil value. Ugh.
The reason why we were doing this hack in the first place was because I wanted to automatically change the createParams on all form instances, to achieve an iconless resizable form. I will open up a new question on how to do this without rtl-breaking hacks (and for now will simply add a nice shiny icon to the forms).
I will mark Mghie's suggestion as the answer, because it has provided me (and anyone reading this thread) with a very large amount of insight. Thanks everyone for contributing!
Delphi中有两个TMonitor:
自Delphi 2009起,System.TMonitor被添加到Delphi中;因此,如果您从 Delphi 5 移植代码,则您的代码使用的是 Forms.TMonitor,而不是 System.TMonitor。
我认为在代码中引用类名时没有单元名称,这造成了混乱。
There are two TMonitor in Delphi:
System.TMonitor is added to Delphi since Delphi 2009; so if you are porting a code from Delphi 5, what your code was using was Forms.TMonitor, not System.TMonitor.
I think the class name is referenced without unit name in your code, and that is making the confusion.