Windows 为什么这个简单的DLL在WM_ACTIVATEAPP到达之前崩溃?

Windows 为什么这个简单的DLL在WM_ACTIVATEAPP到达之前崩溃?,windows,debugging,dll,mdi,Windows,Debugging,Dll,Mdi,下面的最小DLL只使用Win32 API,它只尝试创建一个MDI框架/客户端窗口和一个子窗口,并在卸载DLL时销毁框架窗口。DLL在Windows XP上崩溃,在USER32中执行INT x2B指令时出现异常 对于测试,DLL仅由一个调用LoadLibrary('badcode.DLL')的单行应用程序调用 崩溃发生在最终的“DestroyWindow(framewindowhandle)”内部,就在DLL完成之前,在FrameWindowProc接收WM\u ACTIVATE之后,但在它接收W

下面的最小DLL只使用Win32 API,它只尝试创建一个MDI框架/客户端窗口和一个子窗口,并在卸载DLL时销毁框架窗口。DLL在Windows XP上崩溃,在USER32中执行INT x2B指令时出现异常

对于测试,DLL仅由一个调用LoadLibrary('badcode.DLL')的单行应用程序调用

崩溃发生在最终的“DestroyWindow(framewindowhandle)”内部,就在DLL完成之前,在FrameWindowProc接收WM\u ACTIVATE之后,但在它接收WM\u ACTIVEAPP之前

DLL代码已经从一个大得多的原始代码中尽可能地删减,以隔离错误。尽管销毁框架窗口也会消除当前的崩溃,但大约12年前,人们确定,在NT上运行的Visual Basic等工具会崩溃,除非在卸载DLL之前调用“DestroyWindow(framewindowhandle)”。然而,就在最近,一个新的用来测试一些DLL入口点的小程序突然被发现在XP上崩溃,如上所述

虽然是用Delphi6编写的,但代码只依赖于普通的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. 库badcode;//如果作为程序而不是DLL重写,则可以工作 {$R*.RES}//删除此选项可避免崩溃 使用窗口、消息;//只进行win32调用 //3个MDI窗口句柄 var framewindowhandle、clientwindowhandle、childwindowhandle:hwnd; 函数framewindowproc(windowhandle:hwnd;消息:word;wparam,lparam:longint):longint;stdcall; var-ccs:tclientcreatestruct; 开始//帧窗口已收到一条消息 如果message=WM\u,则创建 开始//创建客户端窗口 ccs.hwindowmenu:=0;ccs.idfirstchild:=0; clientwindowhandle:=createwindow('MDICLIENT','',ws_child+ws_clipcchildren+ws_visible,10,10,50,50,windowhandle,0,hinstance,@ccs); 结果:=0;//我们处理了这个消息 结束 else//执行默认处理 结果:=defframeproc(windowhandle、clientwindowhandle、message、wparam、LPRAM); 结束; 函数childwindowproc(windowhandle:hwnd;message:word;wparam,lparam:longint):longint;stdcall; 开始//子窗口已收到消息,执行默认处理 结果:=defmdichildproc(窗口句柄、消息、wparam、lparam); 结束; 过程DLLHandler(原因:整数); 开始 如果原因=DLL\u进程\u分离,则//卸载DLL 销毁窗口(framewindowhandle);//导致崩溃,永远不会回来 结束; var-wc:twndclass;mcs:tmdicreatestruct; 开始//DLL加载时间 DLLProc:=@DLLHandler;//所以我们可以检测到卸载 wc.hinstance:=hinstance; wc.lpfnwndproc:=@framewindowproc; wc.style:=0;wc.cbclsextra:=0;wc.cbwndextra:=0; wc.hicon:=加载图标(0,IDI_星号); wc.hcursor:=加载光标(0,IDC箭头); wc.hbrbackground:=0; wc.lpszmenuname:=“菜单栏”;//更改为“”可避免崩溃 wc.lpszclassname:=“坏”; 注册表类(wc);//注册帧窗口 wc.lpfnwndproc:=@childwindowproc; wc.lpszmenuname:=''; wc.lpszclassname:=“数据”; 寄存器类(wc);//注册子窗口 framewindowhandle:=createwindow('BAD','frame',WS_OVERLAPPEDWINDOW+WS_CLIPCHILDREN,100100400600,0,0,hinstance,nil); mcs.szclass:=“数据”;mcs.sztitle:=“儿童”;mcs.howner:=hinstance; mcs.x:=50;mcs.y:=50;mcs.cx:=50;mcs.cy:=50;mcs.style:=WS_最小化;//更改样式可避免崩溃 childwindowhandle:=sendmessage(clientwindowhandle,WM_MDICREATE,0,longint(@mcs)); sendmessage(clientwindowhandle,WM_MDIRESTORE,childwindowhandle,0);//跳过此选项可避免崩溃 结束。
使用这个优秀的工具,我发现我机器上的一些旧扫描仪软件配置USER32在执行any程序时挂接与OCR相关的DLL,并且该DLL发出了一些看起来可疑的调用,包括出于某种原因被加载了两次。卸载扫描器软件使崩溃消失,所有O/S DLL加载/卸载看起来更加合理。尽管如此,我仍将修改我的DLL,使其在附加/分离期间不做任何操作,并包括用于启动/停止的新入口点。

不同操作系统的不同行为可能是由于相应操作系统的实现细节造成的。你所做的一直被认为是不安全的。请考虑以下评论中的引用“调用需要Kernel32.dll以外的dll的函数可能会导致难以诊断的问题。例如,调用User、Shell和COM函数可能会导致访问冲突错误,因为某些函数会加载其他系统组件。相反,在终止期间调用此类函数可能会导致访问冲突错误,因为相应的组件可能已经卸载或未初始化。”