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

博客 动态
0 278
优雅殿下
优雅殿下 2022-02-15 16:55:22
悬赏:0 积分 收藏

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

消息机制篇之初识消息与消息队列,简单介绍消息和消息队列的相关知识。

写在前面

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

你如果是从中间插过来看的,请仔细阅读 羽夏看Win系统内核——简述 ,方便学习本教程。

??看此教程之前,问几个问题,基础知识储备好了吗?保护模式篇学会了吗?练习做完了吗?没有的话就不要继续了。

?


?? 华丽的分割线 ??


?

前言

??由于win32k.sys里面大量的所需符号和结构体都没有,有很多东西我目前只能浅尝辄止,UI是一个很复杂的东西,我的能力有限也只能具体讲解一下它的功能、大体流程和某些小细节。对于代码参考,是基于ReactOS 0.2.0版本和泄露的WinXP SP1代码。ReactOS是基于逆向Windows得到,所以基本类似,建议此版本,高版本的有自己的实现了,就不太一样了。

引子

??学过Win32图形界面编程的都知道我们要显示一个窗体是十分麻烦的,如下是创建项目自己生成的,我进行略加修改,最终代码如下:

#include "stdafx.h"#include <windows.h>#define WM_MyMessage WM_USER+1HINSTANCE 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 _KTHREADntdll!_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.\***************************************************************************/WINUSERAPIDWORDWINAPICreateSystemThreads (    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来负责。

下一篇

??消息机制篇——消息处理

知识共享许可协议
知识共享许可协议
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可
本文来自博客园,作者:寂静的羽夏 ,一个热爱计算机技术的菜鸟
转载请注明原文链接:https://www.cnblogs.com/wingsummer/p/15897079.html
posted @ 2022-02-15 16:23 寂静的羽夏 阅读(0) 评论(0) 编辑 收藏 举报
回帖
    优雅殿下

    优雅殿下 (王者 段位)

    2018 积分 (2)粉丝 (47)源码

    小小码农,大大世界

     

    温馨提示

    亦奇源码

    最新会员