【转】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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值