• 消息机制篇——初识消息与消息队列


    写在前面

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

    你如果是从中间插过来看的,请仔细阅读 羽夏看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。我们一步步跟下去如下结果所示:

    系统调用
    CreateWindowExW
    CreateWindowEx
    VerNtUserCreateWindowEx
    NtUserCreateWindowEx
    w32k!NtUserCreateWindowEx

      也就是说,所有的窗体都是0环绘制的。窗体的绘制都是win32k.sys来负责。

    下一篇

      消息机制篇——消息处理

  • 相关阅读:
    SQLite 中的日期和时间
    一文搞懂前端兼容问题
    模拟面试01
    PDF怎么转换成Word文档呢?不妨试试这两种方法!
    Redis简单介绍以及在Springboot中使用redis
    JS 算法专辑【4】链表
    android NDK 开发包,网盘下载,不限速
    uni-app开发
    QPainter绘制时【数据类型】和【抗锯齿】对效果的影响【2】
    yml文件中&、<<、 * 是什么意思
  • 原文地址:https://www.cnblogs.com/wingsummer/p/15897079.html