【发布时间】:2012-11-29 07:28:36
【问题描述】:
下面的最小 DLL,它只使用 Win32 API,它只尝试创建一个 MDI 框架/客户端窗口和一个子窗口,并在 DLL 卸载时销毁框架窗口。 DLL 在 Windows XP 上崩溃,并在 USER32 中执行 INT x2B 指令时出现异常。
为了测试,DLL 仅由调用 LoadLibrary('badcode.dll') 的单行应用程序调用。
崩溃发生在最终的“DestroyWindow(framewindowhandle)”内,就在 DLL 完成之前,在 FrameWindowProc 收到 WM_ACTIVATE 但在它收到 WM_ACTIVEAPP 之前。
DLL 代码已从更大的原始代码中尽可能地减少,以隔离错误。虽然不销毁框架窗口也会使当前的崩溃消失,但大约 12 年前,人们确定除非在 DLL 之前调用“DestroyWindow(framewindowhandle)”,否则在 NT 上运行的 Visual Basic 等工具会崩溃被卸载了。然而,就在最近,突然发现为测试某些 DLL 入口点而编写的新小程序在 XP 上崩溃,如上所述。
虽然是用 Delphi 6 编写的,但代码只依赖于 vanilla Win32 API。
库错误代码; // 如果重写为程序而不是 DLL 则有效 {$R *.RES} // 删除它可以避免崩溃 使用窗口、消息; // 只进行 win32 调用 // 3 个 MDI 窗口句柄 var framewindowhandle, clientwindowhandle, childwindowhandle: hwnd; 函数 framewindowproc(windowhandle: hwnd; 消息: word; wparam, lparam: longint): longint;标准调用; var ccs: tclientcreatestruct; begin // 框架窗口已收到消息 如果消息 = WM_CREATE 那么 begin // 创建客户端窗口 ccs.hwindowmenu := 0; ccs.idfirstchild := 0; clientwindowhandle := createwindow('MDICLIENT', '', ws_child + ws_clipchildren + ws_visible, 10, 10, 50, 50, windowhandle, 0, hinstance, @ccs); 结果:= 0; // 我们处理了消息 结尾 else // 做默认处理 结果:= defframeproc(窗口句柄,客户端窗口句柄,消息,wparam,lparam); 结尾; 函数 childwindowproc(windowhandle: hwnd; 消息: word; wparam, lparam: longint): longint;标准调用; begin // 子窗口收到消息,做默认处理 结果:= defmdichildproc(窗口句柄,消息,wparam,lparam); 结尾; 程序 DLLHandler(原因:整数); 开始 if reason = DLL_PROCESS_DETACH then // 卸载 dll 销毁窗口(框架窗口句柄); // 导致崩溃,永不返回 结尾; var wc: twndclass; mcs:tmdicreatestruct; begin // DLL 加载时间 DLLProc := @DLLHandler; // 所以我们可以检测卸载 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.hbr背景:= 0; wc.lpszmenuname := '菜单栏'; // 更改为 '' 避免崩溃 wc.lpszclassname := 'BAD'; 注册类(厕所); //注册框架窗口 wc.lpfnwndproc := @childwindowproc; wc.lpszmenuname := ''; wc.lpszclassname := '数据'; 注册类(厕所); //注册子窗口 framewindowhandle := createwindow('BAD', 'frame', WS_OVERLAPPEDWINDOW + WS_CLIPCHILDREN, 100, 100, 400, 600, 0, 0, hinstance, nil); mcs.szclass := '数据'; mcs.sztitle := '孩子'; mcs.howner := 障碍; mcs.x := 50; mcs.y := 50; mcs.cx := 50; mcs.cy := 50; mcs.style := WS_MINIMIZE; // 改变样式避免崩溃 childwindowhandle := sendmessage(clientwindowhandle, WM_MDICREATE, 0, longint(@mcs)); 发送消息(客户端窗口句柄,WM_MDIRESTORE,子窗口句柄,0); // 跳过这个可以避免崩溃 结尾。【问题讨论】:
-
不同操作系统的不同行为可能是由于相应操作系统的实现细节造成的。你所做的一直被认为是不安全的。考虑以下来自DllMain entry point 的评论中的引用
-
"调用需要除 Kernel32.dll 之外的 DLL 的函数可能会导致难以诊断的问题。例如,调用 User、Shell 和 COM 函数可能会导致访问冲突错误,因为某些函数会加载其他系统组件。相反,在终止期间调用此类函数可能会导致访问冲突错误,因为相应的组件可能已经被卸载或未初始化。”