转自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异常原因汇总相关推荐

  1. Android学习——R文件丢失异常原因汇总

    Console报错:R.java was modified manually! Reverting to generated version! 引言: R文件丢失异常在java开发中是个比较常见的异常 ...

  2. android r文件错误,Android R文件丢失异常原因汇总

    Console报错:R.java was modified manually! Reverting to generated version! 引言: R文件丢失异常在java开发中是个比较常见的异常 ...

  3. 免费申请邮箱账号,常见邮件退信问题原因汇总

    在使用邮箱过程中,遇到邮件退信,是很头疼的事.邮件退信不仅会影响工作效率,严重时还会影响客户对我们企业的信任.为此,作为一个在职场打拼多年的"社会人",以TOM VIP邮箱举例,汇 ...

  4. java.lang.NullPointerException异常原因及解决

    java.lang.NullPointerException异常原因是因为创建了一个引用类型的变量却没有指向任何对象而又去通过这个引用类型变量加点的形式去访问非静态的方法及属性. 给出三种情况, 第一 ...

  5. Springboot 抛出Failed to determine a suitable driver class异常原因

    Springboot 抛出Failed to determine a suitable driver class异常原因 参考文章: (1)Springboot 抛出Failed to determi ...

  6. Mapped Statements collection does not contain value for 之运行异常原因

    Mapped Statements collection does not contain value for 之运行异常原因 参考文章: (1)Mapped Statements collectio ...

  7. java.util.ConcurrentModificationException异常原因及解决方法

    java.util.ConcurrentModificationException异常原因及解决方法 参考文章: (1)java.util.ConcurrentModificationExceptio ...

  8. Java ConcurrentModificationException异常原因和解决方法

    Java ConcurrentModificationException异常原因和解决方法 参考文章: (1)Java ConcurrentModificationException异常原因和解决方法 ...

  9. 电脑运行慢的原因汇总

    电脑运行慢的原因汇总 病因一:病毒作怪          由于计算机中感染了病毒这个可恨的家伙,使得计算机运行速度大幅度变慢.其作用机理有二:一是病毒***后,首先占领内存这个据点,然后便以此为根据地 ...

最新文章

  1. AD环境部署文件服务器2012,Windows_server_2012部署AD域及辅域环境.doc
  2. 公有云还能信任吗?Azure遭雷击中断超过一天
  3. Want to archive tables? Use Percona Toolkit’s pt-archiver--转载
  4. OpenCV camshift算法的实例(附完整代码)
  5. orm2 中文文档 4.1 hasOne(多对一关系)
  6. 63.magento 后台重置密码
  7. datatable java实现_在JAVA实现DataTable对象(一)
  8. HBuilder启动vue项目
  9. 若邻网络结盟职友集,共创人脉网络与职位搜索结合的求职新模式
  10. 【深入理解计算机系统】CSAPP-实验四:ArchLab全网最详细
  11. 适合女士开的车15万左右买哪个?凌渡怎么样?
  12. Linux服务器期末复习总结
  13. BERT代码的解读1---数据处理部分
  14. java调用kettle自定义kettle.properties配置文件路径
  15. html里c3动画是什么,C3动画+H5知识点使用总结
  16. 濒死状态下的静息态网络激活和功能连接
  17. 阿里巴巴 OSS与AWS(亚马逊) S3 和腾讯云cos 存储服务 介绍篇
  18. python-office的使用
  19. 请简述什么是spring的ioc和di_请简述什么是 Spring 的 IoC 和 DI?_学小易找答案
  20. 【历史】- UNIX发展史(BSD,GNU,linux)

热门文章

  1. Android IOC模块,利用了Java反射和Java注解
  2. 浅析三层架构与MVC模式的区别
  3. Apache Spark学习:将Spark部署到Hadoop 2.2.0上
  4. linux命令lsof
  5. linux命令ssh
  6. clang-format-3.6格式化代码
  7. C++中#if 0 ... #endif的用法
  8. IAR中断定义#pragma vector = P0INT_VECTOR __interrupt void P0_ISR(void)啥意思?
  9. plsql生成awr报告
  10. oracle Schema Object Dependencies