消息機制篇——初識消息與消息隊列
- 2022 年 2 月 15 日
- 筆記
- Win系統內核, 羽夏看Win系統內核
寫在前面
此系列是本人一個字一個字碼出來的,包括示例和實驗截圖。由於系統內核的複雜性,故可能有錯誤或者不全面的地方,如有錯誤,歡迎批評指正,本教程將會長期更新。 如有好的建議,歡迎回饋。碼字不易,如果本篇文章有幫助你的,如有閑錢,可以打賞支援我的創作。如想轉載,請把我的轉載資訊附在文章後面,並聲明我的個人資訊和本人部落格地址即可,但必須事先通知我。
你如果是從中間插過來看的,請仔細閱讀 羽夏看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
的實現方式,版本Win95
、Win98
和很多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
來負責。
下一篇
消息機制篇——消息處理