【转】CreateWindowEx异常原因汇总
轉自http://blogs.msdn.com/b/dsui_team/archive/2012/11/05/troubleshooting-createwindowex-failures.aspx
四級翻譯水平,歡迎指正。
Several years back, my team was handling multiple issues where Windows Forms application would throw an exception while trying to create a new form, control, etc. While the description string that Windows Forms included in the exception ("Error creating window handle.") indicated that a call to CreateWindowEx failed, the exception did not provide any information on why the call failed. At the time, I wrote a small document that described the steps for debugging native and .NET applications to determine why a given CreateWindowEx call fails. One of my colleagues suggested creating a blog post with the information from that document, so here we go.
?幾年前,我們團隊在開發Windows窗體應用程序的時候,經常在創建一個新的窗體或控件時碰到異常。在調用CreateWindowEx的時候遇到異常“Error creating window handle.”表示調用失敗。調用失敗時并沒有提供更多的信息。那時候,我寫了一篇文檔描述調試的步驟,.Net應用導致了調用CreateWindowEx的失敗。我的一位同事建議我把這篇文檔的信息寫到博客上。我們開始吧。
The CreateWindowEx function(CreateWindowEx函數)
We should first discuss how the CreateWindowEx function works before diving into how to troubleshoot CreateWindowEx failures.Windows applications, including .NET applications that use Windows Forms or Windows Presentation Foundation, create their windows by calling the CreateWindowExA or CreateWindowExW functions. Both functions internally call a common USER32 function that will perform some?parameter validation, such as the window styles and handles specified in the call, handle creating a MDI child window if the WS_EX_MDICHILD extended window style is specified and processes the current activation context for the calling thread. If all is well on the USER32-side of the CreateWindowEx call, it then calls into the kernel-mode (WIN32K) implementation of CreateWindowEx.
我們在解決CreateWindowEx創建失敗原因之前先討論該函數的工作原理。Windows應用,包括使用Windows窗體的.Net應用,在創建窗體時都要調用CreateWindowExA或者CreateWindowExW函數。這些函數內部都調用了一個共同的USER32函數使一些參數生效(比如調用中指定的窗體風格和句柄),處理創建一個指明WS_EX_MDICHILD擴展窗體風格的MDI子窗體的創建過程,為調用的線程激活當前的設備環境。如果CreateWindowEx在調用USER32時一切正常,它將調用核心模式(WIN32K)來實現窗體的創建。
The kernel-side of the CreateWindowEx call will first check the following conditions:
CreateWindowEx調用的核心首先要檢查下面幾個條件:
- Does the specified window class exist??指定的窗口類存在么?
- Can the text for the atom be retrieved, if the caller specified an atom for the class name?
- Was a parent window specified, if creating a child window?創建子窗體時,它的父窗體指定了嗎?
- Will the new window exceed the nested window depth limit, if creating a child window or an owned window? The default limit?is 50 levels deep and can be configured via the USERNestedWindowLimit registry value.在創建子窗口時,嵌套窗口的個數有沒有超過限制?默認深度是50,可以通過USERNestedWindowLimit的注冊表的值來設置。
- Does the parent/owner window belong to the same desktop as the calling thread?調用線程的父窗口屬于同一個桌面么?
Assuming that each of these checks pass, CreateWindowEx will perform the following tasks when attempting to?create a new window object:
假設這些檢查通過,CreateWindowEx 將會通過以下任務來完成新窗體的創建:
- Determine if creating a handle for the new window?object will exceed the?User handle quota limit for the calling process.
- Allocates memory for the new window object from the desktop's heap.在桌面堆中為新窗體分配內存。
- Initializes the memory for the new window object為新窗體初始化內存。
- Creates a handle for the new window object in the User handle table為新窗體在用戶句柄表中創建一個句柄。
Assuming that the window object was created successfully, CreateWindowEx will notify registered WinEvent and WH_CBT hooks that a new window object has been created, determine the location, size, z-order position, etc. of the new window, followed by calling the window's WndProc to process WM_NCCREATE and WM_CREATE window messages. CreateWindowEx then returns the handle of the new window object.
?假設窗體已經成功創建,CreateWindowEx將會通知注冊WinEvent和WH_CBT hooks,一個新窗體已經創建完成,決定位置、尺寸,z軸位置等。新窗體將會調用Window的WndProc函數處理WM_NCCREATE和WM_CREATE窗口消息,CreateWindowEx將返回新窗體的句柄。
Causes of CreateWindowEx failures(CreateWindowEx創建失敗的原因)
You may have noticed some of the conditions that will cause CreateWindowEx to fail from the previous section. Let's enumerate conditions that will cause CreateWindowEx to fail:
通過前面的章節你需要注意到引起CreateWindowEx創建失敗的原因。
- The specified window class does not exist.
- Using invalid window styles or extended window styles.
- Using invalid User handles, such as window handles and menu handles.
- Attempting to create a child window without specifying a parent window.
- Attempting to create a child window or an owned window and the specified parent/owner belongs to a different desktop than the calling thread.
- Creating a child or owned window will exceed the nested window limit.
- Creating a new window object will exceed the handle quota for the calling process.
- There is insufficient heap available in the desktop’s heap to allocate memory for the new window object.
- There are no available entries in the User handle table.
Note that while?a new window object is successfully created, the CreateWindowEx function may still return NULL. How can this happed?
Recall that before returning, CreateWindowEx will call the window's WndProc to process a WM_NCCREATE and a WM_CREATE window message. The WndProc can stop the window creation by returning?0 when handling the WM_NCCREATE window message or by returning -1 when handling the WM_CREATE window message. CreateWindowEx handles this situation by destroying the window object and returning NULL.?Additionally, a WH_CBT hook?can stop the window creation by returning 0 from its hook procedure when handling the HCBT_CREATEWND notification.
?
CreateWindowEx failures and GetLastError
The Windows SDK documentation for the CreateWindowEx function states that extended error information can be obtained by calling GetLastError when CreateWindowEx returns NULL. But how useful is the error code returned by GetLastError?
The answer is that it depends on the error condition that caused CreateWindowEx to return NULL.
For example, GetLastError will return ERROR_CANNOT_FIND_WND_CLASS if the window class does not exist. On the other hand, GetLastError may return NOERROR or some error code that seems unrelated to calling CreateWindowEx?if the window's?WndProc stopped the window creation when handling the WM_NCCREATE or WM_CREATE messages.
?
Troubleshooting CreateWindowEx failures
Although GetLastError does not always return a valid error code on a CreateWindowEx failure, you should always check the error code as it may help diagnose the source of the failure. Current versions of the .NET Framework included the value returned by GetLastError when throwing a Win32Exception. Earlier versions of the .NET Framework did not include the error code when throwing?a Win32Exception.
Hitting the User handle quota limit for the process?should be pretty simple to identify. You can view the User object handle count for a process in Task Manager (taskmgr.exe) or by calling the GetGuiResources function. If the handle count is approaching the User handle quota limit, then Task Manager and GetGuiResources is likely reporting a value near the limit. Additionally, GetLastError should return ERROR_NO_MORE_USER_HANDLES. The default limit is 10,000 and can be configured via the USERProcessHandleQuota registry value.
Running out of desktop heap is a little more difficult to identify since there is no mechanism that ships with Windows to measure available desktop heap for a given desktop.?However, GetLastError should return ERROR_NOT_ENOUGH_MEMORY. You can find more information on desktop heap athttp://blogs.msdn.com/b/ntdebugging/archive/2007/01/04/desktop-heap-overview.aspx.
Then there are the scenarios where CreateWindowEx fails and GetLastError returns NOERROR or what appears to be an invalid error code.?Generally, in this scenario, the failure is caused by the window's WndProc stopping the window creation. There are some user-mode?troubleshooting options?that you can use?before you start stepping through the disassembly of the kernel side of the CreateWindowEx function.
One approach is to?set a?WH_CBT window hook on the thread calling CreateWindowEx to determine if a window object has been created for a given CreateWindowEx call. Recall that?WH_CBT hook procedures?are called with the HCBT_CREATEWND notification after?a window object has been created and before the window procedure is called to handle the WM_NCCREATE and WM_CREATE window?messages.
You can then set a WH_CALLWNDPROC window hook on the thread calling CreateWindowEx or set a breakpoint on the window's WndProc (if known)?to determine if the window's WndProc is called to handle the WM_NCCREATE and WM_CREATE window messages. You can also use the Spy++ tool included with Visual Studio to monitor when windows owned by a thread process WM_NCCREATE and WM_CREATE window messages.
?
Debugging CreateWindowEx with WinDBG
I tend to use the Debugging Tools for Windows for most of my debugging work, specifically WinDBG. The debugging tasks described below are from a 32-bit Windows?7 system using the version of WinDBG included with the Windows SDK for Windows?7 and the public Microsoft symbol server.
You may encounter a scenario where you are trying to troubleshoot a CreateWindowEx failure where you do not know the address of the window's WndProc, such as a third-party control. How do you set a breakpoint on the WndProc?
Recall that most of the work for creating a window is done in kernel mode, which includes calling the window's WndProc to process?WM_NCCREATE and WM_CREATE window messages. Since the kernel side of CreateWindowEx tries to call?a user mode function, Windows needs to transition back into user mode to call?the WndProc, which is done via the DispatchClientMessage function.
You can use a breakpoint on CreateWindowEx that enables a breakpoint on DispatchClientMessage and another breakpoint when CreateWindowEx returns that disables the breakpoint on DispatchClientMessage. This should allow you to determine if the window procedure of the window is being called.
Here are breakpoints that I used when debugging an instance of?Notepad:
0:000> bl
?0 e 75934ec3???? 0001 (0001)? 0:**** USER32!DispatchClientMessage ".echo ***** DispatchClientMessage *****;dd @esp+4 L4;g"
?1 e 7592ec7c???? 0001 (0001)? 0:**** USER32!CreateWindowExW ".echo ***** CreateWindowExW called *****;be 0;g"
?2 e 7592ecb0???? 0001 (0001)? 0:**** USER32!CreateWindowExW+0x34 ".echo ***** CreateWindowExW returned *****;bd 0;r @eax;g"
Breakpoint 0 is initially disabled. It will be enabled when CreateWindowEx is called and disabled when CreateWindowEx returns. When hit, the breakpoint will dump the parameters passed to the function and then resume execution. You could also set up the breakpoint to halt in the debugger where you could then step into the window procedure. Note in the debug output below that the last parameter to DispatchClientMessage is the address of the window procedure.
Breakpoint 1 displays a message that CreateWindowEx is being called, tries to display the class name passed to CreateWindowEx (which will fail if an atom is passed) and enables the breakpoint on DispatchClientMessage.
Breakpoint 2 displays a message that CreateWindowEx is returning, displays the value of the handle that it is returning and disables breakpoint 0.
Below is the debugger ouput from the first CreateWindowEx call in the process:
***** CreateWindowExW called *****
***** DispatchClientMessage *****
001bf504? 00b9fc18 00000081 00000000 001bf56c
***** DispatchClientMessage *****
001bf548? 00b9fc18 00000083 00000000 001bf594
***** DispatchClientMessage *****
001bf4d4? 00b9fc18 00000001 00000000 001bf53c
***** DispatchClientMessage *****
001bf554? 00b9fc18 00000005 00000000 00000000
***** DispatchClientMessage *****
001bf554? 00b9fc18 00000003 00000000 00000000
***** CreateWindowExW returned *****
eax=000602fe
Note the window messages that are being received by the window. WM_NCCREATE is 0x81 and WM_CREATE is 0x1. The other messages are also a part of normal window creation.
In the example above, you might have noticed that I skipped how I found the address of the return from CreateWindowEx in order to set breakpoint 2. The method that I used was to examine the disassembly of the CreateWindowExW function and looked for the return instruction, highlighted below.
0:000> u user32!CreateWindowExW L13
USER32!CreateWindowExW:
7592ec7c 8bff??????????? mov???? edi,edi
7592ec7e 55????????????? push??? ebp
7592ec7f 8bec??????????? mov???? ebp,esp
7592ec81 6800000040????? push??? 40000000h
7592ec86 ff7534????????? push??? dword ptr [ebp+34h]
7592ec89 ff7530????????? push??? dword ptr [ebp+30h]
7592ec8c ff752c????????? push??? dword ptr [ebp+2Ch]
7592ec8f ff7528????????? push??? dword ptr [ebp+28h]
7592ec92 ff7524????????? push??? dword ptr [ebp+24h]
7592ec95 ff7520????????? push??? dword ptr [ebp+20h]
7592ec98 ff751c????????? push??? dword ptr [ebp+1Ch]
7592ec9b ff7518????????? push??? dword ptr [ebp+18h]
7592ec9e ff7514????????? push??? dword ptr [ebp+14h]
7592eca1 ff7510????????? push??? dword ptr [ebp+10h]
7592eca4 ff750c????????? push??? dword ptr [ebp+0Ch]
7592eca7 ff7508????????? push??? dword ptr [ebp+8]
7592ecaa e8edfeffff????? call??? USER32!_CreateWindowEx (7592eb9c)
7592ecaf 5d????????????? pop???? ebp
7592ecb0 c23000????????? ret???? 30h
I then use the following syntax to set the breakpoint:
bp2 7592ecb0 ".echo ***** CreateWindowExW returned *****;bd 0;r @eax;g"
Alternately, you can set a conditional breakpoint when CreateWindowEx returns and use the !gle command to dump the value that will be returned by GetLastError if CreateWindowEx fails, where the EAX register is zero. In the scenario below, I created a simple Windows Forms application that repeatedly creates child windows until the User handle process quota is reached and CreateWindowEx fails.
I use the following command to set a conditional breakpoint on the return from CreateWindowExW that breaks in the debugger when the function fails:
bp 7592ecb0? "j (@eax==0) '.echo ***** CreateWindowExW failed. *****';'g'"
Once the debugger breaks on a CreateWindowEx failure, you can obtain the last error value with the !gle command:
0:000> !gle
LastErrorValue: (Win32) 0x486 (1158) - The current process has used all of its system allowance of handles for Window Manager objects.
You can also execute other debugger commands, including .NET debugger extension commands such as !clrstack:
0:000> .loadby sos clr
0:000> !clrstack
OS Thread Id: 0xea4 (0)
Child SP IP?????? Call Site
001ae9c4 7592ecb0 [InlinedCallFrame: 001ae9c4]?
001ae9a8 5e6c50ef DomainBoundILStubClass.IL_STUB_PInvoke(Int32, System.String, System.String, Int32, Int32, Int32, Int32, Int32, System.Runtime.InteropServices.HandleRef, System.Runtime.InteropServices.HandleRef, System.Runtime.InteropServices.HandleRef, System.Object)
001ae9c4 5e6a978c [InlinedCallFrame: 001ae9c4] System.Windows.Forms.UnsafeNativeMethods.IntCreateWindowEx(Int32, System.String, System.String, Int32, Int32, Int32, Int32, Int32, System.Runtime.InteropServices.HandleRef, System.Runtime.InteropServices.HandleRef, System.Runtime.InteropServices.HandleRef, System.Object)
001aea88 5e6a978c System.Windows.Forms.UnsafeNativeMethods.CreateWindowEx(Int32, System.String, System.String, Int32, Int32, Int32, Int32, Int32, System.Runtime.InteropServices.HandleRef, System.Runtime.InteropServices.HandleRef, System.Runtime.InteropServices.HandleRef, System.Object)
001aead0 5e6a8e9f System.Windows.Forms.NativeWindow.CreateHandle(System.Windows.Forms.CreateParams)
001aeb54 5e6a8b82 System.Windows.Forms.Control.CreateHandle()
001aebac 5e6a8921 System.Windows.Forms.Control.CreateControl(Boolean)
001aebe4 5e6a8818 System.Windows.Forms.Control.CreateControl()
001aebfc 5e6b1c31 System.Windows.Forms.Control+ControlCollection.Add(System.Windows.Forms.Control)
001aec38 5e6b1aab System.Windows.Forms.Form+ControlCollection.Add(System.Windows.Forms.Control)
001aec4c 5ef8f027 System.Windows.Forms.Control.set_ParentInternal(System.Windows.Forms.Control)
001aec58 5ebfd59a System.Windows.Forms.Control.set_Parent(System.Windows.Forms.Control)
001aec60 002903c3 WindowsFormsApplication1.Form1.button1_Click(System.Object, System.EventArgs)*** WARNING: Unable to verify checksum for WindowsFormsApplication1.exe
?[D:\Cases\WindowsFormsApplication1\WindowsFormsApplication1\Form1.cs @ 24]
001aec80 5e654507 System.Windows.Forms.Control.OnClick(System.EventArgs)
001aec94 5e656ca2 System.Windows.Forms.Button.OnClick(System.EventArgs)
001aecac 5ec3a480 System.Windows.Forms.Button.OnMouseUp(System.Windows.Forms.MouseEventArgs)
001aecc8 5ec03dd1 System.Windows.Forms.Control.WmMouseUp(System.Windows.Forms.Message ByRef, System.Windows.Forms.MouseButtons, Int32)
001aed5c 5efa6a2f System.Windows.Forms.Control.WndProc(System.Windows.Forms.Message ByRef)
001aed60 5efae391 [InlinedCallFrame: 001aed60]?
001aedb4 5efae391 System.Windows.Forms.ButtonBase.WndProc(System.Windows.Forms.Message ByRef)
001aedf8 5e6c19f8 System.Windows.Forms.Button.WndProc(System.Windows.Forms.Message ByRef)
001aee04 5e6aa393 System.Windows.Forms.Control+ControlNativeWindow.OnMessage(System.Windows.Forms.Message ByRef)
001aee0c 5e6aa311 System.Windows.Forms.Control+ControlNativeWindow.WndProc(System.Windows.Forms.Message ByRef)
001aee20 5e6aa256 System.Windows.Forms.NativeWindow.Callback(IntPtr, Int32, IntPtr, IntPtr)
001aefc4 00840ab8 [InlinedCallFrame: 001aefc4]?
001aefc0 5e6c6c1c DomainBoundILStubClass.IL_STUB_PInvoke(MSG ByRef)
001aefc4 5e6ba99f [InlinedCallFrame: 001aefc4] System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG ByRef)
001af008 5e6ba99f System.Windows.Forms.Application+ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr, Int32, Int32)
001af00c 5e6ba5cc [InlinedCallFrame: 001af00c]?
001af0a4 5e6ba5cc System.Windows.Forms.Application+ThreadContext.RunMessageLoopInner(Int32, System.Windows.Forms.ApplicationContext)
001af0fc 5e6ba421 System.Windows.Forms.Application+ThreadContext.RunMessageLoop(Int32, System.Windows.Forms.ApplicationContext)
001af12c 5e642815 System.Windows.Forms.Application.Run(System.Windows.Forms.Form)
001af140 002900ae WindowsFormsApplication1.Program.Main() [D:\Cases\WindowsFormsApplication1\WindowsFormsApplication1\Program.cs @ 18]
001af370 710d21bb [GCFrame: 001af370]?
Conclusion
Hopefully you will be able to apply some of the techniques described in this post if you find yourself in the situation where you are trying to troubleshoot CreateWindowEx failures in managed and native applications.
轉載于:https://www.cnblogs.com/xingyi7/p/4925870.html
總結
以上是生活随笔為你收集整理的【转】CreateWindowEx异常原因汇总的全部內容,希望文章能夠幫你解決所遇到的問題。