消息机制篇——消息处理

写在前面

  此系列是本人一个字一个字码出来的,包括示例和实验截图。由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我

你如果是从中间插过来看的,请仔细阅读 羽夏看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;
}

下一篇

  消息机制篇——总结与提升

posted @ 2022-02-15 19:22  寂静的羽夏  阅读(439)  评论(0编辑  收藏  举报