The minimal DLL below, which uses only the Win32 API, tries to do nothing more than create an MDI frame/client window and one child window, and destroy the frame window when the DLL unloads. The DLL crashes on Windows XP with an exception upon executing an INT x2B instruction in USER32.
For testing, the DLL is simply invoked by a one-line application calling LoadLibrary('badcode.dll').
The crash happens inside the final "DestroyWindow(framewindowhandle)" just before the DLL finishes, after FrameWindowProc receives WM_ACTIVATE but before it receives WM_ACTIVEAPP.
The DLL code has been trimmed down from a much larger original as much as possible to isolate the bug. Although not destroying the frame window also makes the current crash go away, about 12 years ago it was determined that tools like Visual Basic running on NT would crash unless "DestroyWindow(framewindowhandle)" was called before the DLL was unloaded. Just recently, however, a new small program written to test some of the DLL entrypoints was suddenly found to be crashing on XP as described above.
Although written in Delphi 6, the code only relies on the vanilla Win32 API.
library badcode; // works if rewritten as a program instead of DLL
{$R *.RES} // removing this avoids crash
uses windows, messages; // only win32 calls are made
// 3 MDI window handles
var framewindowhandle, clientwindowhandle, childwindowhandle: hwnd;
function framewindowproc(windowhandle: hwnd; message: word; wparam, lparam: longint): longint; stdcall;
var ccs: tclientcreatestruct;
begin // frame window has received a message
if message = WM_CREATE then
begin // create the client window
ccs.hwindowmenu := 0; ccs.idfirstchild := 0;
clientwindowhandle := createwindow('MDICLIENT', '', ws_child + ws_clipchildren + ws_visible, 10, 10, 50, 50, windowhandle, 0, hinstance, @ccs);
result := 0; // we handled the message
end
else // do default handling
result := defframeproc(windowhandle, clientwindowhandle, message, wparam, lparam);
end;
function childwindowproc(windowhandle: hwnd; message: word; wparam, lparam: longint): longint; stdcall;
begin // child window has received a message, do default handling
result := defmdichildproc(windowhandle, message, wparam, lparam);
end;
procedure DLLHandler(reason: integer);
begin
if reason = DLL_PROCESS_DETACH then // unloading dll
DestroyWindow(framewindowhandle); // causes the crash, never returns
end;
var wc: twndclass; mcs: tmdicreatestruct;
begin // DLL loading time
DLLProc := @DLLHandler; // so we can detect unload
wc.hinstance := hinstance;
wc.lpfnwndproc := @framewindowproc;
wc.style := 0; wc.cbclsextra := 0; wc.cbwndextra := 0;
wc.hicon := loadicon(0, IDI_ASTERISK);
wc.hcursor := loadcursor(0, IDC_ARROW);
wc.hbrbackground := 0;
wc.lpszmenuname := 'MENUBAR'; // changing to '' avoids the crash
wc.lpszclassname := 'BAD';
registerclass(wc); // register the frame window
wc.lpfnwndproc := @childwindowproc;
wc.lpszmenuname := '';
wc.lpszclassname := 'DATA';
registerclass(wc); // register the child window
framewindowhandle := createwindow('BAD', 'frame', WS_OVERLAPPEDWINDOW + WS_CLIPCHILDREN, 100, 100, 400, 600, 0, 0, hinstance, nil);
mcs.szclass := 'DATA'; mcs.sztitle := 'child'; mcs.howner := hinstance;
mcs.x := 50; mcs.y := 50; mcs.cx := 50; mcs.cy := 50; mcs.style := WS_MINIMIZE; // changing the style avoids the crash
childwindowhandle := sendmessage(clientwindowhandle, WM_MDICREATE, 0, longint(@mcs));
sendmessage(clientwindowhandle, WM_MDIRESTORE, childwindowhandle, 0); // skipping this avoids the crash
end.