views:

412

answers:

4

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(???)
A: 

There are two TMonitor in Delphi:

  1. System.TMonitor; which is a record, and is used for thread synchronization.
  2. Forms.TMonitor; which is a class representing an attached monitor (display device).

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.

vcldeveloper
I don't think so. Any unit that mentioned the Forms class must have included Forms on its *uses* clause. Wherever that happens, all the things in Forms have a nearer scope than anything in System — the most recently used unit's stuff is found first during name resolution. Any old code that used the bare TMonitor identifier to refer to Forms.TMonitor will continue to refer to that class. Only code that doesn't use Forms will refer to System.TMonitor, but in Delphi 5, such code never would have compiled. The problem lies elsewhere.
Rob Kennedy
I'm not using Forms.TMonitor. I know what Forms.TMonitor does, I get the basic idea of what the new System.TMonitor does, I just don't know why it explodes in my face (without my code using it explicitly).
Paul-Jan
+2  A: 

An invalid pointer operation means your program attempted to free a pointer, but there was one of three things wrong with it:

  • It was allocated by some other memory manager.
  • It had already been freed once before.
  • It had never been allocated by anything.

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.

Rob Kennedy
Good generic pointers, thanks. IsMultithread is definitely on, very much required with Delphi 5 as well. Callstack from TMonitor destructor is not so useful (as is common with destructor callstacks), I'll add it to the question. FastMM is next on my list.
Paul-Jan
FastMM's fulldebugmode, usually my best friend in situations like these, yields nothing.
Paul-Jan
+4  A: 

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 being nil in the BeforeDestruction method of your forms, for a straight Delphi 5 port there shouldn't be any monitors assigned. Something like

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

If 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:

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

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:

ba w4 A32D00

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.

mghie
Good tip! Managed to reproduce the problem outside the DLL, which helps a lot getting the IDE to actually stop the moment things go haywire.
Paul-Jan
A: 

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)

PClass(TForm)^ := TMyOwnClass 

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!

Paul-Jan