$GetMessage-PeekMessage-SendMessage内核解析.docx





《$GetMessage-PeekMessage-SendMessage内核解析.docx》由会员分享,可在线阅读,更多相关《$GetMessage-PeekMessage-SendMessage内核解析.docx(44页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、$GetMessage-PeekMessage-SendMessage内核解析最近忙公司的项目(或是毕设吧),发觉很长时间没有总结了。是该换换脑子了。“为什么没有SendThreadMessage呢?”这个问题,就来自自己平常实现的一些程序逻辑中。在一些详细的场景中,对像我这样的初学者来说,往往喜爱通过windwos的消息机制来完成UI线程和worker线程之间的同步,而不是去通过信号量或其他的去做。所以,这个问题始终困惑了自己很久。而现在,就来搞明白这个、 google一下,这个问题,在一个大牛(Raymond Chen) Chen自己的看法。”想象中的SendThreadMessage是如
2、何工作的呢?调用SendMessage 把消息干脆分发给窗口过程?但是我们没有看到消息泵,想象中的SendThreadMessage将会把消息分发给谁呢?因为我们没有thread window procedure这样的东东去处理我们的消息。是的,我们可以自己在我们的线程中做一个消息泵,但是,想象中的SendThreadMessage,须要等待这个消息处理完毕。但是,我们怎么能够知道这个消息处理完毕了?因为我们不行能等待DispatchMessage返回,而DispatchMessage失败则是因为我们并不知道应当往哪一个窗口分发消息。window manager给线程发送一个消息,仅此而已。你
3、可能会认为,我们可以等待知道下一个GetMessage or PeekMessage,这样我们可以确定这个消息解决了。但是,我们却不能保证下一个消息检索函数(GetMessage PeekMessage),是来自我们之前的消息泵。比如,我们这个线程消息,启动了一个模态窗口,是的。当我们的消息检索函数告知我们这个消息已经处理完毕了。但是,事实上那个模态窗口还在,因为他自己又创建了一个消息泵。“ 这段虽然不长,但是却另我头大无比。GetMessage , DispatchMessage。这2个基本的函数,每天用,但是却对他们的行为知之甚少,算上第一次写HelloWorld 到现在,至少也有1年了,
4、依旧朦胧,感到非常惭愧。而这也就是这篇总结要做的。而这的确是一个浩大的工程,因为要了解这2个函数,须要把握windows的消息机制。而windwos 并没有给我们源代码参考,这里参考ReactOS的实现,虽然不是windows正统,但是,应当差不远,至少是和win2003的相像。起先步入正题。我们首先须要了解的是,UI线程 和我们的一般的Worker线程之间的区分是什么。msdn ”To avoid the overhead of creating a message queue for nonGUI threads, all threads are created initially wit
5、hout a message queue. The system creates a thread-specific message queue only when the thread makes its first call to one of the specific user functions; no GUI function calls result in the creation of a message queue.“ 既然,系统创建每一个线程时都是一般的nonGUI thread,直到GDI, User函数调用,才为线程创建消息队列,那么我们就从这些函数调用起先。windwo
6、s在起先时,和linux一样 图形这部分是在用户空间中的进程负责,后面为了削减进程之间的环境切换,而放入了内核中。那么在系统调用这层,我们就看到了有2种状况。一种调用是原来的”内核”的调用,而另一种是新加进来的原来在用户空间的调用,这部分被称为扩充系统调用,这部分代码被放在了可以动态安装的模块win32k.sys。与之对应,系统的调用表就有了2个,一个是只包括之前的”来自内核的系统调用“,另一个则在之前的基础上,增加了图形图像的系统调用。当我们的系统调用被发觉是扩充系统调用时,也就是,原来的的表不能满意我们的要求。windwos会将会扩充系统调用表。并装载win32k.sys模块。那么,我们的
7、普一般通的线程就起先变为GUI线程了。激烈人心的旅程就从这里起先了。开源代码就是好,随意都能够贴出来。NTSTATUS NTAPI PsConvertToGuiThread(VOID) ULONG_PTR NewStack; PVOID OldStack; PETHREAD Thread = PsGetCurrentThread(); PEPROCESS Process = PsGetCurrentProcess(); NTSTATUS Status; PAGED_CODE(); /* Validate the previous mode */ if (KeGetPreviousMode()
8、= KernelMode) return STATUS_INVALID_PARAMETER; /* If no win32k, crashes later */ ASSERT(PspW32ProcessCallout != NULL); /* Make sure win32k is here */ if (!PspW32ProcessCallout) return STATUS_ACCESS_DENIED; /* Make sure its not already win32 */ if (Thread->Tcb.ServiceTable != KeServiceDescriptorTa
9、ble) /* Were already a win32 thread */ return STATUS_ALREADY_WIN32; /* Check if we dont already have a kernel-mode stack */ if (!Thread->Tcb.LargeStack) /* We dont create one */ NewStack = (ULONG_PTR)MmCreateKernelStack(TRUE, 0); if (!NewStack) /* Panic in user-mode */ NtCurrentTeb()->LastErro
10、rValue = ERROR_NOT_ENOUGH_MEMORY; return STATUS_NO_MEMORY; /* Were about to switch stacks. Enter a guarded region */ KeEnterGuardedRegion(); /* Switch stacks */ OldStack = KeSwitchKernelStack(PVOID)NewStack, (PVOID)(NewStack - KERNEL_STACK_SIZE); /* Leave the guarded region */ KeLeaveGuardedRegion()
11、; /* Delete the old stack */ MmDeleteKernelStack(OldStack, FALSE); /* This check is bizare. Check out win32k later */ if (!Process->Win32Process) /* Now tell win32k about us */ Status = PspW32ProcessCallout(Process, TRUE); if (!NT_SUCCESS(Status) return Status; /* Set the new service table */ Thr
12、ead->Tcb.ServiceTable = KeServiceDescriptorTableShadow; ASSERT(Thread->Tcb.Win32Thread = 0); /* Tell Win32k about our thread */ Status = PspW32ThreadCallout(Thread, PsW32ThreadCalloutInitialize); if (!NT_SUCCESS(Status) /* Revert our table */ Thread->Tcb.ServiceTable = KeServiceDescriptorTa
13、ble; /* Return status */ return Status; 之前没有提到的是,这里推断了一下线程system stack的大小,因为GUI线程要比一般的线程增加了更多的嵌套调用,从而须要更多的system stack。MmCreateKernelStack就是安排空间的函数。这里只是安排了64K的大小,一般的thread system stack大小为12K。当然,根据惯例,这里64K的堆栈,只是提交了其中12K的大小。并设置好guard page。超过12K则产生异样然后再安排空间。一个进程,假如有一个线程是GUI线程,那么这个进程就是GUI 进程,那么,假如不是GUI进
14、程,我们当然先得把进程转过来。PspW32ProcessCallout是一个函数指针,指向Win32kProcessCallback。这里就是干这个了,会初始化一系列的结构体,键盘格式,GDI 句柄表等等。我们这里略过这些细微环节。我们看到,系统的ServiceTable换成了大的表。而PspW32ThreadCallout指向Win32kThreadCallback,这里就完成了把一般线程转换成GUI线程的过程。对于操作系统这么困难的东东来说,要初始化的结构体真是茫茫的多。我们这里关注一点,在Win32kThreadCallback中,我们找到了创建消息队列的入口。Win32Thread-&
15、gt;MessageQueue = MsqCreateMessageQueue(Thread); 系统有了消息队列,但是,并不能构成真正的win32应用程序。我们开发者,还须要在自己的窗口程序中构造一个简洁的Message Dump,让我们看看这个GetMessage,究竟做了什么。GetMessage,最终会调用NtUserGetMessage。BOOL APIENTRY NtUserGetMessage(PMSG pMsg, HWND hWnd, UINT MsgFilterMin, UINT MsgFilterMax ) MSG Msg; BOOL Ret; if ( (MsgFilte
16、rMin|MsgFilterMax) WM_MAXIMUM ) EngSetLastError(ERROR_INVALID_PARAMETER); return FALSE; UserEnterExclusive(); RtlZeroMemory(Msg, sizeof(MSG); Ret = co_IntGetPeekMessage(Msg, hWnd, MsgFilterMin, MsgFilterMax, PM_REMOVE, TRUE); UserLeave(); if (Ret) _SEH2_TRY ProbeForWrite(pMsg, sizeof(MSG), 1); RtlCo
17、pyMemory(pMsg, Msg, sizeof(MSG); _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) SetLastNtError(_SEH2_GetExceptionCode(); Ret = FALSE; _SEH2_END; return Ret; 宽恕我略过一些茫茫多的细微环节。BOOL FASTCALL co_IntGetPeekMessage( PMSG pMsg, HWND hWnd, UINT MsgFilterMin, UINT MsgFilterMax, UINT RemoveMsg, BOOL bGMSG ) /. do Pre
18、sent = co_IntPeekMessage( pMsg, Window, MsgFilterMin, MsgFilterMax, RemoveMsg, bGMSG ); if (Present) /* GetMessage or PostMessage must never get messages that contain pointers */ ASSERT(FindMsgMemory(pMsg->message) = NULL); if (pMsg->message != WM_PAINT pMsg->message != WM_QUIT) pti->tim
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- GetMessage PeekMessage SendMessage 内核 解析

限制150内