2010-01-14 100 views
2

我目前正在移植现有的Delphi 5应用到2010年德尔福无效的指针操作TMonitor.Destroy

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

由于这是一个现有的和有点复杂的应用程序,我有方向的很多寻找到,和Delphi帮助 甚至没有记录 勉强文件这个特殊TMonitor类开始(我追查给一些艾伦鲍尔的帖子提供了额外的信息)......所以我想我首先会问是否有人遇到过这个问题,或者对可能导致这个问题的建议有任何建议。 为了记录:我没有在代码中明确使用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(???) 
+1

看起来记录在我身上:http://docwiki.embarcadero.com/VCL/en/System.TMonitor – 2010-01-14 14:35:40

+2

我站好了。我信任F1带我去那里。傻我。 :-) – 2010-01-14 15:36:40

回答

7

指向每个对象的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; 

如果这事情是在你原来的代码有问题,你应该能够通过使用激活的所有检查FastMM4内存管理器检测到它在Delphi 5版本的DLL的。 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。继续执行,当监视器被分配时,调试器应该到达断点。使用各种调试器视图(模块,线程,堆栈),您可能会获得有关写入该地址的代码的重要信息。

+0

好的提示!设法在DLL之外重现问题,这有助于让IDE实际停止一切事情发生故障的时刻。 – 2010-01-14 15:46:22

0

有两个TMonitor在Delphi:

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

自Delphi 2009开始,System.TMonitor被添加到Delphi中;所以如果你从Delphi 5移植代码,你的代码使用的是Forms.TMonitor,而不是System.TMonitor。

我认为在你的代码中引用的类名没有单元名,这就使得混淆。

+0

我不这么认为。任何提到Forms类的单元都必须在* uses *子句中包含表单。无论发生什么情况,Forms中的所有东西都比System中的任何东西都要近 - 最近使用过的单元的东西在名称解析期间首先找到。任何使用裸TMonitor标识符引用Forms.TMonitor的旧代码将继续引用该类。只有不使用表单的代码才会引用System.TMonitor,但在Delphi 5中,这样的代码永远不会编译。问题在于别处。 – 2010-01-14 14:40:35

+0

我没有使用Forms.TMonitor。我知道什么是Forms.TMonitor,我了解了新的System.TMonitor的基本概念,我只是不知道为什么它会爆炸(没有我的代码明确使用它)。 – 2010-01-14 15:54:55

3

无效指针操作意味着你的程序试图释放一个指针,但有三件事情不对的地方之一:

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

你不可能有多个内存管理器分配TMonitor记录,所以我认为我们可以排除第一种可能性。对于第二种可能性,如果程序中有一个类没有自定义的析构函数,或者没有在析构函数中释放任何内存,那么该对象的第一次实际的内存释放可能在TObject,它释放物体的显示器。如果您有该类的实例并尝试释放两次,则该问题可能以TMonitor中的异常形式出现。在程序中寻找双重错误。 debugging options in FastMM可以帮助你。另外,当你得到这个异常时,使用call stack来了解你如何到达TMonitor的析构函数。

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

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

+0

好的通用指针,谢谢。 IsMultithread绝对是开放的,Delphi 5也非常需要。来自TMonitor析构函数的Callstack并不是那么有用(与析构函数调用堆栈相同),我会将其添加到问题中。 FastMM在我的名单上。 – 2010-01-14 15:44:11

+0

FastMM的fulldebugmode,通常是我最好的朋友,在这种情况下,什么都不会产生。 – 2010-01-14 16:00:09

1

很多挖后原来我是做一个很好的(阅读:可怕的,但它已被正确地做自己的工作在我们的Delphi 5个应用了好半天

PClass(TForm)^ := TMyOwnClass 

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

原因为什么我们这样做是因为我想在所有表单实例上自动更改createParams,以实现无图标的可调整大小的窗体。我将开辟一个新的问题来解决如何在没有rtl破解的情况下做到这一点(现在只需要为表单添加一个不错的闪亮图标)。

我会将Mghie的建议标记为答案,因为它提供了我(和任何阅读此主题的人)的大量洞察。感谢大家的贡献!