还可以通过AddVectoredContinueHandler、RemoveVectoredContinueHandler管理一组过滤函数,由UnhandledExceptionFilter在用户顶层过滤函数(SetUnhandledExceptionFilter)之后调用。 5. 两种情况下调试器会通知用户发生异常:(1)打开IDE相应开关后,一抛出异常就触发断点。
另外,无论是否打开开关,调试器都在输出窗口打印异常相关信息。调试器显然通过AddVectoredExceptionHandler注册了VEH函数。可以模仿调试器来记录异常。(2)对于用户没有处理的异常,被调试状态下的UnhandledExceptionFilter内部会通知调试器。
6. 对异常发生时调试器弹出框的解释:(1)中断。保持中断的状态,便于调试。(2)继续。如
果对话框在VEH函数中弹出,即异常刚抛出,这个选项会让VEH函数返回EXCEPTION_CONTINUE_SEARCH,继续查找下个处理函数。如果对话框是在UnhandledExceptionFilter中弹出,继续选项等价于忽略。(3)忽略。VEH过滤函数或UnhandledExceptionFilter中代码返回EXCEPTION_CONTINUE_EXECUTION,因此该选项用于忽略包括C++异常在内的软件异常(RaiseException)。
第26章 错误报告与应用程序恢复
1. 本章介绍的WER(Windows Error Reporting,Windows错误报告)内容主要在Vista以上可用。 2. %SystemRoot%\\system32\\wercon.exe可以显示系统中出现过的错误。
3. WerSetFlags可以影响WER的行为,比如要求不dump堆、发送报告到MS网站等。WerAddEx
cludedApplication可以指定一些程序崩溃后跳过WER机制,适合正在调试的程序等。 4. WebRegisterMemroyBlock-指定WER的dump数据中要包括指定位置的内存。WerRegisterFile-要求将指定文件加入报告中。
5. 定制WER报告:WerReportCreate、WerReportSetParameter、WerReportAddDump、。WerRepor
tAddFile、WerReportSetUIOption、WerReportSubmit、WerReportCloseHandle。 6. RegisterApplicationRestart-可以指定在何种错误情况下WER以特定参数重启程序。
7. RegisterApplicationRecoveryCallback-注册一个回调,进程将要非正常结束的时候被调用,以便用
户自由备份一些状态等。用户可以在回调中以ApplicationRecoveryInProgress、ApplicationRecoveryFinished来通知UI进度。
附录
1. 要在输出窗口中打印调试信息,区别#pragma message和OutputDebugString,前者是在编译期打
印,后者是运行时打印。
2. 由于MS提供的函数DebugBreak会断点在Kernel32.DLL中,需要两次才能单步到下一行(第
一次跳出Kernel32.DLL);而__asm int 3;是断点在用户代码中,更容易使用。两者都只适合调试器存在时,非调试状态这个断点异常无法捕获会崩掉程序。 3. 自己编写发布版本也有效的断言:VERIFY。
4. #pragma comment(linker, \name='Microsoft.Windows.Commo
n-Controls'…”) 使GUI程序能够自动查找正确版本的ComCtrl32.DLL来自绘,达到自适应系统主题(Theme)的效果。
5. WindowsX.h中包含一些简单函数便于操作窗口,分别是消息处理宏、子控件宏和API宏。
第26章 窗口消息(第4版)
1. 一个进程可以创建上万个用户对象(User Object)。内核对象属于内核,可以跨进程使用,不
会随任何进程自动删除;图符、光标、窗口类、菜单、加速键表等用户对象属于进程,允许跨线程访问,进程结束后自动删除;窗口、挂钩两种用户对象属于线程,拥有者线程结束后自动删除。 2. 线程的内部数据结构THREADINFO中至少包括以下内容:Post来的消息队列、Send来的消息
队列、Send的应答队列、ExitCode、激活标志、消息队列状态标志(QueueStatus)、虚拟输入队列(VIQ)、局部输入状态(鼠标/键盘焦点窗口、光标外形和可见性等)。 3. PostMessage、PostThreadMessage、PostQuitMessage、GetWindowThreadProcessId。
4. SendMessage发送消息时,如果目标窗口位于发送线程,则SendMessage内部直接调用窗口过程
并返回,如果目标线程不是当前线程甚至位于其他进程,SendMessage往目标线程的Send消息队列内添加项过后(并设置QS_SENDMESSAGE),用MsgWaitForMultipleObjects等待处理完成通知(同时还处理本线程消息)。而SendMessageTimeout则包含等待Send处理完毕、处理本线程消息、检测超时三项功能,传入SMTO_BLOCK参数后只进行有超时的等待而不处理消息。 5. SendMessageCallback的目标线程是当前线程时,直接调用窗口过程并用回调通知;如果目标线
程是其他线程,Send消息后直接返回,之后本线程应该用GetMessage来响应其他线程Post回来的Send处理完毕的通知,该通知的处理函数会调用注册的回调。SendNotifyMessage相当于回调为空的SendMessageCallback,它不关心完成通知,相比PostMessage它还是具有Send消息的一些特点:比Post消息优先处理、目标线程是当前线程时直接调用窗口过程。一种获取所有窗口句柄的方法:以HWND_BROADCAST 为参数调用SendMessageCallback,然后GetMessage、DispatchMessage处理回调,在回调中搜集所有的窗口句柄。
6. SendMessage会在目标线程不是当前线程时阻塞等待,为避免不必要的阻塞发送线程,消息处理
函数一旦确定处理结果就可以马上调用ReplayMessage传入结果值来激活Send线程,处理函数后半段即使进行费时操作也不再干扰Send线程(处理函数的返回值也被忽略)。
7. InSendMessage可以在消息处理过程中判断当前线程是否是Send线程(线程不同返回TRUE)。
InSendMessageEx还可以判断抛出消息的具体函数,以及当前是否已经Reply了结果。 8. GetQueueStatus,检测当前线程的消息队列状态,是否有Post消息、Send消息、虚拟输入、以
及QS_QUIT、QS_TIMER等特殊标志。
9. TranslateMessage在遇到WM_KEYDOWN/WM_SYSKEYDOWN时,会Post一个WM_CHAR/
WM_SYSCHAR。因此如果使用了TranslateMessage,消息的处理顺序会变成WM_KEYDOWN->WM_CHAR->WM_KEYUP。
10. GetMessage/PeekMessage内部算法:先判断线程消息队列状态是否有QS_SENDMESSAGE标志,
如果有则从Send队列取消息并处理但不返回(即GetMessage内部检测到Send的消息后,会ReplayMessage(DispatchMessage(msg));而如果将Send消息交给用户代码来Dispatch,后者可能忘记需要答复发送线程);再判断是否有QS_POSTMESSAGE,如果有则从Post队列取消息填充MSG结构然后返回(因此,用户通过MSG结构从GetMessage处取得的消息只能是Post的消息);判断是否有QS_QUIT标记,如有则表示已经PostQuitMessage于是填充MSG结构并返回(因此即使先PostQuitMessage再Post用户消息,也能保证退出前用户消息被处理)。再判断是否有QS_INPUT标志,如果VIQ中有输入则填充MSG结构并返回(因此即使有输入也可以退出且如果有输入则不重绘)。判断是否有QS_PAINT标志,有则表示窗口仍然有脏区域(直到BeginPaint)于是填充MSG产生一个WM_PAINT消息。最后判断是否有QS_TIMER标志,如有则表示刚到时,于是移除标志并填充MSG结构返回。可以看见有几种消息被赋予了相当低的优先级,并不加入消息队列:WM_QUIT是为了保证退出前处理完所有普通消息;WM_PAINT是因为开销大,只在空闲时处理;WM_TIMER是为了避免处理慢触发快而导致消息队列溢出。 11. MsgWaitForMultipleObjects实现为,在事件对象数组后追加一项,如果要检测的消息队列标志被
置位则触发新追加的事件对象。关于输入消息的监听,由于MS设计为只在新增输入消息时事件
对象才触发,因此需要以MWMO_INPUTAVAILABLE为参数来调用MsgWaitForMultipleObjectsEx,达到一旦输入队列非空就触发的效果。另外MsgWaitForMultipleObjectsEx还支持WaitAll及APC等功能。
12. 对于跨进程用SendMessage发送WM_GETTEXT、WM_SETTEXT等消息,系统会自动使用共享
内存来转换消息参数的地址值以跨越进程边界。显然用户自定义消息需要自己来处理跨进程问题。WM_COPYDATA可以用来跨进程发送数据,发送进程传入一个有数据的缓冲,接受进程得到的缓冲地址转而指向一块相同内容的共享内存,系统在SendMessage返回时释放共享内存(故这个消息只能Send)。
13. 任意一个窗口都有编码属性,这个属性在绑定消息处理函数时确定(即调用RegisterClassA或以
GWLP_WNDPROC调用SetWindowLongPtrA表示这是一个ANSI窗口而不是Unicode窗口),通过系统在不同窗口间转发数据时,系统会自动进行编码转换。判断窗口的编码IsWindowUnicode。
14. 对GetKeyState和GetAsyncKeyState的理解:线程的局部输入状态中有一份键盘状态表,在处理
每个键盘消息的时候更新。GetKeyboardState获取整个表,GetKeyState获取某个表项,由于键盘消息不一定能够及时处理,因此内部表不一定够新,要获得实事状态,用GetAsyncKeyState,该API通过硬件中断获得最新按键状态。考虑一种GetAsyncKeyState的实现:线程先设置中断函数,再等待一个事件,中断到来时发现该线程中断函数指针非空于是执行函数,函数内部查询最新按键状态然后触发事件,中断结束后线程从等待的事件中被唤醒,最后返回按键状态。
第27章 硬件输入模型和局部输入状态(第4版)
1. 系统启动后创建RIT(Raw Input Thread,原始输入线程),它维护一个结构叫SHIQ(System
Hardware Input Queue,系统硬件输入队列),鼠标键盘的硬件驱动将各自的消息添加到SHIQ中,如果消息是鼠标消息,RIT就检测当前光标下方的窗口,然后将鼠标消息抛到该窗口创建线程的VIQ中(Virtual Input Queue,虚拟输入队列),除非某个窗口调用了SetCapture,则RIT把鼠标消息抛给捕获窗口所在的线程;如果是键盘消息,RIT将消息抛给前台窗口所在的线程的VIQ中,前台窗口由SetForegroundWindow设置或者用户通过Alt+Tab/Alt+Esc/Ctrl+Alt+Del激活,线程接到键盘消息后根据局部输入状态将消息交给焦点窗口