消息机制篇——消息处理
写在前面
此系列是本人一个字一个字码出来的,包括示例和实验截图。由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我。
你如果是从中间插过来看的,请仔细阅读 羽夏看Win系统内核——简述 ,方便学习本教程。
看此教程之前,问几个问题,基础知识储备好了吗?保护模式篇学会了吗?练习做完了吗?没有的话就不要继续了。
🔒 华丽的分割线 🔒
消息发送
有关消息发送,我们都知道可以通过SendMessage
或者PostMessage
进行发送,它们有所不同,前者是同步的,后者是异步的,我们可以做一个实验验证(接收消息的程序见上篇):
#include "stdafx.h"
#include <iostream>
#include <windows.h>
#define WM_MyMessage WM_USER+1
int main(int argc, char* argv[])
{
HWND hwnd=FindWindow(NULL,"WingSummerTest");
if (hwnd!=NULL)
{
puts("SendMessage Test");
SendMessage(hwnd,WM_MyMessage,NULL,NULL);
puts("Send Successfully!");
puts("");
puts("PostMessage Test");
PostMessage(hwnd,WM_MyMessage,NULL,NULL);
puts("Post Successfully!");
}
else
{
puts("Hwnd NULL Error!!");
}
system("pause");
return 0;
}
效果图如下:
有关消息发送,就讨论这些。
消息接收与派发
有关消息处理我们总是写下面的代码:
// 主消息循环
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
我们都知道GetMessage
的具体作用就是从消息队列的取出消息,那么它的功能仅仅这些吗?循环里的两个函数的作用是什么?
我们把循环的两行代码注释掉,看看效果:
可以看出,我点击标题栏上的按钮和试图移动都不起效果,原因就是GetMessage
不会处理这些消息的,但它会处理一种消息,那就是通过SendMessage
发送的消息,我们运行发送消息的程序。我们来看一下效果:
可以发现,我的SendMessage
发送的消息被处理了,但是PostMessage
发过来的并没有处理,也就是说,GetMessage
可以拿出消息并处理Send
的消息。
然后我们把DispatchMessage
取消注释,窗体的那些操作又得到了恢复:
还有一个函数TranslateMessage
没有说明,貌似没啥作用,我们略微修改代码:往消息处理加一个case
:
case WM_CHAR:
{
MessageBox(NULL,"WingSummer Char Recived!", "CnBlog",MB_ICONINFORMATION);
}
break;
然后保持TranslateMessage
被注释,运行一下按一下键盘上的任意字母,发现没有动静。如果去掉注释状态,就会有消息框弹出,效果如下所示:
上面的函数也就是把键盘按下消息翻译转化一下成字符消息,如果不需要这样的功能,就不需要这行代码。
深入分析
GetMessage
GetMessage
在内核最终实现是xxxInternalGetMessage
实现,我们来看一下它的源代码:
BOOL xxxInternalGetMessage(
LPMSG lpMsg,
HWND hwndFilter,
UINT msgMin,
UINT msgMax,
UINT flags,
BOOL fGetMessage)
{
#ifdef MESSAGE_PUMP_HOOK
PCLIENTTHREADINFO pcti = gptiCurrent->pcti;
if (IsInsideMPH()) {
/*
* This thread has MPH's installed, so we need to callback into User
* mode to allow the application to provide an implementation
*/
return ClientGetMessageMPH(lpMsg, hwndFilter, msgMin, msgMax, flags, fGetMessage);
} else {
/*
* This thread does not have any MPH's installed, so we can just
* directly process.
*/
return xxxRealInternalGetMessage(lpMsg, hwndFilter, msgMin, msgMax, flags, fGetMessage);
}
}
我们来先看看ClientGetMessageMPH
这个函数:
BOOL ClientGetMessageMPH(
IN MSG * pmsg,
IN HWND hwndFilter,
IN UINT msgMin,
IN UINT msgMax,
IN UINT flags,
IN BOOL fGetMessage)
{
SETUP(CLIENTGETMESSAGEMPH)
BEGINSEND(CLIENTGETMESSAGEMPH)
MSGDATA()->hwndFilter = hwndFilter;
MSGDATA()->msgMin = msgMin;
MSGDATA()->msgMax = msgMax;
MSGDATA()->flags = flags;
MSGDATA()->fGetMessage = fGetMessage;
MAKECALL(CLIENTGETMESSAGEMPH);
CHECKRETURN();
OUTSTRUCT(pmsg, MSG);
TRACECALLBACK("ClientGetMessageMPH");
ENDSEND(BOOL,0);
}
#define SETUP(api) \
api ## MSG m; \
api ## MSG *mp = &m; \
BYTE Buffer[CBBUFSIZE]; \
PCALLBACKSTATUS pcbs; \
ULONG cbCBStatus; \
ULONG_PTR retval; \
NTSTATUS Status;
#define MSGDATA() (mp)
#define MAKECALL(api) \
UserAssert(!(PtiCurrent()->TIF_flags & TIF_INCLEANUP)); \
LeaveCrit(); \
Status = KeUserModeCallback( \
FI_ ## api, \
mp, \
sizeof(*mp), \
&pcbs, \
&cbCBStatus); \
EnterCrit();
#define ENDSEND(type, error) \
return (type)retval; \
goto errorexit; \
} \
errorexit: \
return (type)error
代码并没有啥难度,最终会调用KeUserModeCallback
来执行3环的函数,再看看剩下的,由于代码很长,故被折叠节省篇幅:
🔒 点击查看代码 🔒
BOOL xxxRealInternalGetMessage(
LPMSG lpMsg,
HWND hwndFilter,
UINT msgMin,
UINT msgMax,
UINT flags,
BOOL fGetMessage)
{
#endif MESSAGE_PUMP_HOOK
UINT fsWakeBits;
UINT fsWakeMask;
UINT fsRemoveBits;
PTHREADINFO ptiCurrent;
PW32PROCESS W32Process;
PWND pwndFilter;
BOOL fLockPwndFilter;
TL tlpwndFilter;
BOOL fRemove;
BOOL fExit;
PQ pq;
#ifdef MARKPATH
DWORD pathTaken = 0;
#endif
CheckCritIn();
UserAssert(IsWinEventNotifyDeferredOK());
ptiCurrent = PtiCurrent();
/*
* PeekMessage accepts NULL, 0x0000FFFF, and -1 as valid HWNDs.
* If hwndFilter is invalid we can't just return FALSE because that will
* hose existing badly behaved apps who might attempt to dispatch
* the random contents of pmsg.
*/
if ((hwndFilter == (HWND)-1) || (hwndFilter == (HWND)0x0000FFFF)) {
hwndFilter = (HWND)1;
}
if ((hwndFilter != NULL) && (hwndFilter != (HWND)1)) {
if ((pwndFilter = ValidateHwnd(hwndFilter)) == NULL) {
lpMsg->hwnd = NULL;
lpMsg->message = WM_NULL;
PATHTAKEN(1);
DUMPPATHTAKEN();
if (fGetMessage)
return -1;
else
return 0;
}
ThreadLockAlwaysWithPti(ptiCurrent, pwndFilter, &tlpwndFilter);
fLockPwndFilter = TRUE;
} else {
pwndFilter = (PWND)hwndFilter;
fLockPwndFilter = FALSE;
}
/*
* Add one to our spin count. At this end of this routine we'll check
* to see if the spin count gets >= CSPINBACKGROUND. If so we'll put this
* process into the background.
*/
ptiCurrent->pClientInfo->cSpins++;
/*
* Check to see if the startglass is on, and if so turn it off and update.
*/
W32Process = W32GetCurrentProcess();
if (W32Process->W32PF_Flags & W32PF_STARTGLASS) {
/*
* This app is no longer in "starting" mode. Recalc when to hide
* the app starting cursor.
*/
W32Process->W32PF_Flags &= ~W32PF_STARTGLASS;
/*
* Don't need DeferWinEventNotify() - xxxDoSysExpunge below doesn't
*/
zzzCalcStartCursorHide(NULL, 0);
}
/*
* Next check to see if any .dlls need freeing in
* the context of this client (used for windows hooks).
*/
if (ptiCurrent->ppi->cSysExpunge != gcSysExpunge) {
ptiCurrent->ppi->cSysExpunge = gcSysExpunge;
if (ptiCurrent->ppi->dwhmodLibLoadedMask & gdwSysExpungeMask)
xxxDoSysExpunge(ptiCurrent);
}
/*
* Set up BOOL fRemove local variable from for ReadMessage()
*/
fRemove = flags & PM_REMOVE;
/*
* Unlock the system queue if it's owned by us.
*/
/*
* If we're currently processing a message, unlock the input queue
* because the sender, who is blocked, might be the owner, and in order
* to reply, the receiver may need to read keyboard / mouse input.
*/
/*
* If this thread has the input queue locked and the last message removed
* is the last message we looked at, then unlock - we're ready for anyone
* to get the next message.
*/
pq = ptiCurrent->pq;
if ( (ptiCurrent->psmsCurrent != NULL)
|| (pq->ptiSysLock == ptiCurrent && pq->idSysLock == ptiCurrent->idLast)
) {
CheckSysLock(1, pq, NULL);
pq->ptiSysLock = NULL;
PATHTAKEN(2);
} else if (pq->ptiSysLock
&& (pq->ptiSysLock->cVisWindows == 0)
&& (PhkFirstGlobalValid(ptiCurrent, WH_JOURNALPLAYBACK) != NULL)) {
/*
* If the thread that has the system queue lock has no windows visible
* (can happen if it just hid its last window), don't expect it to call
* GetMessage() again! - unlock the system queue. --- ScottLu
* This condition creates a hole by which a second thread attached to
* the same queue as thread 1 can alter pq->idSysPeek during a callback
* made by thread 1 so that thread 1 will delete the wrong message
* (losing keystrokes - causing Shift to appear be stuck down editing a
* Graph5 caption embedded in Word32 document #5032. However, MSTEST
* requires this hole, so allow it if Journal Playback is occurring
* #8850 (yes, a hack) Chicago also has this behavior. --- IanJa
*/
CheckSysLock(2, pq, NULL);
pq->ptiSysLock = NULL;
PATHTAKEN(3);
}
if (pq->ptiSysLock != ptiCurrent) {
ptiCurrent->pcti->CTIF_flags &= ~CTIF_SYSQUEUELOCKED;
}
/*
* If msgMax == 0 then msgMax = -1: that makes our range checking only
* have to deal with msgMin < msgMax.
*/
if (msgMax == 0)
msgMax--;
/*
* Compute the QS* mask that corresponds to the message range
* and the wake mask filter (HIWORD(flags))
*/
fsWakeMask = CalcWakeMask(msgMin, msgMax, HIWORD(flags));
ptiCurrent->fsChangeBitsRemoved = 0;
/*
* If we can yield and one or more events were skipped,
* set the wakebits for event
*/
if (!(flags & PM_NOYIELD) && ptiCurrent->TIF_flags & TIF_DELAYEDEVENT) {
ptiCurrent->pcti->fsWakeBits |= QS_EVENT;
ptiCurrent->pcti->fsChangeBits |= QS_EVENT;
ptiCurrent->TIF_flags &= ~TIF_DELAYEDEVENT;
ptiCurrent->pClientInfo->dwTIFlags = ptiCurrent->TIF_flags;
}
while (TRUE) {
/*
* Restore any wake bits saved while journalling
*/
ptiCurrent->pcti->fsWakeBits |= ptiCurrent->pcti->fsWakeBitsJournal;
/*
* If we need to recalc queue attachments, do it here. Do it on the
* right desktop or else the queues will get created in the wrong
* heap.
*/
if (ptiCurrent->rpdesk == gpdeskRecalcQueueAttach) {
gpdeskRecalcQueueAttach = NULL;
if (ptiCurrent->rpdesk != NULL && !FJOURNALRECORD() && !FJOURNALPLAYBACK()) {
/*
* No need to DeferWinEventNotify(): a call to
* xxxReceiveMessages is made just below
*/
zzzReattachThreads(FALSE);
PATHTAKEN(4);
}
}
/*
* Remember what change bits we're clearing. This is important to
* fix a bug in the input model: If an app receives a sent message
* from within SleepThread(), then does PostMessage() (which sets
* QS_POSTMESSAGE), then does a PeekMessage(...) for some different
* posted message (clears QS_POSTMESSAGE in fsChangeBits), then returns
* back into SleepThread(), it won't wake up to retrieve that newly
* posted message because the change bits are cleared.
*
* What we do is remember the change bits that are being cleared.
* Then, when we return to SleepThread(), we put these remembered
* bits back into the change bits that also have corresponding
* bits in the wakebits (so we don't set changebits that represent
* input that isn't there anymore). This way, the app will retrieve
* the newly posted message refered to earlier.
* - scottlu
*
* New for NT5: Since QS_SENDMESSAGE was never set it fsWakeMask before (NT4),
* it was never cleared from fsChangeBits. For compatibility, we won't clear
* it now even if specified in fsWakeMask; hence we won't affect any one
* checking for QS_SENDMESSAGE in pcti->fsChangeBits.
*/
fsRemoveBits = fsWakeMask & ~QS_SENDMESSAGE;
ptiCurrent->fsChangeBitsRemoved |= ptiCurrent->pcti->fsChangeBits & fsRemoveBits;
/*
* Clear the change bits that we're looking at, in order to detect
* incoming events that may occur the last time we checked the wake
* bits.
*/
ptiCurrent->pcti->fsChangeBits &= ~fsRemoveBits;
/*
* Check for sent messages. Check the the actual wake bits (i.e, from pcti)
* so we know for real.
*/
if (ptiCurrent->pcti->fsWakeBits & fsWakeMask & QS_SENDMESSAGE) {
xxxReceiveMessages(ptiCurrent);
} else if (ptiCurrent->pcti->fsWakeBits & QS_SENDMESSAGE) {
RIPMSG2(RIP_WARNING, "xxxInternalGetMessage:(1st test) sendmsgs pending. Bits:%#lx Mask:%#lx",
ptiCurrent->pcti->fsWakeBits, fsWakeMask);
goto NoMessages;
}
/*
* Check to see if we have any input we want.
*/
if ((ptiCurrent->pcti->fsWakeBits & fsWakeMask) == 0) {
PATHTAKEN(8);
goto NoMessages;
}
fsWakeBits = ptiCurrent->pcti->fsWakeBits;
/*
* If the queue lock is != NULL (ptiSysLock) and it is this thread that
* locked it, then go get the message from the system queue. This is
* to prevent messages posted after a PeekMessage/no-remove from being
* seen before the original message from the system queue. (Aldus
* Pagemaker requires this) (bobgu 8/5/87).
*/
if (ptiCurrent->pq->ptiSysLock == ptiCurrent &&
(ptiCurrent->pq->QF_flags & QF_LOCKNOREMOVE)) {
/*
* Does the caller want mouse / keyboard?
*/
if (fsWakeBits & fsWakeMask & (QS_INPUT | QS_EVENT)) {
/*
* It should never get here during exit.
*/
UserAssert(gbExitInProgress == FALSE);
if (xxxScanSysQueue(ptiCurrent, lpMsg, pwndFilter,
msgMin, msgMax, flags,
fsWakeBits & fsWakeMask & (QS_INPUT | QS_EVENT))) {
PATHTAKEN(0x10);
break;
}
} else if (fsWakeBits & QS_EVENT) {
RIPMSG2(RIP_WARNING,
"xxxInternalGetMessage:(1st test)events pending. Bits:%#lx Mask:%#lx",
fsWakeBits, fsWakeMask);
goto NoMessages;
}
}
/*
* See if there's a message in the application queue.
*/
if (fsWakeBits & fsWakeMask & QS_POSTMESSAGE) {
if (xxxReadPostMessage(ptiCurrent, lpMsg, pwndFilter,
msgMin, msgMax, fRemove)) {
PATHTAKEN(0x20);
break;
}
}
/*
* Time to scan the raw input queue for input. First check to see
* if the caller wants mouse / keyboard input.
*/
if (fsWakeBits & fsWakeMask & (QS_INPUT | QS_EVENT)) {
/*
* It should never get here during exit.
*/
UserAssert(gbExitInProgress == FALSE);
if (xxxScanSysQueue(ptiCurrent, lpMsg, pwndFilter,
msgMin, msgMax, flags,
fsWakeBits & fsWakeMask & (QS_INPUT | QS_EVENT))) {
PATHTAKEN(0x40);
break;
}
} else if (fsWakeBits & QS_EVENT) {
RIPMSG2(RIP_WARNING, "xxxInternalGetMessage:(2nd test)events pending. Bits:%#lx Mask:%#lx",
fsWakeBits, fsWakeMask);
goto NoMessages;
}
/*
* Check for sent messages. Check the the actual wake bits (i.e, from pcti)
* so we know for real.
*/
if (ptiCurrent->pcti->fsWakeBits & fsWakeMask & QS_SENDMESSAGE) {
xxxReceiveMessages(ptiCurrent);
} else if (ptiCurrent->pcti->fsWakeBits & QS_SENDMESSAGE) {
RIPMSG2(RIP_WARNING, "xxxInternalGetMessage:(2nd test)sendmsgs pending. Bits:%#lx Mask:%#lx",
ptiCurrent->pcti->fsWakeBits, fsWakeMask);
goto NoMessages;
}
/*
* Get new input bits.
*/
if ((ptiCurrent->pcti->fsWakeBits & fsWakeMask) == 0) {
PATHTAKEN(0x80);
goto NoMessages;
}
fsWakeBits = ptiCurrent->pcti->fsWakeBits;
/*
* Does the caller want paint messages? If so, try to find a paint.
*/
if (fsWakeBits & fsWakeMask & QS_PAINT) {
if (xxxDoPaint(pwndFilter, lpMsg)) {
PATHTAKEN(0x100);
break;
}
}
/*
* We must yield for 16 bit apps before checking timers or an app
* that has a fast timer could chew up all the time and never let
* anyone else run.
*
* NOTE: This could cause PeekMessage() to yield TWICE, if the user
* is filtering with a window handle. If the DoTimer() call fails
* then we end up yielding again.
*/
if (!(flags & PM_NOYIELD)) {
/*
* This is the point where windows would yield. Here we wait to wake
* up any threads waiting for this thread to hit "idle state".
*/
zzzWakeInputIdle(ptiCurrent);
/*
* Yield and receive pending messages.
*/
xxxUserYield(ptiCurrent);
/*
* Check new input buts and receive pending messages.
*/
if (ptiCurrent->pcti->fsWakeBits & fsWakeMask & QS_SENDMESSAGE) {
xxxReceiveMessages(ptiCurrent);
} else if (ptiCurrent->pcti->fsWakeBits & QS_SENDMESSAGE) {
RIPMSG2(RIP_WARNING, "xxxInternalGetMessage:(3rd test) sendmsgs pending. Bits:%#lx Mask:%#lx",
ptiCurrent->pcti->fsWakeBits, fsWakeMask);
goto NoMessages;
}
if ((ptiCurrent->pcti->fsWakeBits & fsWakeMask) == 0) {
PATHTAKEN(0x200);
goto NoMessages;
}
fsWakeBits = ptiCurrent->pcti->fsWakeBits;
}
/*
* Does the app want timer messages, and if there one pending?
*/
if (fsWakeBits & fsWakeMask & QS_TIMER) {
if (DoTimer(pwndFilter)) {
/*
* DoTimer() posted the message into the app's queue,
* so start over and we'll grab it from there.
*/
PATHTAKEN(0x400);
continue;
}
}
NoMessages:
/*
* Looks like we have no input. If we're being called from GetMessage()
* then go to sleep until we find something.
*/
if (!fGetMessage) {
/*
* This is one last check for pending sent messages. It also
* yields. Win3.1 does this.
*/
if (!(flags & PM_NOYIELD)) {
/*
* This is the point where windows yields. Here we wait to wake
* up any threads waiting for this thread to hit "idle state".
*/
zzzWakeInputIdle(ptiCurrent);
/*
* Yield and receive pending messages.
*/
xxxUserYield(ptiCurrent);
}
PATHTAKEN(0x800);
goto FalseExit;
}
/*
* This is a getmessage not a peekmessage, so sleep. When we sleep,
* zzzWakeInputIdle() is called to wake up any apps waiting on this
* app to go idle.
*/
if (!xxxSleepThread(fsWakeMask, 0, TRUE))
goto FalseExit;
} /* while (TRUE) */
/*
* If we're here then we have input for this queue. Call the
* GetMessage() hook with this input.
*/
if (IsHooked(ptiCurrent, WHF_GETMESSAGE))
xxxCallHook(HC_ACTION, flags, (LPARAM)lpMsg, WH_GETMESSAGE);
/*
* If called from PeekMessage(), return TRUE.
*/
if (!fGetMessage) {
PATHTAKEN(0x1000);
goto TrueExit;
}
/*
* Being called from GetMessage(): return FALSE if the message is WM_QUIT,
* TRUE otherwise.
*/
if (lpMsg->message == WM_QUIT) {
PATHTAKEN(0x2000);
goto FalseExit;
}
/*
* Fall through to TrueExit...
*/
TrueExit:
/*
* Update timeLastRead. We use this for hung app calculations.
*/
SET_TIME_LAST_READ(ptiCurrent);
fExit = TRUE;
#ifdef GENERIC_INPUT
if (fRemove) {
/*
* This version simply frees the previous HIDDATA.
*/
if (ptiCurrent->hPrevHidData) {
PHIDDATA pPrevHidData = HMValidateHandleNoRip(ptiCurrent->hPrevHidData, TYPE_HIDDATA);
TAGMSG1(DBGTAG_PNP, "xxxInternalGetMessage: WM_INPUT prev=%p", ptiCurrent->hPrevHidData);
if (pPrevHidData) {
FreeHidData(pPrevHidData);
} else {
RIPMSG1(RIP_WARNING, "xxxInternalGetMessage: WM_INPUT bogus hPrev=%p",
ptiCurrent->hPrevHidData);
}
ptiCurrent->hPrevHidData = NULL;
}
if (lpMsg->message == WM_INPUT) {
if (lpMsg->wParam == RIM_INPUT
#ifdef GI_SINK
|| lpMsg->wParam == RIM_INPUTSINK
#endif
) {
ptiCurrent->hPrevHidData = (HANDLE)lpMsg->lParam;
#if DBG
{
PHIDDATA pHidData = HMValidateHandle((HANDLE)lpMsg->lParam, TYPE_HIDDATA);
TAGMSG1(DBGTAG_PNP, "xxxInternalGetMessage: WM_INPUT new=%p", PtoH(pHidData));
if (pHidData == NULL) {
RIPMSG2(RIP_WARNING, "xxxInternalGetMessage: WM_INPUT bogus parameter wp=%x, lp=%x",
lpMsg->wParam, lpMsg->lParam);
}
}
#endif
} else {
RIPMSG1(RIP_WARNING, "xxxInternalGetMessage: WM_INPUT bogus wParam %x",
lpMsg->wParam);
}
}
}
#endif
PATHTAKEN(0x4000);
goto Exit;
FalseExit:
fExit = FALSE;
Exit:
if (fLockPwndFilter)
ThreadUnlock(&tlpwndFilter);
/*
* see CheckProcessBackground() comment above
* Check to see if we need to move this process into background
* priority.
*/
if (ptiCurrent->pClientInfo->cSpins >= CSPINBACKGROUND) {
ptiCurrent->pClientInfo->cSpins = 0;
if (!(ptiCurrent->TIF_flags & TIF_SPINNING)) {
ptiCurrent->TIF_flags |= TIF_SPINNING;
ptiCurrent->pClientInfo->dwTIFlags = ptiCurrent->TIF_flags;
if (!(ptiCurrent->ppi->W32PF_Flags & W32PF_FORCEBACKGROUNDPRIORITY)) {
ptiCurrent->ppi->W32PF_Flags |= W32PF_FORCEBACKGROUNDPRIORITY;
if (ptiCurrent->ppi == gppiWantForegroundPriority) {
SetForegroundPriority(ptiCurrent, FALSE);
}
}
}
/*
* For spinning Message loops, we need to take the 16bit-thread out
* of the scheduler temporarily so that other processes can get a chance
* to run. This is appearent in OLE operations where a 16bit foreground
* thread starts an OLE activation on a 32bit process. The 32bit process
* gets starved of CPU while the 16bit thread spins.
*/
if (ptiCurrent->TIF_flags & TIF_16BIT) {
/*
* Take the 16bit thread out of the scheduler. This wakes any
* other 16bit thread needing time, and takes the current thread
* out. We will do a brief sleep so that apps can respond in time.
* When done, we will reschedule the thread. The zzzWakeInputIdle()
* should have been called in the no-messages section, so we have
* already set the Idle-Event.
*/
xxxSleepTask(FALSE, HEVENT_REMOVEME);
LeaveCrit();
ZwYieldExecution();
EnterCrit();
xxxDirectedYield(DY_OLDYIELD);
}
}
PATHTAKEN(0x8000);
DUMPPATHTAKEN();
return fExit;
}
代码注释挺详细的,故不再做赘述了。
TranslateMessage
该函数在内核的实现就是xxxTranslateMessage
,源码如下:
BOOL xxxTranslateMessage(
LPMSG pmsg,
UINT uiTMFlags)
{
PTHREADINFO pti;
UINT wMsgType;
int cChar;
BOOL fSysKey = FALSE;
DWORD dwKeyFlags;
LPARAM lParam;
UINT uVirKey;
PWND pwnd;
WCHAR awch[16];
WCHAR *pwch;
switch (pmsg->message) {
default:
return FALSE;
case WM_SYSKEYDOWN:
/*
* HACK carried over from Win3 code: system messages
* only get posted during KEYDOWN processing - so
* set fSysKey only for WM_SYSKEYDOWN.
*/
fSysKey = TRUE;
/*
* Fall thru...
*/
case WM_SYSKEYUP:
case WM_KEYDOWN:
case WM_KEYUP:
pti = PtiCurrent();
if ((pti->pMenuState != NULL) &&
(HW(pti->pMenuState->pGlobalPopupMenu->spwndPopupMenu) ==
pmsg->hwnd)) {
uiTMFlags |= TM_INMENUMODE;
} else {
uiTMFlags &= ~TM_INMENUMODE;
}
/*
* Don't change the contents of the passed in structure.
*/
lParam = pmsg->lParam;
/*
* For backward compatibility, mask the virtual key value.
*/
uVirKey = LOWORD(pmsg->wParam);
cChar = xxxInternalToUnicode(uVirKey, // virtual key code
HIWORD(lParam), // scan code, make/break bit
pti->pq->afKeyState,
awch, sizeof(awch)/sizeof(awch[0]),
uiTMFlags, &dwKeyFlags, NULL);
lParam |= (dwKeyFlags & ALTNUMPAD_BIT);
/*
* LATER 12/7/90 - GregoryW
* Note: Win3.x TranslateMessage returns TRUE if ToAscii is called.
* Proper behavior is to return TRUE if any translation is
* performed by ToAscii. If we have to remain compatible
* (even though apps clearly don't currently care about the
* return value) then the following return should be changed
* to TRUE. If we want the new 32-bit apps to have a meaningful
* return value we should leave this as FALSE.
*
* If console is calling us with the TM_POSTCHARBREAKS flag then we
* return FALSE if no char was actually posted
*
* !!! LATER get console to change so it does not need private API
* TranslateMessageEx
*/
if (!cChar) {
if (uiTMFlags & TM_POSTCHARBREAKS)
return FALSE;
else
return TRUE;
}
/*
* Some translation performed. Figure out what type of
* message to post.
*
*/
if (cChar > 0)
wMsgType = (fSysKey) ? (UINT)WM_SYSCHAR : (UINT)WM_CHAR;
else {
wMsgType = (fSysKey) ? (UINT)WM_SYSDEADCHAR : (UINT)WM_DEADCHAR;
cChar = -cChar; // want positive value
}
if (dwKeyFlags & KBDBREAK) {
lParam |= 0x80000000;
} else {
lParam &= ~0x80000000;
}
/*
* Since xxxInternalToUnicode can leave the crit sect, we need to
* validate the message hwnd here.
*/
pwnd = ValidateHwnd(pmsg->hwnd);
if (!pwnd) {
return FALSE;
}
for (pwch = awch; cChar > 0; cChar--) {
/*
* If this is a multi-character posting, all but the last one
* should be marked as fake keystrokes for Console/VDM.
*/
_PostMessage(pwnd, wMsgType, (WPARAM)*pwch,
lParam | (cChar > 1 ? FAKE_KEYSTROKE : 0));
*pwch = 0; // zero out old character (why?)
pwch += 1;
}
return TRUE;
}
}
可以看出,这个函数只是转换了消息,并没有取消息的操作。
DispatchMessage
该函数的内核实现是xxxDispatchMessage
函数,如下是其伪代码,由于篇幅过长,请打开查看:
🔒 点击查看代码 🔒
LRESULT xxxDispatchMessage(
LPMSG pmsg)
{
LRESULT lRet;
PWND pwnd;
WNDPROC_PWND lpfnWndProc;
TL tlpwnd;
pwnd = NULL;
if (pmsg->hwnd != NULL) {
if ((pwnd = ValidateHwnd(pmsg->hwnd)) == NULL)
return 0;
}
/*
* If this is a synchronous-only message (takes a pointer in wParam or
* lParam), then don't allow this message to go through since those
* parameters have not been thunked, and are pointing into outer-space
* (which would case exceptions to occur).
*
* (This api is only called in the context of a message loop, and you
* don't get synchronous-only messages in a message loop).
*/
if (TESTSYNCONLYMESSAGE(pmsg->message, pmsg->wParam)) {
/*
* Fail if 32 bit app is calling.
*/
if (!(PtiCurrent()->TIF_flags & TIF_16BIT)) {
RIPERR1(ERROR_MESSAGE_SYNC_ONLY, RIP_WARNING, "xxxDispatchMessage: Sync only message 0x%lX",
pmsg->message);
return 0;
}
/*
* For wow apps, allow it to go through (for compatibility). Change
* the message id so our code doesn't understand the message - wow
* will get the message and strip out this bit before dispatching
* the message to the application.
*/
pmsg->message |= MSGFLAG_WOW_RESERVED;
}
ThreadLock(pwnd, &tlpwnd);
/*
* Is this a timer? If there's a proc address, call it,
* otherwise send it to the wndproc.
*/
if ((pmsg->message == WM_TIMER) || (pmsg->message == WM_SYSTIMER)) {
if (pmsg->lParam != 0) {
/*
* System timers must be executed on the server's context.
*/
if (pmsg->message == WM_SYSTIMER) {
/*
* Verify that it's a valid timer proc. If so,
* don't leave the critsect to call server-side procs
* and pass a PWND, not HWND.
*/
PTIMER ptmr;
lRet = 0;
ptmr = FindSystemTimer(pmsg);
if (ptmr) {
ptmr->pfn(pwnd, WM_SYSTIMER, (UINT)pmsg->wParam,
NtGetTickCount());
}
goto Exit;
} else {
/*
* WM_TIMER is the same for Unicode/ANSI.
*/
PTHREADINFO ptiCurrent = PtiCurrent();
if (ptiCurrent->TIF_flags & TIF_SYSTEMTHREAD) {
lRet = 0;
goto Exit;
}
/*
* Check the legitimacy of this WM_TIMER callback,
* and bail out if it's not valid.
*/
if (!ValidateTimerCallback(ptiCurrent, pwnd, pmsg->wParam, pmsg->lParam)) {
lRet = 0;
goto Exit;
}
lRet = CallClientProcA(pwnd, WM_TIMER,
pmsg->wParam, NtGetTickCount(), pmsg->lParam);
goto Exit;
}
}
}
/*
* Check to see if pwnd is NULL AFTER the timer check. Apps can set
* timers with NULL hwnd's, that's totally legal. But NULL hwnd messages
* don't get dispatched, so check here after the timer case but before
* dispatching - if it's NULL, just return 0.
*/
if (pwnd == NULL) {
lRet = 0;
goto Exit;
}
/*
* If we're dispatching a WM_PAINT message, set a flag to be used to
* determine whether it was processed properly.
*/
if (pmsg->message == WM_PAINT)
SetWF(pwnd, WFPAINTNOTPROCESSED);
/*
* If this window's proc is meant to be executed from the server side
* we'll just stay inside the semaphore and call it directly. Note
* how we don't convert the pwnd into an hwnd before calling the proc.
*/
if (TestWF(pwnd, WFSERVERSIDEPROC)) {
ULONG_PTR fnMessageType;
fnMessageType = pmsg->message >= WM_USER ? (ULONG_PTR)SfnDWORD :
(ULONG_PTR)gapfnScSendMessage[MessageTable[pmsg->message].iFunction];
/*
* Convert the WM_CHAR from ANSI to UNICODE if the source was ANSI
*/
if (fnMessageType == (ULONG_PTR)SfnINWPARAMCHAR && TestWF(pwnd, WFANSIPROC)) {
UserAssert(PtiCurrent() == GETPTI(pwnd)); // use receiver's codepage
RtlMBMessageWParamCharToWCS(pmsg->message, &pmsg->wParam);
}
lRet = pwnd->lpfnWndProc(pwnd, pmsg->message, pmsg->wParam,
pmsg->lParam);
goto Exit;
}
/*
* Cool people dereference any window structure members before they
* leave the critsect.
*/
lpfnWndProc = pwnd->lpfnWndProc;
{
/*
* If we're dispatching the message to an ANSI wndproc we need to
* convert the character messages from Unicode to Ansi.
*/
if (TestWF(pwnd, WFANSIPROC)) {
UserAssert(PtiCurrent() == GETPTI(pwnd)); // use receiver's codepage
RtlWCSMessageWParamCharToMB(pmsg->message, &pmsg->wParam);
lRet = CallClientProcA(pwnd, pmsg->message,
pmsg->wParam, pmsg->lParam, (ULONG_PTR)lpfnWndProc);
} else {
lRet = CallClientProcW(pwnd, pmsg->message,
pmsg->wParam, pmsg->lParam, (ULONG_PTR)lpfnWndProc);
}
}
/*
* If we dispatched a WM_PAINT message and it wasn't properly
* processed, do the drawing here.
*/
if (pmsg->message == WM_PAINT && RevalidateHwnd(pmsg->hwnd) &&
TestWF(pwnd, WFPAINTNOTPROCESSED)) {
//RIPMSG0(RIP_WARNING,
// "Missing BeginPaint or GetUpdateRect/Rgn(fErase == TRUE) in WM_PAINT");
ClrWF(pwnd, WFWMPAINTSENT);
xxxSimpleDoSyncPaint(pwnd);
}
Exit:
ThreadUnlock(&tlpwnd);
return lRet;
}
可以看出该函数不是并不是所谓的派发,而是执行相关的3环的消息回调。有关细节请自行阅读源代码。
Send 与 Post
有关这两个函数我就不直接放源代码了,我们只是看看它们的挂的链表的不同,如下所示:
SendMessage
相关部分:
psms->message = message;
psms->wParam = wParam;
psms->lParam = lParam;
psms->flags = 0;
/*
* Link into gpsmsList
*/
psms->psmsNext = gpsmsList;
gpsmsList = psms;
/*
* Time stamp message
*/
psms->tSent = NtGetTickCount();
/*
* Set queue fields
*/
psms->ptiReceiver = ptiReceiver;
psms->ptiSender = ptiSender;
psms->ptiCallBackSender = NULL;
PostMessage
相关部分:
/*
* Put this message on the 'post' list.
*/
if ((pqmsg = AllocQEntry(&pti->mlPost)) == NULL) {
RIPMSG1(RIP_WARNING, "_PostThreadMessage: Failed to alloc Q entry: Target pti=0x%p",
pti);
return FALSE;
}
下一篇
本文来自博客园,作者:寂静的羽夏 ,一个热爱计算机技术的菜鸟
转载请注明原文链接:https://www.cnblogs.com/wingsummer/p/15897870.html