訊息機制篇——初識訊息與訊息佇列

寂靜的羽夏發表於2022-02-15

寫在前面

  此係列是本人一個字一個字碼出來的,包括示例和實驗截圖。由於系統核心的複雜性,故可能有錯誤或者不全面的地方,如有錯誤,歡迎批評指正,本教程將會長期更新。 如有好的建議,歡迎反饋。碼字不易,如果本篇文章有幫助你的,如有閒錢,可以打賞支援我的創作。如想轉載,請把我的轉載資訊附在文章後面,並宣告我的個人資訊和本人部落格地址即可,但必須事先通知我

你如果是從中間插過來看的,請仔細閱讀 羽夏看Win系統核心——簡述 ,方便學習本教程。

  看此教程之前,問幾個問題,基礎知識儲備好了嗎?保護模式篇學會了嗎?練習做完了嗎?沒有的話就不要繼續了。


? 華麗的分割線 ?


前言

  由於win32k.sys裡面大量的所需符號和結構體都沒有,有很多東西我目前只能淺嘗輒止,UI是一個很複雜的東西,我的能力有限也只能具體講解一下它的功能、大體流程和某些小細節。對於程式碼參考,是基於ReactOS 0.2.0版本和洩露的WinXP SP1程式碼。ReactOS是基於逆向Windows得到,所以基本類似,建議此版本,高版本的有自己的實現了,就不太一樣了。

引子

  學過Win32圖形介面程式設計的都知道我們要顯示一個窗體是十分麻煩的,如下是建立專案自己生成的,我進行略加修改,最終程式碼如下:

#include "stdafx.h"
#include <windows.h>

#define WM_MyMessage WM_USER+1

HINSTANCE hInst;                                // 當前例項
char szTitle[] = "WingSummerTest";                  // 標題欄文字
char szWindowClass[]= "CnBlog";            // 主視窗類名

// 此程式碼模組中包含的函式的前向宣告
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);

int APIENTRY wWinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                      LPWSTR    lpCmdLine,
                      int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    MyRegisterClass(hInstance);

    // 執行應用程式初始化
    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }
 
    MSG msg;

    // 主訊息迴圈
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int) msg.wParam;
}


//
//  函式: MyRegisterClass()
//  目標: 註冊視窗類。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEX wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = NULL;
    wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = NULL;
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = NULL;

    return RegisterClassEx(&wcex);
}

//
//   函式: InitInstance(HINSTANCE, int)
//   目標: 儲存例項控制程式碼並建立主視窗
//   註釋: 在此函式中,我們在全域性變數中儲存例項控制程式碼並建立和顯示主程式視窗。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   hInst = hInstance; // 將例項控制程式碼儲存在全域性變數中

   HWND hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

//
//  函式: WndProc(HWND, UINT, WPARAM, LPARAM)
//  目標: 處理主視窗的訊息。
//  WM_COMMAND  - 處理應用程式選單
//  WM_PAINT    - 繪製主視窗
//  WM_DESTROY  - 傳送退出訊息並返回
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: 在此處新增使用 hdc 的任何繪圖程式碼...
            EndPaint(hWnd, &ps);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    case WM_MyMessage:
        MessageBox(NULL,"WingSummer Test Recived!","CnBlog",MB_ICONINFORMATION);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

  如果我們要實現建立並顯示一個窗體,首先得建立一個窗體類,並將它註冊。然後呼叫CreateWindow建立窗體,然後呼叫ShowWindow顯示出來,而WndProc就是我在註冊的時候的窗體訊息處理過程。執行完上面的程式碼後,我們開始進入訊息處理,呼叫熟悉的三個函式就可以實現訊息處理。
  但是,你們知道是為什麼必須嗎?這所謂的訊息是什麼?訊息佇列在哪裡?

訊息與窗體

  如下是維基百科的解釋:

There are two main senses of the word "message" in computing: messages between the human users of computer systems that are delivered by those computer systems, and messages passed between programs or between components of a single program, for their own purposes.

  在Windows中,訊息可以來自鍵盤、滑鼠等硬體,也可以來自於其他程式執行緒傳送來的訊息。我們既然瞭解了什麼是訊息,那麼訊息佇列是什麼。

訊息機制篇——初識訊息與訊息佇列

  如上是老的Windows的實現方式,版本Win95Win98和很多Linux的實現方式都是它。在3環每一個使用者空間都有一個訊息佇列。如果捕捉訊息,必然有一個專用程式進行捕獲封裝,因為我們程式設計的時候從來沒有寫過自己捕獲訊息的程式碼。當專用程式捕獲到訊息,就會往指定目標程式插入一條訊息,實現方式只能是跨程式通訊,但是這有不足的地方,會耗費大量的時間於通訊上。那麼微軟的最終實現是什麼,就是把訊息佇列搬到0環,使用GUI執行緒,你想要獲取訊息僅需要通過系統呼叫的方式直接獲得,而不必進行跨程式通訊,大大提升了效率。
  當執行緒剛建立的時候,都是普通執行緒,之前我們講過可以通過執行緒結構體的ServiceTable進行判斷。當執行緒第一次呼叫Win32k.sys實現的函式時時,會呼叫PsConvertToGuiThread,以擴充核心棧,換成64KB的大核心棧,原普通核心棧只有12KB大小;建立一個包含訊息佇列的結構體,並掛到KTHREAD上。ServiceTable指向的地址變為KeServiceDescriptorTableShadow;把需要的記憶體資料對映到本程式空間。
  那麼訊息佇列在在哪裡呢?我們看下面的結構體:

kd> dt _KTHREAD
ntdll!_KTHREAD
   ……
   +0x12c CallbackStack    : Ptr32 Void
   +0x130 Win32Thread      : Ptr32 Void
   +0x134 TrapFrame        : Ptr32 _KTRAP_FRAME
   +0x138 ApcStatePointer  : [2] Ptr32 _KAPC_STATE
   ……

  看到Win32Thread了嗎,這裡面儲存了訊息佇列,但訊息佇列不僅僅有一個,只有圖形介面才有有效值,指向一個結構體。如下所示:

typedef struct tagTHREADINFO {
    W32THREAD;

//***************************************** begin: USER specific fields

    PTL             ptl;                // Listhead for thread lock list

    PPROCESSINFO    ppi;                // process info struct for this thread

    PQ              pq;                 // keyboard and mouse input queue

    PKL             spklActive;         // active keyboard layout for this thread

    PCLIENTTHREADINFO pcti;             // Info that must be visible from client

    PDESKTOP        rpdesk;
    PDESKTOPINFO    pDeskInfo;          // Desktop info visible to client
    PCLIENTINFO     pClientInfo;        // Client info stored in TEB

    DWORD           TIF_flags;          // TIF_ flags go here.

    PUNICODE_STRING pstrAppName;        // Application module name.

    PSMS            psmsSent;           // Most recent SMS this thread has sent
    PSMS            psmsCurrent;        // Received SMS this thread is currently processing
    PSMS            psmsReceiveList;    // SMSs to be processed

    LONG            timeLast;           // Time and ID of last message
    ULONG_PTR       idLast;

    int             exitCode;

    HDESK           hdesk;              // Desktop handle
    int             cPaintsReady;
    UINT            cTimersReady;

    PMENUSTATE      pMenuState;

    union {
        PTDB            ptdb;           // Win16Task Schedule data for WOW thread
        PWINDOWSTATION  pwinsta;        // Window station for SYSTEM thread
    };

    PSVR_INSTANCE_INFO psiiList;        // thread DDEML instance list
    DWORD           dwExpWinVer;
    DWORD           dwCompatFlags;      // The Win 3.1 Compat flags
    DWORD           dwCompatFlags2;     // new DWORD to extend compat flags for NT5+ features

    PQ              pqAttach;           // calculation variabled used in
                                        // zzzAttachThreadInput()

    PTHREADINFO     ptiSibling;         // pointer to sibling thread info

    PMOVESIZEDATA   pmsd;

    DWORD           fsHooks;                // WHF_ Flags for which hooks are installed
    PHOOK           sphkCurrent;            // Hook this thread is currently processing

    PSBTRACK        pSBTrack;

    HANDLE          hEventQueueClient;
    PKEVENT         pEventQueueServer;
    LIST_ENTRY      PtiLink;            // Link to other threads on desktop
    int             iCursorLevel;       // keep track of each thread's level
    POINT           ptLast;             // Position of last message

    PWND            spwndDefaultIme;    // Default IME Window for this thread
    PIMC            spDefaultImc;       // Default input context for this thread
    HKL             hklPrev;            // Previous active keyboard layout
    int             cEnterCount;
    MLIST           mlPost;             // posted message list.
    USHORT          fsChangeBitsRemoved;// Bits removed during PeekMessage
    WCHAR           wchInjected;        // character from last VK_PACKET
    DWORD           fsReserveKeys;      // Keys that must be sent to the active
                                        // active console window.
    PKEVENT        *apEvent;            // Wait array for xxxPollAndWaitForSingleObject
    ACCESS_MASK     amdesk;             // Granted desktop access
    UINT            cWindows;           // Number of windows owned by this thread
    UINT            cVisWindows;        // Number of visible windows on this thread

    PHOOK           aphkStart[CWINHOOKS];   // Hooks registered for this thread
    CLIENTTHREADINFO  cti;              // Use this when no desktop is available

#ifdef GENERIC_INPUT
    HANDLE          hPrevHidData;
#endif
#if DBG
    UINT            cNestedCalls;
#endif
} THREADINFO;

  上面的程式碼是洩露的WinXP SP1的程式碼,符號裡面沒有這個結構體,故無法dt進行檢視,註釋還比較詳細,我們就能知道訊息佇列在哪裡:

PQ pq;  // keyboard  and mouse input queue

  我們建立一個程式或者執行緒,都會在核心中建立一個結構體。同樣窗體也是一樣的,當你建立一個窗體,就會在核心建立一個核心物件,在ReactOS中為稱之為_WINDOW_OBJECT物件(在最新版的已經沒了):

typedef struct _WINDOW_OBJECT
{
  /* Pointer to the window class. */
  PWNDCLASS_OBJECT Class;
  /* Extended style. */
  DWORD ExStyle;
  /* Window name. */
  UNICODE_STRING WindowName;
  /* Style. */
  DWORD Style;
  /* Context help id */
  DWORD ContextHelpId;
  /* system menu handle. */
  HMENU SystemMenu;
  /* Handle of the module that created the window. */
  HINSTANCE Instance;
  /* Entry in the thread's list of windows. */
  LIST_ENTRY ListEntry;
  /* Pointer to the extra data associated with the window. */
  PCHAR ExtraData;
  /* Size of the extra data associated with the window. */
  ULONG ExtraDataSize;
  /* Position of the window. */
  RECT WindowRect;
  /* Position of the window's client area. */
  RECT ClientRect;
  /* Handle for the window. */
  HANDLE Self;
  /* Window flags. */
  ULONG Flags;
  /* Window menu handle or window id */
  UINT IDMenu;
  /* Handle of region of the window to be updated. */
  HANDLE UpdateRegion;
  HANDLE NCUpdateRegion;
  /* Pointer to the owning thread's message queue. */
  PUSER_MESSAGE_QUEUE MessageQueue;
  struct _WINDOW_OBJECT* FirstChild;
  struct _WINDOW_OBJECT* LastChild;
  /* Lock for the list of child windows. */
  FAST_MUTEX ChildrenListLock;
  struct _WINDOW_OBJECT* NextSibling;
  struct _WINDOW_OBJECT* PrevSibling;
  /* Entry in the list of thread windows. */
  LIST_ENTRY ThreadListEntry;
  /* Pointer to the parent window. */
  struct _WINDOW_OBJECT* Parent;
  /* Pointer to the owner window. */
  struct _WINDOW_OBJECT* Owner;
  /* DC Entries (DCE) */
  PDCE Dce;
  /* Property list head.*/
  LIST_ENTRY PropListHead;
  FAST_MUTEX PropListLock;
  ULONG PropListItems;
  /* Scrollbar info */
  PSCROLLBARINFO pHScroll;
  PSCROLLBARINFO pVScroll;
  PSCROLLBARINFO wExtra;
  LONG UserData;
  BOOL Unicode;
  WNDPROC WndProcA;
  WNDPROC WndProcW;
  PETHREAD OwnerThread;
  HWND hWndLastPopup; /* handle to last active popup window (wine doesn't use pointer, for unk. reason)*/
  PINTERNALPOS InternalPos;
} WINDOW_OBJECT; /* PWINDOW_OBJECT already declared at top of file */

  注意上述結構體不同版本的結構和順序會有差異,有的版本的會有下面的成員:

/* Pointer to the thread information */
  PW32THREADINFO ti;

  這個就和執行緒有一個成員指向自己程式地址一樣,通過窗體核心物件對應相應的執行緒。這塊僅供瞭解,由於沒有真正的Windows原始碼,不好定奪。如下是從洩露的程式碼找出的窗體結構:

typedef struct tagWND {
    THRDESKHEAD   head;

    WW;         // WOW-USER common fields. Defined in wowuserp.h
                // The presence of "state" at the start of this structure is
                // assumed by the STATEOFFSET macro.

    PWND                 spwndNext;    // Handle to the next window
    PWND                 spwndPrev;    // Handle to the previous window
    PWND                 spwndParent;  // Backpointer to the parent window.
    PWND                 spwndChild;   // Handle to child
    PWND                 spwndOwner;   // Popup window owner field

    RECT                 rcWindow;     // Window outer rectangle
    RECT                 rcClient;     // Client rectangle

    WNDPROC_PWND         lpfnWndProc;  // Can be WOW address or standard address

    PCLS                 pcls;         // Pointer to window class

    KHRGN                hrgnUpdate;   // Accumulated paint region

    PPROPLIST            ppropList;    // Pointer to property list
    PSBINFO              pSBInfo;      // Words used for scrolling

    PMENU                spmenuSys;    // Handle to system menu
    PMENU                spmenu;       // Menu handle or ID

    KHRGN                hrgnClip;     // Clipping region for this window

    LARGE_UNICODE_STRING strName;
    int                  cbwndExtra;   // Extra bytes in window
    PWND                 spwndLastActive; // Last active in owner/ownee list
    KHIMC                hImc;         // Associated input context handle
    KERNEL_ULONG_PTR     dwUserData;   // Reserved for random application data
    struct _ACTIVATION_CONTEXT  * KPTR_MODIFIER pActCtx;
#ifdef LAME_BUTTON
    KERNEL_PVOID    pStackTrace;       // Creation stack trace; used by lame
                                       // button.
#endif // LAME_BUTTON
} WND;

訊息與訊息佇列

  一個GUI執行緒對應一個訊息佇列,那麼訊息從哪裡來呢?
  當我們單擊窗體的關閉按鈕,或者游標在窗體上移動,點選鍵盤,就會產生大量訊息。那些訊息就是win32k.sys的執行緒監控捕獲的,我們來定位一下它的建立執行緒地方:

void xxxCreateSystemThreads(BOOL bRemoteThread)
{
    UINT uThreadID;
    PVOID pvoid;

    /*
     * Do not allow any process other than CSRSS to call this function.
     * The only exception is Ghost thread case since now we allow it to launch
     * in the context of the shell process
     */
    if (!bRemoteThread && !ISCSRSS()) {
        RIPMSG0(RIP_WARNING, "xxxCreateSystemThreads get called from a Process other than CSRSS");
        return;
    }

    if (!CSTPop(&uThreadID, &pvoid, NULL, bRemoteThread)) {
        return;
    }

    LeaveCrit();
    switch (uThreadID) {
        case CST_DESKTOP:
            xxxDesktopThread((PTERMINAL)pvoid);
            break;
        case CST_RIT:
            RawInputThread(pvoid);
            break;
        case CST_GHOST:
            GhostThread((PDESKTOP)pvoid);
            break;
       case CST_POWER:
            VideoPortCalloutThread(pvoid);
            break;
    }
    EnterCrit();
}

  而這個函式又是該函式呼叫的:

/***************************************************************************\
* CreateSystemThreads
*
* Simply calls xxxCreateSystemThreads, which will call the appropriate
* thread routine (depending on uThreadID).
*
* History:
* 20-Aug-00 MSadek      Created.
\***************************************************************************/
WINUSERAPI
DWORD
WINAPI
CreateSystemThreads (
    LPVOID pUnused)
{
    UNREFERENCED_PARAMETER(pUnused);

    NtUserCallOneParam(TRUE, SFI_XXXCREATESYSTEMTHREADS);
    ExitThread(0);
}

  這玩意挺複雜,還加入了會話隔離機制,由於本人水平有限就定位到這裡。上面的程式碼和我們XP的逆向程式碼一致。要想要具體的細節,自己可以研究。
  訊息佇列並不是僅僅有一個,我們可以看看上面所謂的USER_MESSAGE_QUEUE結構體:

typedef struct _USER_MESSAGE_QUEUE
{
  /* Owner of the message queue */
  struct _ETHREAD *Thread;
  /* Queue of messages sent to the queue. */
  LIST_ENTRY SentMessagesListHead;
  /* Queue of messages posted to the queue. */
  LIST_ENTRY PostedMessagesListHead;
  /* Queue of sent-message notifies for the queue. */
  LIST_ENTRY NotifyMessagesListHead;
  /* Queue for hardware messages for the queue. */
  LIST_ENTRY HardwareMessagesListHead;
  /* Lock for the hardware message list. */
  FAST_MUTEX HardwareLock;
  /* Lock for the queue. */
  FAST_MUTEX Lock;
  /* True if a WM_QUIT message is pending. */
  BOOLEAN QuitPosted;
  /* The quit exit code. */
  ULONG QuitExitCode;
  /* Set if there are new messages in any of the queues. */
  KEVENT NewMessages;  
  /* FIXME: Unknown. */
  ULONG QueueStatus;
  /* Current window with focus (ie. receives keyboard input) for this queue. */
  HWND FocusWindow;
  /* True if a window needs painting. */
  BOOLEAN PaintPosted;
  /* Count of paints pending. */
  ULONG PaintCount;
  /* Current active window for this queue. */
  HWND ActiveWindow;
  /* Current capture window for this queue. */
  HWND CaptureWindow;
  /* Current move/size window for this queue */
  HWND MoveSize;
  /* Current menu owner window for this queue */
  HWND MenuOwner;
  /* Identifes the menu state */
  BYTE MenuState;
  /* Caret information for this queue */
  PTHRDCARETINFO CaretInfo;
  
  /* Window hooks */
  PHOOKTABLE Hooks;

  /* queue state tracking */
  WORD WakeBits;
  WORD WakeMask;
  WORD ChangedBits;
  WORD ChangedMask;
  
  /* extra message information */
  LPARAM ExtraInfo;

} USER_MESSAGE_QUEUE, *PUSER_MESSAGE_QUEUE;

  從這裡看到最起碼就有四個訊息佇列,細節我們將會在下一篇進行。

窗體控制程式碼初探

  我們來查詢窗體的時候都是用FindWindow進行查詢,我們僅通過名稱就可以呼叫,我們可以猜測窗體控制程式碼就是全域性的。我們來定位一下它的程式碼:

PWND _FindWindowEx(
    PWND   pwndParent,
    PWND   pwndChild,
    LPCWSTR ccxlpszClass,
    LPCWSTR ccxlpszName,
    DWORD  dwType)
{
    /*
     * Note that the Class and Name pointers are client-side addresses.
     */

    PBWL    pbwl;
    HWND    *phwnd;
    PWND    pwnd;
    WORD    atomClass = 0;
    LPCWSTR lpName;
    BOOL    fTryMessage = FALSE;

    if (ccxlpszClass != NULL) {
        /*
         * note that we do a version-less check here, then call FindClassAtom right away.
         */
        atomClass = FindClassAtom(ccxlpszClass);
        if (atomClass == 0) {
            return NULL;
        }
    }

    /*
     * Setup parent window
     */
    if (!pwndParent) {
        pwndParent = _GetDesktopWindow();
        /*
         * If we are starting from the root and no child window
         * was specified, then check the message window tree too
         * in case we don't find it on the desktop tree.
         */

        if (!pwndChild)
            fTryMessage = TRUE;
    }

TryAgain:
    /*
     * Setup first child
     */
    if (!pwndChild) {
        pwndChild = pwndParent->spwndChild;
    } else {
        if (pwndChild->spwndParent != pwndParent) {
            RIPMSG0(RIP_WARNING,
                 "FindWindowEx: Child window doesn't have proper parent");
            return NULL;
        }

        pwndChild = pwndChild->spwndNext;
    }

    /*
     * Generate a list of top level windows.
     */
    if ((pbwl = BuildHwndList(pwndChild, BWL_ENUMLIST, NULL)) == NULL) {
        return NULL;
    }

    /*
     * Set pwnd to NULL in case the window list is empty.
     */
    pwnd = NULL;

    try {
        for (phwnd = pbwl->rghwnd; *phwnd != (HWND)1; phwnd++) {

            /*
             * Validate this hwnd since we left the critsec earlier (below
             * in the loop we send a message!
             */
            if ((pwnd = RevalidateHwnd(*phwnd)) == NULL)
                continue;

            /*
             * make sure this window is of the right type
             */
            if (dwType != FW_BOTH) {
                if (((dwType == FW_16BIT) && !(GETPTI(pwnd)->TIF_flags & TIF_16BIT)) ||
                    ((dwType == FW_32BIT) && (GETPTI(pwnd)->TIF_flags & TIF_16BIT)))
                    continue;
            }

            /*
             * If the class is specified and doesn't match, skip this window
             * note that we do a version-less check here, use pcls->atomNVClassName
             */
            if (!atomClass || (atomClass == pwnd->pcls->atomNVClassName)) {
                if (!ccxlpszName)
                    break;

                if (pwnd->strName.Length) {
                    lpName = pwnd->strName.Buffer;
                } else {
                    lpName = szNull;
                }

                /*
                 * Is the text the same? If so, return with this window!
                 */
                if (_wcsicmp(ccxlpszName, lpName) == 0)
                    break;
            }

            /*
             * The window did not match.
             */
            pwnd = NULL;
        }
    } except (W32ExceptionHandler(FALSE, RIP_WARNING)) {
        pwnd = NULL;
    }

    FreeHwndList(pbwl);

    if (!pwnd && fTryMessage) {
        fTryMessage = FALSE;
        pwndParent = _GetMessageWindow();
        pwndChild = NULL;
        goto TryAgain;
    }

    return ((*phwnd == (HWND)1) ? NULL : pwnd);
}

  上面的程式碼和核心檔案是一樣的,整體閱讀程式碼後,發現是從BuildHwndList產生的所謂的列表查詢的,我們看一下它的原始碼:

PBWL BuildHwndList(
    PWND pwnd,
    UINT flags,
    PTHREADINFO pti)
{
    PBWL pbwl;

    CheckCritIn();

    if ((pbwl = pbwlCache) != NULL) {

        /*
         * We're using the cache now; zero it out.
         */
#if DBG
        pbwlCachePrev = pbwlCache;
#endif
        pbwlCache = NULL;

#if DBG
        {
            PBWL pbwlT;
            /*
             * pbwlCache shouldn't be in the global linked list.
             */
            for (pbwlT = gpbwlList; pbwlT != NULL; pbwlT = pbwlT->pbwlNext) {
                UserAssert(pbwlT != pbwl);
            }
        }
#endif
    } else {

        /*
         * sizeof(BWL) includes the first element of array.
         */
        pbwl = (PBWL)UserAllocPool(sizeof(BWL) + sizeof(PWND) * CHWND_BWLCREATE,
                TAG_WINDOWLIST);
        if (pbwl == NULL)
            return NULL;

        pbwl->phwndMax = &pbwl->rghwnd[CHWND_BWLCREATE - 1];
    }
    pbwl->phwndNext = pbwl->rghwnd;

    /*
     * We'll use ptiOwner as temporary storage for the thread we're
     * scanning for. It will get reset to the proper thing at the bottom
     * of this routine.
     */
    pbwl->ptiOwner = pti;

#ifdef OWNERLIST
    if (flags & BWL_ENUMOWNERLIST) {
        pbwl = InternalBuildHwndOwnerList(pbwl, pwnd, NULL);
    } else {
        pbwl = InternalBuildHwndList(pbwl, pwnd, flags);
    }
#else
    pbwl = InternalBuildHwndList(pbwl, pwnd, flags);
#endif

    /*
     * If phwndNext == phwndMax, it indicates that the pbwl has failed to expand.
     * The list is no longer valid, so we should just bail.
     */
    if (pbwl->phwndNext >= pbwl->phwndMax) {
        UserAssert(pbwl->phwndNext == pbwl->phwndMax);
        /*
         * Even if we had picked pbwl from the global single cache (pbwlCache),
         * it should have already been unlinked from the global link list when it was put in the cache.
         * So we should just free it without manupilating the link pointers.
         * If we have allocated the pwbl for ourselves, we can simply free it.
         * In both cases, we should just call UserFreePool().
         * As the side effect, it may make some room by providing a free pool block.
         */
        UserFreePool(pbwl);
        return NULL;
    }

    /*
     * Stick in the terminator.
     */
    *pbwl->phwndNext = (HWND)1;

#ifdef FE_IME
    if (flags & BWL_ENUMIMELAST) {
        UserAssert(IS_IME_ENABLED());
        /*
         * For IME windows.
         * Rebuild window list for EnumWindows API. Because ACCESS 2.0 assumes
         * the first window that is called CallBack Functions in the task is
         * Q-Card Wnd. We should change the order of IME windows
         */
        pbwl = InternalRebuildHwndListForIMEClass(pbwl,
                    (flags & BWL_REMOVEIMECHILD) == BWL_REMOVEIMECHILD);
    }
#endif

    /*
     * Finally link this guy into the list.
     */
    pbwl->ptiOwner = PtiCurrent();
    pbwl->pbwlNext = gpbwlList;
    gpbwlList = pbwl;


    /*
     * We should have given out the cache if it was available
     */
    UserAssert(pbwlCache == NULL);

    return pbwl;
}

  如果是專案是除錯模式,就會有如下程式碼,這塊程式碼值得我們注意:

PBWL pbwlT;
/*
 * pbwlCache shouldn't be in the global linked list.
 */
for (pbwlT = gpbwlList; pbwlT != NULL; pbwlT = pbwlT->pbwlNext) {
    UserAssert(pbwlT != pbwl);
}

  綜上所述:窗體控制程式碼是全域性的。

窗體繪製初探

  由於我們的教程主要是用來介紹作業系統是怎樣執行的,所以我們只看看流程,不挖掘其細節。在3環,我們都是通過CreateWindow這個API來進行的,但它是一個巨集,被翻譯成CreateWindowEx。我們一步步跟下去如下結果所示:

graph TD CreateWindowExW --> CreateWindowEx --> VerNtUserCreateWindowEx --> NtUserCreateWindowEx -.系統呼叫.-> w32k!NtUserCreateWindowEx

  也就是說,所有的窗體都是0環繪製的。窗體的繪製都是win32k.sys來負責。

下一篇

  訊息機制篇——訊息處理

相關文章