消息与钩子

众所周知,Windows系统是消息驱动的,现在我们就来看Windows的消息机制.

 

早期的Windows的窗口图形机制是在用户空间实现的,后来为了提高图形处理效率,将这部分移入内核空间,Win32k.sys模块中实现。这个模块作为一个扩展的内核模块,提高了一个扩展额系统服务表,专用于窗口图形操作,相应的,这个模块中添加了一个扩展系统调用服务表Shadow SSDT,以及一个扩展的系统调用服务表描述符表:KeServiceDescriptorTableShadow.(系统中
不仅有两张SSDT,还有两张系统服务表描述符表)。当一个线程首次调用这个模块中的系统服务函数时,这个线程就自然变成了GUI线程。GUI线程结构的ServiceTable指向的就是这个shadow描述符表。

指向这个表的系统服务号的bit12位(也即第13位)为1,如0x1XXX表示使用的是shadow服务表。

每个线程创建时都是普通线程,但是只要那个线程在运行的过程中发起了一次对win32k.sys模块中的系统调用,就会转变成GUI线程,下面的函数就是这个用途。

 

NTSTATUS  PsConvertToGuiThread(VOID)

{

   
ULONG_PTR NewStack;

   
PVOID OldStack;

   
PETHREAD Thread
= PsGetCurrentThread();

   
PEPROCESS Process
= PsGetCurrentProcess();

   
NTSTATUS Status;

   
if (KeGetPreviousMode()
== KernelMode) return
STATUS_INVALID_PARAMETER;

   
ASSERT(PspW32ProcessCallout
!= NULL);//确保win32k.sys模块已加载到内存

   
if (Thread->Tcb.ServiceTable !=
KeServiceDescriptorTable)

       
return STATUS_ALREADY_WIN32;//表示先前已经转换为GUI线程了

   
if (!Thread->Tcb.LargeStack)//if
尚未换成大内核栈

   
{

       
NewStack = (ULONG_PTR)MmCreateKernelStack(TRUE,
0);//分配一个64KB的大内核栈

       
//更为大内核栈

       
OldStack = KeSwitchKernelStack(NewStack, (NewStack
– KERNEL_STACK_SIZE));

       
MmDeleteKernelStack(OldStack, FALSE);//销毁原来的普通内核栈

   
}

   
if (!Process->Win32Process)//if 尚未分配W32PROCESS结构(也即if是该进程中的第一个GUI线程)

       
Status = PspW32ProcessCallout(Process, TRUE);//分配Win32Process结构(表示GUI进程)

Thread->Tcb.ServiceTable = KeServiceDescriptorTableShadow;//关键。更改描述符表

//为当前线程分配一个W32THREAD结构

   
Status = PspW32ThreadCallout(Thread, PsW32ThreadCalloutInitialize);

   
if (!NT_SUCCESS(Status)) Thread->Tcb.ServiceTable = KeServiceDescriptorTable;//改为原来的

   
return Status;

}

如上,每个线程在转换为GUI线程时,必须换用64KB的大内核栈,因为普通的内核栈只有12KB大小,不能支持开销大的图形任务。然后分配一个W32PROCESS结构,将进程转换为GUI进程,然后分配W32THREAD结构,更改系统服务表描述符表。上面的PspW32ProcessCalloutPspW32ThreadCallout函数都是回调函数,分别指向win32k.sys模块中的Win32kProcessCallbackWin32kThreadCallback函数。

 

NTSTATUS

Win32kProcessCallback(struct _EPROCESS *Process,

                      BOOLEAN
Create)//指是要创建还是要销毁

{

   
PPROCESSINFO Win32Process;

   
Win32Process = PsGetProcessWin32Process(Process);//获得当前进程的W32PROCESS结构指针

   
if (!Win32Process)//if
尚未分配该结构,就分配一个

   
{

       
Win32Process = ExAllocatePoolWithTag(NonPagedPool,sizeof(PROCESSINFO),’p23W’);

       
RtlZeroMemory(Win32Process,
sizeof(PROCESSINFO));

       
PsSetProcessWin32Process(Process, Win32Process);

   
}

    if (Create)

   
{

     
SIZE_T ViewSize
= 0;

     
LARGE_INTEGER Offset;

     
PVOID UserBase
= NULL;

     
NTSTATUS Status;

     
extern PSECTION_OBJECT
GlobalUserHeapSection;

     
Offset.QuadPart
= 0;

     
//将全局用户堆映射到本进程的地址空间

     
Status = MmMapViewOfSection(GlobalUserHeapSection,PsGetCurrentProcess(),&UserBase,

                                  0,0,&Offset,&ViewSize,ViewUnmap,SEC_NO_CHANGE,

                                  PAGE_EXECUTE_READ);

     
Win32Process->HeapMappings.Next =
NULL;

     
Win32Process->HeapMappings.KernelMapping
= (PVOID)GlobalUserHeap;

     
Win32Process->HeapMappings.UserMapping
= UserBase;

     
Win32Process->HeapMappings.Count
= 1;

 

     
InitializeListHead(&Win32Process->ClassList);

    
 InitializeListHead(&Win32Process->MenuListHead);

     
InitializeListHead(&Win32Process->GDIBrushAttrFreeList);

     
InitializeListHead(&Win32Process->GDIDcAttrFreeList);

     
InitializeListHead(&Win32Process->PrivateFontListHead);

     
ExInitializeFastMutex(&Win32Process->PrivateFontListLock);

     
InitializeListHead(&Win32Process->DriverObjListHead);

     
ExInitializeFastMutex(&Win32Process->DriverObjListLock);

 

     
Win32Process->KeyboardLayout = W32kGetDefaultKeyLayout();

 

     
if(Process->Peb != NULL)

     
{

       
 //映射全局的GDI对象句柄表到本进程的地址空间中

       
 Process->Peb->GdiSharedHandleTable
= GDI_MapHandleTable(GdiTableSection,
Process);

       
 Process->Peb->GdiDCAttributeList
= GDI_BATCH_LIMIT;

     
}

 

     
  Win32Process->peProcess = Process;

     
  Win32Process->W32PF_flags = 0;

   
}

    else

    {

    
   IntCleanupMenus(Process, Win32Process);

    
   IntCleanupCurIcons(Process, Win32Process);

    
   CleanupMonitorImpl();

    
   DestroyProcessClasses(Win32Process);

     
  GDI_CleanupForProcess(Process);

    
   co_IntGraphicsCheck(FALSE);

    
   if(LogonProcess == Win32Process)

      
     LogonProcess
= NULL;

    }

    return  STATUS_SUCCESS;

}

每个含有GUI线程的进程都是GUI进程,每个GUI进程的EPROCESS结构含有W32PROCESS结构

typedef struct _W32PROCESS

{

  PEPROCESS     peProcess;

  DWORD         RefCount;

  ULONG         W32PF_flags;

  PKEVENT       InputIdleEvent;

  DWORD         StartCursorHideTime;

  struct _W32PROCESS *
NextStart;

  PVOID         pDCAttrList;

  PVOID         pBrushAttrList;

  DWORD         W32Pid;

  LONG          GDIHandleCount;

  LONG          UserHandleCount;

  PEX_PUSH_LOCK GDIPushLock;  /* Locking Process
during access to structure. */

  RTL_AVL_TABLE GDIEngUserMemAllocTable;  /* Process AVL
Table. */

  LIST_ENTRY    GDIDcAttrFreeList;

  LIST_ENTRY    GDIBrushAttrFreeList;

} W32PROCESS,
*PW32PROCESS;

 

 

 

 

 

NTSTATUS

Win32kThreadCallback(struct _ETHREAD *Thread,

                     PSW32THREADCALLOUTTYPE
Type) //指是初始化还是清理

{

   
struct _EPROCESS
*Process;

   
PTHREADINFO Win32Thread;

   
DECLARE_RETURN(NTSTATUS);

   
Process = Thread->ThreadsProcess;

   
Win32Thread = PsGetThreadWin32Thread(Thread);

   
if (!Win32Thread)//if
尚未分配Win32Thread结构,就分配

   
{

  
     Win32Thread
= ExAllocatePoolWithTag(NonPagedPool,sizeof(THREADINFO),’t23W’);

       
RtlZeroMemory(Win32Thread,
sizeof(THREADINFO));

       
PsSetThreadWin32Thread(Thread, Win32Thread);

   
}

    if (Type == PsW32ThreadCalloutInitialize)//if 初始化

   
{

     
HWINSTA hWinSta
= NULL;

     
PTEB pTeb;

     
HDESK hDesk
= NULL;

     
NTSTATUS Status;

     
PUNICODE_STRING DesktopPath;

      PRTL_USER_PROCESS_PARAMETERS ProcessParams = (Process->Peb ? Process->Peb->ProcessParameters
: NULL);

     
InitializeListHead(&Win32Thread->WindowListHead);

     
InitializeListHead(&Win32Thread->W32CallbackListHead);

     
InitializeListHead(&Win32Thread->PtiLink);

 

      DesktopPath = (ProcessParams
? ((ProcessParams->DesktopInfo.Length > 0) ? &ProcessParams->DesktopInfo : NULL)
: NULL);

     
Status = IntParseDesktopPath(Process,DesktopPath,&hWinSta,&hDesk);

     
if(NT_SUCCESS(Status))

     
{

         

     
    Win32Thread->MessageQueue = MsqCreateMessageQueue(Thread);//关键。创建消息队列

    
     Win32Thread->KeyboardLayout = W32kGetDefaultKeyLayout();

     
    Win32Thread->pEThread = Thread;

   
  }

}

Else …

    Return STATUS_SUCCESS;

}

 

 

typedef struct _W32THREAD

{

   
PETHREAD pEThread;

   
ULONG RefCount;

   
PTL ptlW32;

   
PVOID pgdiDcattr;

   
PVOID pgdiBrushAttr;

   
PVOID pUMPDObjs;

   
PVOID pUMPDHeap;

   
DWORD dwEngAcquireCount;

   
PVOID pSemTable;

   
PVOID pUMPDObj;

} W32THREAD,
*PW32THREAD;

typedef struct _THREADINFO

{

   
W32THREAD; //开头是一个W32THREAD结构

   
PTL                 ptl;

   
PPROCESSINFO        ppi;

   
struct _USER_MESSAGE_QUEUE*
MessageQueue;

   
struct _KBL*        KeyboardLayout;

   
PCLIENTTHREADINFO   pcti;

   
struct _DESKTOP*    rpdesk;

   
PDESKTOPINFO        pDeskInfo;

   
PCLIENTINFO         pClientInfo;

   
FLONG               TIF_flags;

   
PUNICODE_STRING     pstrAppName;

   
LONG                timeLast;

   
ULONG_PTR           idLast;

   
INT                 exitCode;

   
HDESK               hdesk;

   
UINT  
             cPaintsReady; /* Count of
paints pending. */

   
UINT                cTimersReady;
/* Count of timers pending. */

   
DWORD               dwExpWinVer;

   
DWORD               dwCompatFlags;

   
DWORD               dwCompatFlags2;

   
struct _USER_MESSAGE_QUEUE*
pqAttach;

   
PTHREADINFO         ptiSibling;

   
ULONG               fsHooks;

   
PHOOK               sphkCurrent;

   
LPARAM              lParamHkCurrent;

   
WPARAM              wParamHkCurrent;

   
struct tagSBTRACK*  pSBTrack;

   
HANDLE              hEventQueueClient;

   
PKEVENT             pEventQueueServer;

   
LIST_ENTRY          PtiLink;

 

   
CLIENTTHREADINFO    cti;  // Used only when
no Desktop or pcti NULL.

  /* ReactOS */

  LIST_ENTRY WindowListHead;

  LIST_ENTRY W32CallbackListHead;

  SINGLE_LIST_ENTRY 
ReferencesList;

} THREADINFO;

 

typedef struct _WINDOW_OBJECT  //每个窗口对象的内部结构

{

  THRDESKHEAD head;

  PWND Wnd;//内部结构

  PTHREADINFO pti; //所属线程

  HMENU SystemMenu;//左上角的系统菜单

  HWND hSelf;//窗口句柄是内核全局的

  ULONG state;

  HANDLE hrgnUpdate;//当前无效区域(指更新区域)的句柄

  HANDLE hrgnClip;//剪裁区域的句柄

  struct _WINDOW_OBJECT*
spwndChild;//第一个子窗口

  struct _WINDOW_OBJECT*
spwndNext;//下一个兄弟窗口

  struct _WINDOW_OBJECT*
spwndPrev;//上一个兄弟窗口

  struct _WINDOW_OBJECT*
spwndParent;//父窗口

  struct _WINDOW_OBJECT*
spwndOwner;//拥有者窗口与父窗口是两码事

  PSBINFOEX pSBInfo;//滚动条信息

  LIST_ENTRY ThreadListEntry;//用来挂入线程的窗口链表

} WINDOW_OBJECT;

 

typedef struct _WND

{

   
THRDESKHEAD head;

   
DWORD state;

   
DWORD state2;

   
DWORD ExStyle;//扩展样式

   
DWORD style;//标准样式

   
HINSTANCE hModule;//创建本窗口的模块

   
DWORD fnid;

   
struct _WND
*spwndNext;

   
struct _WND
*spwndPrev;

   
struct _WND
*spwndParent;

   
struct _WND
*spwndChild;

   
struct _WND
*spwndOwner;

   
RECT rcWindow;//整个区域

   
RECT rcClient;//客户区域

   
WNDPROC lpfnWndProc;//关键。窗口过程

   
PCLS pcls;//窗口类

   
HRGN hrgnUpdate;

   
LIST_ENTRY PropListHead;//属性链表

   
ULONG PropListItems;

   
PSBINFO pSBInfo;//滚动条信息

   
HMENU SystemMenu;

   
UINT IDMenu;

   
HRGN hrgnClip;

   
HRGN hrgnNewFrame;

   
LARGE_UNICODE_STRING strName;//窗口标题

   
ULONG cbwndExtra;//附加数据区的大小

   
HWND hWndLastActive;

   
struct _WND
*spwndLastActive;

   
LONG dwUserData;//用户自定义数据

   
struct _WND
*spwndClipboardListener;

   
DWORD ExStyle2;

   
struct

   
{

       
RECT NormalRect;

       
POINT IconPos;

  
     POINT
MaxPos;

   
} InternalPos;

   
UINT Unicode
: 1; // !(WNDS_ANSICREATOR|WNDS_ANSIWINDOWPROC) ?

   
UINT InternalPosInitialized
: 1;

   
UINT HideFocus
: 1; // WS_EX_UISTATEFOCUSRECTHIDDEN ?

   
UINT HideAccel
: 1; // WS_EX_UISTATEKBACCELHIDDEN ?

} WND,
*PWND;

 

下面重点讲述消息循环机制中的几个重点函数:

GetMessage、DispatchMessage SendMessagePostMessagePeekMessage

 

下面的函数从本线程的消息队列中取出一条符合指定过滤条件的消息

BOOL

GetMessageW(LPMSG lpMsg,//返回从队列中摘下来的消息

         HWND hWnd,//过滤条件一:发给这个窗口的消息

         UINT wMsgFilterMin, //过滤条件二:最小值

         UINT wMsgFilterMax) //过滤条件三:最大值

{

  BOOL Res;

  MSGCONVERSION Conversion;//用于内核消息、用户模式消息互转

  NTUSERGETMESSAGEINFO Info;//一个专用于NtUserGetMessage函数的中间结构

  PUSER32_THREAD_DATA ThreadData
= User32GetThreadData();//每个线程的User32 tls数据

 

  MsgConversionCleanup(lpMsg,
FALSE, FALSE,
NULL);

//实质函数。从本线程的消息队列中寻找符合对应过滤条件的一条消息,摘下来。

 Res = NtUserGetMessage(&Info,
hWnd, wMsgFilterMin,
wMsgFilterMax);

  if (-1 == (int) Res)  return Res;

  Conversion.LParamSize
= Info.LParamSize;

  Conversion.KMMsg = Info.Msg;

 
//将内核模式的消息格式转换为用户模式的消息格式

  if (! MsgiKMToUMMessage(&Conversion.KMMsg,
&Conversion.UnicodeMsg))

     
return (BOOL)
-1;

 
*lpMsg = Conversion.UnicodeMsg;//返回取得的转换后的消息

  Conversion.Ansi = FALSE;

  Conversion.FinalMsg
= lpMsg;

  MsgConversionAdd(&Conversion);//加入全局的转换数组

  if (Res && lpMsg->message
!= WM_PAINT && lpMsg->message != WM_QUIT)

     
ThreadData->LastMessage
= Info.Msg;//记录本线程上次取下来的消息

  return Res;

}

 

typedef struct tagNTUSERGETMESSAGEINFO //中间结构

{

  MSG Msg;

  ULONG LParamSize;//L参数附件包的长度(注意有的消息类型是带有L附件包的)

} NTUSERGETMESSAGEINFO,
*PNTUSERGETMESSAGEINFO;

typedef struct tagMSG

{

     HWND hwnd;

     UINT message;

     WPARAM wParam;

     LPARAM lParam;//lparam有可能是个指针

     DWORD time;//消息进队时间

     POINT pt;//消息进队时,桌面光标的位置

} MSG,*LPMSG,*PMSG;

 

我们看实质的内核函数:NtUserGetMessage

BOOL

NtUserGetMessage( PNTUSERGETMESSAGEINFO
UnsafeInfo,//用户空间中的不安全地址

                  HWND
hWnd,

                  UINT
MsgFilterMin,

                  UINT
MsgFilterMax )

{

  
BOOL GotMessage;

  
NTUSERGETMESSAGEINFO Info;//内核中的安全缓冲

  
NTSTATUS Status;

  
PWINDOW_OBJECT Window
= NULL;

  
PMSGMEMORY MsgMemoryEntry;

  
PVOID UserMem;

  
UINT Size;

  
USER_MESSAGE Msg;//消息队列中的原生消息结构

  
DECLARE_RETURN(BOOL);

  
UserEnterExclusive();

  
//检测窗口句柄是否无效

  
if (hWnd
&& !(Window = UserGetWindowObject(hWnd)))

     
RETURN(-1);

   //过滤条件自相矛盾,就不过滤

  
if (MsgFilterMax
< MsgFilterMin)

  
{

     
MsgFilterMin = 0;

     
MsgFilterMax = 0;

  
}

  
//循环等待,直到取出一条符合指定条件的消息出来

  
do

  
{

     
GotMessage = co_IntPeekMessage(&Msg, Window, MsgFilterMin, MsgFilterMax,
PM_REMOVE);

     
if (GotMessage)//如果取出了

     
{

   
     Info.Msg = Msg.Msg;

//检测这种消息是否带有L参数附件包,若有,返回它的L附件包大小计算法

        
MsgMemoryEntry = FindMsgMemory(Info.Msg.message);

        
if (NULL
== MsgMemoryEntry)//若不带L附件包

            Info.LParamSize = 0;

        
else

        
{

            //根据这种消息的L附件包长度计算法 计算出该消息的L附件包大小

            Size
= MsgMemorySize(MsgMemoryEntry,
Info.Msg.wParam,Info.Msg.lParam);

            Info.LParamSize = Size;

            UserMem
= NULL;//用户空间的L附件包地址

            //在当前进程的用户空间分配一块L附件包大小的内存

            Status
= ZwAllocateVirtualMemory(NtCurrentProcess(), &UserMem,
0,

                                            
&Info.LParamSize,
MEM_COMMIT, PAGE_READWRITE);

            //关键。将内核中的L附件包转移到用户空间

            Status
= MmCopyToCaller(UserMem,
(PVOID) Info.Msg.lParam, Size);

            Info.Msg.lParam = (LPARAM) UserMem;//重定向指向用户空间中的地址

        
}

        
if (Msg.FreeLParam && NULL != Msg.Msg.lParam)

             ExFreePool((void *) Msg.Msg.lParam);//释放内核中的L附件包

        
//将取下来的消息上传给用户

        
Status = MmCopyToCaller(UnsafeInfo, &Info,
sizeof(NTUSERGETMESSAGEINFO));

        
if (! NT_SUCCESS(Status))

        
{

            SetLastNtError(Status);

            RETURN(
(BOOL) -1);

        
}

     
}

     
//一直等待消息队列中出现对应过滤条件的消息

     
else if
(! co_IntWaitMessage(Window,
MsgFilterMin, MsgFilterMax))

     
{

        
RETURN( (BOOL)
-1);

     
}

  
}

  
while (! GotMessage);

  
RETURN( WM_QUIT
!= Info.Msg.message);

CLEANUP:

  
UserLeave();

  
END_CLEANUP;

}

 

上面这个函数首先会判断窗口局部是否有效。它根据窗口句柄从内核中的全局用户对象句柄表中检索出对应的用户对象(所谓用户对象指窗口、菜单、快捷键、光标、钩子等相对于内核对象的GUI对象),由于该句柄表是全局的,所有用户对象的句柄因此也是全局的,各个进程通用,不像内核对象的句柄是局限在各个进程的句柄表中。

下面的函数根据窗口句柄找到对应的窗口对象

WINDOW_OBJECT* FASTCALL
UserGetWindowObject(HWND
hWnd)

{

  
THREADINFO* ti;

  
WINDOW_OBJECT* Window;

  
if (PsGetCurrentProcess()
!= PsInitialSystemProcess)//若不是’system’进程

  
{

       ti = GetW32ThreadInfo();

      
if (ti
== NULL)

      
{

         
SetLastWin32Error(ERROR_ACCESS_DENIED);

         
return NULL;

      
}

  
}

  
//实质函数,gHandleTable指向全局的GUI对象句柄表

  
Window = (WINDOW_OBJECT*)UserGetObject(gHandleTable,
hWnd, otWindow);

  
if (!Window
|| 0 != (Window->state
& WINDOWSTATUS_DESTROYED))

  
{

     
SetLastWin32Error(ERROR_INVALID_WINDOW_HANDLE);

     
return NULL;

  
}

  
return Window;

}

PVOID UserGetObject(PUSER_HANDLE_TABLE ht,//句柄表

 HANDLE handle,//用户对象的句柄

 USER_OBJECT_TYPE
type )//用户对象类型

{

  
PUSER_HANDLE_ENTRY entry = handle_to_entry(ht,
handle);//根据句柄值找到对应的句柄表项

  
if (entry == NULL || entry->type != type)//若找不到或者类型不匹配

  
{

     
SetLastWin32Error(ERROR_INVALID_HANDLE);

     
return NULL;

  
}

  
return entry->ptr;//返回对应的对象

}

 

 

PUSER_HANDLE_ENTRY handle_to_entry(PUSER_HANDLE_TABLE ht,
HANDLE handle
)

{

  
unsigned short
generation;

  
int index
= (((unsigned int)handle & 0xffff) – FIRST_USER_HANDLE)
>> 1;

  
if (index
< 0 || index >= ht->nb_handles)

     
return NULL;

   if (!ht->handles[index].type)

     
return NULL;

  
generation = (unsigned
int)handle
>> 16;

  
if (generation
== ht->handles[index].generation
|| !generation || generation
== 0xffff)

     
return &ht->handles[index];

  
return NULL;

}

如上,根据handle找到对应的句柄表项,有点类似内核对象的句柄,可以把GUI对象的句柄也看作是一个简单的数组索引值。下面是具体的GUI对象句柄表项的结构

typedef struct _USER_HANDLE_ENTRY

{

   
void 
*ptr;//指向对应的GUI对象

   
union

   
{

       
PVOID pi;

       
PTHREADINFO pti;          // 指向所属线程

       
PPROCESSINFO ppi;         // 指向所属进程

   
};

  
 unsigned
char  type;        

   
unsigned char  flags;

   
unsigned short
generation;//不明

} USER_HANDLE_ENTRY,
* PUSER_HANDLE_ENTRY;

 

 

下面是GUI对象的句柄表

typedef struct _USER_HANDLE_TABLE 
//句柄表描述符

{

  
PUSER_HANDLE_ENTRY handles;//句表的地址

  
PUSER_HANDLE_ENTRY freelist;//空闲表项链表

  
int nb_handles;//表的容量

  
int allocated_handles;//表中实际已分配的表项个数

} USER_HANDLE_TABLE,
* PUSER_HANDLE_TABLE;

 

前面的函数会将消息从队列取下来后,将L附件包转移到用户空间。绝大多数消息都不带L附件包,有的消息则带有L附加包,内核中有一个全局表给出了所有含有L附件包的消息类型以及他们的L附件包大小的计算方法。如下:

static MSGMEMORY
MsgMemory[] =     //全局表

  
{

     
{ WM_CREATE, MMS_SIZE_SPECIAL,
MMS_FLAG_READWRITE },

     
{ WM_DDE_ACK, sizeof(KMDDELPARAM), MMS_FLAG_READ
},

     
{ WM_DDE_EXECUTE, MMS_SIZE_WPARAM, MMS_FLAG_READ
},

     
{ WM_GETMINMAXINFO, sizeof(MINMAXINFO), MMS_FLAG_READWRITE },

     
{ WM_GETTEXT, MMS_SIZE_WPARAMWCHAR,
MMS_FLAG_WRITE },

     
{ WM_NCCALCSIZE, MMS_SIZE_SPECIAL, MMS_FLAG_READWRITE
},

     
{ WM_NCCREATE, MMS_SIZE_SPECIAL,
MMS_FLAG_READWRITE },

     
{ WM_SETTEXT, MMS_SIZE_LPARAMSZ,
MMS_FLAG_READ },

     
{ WM_STYLECHANGED, sizeof(STYLESTRUCT),
MMS_FLAG_READ },

     
{ WM_STYLECHANGING, sizeof(STYLESTRUCT),
MMS_FLAG_READWRITE },

     
{ WM_COPYDATA, MMS_SIZE_SPECIAL,
MMS_FLAG_READ },

     
{ WM_WINDOWPOSCHANGED, sizeof(WINDOWPOS), MMS_FLAG_READ },

     
{ WM_WINDOWPOSCHANGING, sizeof(WINDOWPOS), MMS_FLAG_READWRITE },

  
};

typedef struct tagMSGMEMORY

{

  
UINT Message;//消息ID,即消息类型

  
UINT Size;//该类消息的L附件包大小或计算方法

  
INT Flags;//数据流动方向

}

MSGMEMORY, *PMSGMEMORY;

 

如上面:WM_GETTEXT这种消息的L附件包大小计算方法是:蕴含在它的wparam参数中,wparam参数给定了L附件包的宽字符个数,MMS_FLAG_WRITE标志则表示需要写用户空间,也即将L附件包回写复制到用户空间。

WM_SETTEXT这种消息的L附件包大小计算方法是:蕴含在它的lparam参数,lparam参数是一个以0结尾的字符串,MMS_FLAG_READ标志则表示需要读用户空间,也即将用户空间中的文本复制到内核空间中。

MMS_SIZE_SPECIAL则表示L附件包的大小特定于具体的消息。

除开上面表中的那些消息,其他消息都不带L附件包。

 

下面的函数用于查找指定类型消息的L附件包计算法。

PMSGMEMORY FASTCALL

FindMsgMemory(UINT Msg)

{

  
PMSGMEMORY MsgMemoryEntry;

  
for (MsgMemoryEntry
= MsgMemory;

        
MsgMemoryEntry < MsgMemory + sizeof(MsgMemory) / sizeof(MSGMEMORY);

        
MsgMemoryEntry++)

  
{

     
if (Msg
== MsgMemoryEntry->Message)

        
return MsgMemoryEntry;

  
}

  
return NULL;

}

 

下面的函数根据计算的计算法计算对应消息的L附件包大小

UINT FASTCALL

MsgMemorySize(PMSGMEMORY
MsgMemoryEntry, WPARAM
wParam, LPARAM
lParam)

{

  
CREATESTRUCTW *Cs;

  
PUNICODE_STRING WindowName;

  
PUNICODE_STRING ClassName;

  
UINT Size
= 0;

  
_SEH2_TRY

  
{

     
if (MMS_SIZE_WPARAM
== MsgMemoryEntry->Size)

        
Size = (UINT)wParam;

     
else if
(MMS_SIZE_WPARAMWCHAR == MsgMemoryEntry->Size)

        
Size = (UINT)
(wParam * sizeof(WCHAR));

     
else if
(MMS_SIZE_LPARAMSZ == MsgMemoryEntry->Size)

        
Size = (UINT)
((wcslen((PWSTR)
lParam) + 1) * sizeof(WCHAR));

     
else if
(MMS_SIZE_SPECIAL == MsgMemoryEntry->Size)//具体消息具体计算

     
{

        
switch(MsgMemoryEntry->Message)

        
{

            …

            case
WM_COPYDATA:

               Size
= sizeof(COPYDATASTRUCT)
+ ((PCOPYDATASTRUCT)lParam)->cbData;

               break;

            …

        
}

     
}

   
  else

        
Size = MsgMemoryEntry->Size;

  
}

  
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)

  
{

     
Size = 0;

  
}

  
_SEH2_END;

  
return Size;

}

 

前面的NtUserGetNessage函数实际上调用co_IntPeekMessage函数从消息队列中取下消息。

消息队列的结构如下:

typedef struct _USER_MESSAGE_QUEUE  //线程的消息队列描述符

{

  LONG References;//本队列的引用计数

  struct _ETHREAD *Thread;//本队列的所属线程

  LIST_ENTRY SentMessagesListHead;//通过SendMessage方式发来的消息进入这个队列(send队列)

  LIST_ENTRY PostedMessagesListHead;
//通过PostMessage方式发来的消息进入这个队列(post队列)

  LIST_ENTRY NotifyMessagesListHead;//专用于存放SendMessage回调函数调用通知的消息队列

  LIST_ENTRY HardwareMessagesListHead;//来自硬件设备的消息(指鼠标消息)队列

  KMUTEX HardwareLock;

  PUSER_MESSAGE MouseMoveMsg;//当前的MouseMove消息(所有MouseMove消息合并成一个消息)

  BOOLEAN QuitPosted;//指队列中是否收到了一个WM_QUIT消息正处于Pengding

  ULONG QuitExitCode;//收到的WM_QUIT消息的退出码(wparam

  PKEVENT NewMessages;//一个标记队列中是否含有WakeMask掩码消息的事件

  HANDLE NewMessagesHandle;//上面事件对象的句柄

  ULONG LastMsgRead;//上次发出PeekMessage请求的时间

  HWND FocusWindow;//当前键盘焦点窗口

  ULONG PaintCount;//阻塞中的WM_PAINT消息计数

  HWND ActiveWindow; //当前活动窗口

  HWND CaptureWindow;//当前鼠标焦点窗口(一般是没有的,鼠标消息一般发给光标处所在窗口)

  HWND MoveSize; /* Current move/size window for this queue */

  HWND MenuOwner; /* Current menu owner window for this queue */

  BYTE MenuState; /* Identifes the menu state */

  PTHRDCARETINFO CaretInfo; /* Caret information for this queue */

  PHOOKTABLE Hooks;//每个线程局部的钩子表(还有一个全局的钩子表)

  WORD WakeMask;//唤醒消息掩码

  WORD QueueBits;

  WORD ChangedBits;

  LPARAM ExtraInfo;

  LIST_ENTRY DispatchingMessagesHead;//本线程已发送出去的,但尚未被目标线程处理的消息队列

  LIST_ENTRY LocalDispatchingMessagesHead;//本线程正在进行Dispatching处理的消息队列

  struct _DESKTOP *Desktop;//本队列所属的Desktop

} USER_MESSAGE_QUEUE,
*PUSER_MESSAGE_QUEUE;

 

 

消息队列按用途分类实际微观上包含好几种,只不过从宏观上我们理解每个线程一个消息队列。

微观上:分为4个接收队列,一个处理中队列和一个已发送等待处理完毕的队列。

当线程收到消息时,会先从接收队列取下来,转入处理中队列,当处理完毕后,再退出处理中队列。

当一个线程通过SendMesage发送消息时,发出的消息会被放到DispatchingMessagesHead队列,当被接收方处理后,从中移出。

消息队列中的每个消息的结构定义如下:

typedef struct _USER_MESSAGE

{

  LIST_ENTRY ListEntry;//用来挂入消息队列

  BOOLEAN FreeLParam;//表示本消息被取出后是否需要是否内核lparam

  MSG Msg;//消息主体

} USER_MESSAGE,
*PUSER_MESSAGE;

 

PostMessage与SendMessage的区别。前者是异步的,发生方直接把消息发送到接收方的消息队列中就返回,不管消息有没有被处理完成。而后者是同步的,要一直等到消息被接收方处理后才会返回。

 

 

回到NtUserGetMessage,我们看到实际调用的正题函数是下面的这个函数

BOOL FASTCALL

co_IntPeekMessage( PUSER_MESSAGE
Msg,//返回取下来的原生消息

                   PWINDOW_OBJECT
Window,//过滤条件

                   UINT
MsgFilterMin,//过滤条件

                   UINT
MsgFilterMax,//过滤条件

                   UINT
RemoveMsg )//表示是否需要从队列中移除

{

  
PTHREADINFO pti;

  
LARGE_INTEGER LargeTickCount;

  
PUSER_MESSAGE_QUEUE ThreadQueue;

  
PUSER_MESSAGE Message;

  
BOOL Present,
RemoveMessages;

  
USER_REFERENCE_ENTRY Ref;

  
USHORT HitTest;

  
pti = PsGetCurrentThreadWin32Thread();

  
ThreadQueue = pti->MessageQueue;

  
RemoveMessages = RemoveMsg & PM_REMOVE;

CheckMessages:

 

  
HitTest = HTNOWHERE;

  
Present = FALSE;//表示是否找到了对应的消息

  
KeQueryTickCount(&LargeTickCount);

  
ThreadQueue->LastMsgRead = LargeTickCount.u.LowPart;//记录上次取消息请求的时间

 

   //在取消息前,先处理完所有其他线程send过来的消息(指SendMessage方式)

  
while (co_MsqDispatchOneSentMessage(ThreadQueue));

  
//检测是否收到了WM_QUIT消息,特殊处理

  
if (ThreadQueue->QuitPosted)

  
{

     
Msg->Msg.hwnd = NULL;

     
Msg->Msg.message = WM_QUIT;

     
Msg->Msg.wParam = ThreadQueue->QuitExitCode;

     
Msg->Msg.lParam = 0;

     
Msg->FreeLParam
= FALSE;

     
if (RemoveMessages)

        
ThreadQueue->QuitPosted = FALSE;

     
goto MsgExit;

  
}

 

  
//先查找post队列中是否有对应的消息(注意键盘消息也是在post队列中)

  
Present = co_MsqFindMessage(
ThreadQueue,FALSE,RemoveMessages,

Window,MsgFilterMin,MsgFilterMax,&Message
);

  
if (Present)

  
{

     
RtlCopyMemory(Msg,
Message, sizeof(USER_MESSAGE));//取下来,返回给用户

     
if (RemoveMessages)

        
MsqDestroyMessage(Message);

     
goto MessageFound;

  
}

 

  
//再检查硬件消息队列中是否有鼠标消息

  
Present = co_MsqFindMessage(
ThreadQueue,TRUE,RemoveMessages,

                                Window,MsgFilterMin,MsgFilterMax,&Message
);

  
if (Present)

  
{

     
RtlCopyMemory(Msg,
Message, sizeof(USER_MESSAGE)); //取下来,返回给用户

     
if (RemoveMessages)

        
MsqDestroyMessage(Message);

     
goto MessageFound;

  
}

 

   //上面的查找过程会耗时一段时间,因此可能会又积累了send消息,处理掉

  
while (co_MsqDispatchOneSentMessage(ThreadQueue));

 

  
//检测是否有WM_PAINT消息

  
if ( IntGetPaintMessage(
Window,MsgFilterMin,MsgFilterMax,pti,&Msg->Msg,RemoveMessages))

  
{

     
Msg->FreeLParam
= FALSE;

     
goto MsgExit;

  
}

 

  
if (PostTimerMessages(Window))//检测是否有WM_TIMER消息

     
goto CheckMessages;

  
if(Present)//如果在队列中找到了消息

  
{

MessageFound:

     
if(RemoveMessages)

     
{

        
PWINDOW_OBJECT MsgWindow
= NULL;

        
if( Msg->Msg.hwnd &&
( MsgWindow = UserGetWindowObject(Msg->Msg.hwnd) ) &&

             Msg->Msg.message >= WM_MOUSEFIRST && Msg->Msg.message <= WM_MOUSELAST )

        
{

            USHORT
HitTest;

            UserRefObjectCo(MsgWindow, &Ref);

            if
( co_IntTranslateMouseMessage( ThreadQueue,&Msg->Msg,&HitTest,TRUE))

            {

               UserDerefObjectCo(MsgWindow);

               goto
CheckMessages;//丢弃该条消息,重新查找

            }

            if(ThreadQueue->CaptureWindow
== NULL)

            {

               co_IntSendHitTestMessages(ThreadQueue, &Msg->Msg);

               if ( ( Msg->Msg.message != WM_MOUSEMOVE && Msg->Msg.message != WM_NCMOUSEMOVE ) &&  IS_BTN_MESSAGE(Msg->Msg.message, DOWN)
&&

                     co_IntActivateWindowMouse(ThreadQueue, &Msg->Msg, MsgWindow,
&HitTest) )

               {

                  UserDerefObjectCo(MsgWindow);

                  goto
CheckMessages; //丢弃该条消息,重新查找

               }

            }

            UserDerefObjectCo(MsgWindow);

        
}

        
else

            co_IntSendHitTestMessages(ThreadQueue, &Msg->Msg);

        
goto MsgExit;

     
}

     
if ( ( Msg->Msg.hwnd &&  Msg->Msg.message >= WM_MOUSEFIRST &&

                               Msg->Msg.message <= WM_MOUSELAST ) &&

           co_IntTranslateMouseMessage(
ThreadQueue,&Msg->Msg,&HitTest,FALSE) )

     
{

        
goto CheckMessages;
//丢弃该条消息,重新查找

     
}

MsgExit:

     
if ( ISITHOOKED(WH_MOUSE) && IS_MOUSE_MESSAGE(Msg->Msg.message))

     
{

         
if(!ProcessMouseMessage(&Msg->Msg, HitTest, RemoveMsg))

                return FALSE;

       }

     
if ( ISITHOOKED(WH_KEYBOARD) && IS_KBD_MESSAGE(Msg->Msg.message))

     
{

         
if(!ProcessKeyboardMessage(&Msg->Msg, RemoveMsg))

              return
FALSE;

     
}

     
//每当取出一个消息后,都会调用WH_GETMESSAGE钩子

     
if (ISITHOOKED(WH_GETMESSAGE))

     
{

         co_HOOK_CallHooks( WH_GETMESSAGE,
HC_ACTION, RemoveMsg
& PM_REMOVE, (LPARAM)&Msg->Msg);

     
}

     
return TRUE;

  
}

  
return Present;

}

 

如上,这个函数会从post消息队列和硬件消息队列中查找,取出一个符合指定条件的消息出来。不过,在进行查找前,会优先处理掉send队列中其他线程发来的消息。

查找消息的顺序是:send队列、post队列、键盘鼠标消息、重绘消息、定时器消息【sendpost、键鼠、重、定时】

 

注意send队列中的消息结构与post队列、硬件队列中的消息结构不同,它的结构定义如下:

typedef struct _USER_SENT_MESSAGE

{

  LIST_ENTRY ListEntry;//用来挂入接收方的Dispatching处理队列

  MSG Msg;//主体

  PKEVENT CompletionEvent;//完成处理后,用于通知发送方的事件

  LRESULT* Result;//处理结果

  struct _USER_MESSAGE_QUEUE*
SenderQueue;//发送方的消息队列(方便用)

  SENDASYNCPROC CompletionCallback;//发送方提供的消息处理完成例程

  ULONG_PTR CompletionCallbackContext;

  LIST_ENTRY DispatchingListEntry;//用来挂入发送方的Dispatching未完成队列

  INT HookMessage;//钩子相关

  BOOL HasPackedLParam;//表示L附件包是否打包到用户空间了

} USER_SENT_MESSAGE,
*PUSER_SENT_MESSAGE;

 

下面的函数用于处理send队列中的一条消息

BOOLEAN FASTCALL

co_MsqDispatchOneSentMessage(PUSER_MESSAGE_QUEUE
MessageQueue)

{

  
PUSER_SENT_MESSAGE Message;

  
PLIST_ENTRY Entry;

  
LRESULT Result;

  
if (IsListEmpty(&MessageQueue->SentMessagesListHead))

     
return(FALSE);

  
Entry = RemoveHeadList(&MessageQueue->SentMessagesListHead);

  
Message = CONTAINING_RECORD(Entry, USER_SENT_MESSAGE,
ListEntry);

  
//转入接收方线程的正在Dispatching处理队列

  
InsertTailList(&MessageQueue->LocalDispatchingMessagesHead,&Message->ListEntry);

//if
这个消息本身就是‘请求执行钩子过程’,那么message表示HookIdhwnd表示code

//其他线程在调用底层键盘鼠标钩子时,会给钩子的原创建者线程发一个消息,请求执行钩子过程

  
if (Message->HookMessage == MSQ_ISHOOK)

  
{

     
//检查、调用指定类型的钩子

     
Result = co_HOOK_CallHooks(Message->Msg.message,Message->Msg.hwnd,

                                 Message->Msg.wParam,Message->Msg.lParam);

  
}

  
else if
(Message->HookMessage
== MSQ_ISEVENT)

  
{

     
Result = co_EVENT_CallEvents(
Message->Msg.message,Message->Msg.hwnd,

                                    Message->Msg.wParam,Message->Msg.lParam);

  
}

  
Else //最典型

  
{

     
//关键。将消息派遣到目标窗口的窗口过程,处理消息,返回处理结果

     
Result = co_IntSendMessage(Message->Msg.hwnd,Message->Msg.message,

                                 Message->Msg.wParam,Message->Msg.lParam);

  
}

  
RemoveEntryList(&Message->ListEntry);//处理完后从接收方的Dispatching队列移除

  
if (!(Message->HookMessage & MSQ_SENTNOWAIT))

  
{

     
if (Message->DispatchingListEntry.Flink
!= NULL)

        
RemoveEntryList(&Message->DispatchingListEntry);//从发送方的Dispatching队列移除

  
}

  
if (Message->Result != NULL)

     
*Message->Result
= Result;//返回处理结果

 

  
if (Message->HasPackedLParam == TRUE
&& Message->Msg.lParam)

     
ExFreePool((PVOID)Message->Msg.lParam);

  
if (Message->CompletionEvent != NULL)//唤醒发送方

     
KeSetEvent(Message->CompletionEvent, IO_NO_INCREMENT,
FALSE);

  
//发送方可以调用SendMessageCallback这个API设置一个完成回调函数

  
if (Message->CompletionCallback != NULL)

  
{

     
//给发送方发送一个通知消息,通知它调用这个回调函数

     
//为什么不直接执行回调函数?因为回调函数是发送方提供的,是在发送方进程地址空间中

     
co_IntCallSentMessageCallback(Message->CompletionCallback,Message->Msg.hwnd,

                                    Message->Msg.message,Message->CompletionCallbackContext,

                                    Result);

  
}

  
if (!(Message->HookMessage & MSQ_SENTNOWAIT))

  
{

     
IntDereferenceMessageQueue(Message->SenderQueue);

      IntDereferenceMessageQueue(MessageQueue);

  
}

  
ExFreePoolWithTag(Message, TAG_USRMSG);

  
return(TRUE);

}

 

看看消息是怎么派遣到目标窗口的窗口过程的

LRESULT FASTCALL

co_IntSendMessage( HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam )

{

  
ULONG_PTR Result
= 0;

  
if(co_IntSendMessageTimeout(hWnd, Msg, wParam, lParam, SMTO_NORMAL, 0, &Result))//不超时

     
return (LRESULT)Result;

  
return 0;

}

 

LRESULT FASTCALL

co_IntSendMessageTimeout( HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam,UINT uFlags,

UINT uTimeout, //发送方可以调用SendMessageTimeout规定一个超时值

ULONG_PTR *uResult )

{

  
PWINDOW_OBJECT DesktopWindow;

  
HWND *Children;

  
HWND *Child;

 

  
if (hWnd!=HWND_BROADCAST)//if不是广播消息

  
{

      return co_IntSendMessageTimeoutSingle(hWnd, Msg, wParam, lParam, uFlags, uTimeout,uResult);

  
}

  
//若是广播消息,则广播发送到桌面所有顶层窗口

  
DesktopWindow = UserGetWindowObject(IntGetDesktopWindow());

  
Children = IntWinListChildren(DesktopWindow);

  
for (Child
= Children; NULL
!= *Child; Child++)//遍历桌面窗口的所有子窗口

     
co_IntSendMessageTimeoutSingle(*Child, Msg, wParam, lParam, uFlags, uTimeout, uResult);

  
ExFreePool(Children);

  
return (LRESULT)
TRUE;

}

 

最终的实质函数如下:

LRESULT FASTCALL

co_IntSendMessageTimeoutSingle( HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam,

                                UINT uFlags,UINT uTimeout,ULONG_PTR *uResult
)

{

  
PWINDOW_OBJECT Window
= NULL;

  
if (!(Window
= UserGetWindowObject(hWnd)))//判断句柄是否有效

      
RETURN( FALSE);

  
UserRefObjectCo(Window, &Ref);

  
Win32Thread = PsGetCurrentThreadWin32Thread();

 

  
IntCallWndProc( Window, hWnd, Msg, wParam, lParam);//调用前,先调用WH_CALLWNDPROC钩子

  
//if 目标窗口就是当前线程中的窗口(一般都这样)

  
if ( NULL
!= Win32Thread && Window->pti->MessageQueue == Win32Thread->MessageQueue)

  
{

     
if (Win32Thread->TIF_flags & TIF_INCLEANUP)//线程正在终止

         
RETURN( FALSE);

     
MsgMemoryEntry = FindMsgMemory(Msg);//查找这种消息的L附件包大小计算法

     
if (NULL
== MsgMemoryEntry)

        
lParamBufferSize = -1;

     
else

        
lParamBufferSize = MsgMemorySize(MsgMemoryEntry,
wParam, lParam);//计算L附件包大小

PackParam(&lParamPacked, Msg, wParam, lParam, FALSE);//L附件包打包到用户空间

 

     
Result = (ULONG_PTR)co_IntCallWindowProc( Window->Wnd->lpfnWndProc,

                                              
!Window->Wnd->Unicode,hWnd,Msg,wParam,

                                               
lParamPacked,lParamBufferSize
);

     
if(uResult)

        
*uResult = Result;//返回处理结果

     
//调用完后,调用WH_CALLWNDPROCRET钩子

     
IntCallWndProcRet( Window, hWnd, Msg, wParam, lParam, (LRESULT *)uResult);

  
   UnpackParam(lParamPacked, Msg, wParam, lParam, FALSE);

     
RETURN( TRUE);

  
}

  
//不是当前线程中的窗口这种情况很少见,略

CLEANUP:

  
if (Window)
UserDerefObjectCo(Window);

  
END_CLEANUP;

}

 

 

 

 

 

下面的函数用于在硬件消息队列或post消息队列中查找消息

BOOLEAN APIENTRY

co_MsqFindMessage(IN PUSER_MESSAGE_QUEUE MessageQueue,

                  IN
BOOLEAN Hardware,//指是在硬件消息队列还是post消息队列中查找

                  IN
BOOLEAN Remove,//是否移除队列

                  IN
PWINDOW_OBJECT Window,//过滤条件

                  IN
UINT MsgFilterLow,//过滤条件

                  IN
UINT MsgFilterHigh,//过滤条件

                  OUT
PUSER_MESSAGE* Message)//返回找到的消息

{

  
PLIST_ENTRY CurrentEntry;

  
PUSER_MESSAGE CurrentMessage;

  
PLIST_ENTRY ListHead;

  
if (Hardware)//硬件消息(鼠标消息)需要特殊处理

  
{

     
return(co_MsqPeekHardwareMessage(
MessageQueue,Window,MsgFilterLow,MsgFilterHigh,

                                        Remove,Message));

  
}

  
CurrentEntry = MessageQueue->PostedMessagesListHead.Flink;

  
ListHead = &MessageQueue->PostedMessagesListHead;

  
while (CurrentEntry
!= ListHead)

  
{

     
CurrentMessage = CONTAINING_RECORD(CurrentEntry,
USER_MESSAGE,ListEntry);

     
if ( ( !Window
||PtrToInt(Window)
== 1 || Window->hSelf
== CurrentMessage->Msg.hwnd ) &&

            ( (MsgFilterLow
== 0 && MsgFilterHigh == 0) ||

              ( MsgFilterLow
<= CurrentMessage->Msg.message
&&

                MsgFilterHigh
>= CurrentMessage->Msg.message ) ) )

     
{

        
if (Remove)

            RemoveEntryList(&CurrentMessage->ListEntry);

        
*Message = CurrentMessage;

        
return(TRUE);

     
}

     
CurrentEntry = CurrentEntry->Flink;

  
}

  
return(FALSE);

}

上面的代码我想不用解释了。

由此可以看到:在GetMessagePeekMessage函数内部,会一直等待到消息队列中出现符合指定条件的消息,这就是为什么GUI线程绝大多数时刻都处于睡眠状态的原因,因为它在等待消息。

另外,在GetMessagePeekMessage内部,即使因为队列中一直没有符合指定条件的消息而等待,也为

在内部不断的处理别的线程通过SendMessage方式发来的消息,以尽快完成这种消息的处理。

Windows消息的产生来源(也即发送方的类型)

1、  一个线程调用SendMessagePostMessage等函数给自己或别的线程发消息,它就是一个发送方线程

2、  
内核中键盘输入守护线程(每当得到一个按键后就发送到当时键盘焦点线程的消息队列)

3、  
内核中的鼠标输入守护线程(每当得到一个鼠标消息后就发送到系统的环形缓冲消息队列)

4、  
系统win32k.sys模块自己生成、发送消息到目标线程的队列(如定时器消息)

 

当应用程序的消息循环中,通过GetMessage/PeekMessage从消息队列中取出来得到一个消息后,会调用TranslateMessage将键盘按键消息转为字符消息,然后调用DispatchMessage将消息派遣到目标窗口的窗口过程进行处理,我们看

LRESULT  DispatchMessageW(CONST MSG *lpmsg)

{

     LRESULT
Ret = 0;

     PWND
Wnd;

     if
(lpmsg->hwnd
!= NULL)

     {

         Wnd
= ValidateHwnd(lpmsg->hwnd);

         if
(!Wnd || Wnd->head.pti != GetW32ThreadInfo())

              return 0;

     }

     else

         Wnd
= NULL;

     if
((lpmsg->message
== WM_TIMER || lpmsg->message == WM_SYSTIMER)
&& lpmsg->lParam
!= 0)

     {

         if
( lpmsg->message
== WM_SYSTIMER )

              return NtUserDispatchMessage( (PMSG) lpmsg );

WNDPROC WndProc = (WNDPROC)lpmsg->lParam;//有回调函数类型的定时器就在此直接调用了

          Ret = WndProc(lpmsg->hwnd,lpmsg->message,lpmsg->wParam,GetTickCount());

     }

     else
if (Wnd != NULL)

     {

         if
( (lpmsg->message
!= WM_PAINT) && !(Wnd->state &
WNDS_SERVERSIDEWINDOWPROC) )

         {

              //调用窗口过程

              Ret = IntCallMessageProc(Wnd,lpmsg->hwnd,lpmsg->message,

lpmsg->wParam,lpmsg->lParam,FALSE);

         }

         else

              Ret = NtUserDispatchMessage( (PMSG) lpmsg );

     }

     return
Ret;

}

LRESULT

IntCallMessageProc(IN PWND Wnd, IN HWND hWnd, IN UINT Msg, IN WPARAM wParam, IN LPARAM lParam, IN BOOL Ansi)

{

     WNDPROC
WndProc;

     BOOL
IsAnsi;

     PCLS
Class;

     Class
= DesktopPtrToUser(Wnd->pcls);

     WndProc
= NULL;

     if
(Class->fnid
<= FNID_GHOST && Class->fnid
>= FNID_FIRST ) …

     else

     {

         IsAnsi
= !Wnd->Unicode;

         WndProc
= Wnd->lpfnWndProc;

     }

     if
(!Ansi)

         return
IntCallWindowProcW(IsAnsi,
WndProc, Wnd,
hWnd, Msg, wParam, lParam);

     else

         return
IntCallWindowProcA(IsAnsi,
WndProc, Wnd,
hWnd, Msg, wParam, lParam);

}

 

 

 

LRESULT FASTCALL

IntCallWindowProcW(BOOL IsAnsiProc,WNDPROC WndProc,PWND pWnd,

                  HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam)

{

     MSG
AnsiMsg;

     MSG
UnicodeMsg;

     BOOL
Hook = FALSE,
MsgOverride = FALSE,
Dialog;

     LRESULT
Result = 0, PreResult
= 0;

     DWORD
Data = 0;

     if
(pWnd)

         Dialog
= (pWnd->fnid
== FNID_DIALOG);//检查是否是个对话框

     else

         Dialog
= FALSE;

 

     Hook
= BeginIfHookedUserApiHook();

     if
(Hook)

     {

         if
(!Dialog)

              MsgOverride = IsMsgOverride( Msg, &guah.WndProcArray);

         else

              MsgOverride = IsMsgOverride( Msg, &guah.DlgProcArray);

     }

     if
(IsAnsiProc) …

     else

     {

         if
(Hook && MsgOverride)//先预处理

         {

              if (!Dialog)

                   PreResult = guah.PreWndProc(hWnd, Msg, wParam, lParam, &Result,
&Data );

              else

                   PreResult = guah.PreDefDlgProc(hWnd,
Msg, wParam, lParam, &Result,
&Data);

         }

         if
(PreResult) goto
Exit;//若预处理完成了处理,直接退出

         Result
= WndProc(hWnd,
Msg, wParam, lParam);//关键。调用目标窗口过程

         if
(Hook && MsgOverride)//后处理

         {

              if (!Dialog)

                   guah.PostWndProc(hWnd, Msg, wParam, lParam,
&Result, &Data
);

              else

                   guah.PostDefDlgProc(hWnd, Msg, wParam, lParam,
&Result, &Data
);

         }

     }

Exit:

     if
(Hook) EndUserApiHook();

     return
Result;

}

 

 

下面看看消息的发送:

BOOL

PostMessageW(  HWND hWnd,

               UINT Msg,

               WPARAM wParam,

               LPARAM lParam)

{

     LRESULT
Ret;

     if
(Msg == CB_DIR
|| Msg == LB_DIR)

     //最典型

if ( (Msg
!= WM_DROPFILES) || ( NtUserQueryWindow(
hWnd, QUERY_WINDOW_UNIQUE_PROCESS_ID)
== PtrToUint(NtCurrentTeb()->ClientId.UniqueProcess)
) )

     {

         return
PostMessageWorker(hWnd,
Msg, wParam, lParam);

     }

     …

}

 

 

BOOL FASTCALL

PostMessageWorker(  HWND Wnd,

                     UINT Msg,

                     WPARAM wParam,

                     LPARAM lParam)

{

     MSG
UMMsg, KMMsg;

     LRESULT
Result;

     UMMsg.hwnd = Wnd;

     UMMsg.message = Msg;

     UMMsg.wParam = wParam;

     UMMsg.lParam = lParam;

MsgiUMToKMMessage(&UMMsg,
&KMMsg, TRUE);

     //调用这个系统服务

     Result
= NtUserPostMessage( Wnd,KMMsg.message,KMMsg.wParam,KMMsg.lParam);

     MsgiUMToKMCleanup(&UMMsg, &KMMsg);

     return
Result;

}

 

BOOL  //系统服务

NtUserPostMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam)

{

   RETURN(
UserPostMessage(hWnd,
Msg, wParam, lParam));

}

 

BOOL FASTCALL

UserPostMessage( HWND Wnd,UINT Msg,WPARAM wParam,LPARAM lParam )

{

   PTHREADINFO
pti;

   MSG
Message;

   LARGE_INTEGER
LargeTickCount;

   if
(FindMsgMemory(Msg)
!= 0)

   {

      SetLastWin32Error(ERROR_MESSAGE_SYNC_ONLY );

      return
FALSE;

   }

 

   if
(Wnd==NULL)

      return
UserPostThreadMessage( PtrToInt(PsGetCurrentThreadId()),Msg,wParam,lParam);

 

   if
(Wnd == HWND_BROADCAST)
…  //广播给桌面所有顶层窗口,略

   else

   {

      PWINDOW_OBJECT
Window;

      Window
= UserGetWindowObject(Wnd);

      pti
= Window->Wnd->head.pti;

      if
( Window->state
& WINDOWSTATUS_DESTROYING )

         return
FALSE;

      if
(WM_QUIT == Msg)//这种消息的发送特殊处理

          MsqPostQuitMessage(Window->pti->MessageQueue, wParam);

      else

      {

         Message.hwnd = Wnd;

         Message.message = Msg;

         Message.wParam = wParam;

         Message.lParam = lParam;

         Message.pt = gpsi->ptCursor;

         KeQueryTickCount(&LargeTickCount);

         pti->timeLast = Message.time = MsqCalculateMessageTime(&LargeTickCount);

         //实质函数

         MsqPostMessage(Window->pti->MessageQueue, &Message,
FALSE, QS_POSTMESSAGE);

      }

   }

   return
TRUE;

}

 

WM_QUIT消息特殊处理,实际上这种消息不进队,会被接收方优先处理。

VOID FASTCALL

MsqPostQuitMessage(PUSER_MESSAGE_QUEUE MessageQueue, ULONG
ExitCode)

{

   MessageQueue->QuitPosted = TRUE;

   MessageQueue->QuitExitCode = ExitCode;

   MessageQueue->QueueBits |= QS_POSTMESSAGE;

   MessageQueue->ChangedBits |= QS_POSTMESSAGE;

   if
(MessageQueue->WakeMask
& QS_POSTMESSAGE) //符合唤醒条件,就唤醒目标线程

      KeSetEvent(MessageQueue->NewMessages,
IO_NO_INCREMENT, FALSE);

}

 

 

VOID FASTCALL

MsqPostMessage(PUSER_MESSAGE_QUEUE MessageQueue, MSG* Msg, BOOLEAN FreeLParam,

               DWORD MessageBits)

{

   PUSER_MESSAGE
Message;

   Message
= MsqCreateMessage(Msg,
FreeLParam);//分配一个队列中的消息结构

   //关键。将消息挂入目标线程的post队列

   InsertTailList(&MessageQueue->PostedMessagesListHead,&Message->ListEntry);

   MessageQueue->QueueBits |= MessageBits;

   MessageQueue->ChangedBits |= MessageBits;

   if
(MessageQueue->WakeMask
& MessageBits)

      KeSetEvent(MessageQueue->NewMessages,
IO_NO_INCREMENT, FALSE);

}

由上可以看出,PostMesage的处理是很简单的,它仅仅将消息挂入目标线程的post消息队列,唤醒目标线程后立即返回。因此,我们说它是异步的。

下面看SendMessage

LRESULT

SendMessageW(HWND Wnd,UINT Msg,WPARAM wParam,LPARAM lParam)

{

     MSG
UMMsg, KMMsg;

     NTUSERSENDMESSAGEINFO
Info;

     LRESULT
Result;

     PWND
Window;

     PTHREADINFO
ti = GetW32ThreadInfo();

     Window
= ValidateHwnd(Wnd);//验证窗口句柄是否有效

     if
(Window==NULL) return
FALSE;

     if
(Wnd != HWND_BROADCAST
&& (Msg < WM_DDE_FIRST
|| Msg > WM_DDE_LAST))

     {

         if
(Window != NULL
&& Window->head.pti == ti
&& !IsThreadHooked(GetWin32ClientInfo()))

              return IntCallMessageProc(Window, Wnd, Msg, wParam, lParam, FALSE);

     }

     UMMsg.hwnd = Wnd;

     UMMsg.message = Msg;

     UMMsg.wParam = wParam;

     UMMsg.lParam = lParam;

     MsgiUMToKMMessage(&UMMsg, &KMMsg, FALSE);

     Info.Ansi = FALSE;

     //调用这个系统服务

     Result
= NtUserSendMessage( KMMsg.hwnd,KMMsg.message,KMMsg.wParam,KMMsg.lParam,&Info);

     if
(! Info.HandledByKernel)//if内部没有执行这条消息(因为同一个线程),就自己处理

     {

         MsgiUMToKMCleanup(&UMMsg, &KMMsg);

         Result
= IntCallWindowProcW( Info.Ansi, Info.Proc,//NtUserSendMessage返回的proc

Window,UMMsg.hwnd,UMMsg.message,UMMsg.wParam,UMMsg.lParam);

     }

     MsgiUMToKMReply(&UMMsg, &KMMsg,
&Result);

     return
Result;

}

如上,上面这个函数真正调用NtUserSendMessage这个系统服务完成消息的发送,不过,若是要发到同一线程的话,就不用发了,直接回到下面,在本线程中直接调用窗口过程就OK了。

LRESULT

NtUserSendMessage( HWND Wnd,UINT Msg,WPARAM wParam,LPARAM lParam,

                   PNTUSERSENDMESSAGEINFO UnsafeInfo
)

{

   RETURN(co_IntDoSendMessage(Wnd,
Msg, wParam, lParam, NULL, UnsafeInfo));

}

 

LRESULT FASTCALL

co_IntDoSendMessage( HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam,

                     PDOSENDMESSAGE dsm,//一般为NULL

                     PNTUSERSENDMESSAGEINFO UnsafeInfo
)//返回处理情况

{

   LRESULT
Result = TRUE;

   PWINDOW_OBJECT
Window = NULL;

   NTUSERSENDMESSAGEINFO
Info;

   RtlZeroMemory(&Info, sizeof(NTUSERSENDMESSAGEINFO));

   pti
= PsGetCurrentThreadWin32Thread();

   if
( HWND_BROADCAST != hWnd
&& NULL != pti
&&

        Window->pti->MessageQueue
== pti->MessageQueue
&&  //同一个线程

       !ISITHOOKED(WH_CALLWNDPROC) && !ISITHOOKED(WH_CALLWNDPROCRET) &&

        ( Msg
< WM_DDE_FIRST || Msg
> WM_DDE_LAST ) )

   {

      Info.HandledByKernel = FALSE;//内部就不发送了,直接由本线程调用窗口过程自己处理

      Status
= MmCopyFromCaller(&(Info.Ansi), &(UnsafeInfo->Ansi),sizeof(BOOL));

      Info.Ansi = !Window->Wnd->Unicode;

      Info.Proc = Window->Wnd->lpfnWndProc;

   }

   else

   {

      Info.HandledByKernel = TRUE;//内部处理

      UserModeMsg.hwnd = hWnd;

      UserModeMsg.message = Msg;

      UserModeMsg.wParam = wParam;

      UserModeMsg.lParam = lParam;

      MsgMemoryEntry
= FindMsgMemory(UserModeMsg.message);

      Status
= CopyMsgToKernelMem(&KernelModeMsg, &UserModeMsg,
MsgMemoryEntry);

      if(!dsm)//典型

      {

         Result
= co_IntSendMessage( KernelModeMsg.hwnd,KernelModeMsg.message,

                                    
KernelModeMsg.wParam,KernelModeMsg.lParam
);

      }

      else

      {

         Result
= co_IntSendMessageTimeout( KernelModeMsg.hwnd,KernelModeMsg.message,

                                           
KernelModeMsg.wParam,KernelModeMsg.lParam,

                                           
dsm->uFlags,dsm->uTimeout,&dsm->Result );

      }

      Status
= CopyMsgToUserMem(&UserModeMsg, &KernelModeMsg);

   }

 

   Status
= MmCopyToCaller(UnsafeInfo,
&Info, sizeof(NTUSERSENDMESSAGEINFO));

   if
(! NT_SUCCESS(Status))

      SetLastWin32Error(ERROR_INVALID_PARAMETER);

   return
(LRESULT)Result;

}

上面的函数,发现窗口所属的目标线程就是当前现成的额话,就不用发送了,直接由本线程自己处理,否则调用co_IntSendMessage发送该消息到目标线程的send消息队列

LRESULT FASTCALL

co_IntSendMessage( HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam )

{

   ULONG_PTR
Result = 0;

   if(co_IntSendMessageTimeout(hWnd,
Msg, wParam, lParam, 0, 0, &Result))

      return
(LRESULT)Result;

   return
0;

}

LRESULT FASTCALL

co_IntSendMessageTimeout( HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam,

                          UINT uFlags,UINT uTimeout,ULONG_PTR *uResult
)

{

   PWINDOW_OBJECT
DesktopWindow;

   HWND
*Children;

   HWND
*Child;

   if
(HWND_BROADCAST != hWnd)

      return co_IntSendMessageTimeoutSingle(hWnd, Msg, wParam, lParam, uFlags, uTimeout, uResult);

   DesktopWindow
= UserGetWindowObject(IntGetDesktopWindow());

   Children
= IntWinListChildren(DesktopWindow);

   for
(Child = Children;
NULL != *Child;
Child++)//广播消息处理

      co_IntSendMessageTimeoutSingle(*Child, Msg, wParam, lParam, uFlags, uTimeout, uResult);

   ExFreePool(Children);

   return
(LRESULT) TRUE;

}

 

LRESULT FASTCALL

co_IntSendMessageTimeoutSingle( HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam,

                                UINT uFlags,UINT uTimeout,ULONG_PTR *uResult
)

{

   PWINDOW_OBJECT
Window = NULL;

   if
(!(Window = UserGetWindowObject(hWnd)))

       RETURN( FALSE);

   UserRefObjectCo(Window, &Ref);

   Win32Thread
= PsGetCurrentThreadWin32Thread();

   IntCallWndProc(
Window, hWnd,
Msg, wParam, lParam);//调用钩子

   //再次检查是否是同一消息队列(因为可能前面是广播窗口的原因),若是,就地处理

   if
( NULL != Win32Thread
&& Window->pti->MessageQueue == Win32Thread->MessageQueue)

   {

      if
(Win32Thread->TIF_flags
& TIF_INCLEANUP)

          RETURN(
FALSE);

      MsgMemoryEntry
= FindMsgMemory(Msg);

      if
(NULL == MsgMemoryEntry)

         lParamBufferSize
= -1;

      else

         lParamBufferSize
= MsgMemorySize(MsgMemoryEntry,
wParam, lParam);

      PackParam(&lParamPacked,
Msg, wParam, lParam, FALSE);//L附件打包到用户空间

      Result
= co_IntCallWindowProc( Window->Wnd->lpfnWndProc,!Window->Wnd->Unicode,

                                    
hWnd,Msg,wParam,lParamPacked,lParamBufferSize );

      if(uResult)

         *uResult
= Result;

      IntCallWndProcRet(
Window, hWnd,
Msg, wParam, lParam, (LRESULT *)uResult);//调用钩子

 

      UnpackParam(lParamPacked, Msg, wParam, lParam, FALSE);

      RETURN(
TRUE);

   }

   //若不是同一线程,就真的发送了

   if
(uFlags & SMTO_ABORTIFHUNG
&& MsqIsHung(Window->pti->MessageQueue))

      RETURN(
FALSE);

   if
(Window->state
& WINDOWSTATUS_DESTROYING)

      RETURN(
FALSE);

   do

   {

      Status
= co_MsqSendMessage( Window->pti->MessageQueue,hWnd,Msg,wParam,lParam,

                                  uTimeout, (uFlags
& SMTO_BLOCK),MSQ_NORMAL,uResult );

   }

   while
((STATUS_TIMEOUT == Status)
&& (uFlags & SMTO_NOTIMEOUTIFNOTHUNG) &&

          !MsqIsHung(Window->pti->MessageQueue));

   IntCallWndProcRet(
Window, hWnd,
Msg, wParam, lParam, (LRESULT *)uResult);

   if
(STATUS_TIMEOUT == Status)

   {

      SetLastWin32Error(ERROR_TIMEOUT);

      RETURN(
FALSE);

   }

   else
if (! NT_SUCCESS(Status))

   {

      SetLastNtError(Status);

      RETURN(
FALSE);

   }

   RETURN(
TRUE);

}

 

NTSTATUS FASTCALL

co_MsqSendMessage(PUSER_MESSAGE_QUEUE MessageQueue,HWND Wnd, UINT Msg, WPARAM wParam,

 LPARAM lParam,UINT uTimeout,

 BOOL Block,//指是否一直要等到消息被接收方处理

 INT HookMessage,//指是否是那个专用的‘底层键盘鼠标钩子调用请求’消息

                  ULONG_PTR *uResult)

{

   PTHREADINFO
pti;

   PUSER_SENT_MESSAGE
Message;//send队列中的消息结构

   KEVENT
CompletionEvent;

   NTSTATUS
WaitStatus;

   LRESULT
Result;

   PUSER_MESSAGE_QUEUE
ThreadQueue;

   LARGE_INTEGER
Timeout;

   PLIST_ENTRY
Entry;

   Message = ExAllocatePoolWithTag(PagedPool,
sizeof(USER_SENT_MESSAGE),
TAG_USRMSG)

   KeInitializeEvent(&CompletionEvent, NotificationEvent,
FALSE);

   pti
= PsGetCurrentThreadWin32Thread();

   ThreadQueue
= pti->MessageQueue;//本线程的消息队列

   ASSERT(ThreadQueue != MessageQueue);//可以肯定

   Timeout.QuadPart = (LONGLONG)
uTimeout * (LONGLONG)
-10000;

   Result
= 0;

   Message->Msg.hwnd = Wnd;

   Message->Msg.message = Msg;

   Message->Msg.wParam = wParam;

   Message->Msg.lParam = lParam;

   Message->CompletionEvent = &CompletionEvent;//关键字段

   Message->Result = &Result;

   Message->SenderQueue = ThreadQueue;//发送发线程的消息队列

   IntReferenceMessageQueue(ThreadQueue);

   Message->CompletionCallback = NULL;

   Message->HookMessage = HookMessage;

   Message->HasPackedLParam = FALSE;

   IntReferenceMessageQueue(MessageQueue);

 

   //关键。一头插入本线程的Dispatching阻塞队列中

   InsertTailList(&ThreadQueue->DispatchingMessagesHead,
&Message->DispatchingListEntry);

    //关键。一头插入目标线程的send队列中

   InsertTailList(&MessageQueue->SentMessagesListHead,
&Message->ListEntry);

   MessageQueue->QueueBits |= QS_SENDMESSAGE;

   MessageQueue->ChangedBits |= QS_SENDMESSAGE;

   if
(MessageQueue->WakeMask
& QS_SENDMESSAGE)

      KeSetEvent(MessageQueue->NewMessages,
IO_NO_INCREMENT, FALSE);

   //下面的等待操作单独括起来

   {

      PVOID
WaitObjects[2];

      WaitObjects[0]
= &CompletionEvent;

      WaitObjects[1]
= ThreadQueue->NewMessages;

      Int count = Block? 1 : 2//等待的对象个数不同

      do

      {

         WaitStatus
= KeWaitForMultipleObjects(count, WaitObjects, WaitAny,
UserRequest,

                                     
UserMode, FALSE,
(uTimeout ? &Timeout
: NULL), NULL);

         if(WaitStatus == STATUS_TIMEOUT)

         {

            //超时醒来的第一件事:清除目标线程send队列中这个消息的完成事件

            Entry
= MessageQueue->SentMessagesListHead.Flink;

            while
(Entry != &MessageQueue->SentMessagesListHead)

            {

               if (CONTAINING_RECORD(Entry, USER_SENT_MESSAGE,
ListEntry)== Message)

               {

                  Message->CompletionEvent
= NULL;

                  Message->Result
= NULL;

                  break;

               }

               Entry = Entry->Flink;

            }

//超时醒来的第二件事:清除本线程Disatching队列中的这个消息

            Entry
= ThreadQueue->DispatchingMessagesHead.Flink;

            while
(Entry != &ThreadQueue->DispatchingMessagesHead)

            {

               if
(CONTAINING_RECORD(Entry,
USER_SENT_MESSAGE, DispatchingListEntry)==
Message)

               {

                  Message->CompletionEvent
= NULL;

                  Message->Result
= NULL;

                  RemoveEntryList(&Message->DispatchingListEntry);//关键

                  Message->DispatchingListEntry.Flink = NULL;

                  break;

               }

               Entry = Entry->Flink;

            }

            break;

         }

         while
(co_MsqDispatchOneSentMessage(ThreadQueue));//扫描执行本线程send队列中的消息

      }

      while
(NT_SUCCESS(WaitStatus)
&& STATUS_WAIT_0 != WaitStatus);

   }

   if(WaitStatus != STATUS_TIMEOUT)

      *uResult
= (STATUS_WAIT_0 == WaitStatus
? Result : -1);

   return
WaitStatus;

}

 

上面函数的关键工作就是把消息挂入目标线程的send队列中,然后一直等待得到处理。另外,可以看出:

不管是GetMessagePeekMessage还是SendMessage内部都会扫描执行本线程send队列中的消息。

 

 

 

 

 

 

 

 

消息钩子:(相信这是大家最感兴趣的了)

下面这个函数用来为指定线程安装一个指定类型的钩子

HHOOK  SetWindowsHookExW(

    int
idHook,//钩子类型

    HOOKPROC
lpfn,//钩子函数

    HINSTANCE
hMod,//钩子函数所在模块

    DWORD
dwThreadId)//为0就表示全局钩子

{

    return IntSetWindowsHook(idHook,
lpfn, hMod, dwThreadId, FALSE);

}

 

HHOOK  FASTCALL

IntSetWindowsHook(

    int
idHook,

    HOOKPROC
lpfn,

    HINSTANCE
hMod,

    DWORD
dwThreadId,

    BOOL
bAnsi)

{

  WCHAR
ModuleName[MAX_PATH];

  UNICODE_STRING
USModuleName;

  if
(NULL != hMod)

  {

      if
(0 == GetModuleFileNameW(hMod, ModuleName, MAX_PATH))

          return
NULL;

      RtlInitUnicodeString(&USModuleName, ModuleName);

  }

  else

      RtlInitUnicodeString(&USModuleName, NULL);

  //调用系统服务

  return
NtUserSetWindowsHookEx(hMod, &USModuleName, dwThreadId, idHook,
lpfn, bAnsi);

}

 

 

HHOOK

NtUserSetWindowsHookEx(HINSTANCE
Mod,

                       PUNICODE_STRING UnsafeModuleName,

                       DWORD ThreadId,//指定线程,0表示全局

                       int
HookId,//钩子类型

                       HOOKPROC HookProc,

                       BOOL Ansi)

{

    BOOLEAN
ThreadReferenced = FALSE;

    ClientInfo
= GetWin32ClientInfo();

    if
(ThreadId!=0)//if 特定线程

{

    
//这些钩子本就是全局的,不支持局部线程

        if (HookId == WH_JOURNALRECORD
||HookId == WH_JOURNALPLAYBACK
||

HookId == WH_KEYBOARD_LL
||HookId == WH_MOUSE_LL
||HookId == WH_SYSMSGFILTER)

        {

            SetLastWin32Error(ERROR_INVALID_PARAMETER);

            RETURN(
NULL);

        }

        Mod = NULL;

        Global
= FALSE;

        PsLookupThreadByThreadId(ThreadId, &Thread);//根据线程ID找到对应的线程对象

     

       

        ThreadReferenced
= TRUE;

        if
(Thread->ThreadsProcess
!= PsGetCurrentProcess())//只能是当前进程中的其他线程

        {

            ObDereferenceObject(Thread);

            SetLastWin32Error(ERROR_INVALID_PARAMETER);

            RETURN( NULL);

        }

    }

    else  //if全局钩子

    {

        if
(HookId == WH_KEYBOARD_LL
|| HookId == WH_MOUSE_LL)

        {

            Mod
= NULL;

            Thread
= PsGetCurrentThread();

            Status
= ObReferenceObjectByPointer(Thread,THREAD_ALL_ACCESS,

                                               
PsThreadType,KernelMode);

            if
(!NT_SUCCESS(Status))//if权限检查失败

            {

                SetLastNtError(Status);

                RETURN( (HANDLE) NULL);

            }

            ThreadReferenced
= TRUE;

        }

        else
if (NULL
==  Mod)//全局钩子必须指定模块

        {

            SetLastWin32Error(ERROR_HOOK_NEEDS_HMOD);

            RETURN(
NULL);

        }

        else

            Thread
= NULL;//全局钩子的目标线程等于NULL,但是底层键盘鼠标类钩子除外

        Global
= TRUE;

    }

 

    Status
= IntValidateWindowStationHandle(PsGetCurrentProcess()->Win32WindowStation,

                                           
KernelMode,0,&WinStaObj);

    if
(!NT_SUCCESS(Status))

    //关键。在对应类型的钩子队列中挂入一个钩子

    Hook
= IntAddHook(Thread,
HookId, Global,
WinStaObj);

    if
(NULL == Hook)

    {

        if
(ThreadReferenced) ObDereferenceObject(Thread);

        ObDereferenceObject(WinStaObj);

        RETURN(
NULL);

    }

    if
(ThreadReferenced)

        Hook->Flags |= HOOK_THREAD_REFERENCED;

    if
(NULL != Mod)
//如果提供了模块,那HookProc就是模块内的相对偏移

    {

        Status
= MmCopyFromCaller(&ModuleName, UnsafeModuleName,
sizeof(UNICODE_STRING));

        Hook->ModuleName.Buffer =
ExAllocatePoolWithTag(PagedPool,

                                                       
ModuleName.MaximumLength,TAG_HOOK);

        Hook->ModuleName.MaximumLength
= ModuleName.MaximumLength;

        Status
= MmCopyFromCaller(Hook->ModuleName.Buffer,ModuleName.Buffer,

                                  ModuleName.MaximumLength);

        Hook->ModuleName.Length =
ModuleName.Length;

        Hook->Proc = (void *)((char *)HookProc – (char *)Mod);//相对偏移

    }

    Else //否则,就是直接的地址

        Hook->Proc = HookProc;

    Hook->Ansi = Ansi;

    Handle
= UserHMGetHandle(Hook);//钩子句柄

 

    ClientInfo->phkCurrent = 0;

    UserDereferenceObject(Hook);

    ObDereferenceObject(WinStaObj);

    RETURN(
Handle);

CLEANUP:…

}

 

实质函数是IntAddHook,这个函数将指定钩子插入指定钩子表的对应类型的钩子队列中。

PHOOK

IntAddHook(PETHREAD Thread, int HookId, BOOLEAN Global, PWINSTATION_OBJECT
WinStaObj)

{

    PTHREADINFO
W32Thread;

PHOOK Hook;

HANDLE Handle;

//全局的钩子表或每个线程局部的钩子表

    PHOOKTABLE
Table = Global
? GlobalHooks : MsqGetHooks(((PTHREADINFO)Thread->Tcb.Win32Thread)->MessageQueue);

    if
(NULL == Table)//分配钩子表

    {

        Table
= IntAllocHookTable();

        if
(Global)

            GlobalHooks
= Table;

        else

            MsqSetHooks(((PTHREADINFO)Thread->Tcb.Win32Thread)->MessageQueue, Table); 

    }

 

    //创建钩子对象(钩子像窗口一样,也是一种GUI对象,放在全局的GUI对象句柄表中)

Hook = UserCreateObject(gHandleTable, NULL,
&Handle, otHook,
sizeof(HOOK));

    Hook->Thread = Thread;

    Hook->HookId = HookId;

    if
(Thread)//局部钩子

    {

        W32Thread
= ((PTHREADINFO)Thread->Tcb.Win32Thread);

        //fsHooks表示钩子掩码

        W32Thread->fsHooks |= HOOKID_TO_FLAG(HookId);

        if
(W32Thread->pClientInfo)

           W32Thread->pClientInfo->fsHooks
= W32Thread->fsHooks;

        if
(W32Thread->pDeskInfo)

           W32Thread->pDeskInfo->fsHooks=
W32Thread->fsHooks;

        Hook->head.pti = W32Thread;

        Hook->head.rpdesk = W32Thread->rpdesk;

    }

RtlInitUnicodeString(&Hook->ModuleName, NULL);//不需要模块

//关键。将钩子挂入钩子表中对应类型的钩子队列中。注意是插在队列头部,这就是为什么后安装的钩子优先得到处理的原因

    InsertHeadList(&Table->Hooks[HOOKID_TO_INDEX(HookId)],
&Hook->Chain);

    return
Hook;

}

 

 

typedef struct tagHOOKTABLE  //钩子表(全局的或每个线程句柄的钩子表)

{

   LIST_ENTRY Hooks[16];//对应16种钩子的16个队列

   UINT      Counts[16];//各队列的引用计数(注意不是钩子个数)

} HOOKTABLE,
*PHOOKTABLE;

typedef struct tagHOOK  //钩子对象的结构

{

   THRDESKHEAD    head;

   LIST_ENTRY    
Chain;  //用来挂入相应类型的钩子队列

   struct _ETHREAD*
Thread;//钩子的目标线程,注意底层键盘鼠标类钩子的这个字段表示原创建者线程

   int           
HookId;//钩子的类型

   HOOKPROC       Proc;//若给定了模块名,就是相对偏移,否则就是地址

   BOOLEAN        Ansi;

   ULONG          Flags;

   UNICODE_STRING ModuleName;//全局钩子的模块名

} HOOK, *PHOOK;

 

通过以上函数的展示:我们知道SetWindowsHookEx这个API最终就是在相应类型的钩子队列中挂入一个钩子,如此简单而已。那么钩子是如何得到调用的呢?当在Dispatching消息时,系统会检查相应类型的钩子队列中有没有钩子,除此之外,在其它每一个可能有钩子的地方也都会调用钩子,如取下消息后会检查调用WH_GETMESSAGE钩子。系统通过下面的函数检查、调用钩子。

 

LRESULT FASTCALL

co_HOOK_CallHooks(INT HookId, INT Code, WPARAM wParam, LPARAM lParam)

{

pti = PsGetCurrentThreadWin32Thread();

//先检查局部钩子表和全局钩子表中是否安装有HookId这种类型的钩子

    Table
= MsqGetHooks(pti->MessageQueue);//当前线程的局部钩子表

    if
(NULL == Table
|| ! (Hook = IntGetFirstValidHook(Table, HookId)))

    {

        Table
= GlobalHooks;

        if
(NULL == Table
|| ! (Hook = IntGetFirstValidHook(Table, HookId)))

            return
0;       

    }

 

    //底层键盘鼠标钩子需要特殊方式调用(发一个内部消息通知原钩子创建者线程去调用钩子,注意原线程必须提供一个消息循环)

    if
((Hook->Thread
!= PsGetCurrentThread()) && (Hook->Thread != NULL))

        return
IntCallLowLevelHook(Hook,
Code, wParam,
lParam);

   

    //每次调用钩子时,递增相应钩子队列(即钩子类型)的引用计数

    Table->Counts[HOOKID_TO_INDEX(HookId)]++;

    if
(Table != GlobalHooks
&& GlobalHooks != NULL)

        GlobalHooks->Counts[HOOKID_TO_INDEX(HookId)]++;

 

    ClientInfo
= GetWin32ClientInfo();

    SaveHook
= ClientInfo->phkCurrent;

    ClientInfo->phkCurrent = Hook;//记录当前正在调用的钩子,为调用下一个钩子做准备

 

    //关键。调用队列中的第一个钩子(也即最后安装的钩子,可以在你面调用CallNextHookEx

    Result
= co_IntCallHookProc(HookId,Code,wParam,lParam,Hook->Proc,Hook->Ansi,

                               
&Hook->ModuleName);

    ClientInfo->phkCurrent = SaveHook;

    Status
= IntValidateWindowStationHandle(PsGetCurrentProcess()->Win32WindowStation,

                                           
KernelMode,0,&WinStaObj);

 

    //递减钩子类型的引用计数,减到0后,就销毁那些已经撤销了,但尚未销毁的钩子

    IntReleaseHookChain(MsqGetHooks(pti->MessageQueue), HookId,
WinStaObj);

    IntReleaseHookChain(GlobalHooks, HookId,
WinStaObj);

    ObDereferenceObject(WinStaObj);

    return
Result;

}

 

 

LRESULT FASTCALL

IntCallLowLevelHook(PHOOK Hook, INT Code, WPARAM wParam, LPARAM lParam)

{

    NTSTATUS
Status;

    ULONG_PTR uResult;

    Timeout
= HKEY_CURRENT_USER\Control
Panel\Desktop\LowLevelHooksTimeout键的值

    Status
= co_MsqSendMessage(((PTHREADINFO)Hook->Thread->Tcb.Win32Thread)->MessageQueue,

                                IntToPtr(Code),Hook->HookId,wParam,lParam,5000,

                                TRUE,//HookMesage=TRUE,表示这是一条专用的‘钩子调用请求’消息

                                MSQ_ISHOOK,&uResult);

    return
NT_SUCCESS(Status)
? uResult : 0;

}

 

 

VOID FASTCALL

IntReleaseHookChain(PHOOKTABLE Table, int HookId, PWINSTATION_OBJECT
WinStaObj)

{

    PLIST_ENTRY
Elem;

    PHOOK
HookObj;

ASSERT(0 != Table->Counts[HOOKID_TO_INDEX(HookId)]);

//递减钩子类型的引用计数,减到0后,销毁那些已经撤销了的钩子

    if
(0 == –Table->Counts[HOOKID_TO_INDEX(HookId)])

    {

        Elem
= Table->Hooks[HOOKID_TO_INDEX(HookId)].Flink;

        while
(Elem != &Table->Hooks[HOOKID_TO_INDEX(HookId)])

        {

            HookObj
= CONTAINING_RECORD(Elem,
HOOK, Chain);

            Elem
= Elem->Flink;

            if
(NULL == HookObj->Proc)

                IntFreeHook(Table, HookObj, WinStaObj);

        }

    }

}

 

UnHookWindowsHookEx函数用来撤销钩子,它将钩子对象的HookProc置为NULL,并检查该钩子是否正在被调用,若是,就立即返回。否则,销毁钩子后再返回。至于它的代码,ReactOS还未实现。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

熟悉了消息机制的原理后,我们看看具体键盘鼠标消息的处理流程。

系统在初始化时会创建两个内核守护线程,分别用来监视、处理键盘输入和鼠标输入

键盘消息的产生、处理:

NTSTATUS FASTCALL

InitInputImpl(VOID)

{

   NTSTATUS
Status;

   KeInitializeEvent(&InputThreadsStart, NotificationEvent,
FALSE);

   MasterTimer
= ExAllocatePoolWithTag(NonPagedPool, sizeof(KTIMER), TAG_INPUT);

   KeInitializeTimer(MasterTimer);

   Status
= PsCreateSystemThread(&RawInputThreadHandle,THREAD_ALL_ACCESS,NULL,NULL,

                                
&RawInputThreadId,RawInputThreadMain,NULL);

   //键盘输入线程:KeyboardThreadMain

   Status
= PsCreateSystemThread(&KeyboardThreadHandle,THREAD_ALL_ACCESS,NULL,NULL,

                                 &KeyboardThreadId,KeyboardThreadMain,NULL);

   //鼠标输入线程:MouseThreadMain

   Status
= PsCreateSystemThread(&MouseThreadHandle,THREAD_ALL_ACCESS,NULL,NULL,

                                
&MouseThreadId,MouseThreadMain,NULL);

   InputThreadsRunning
= TRUE;//标志现在可以开始读取键盘鼠标输入

   KeSetEvent(&InputThreadsStart, IO_NO_INCREMENT,
FALSE);

   return
STATUS_SUCCESS;

}

我们看键盘输入线程:

VOID  KeyboardThreadMain(PVOID
StartContext)

{

   //键盘类驱动中的键盘类设备

   UNICODE_STRING
KeyboardDeviceName = RTL_CONSTANT_STRING(L”\\Device\\KeyboardClass0″);

   PKEYBOARD_INDICATOR_TRANSLATION
IndicatorTrans = NULL;//LED指示灯状态

   UINT
ModifierState = 0;//ctrl、shiftaltwin四个修正键的按住状态

   USHORT
LastMakeCode = 0;

   USHORT
LastFlags = 0;

   UINT
RepeatCount = 0;//后续的重复计数(按住不放引起的)

   InitializeObjectAttributes(&KeyboardObjectAttributes,&KeyboardDeviceName,0,NULL,NULL);

   do

   {

      LARGE_INTEGER
DueTime;

      KEVENT
Event;

      DueTime.QuadPart = (LONGLONG)(-10000000);//1秒

      KeInitializeEvent(&Event, NotificationEvent,
FALSE);

      Status = KeWaitForSingleObject(&Event,
Executive, KernelMode,
FALSE, &DueTime);

      Status
= NtOpenFile(&KeyboardDeviceHandle,FILE_ALL_ACCESS,&KeyboardObjectAttributes,

&Iosb,0,FILE_SYNCHRONOUS_IO_ALERT);

   } while
(!NT_SUCCESS(Status));

   //上面的循环尝试打开键盘类设备,直到成功为止

Status = Win32kInitWin32Thread(PsGetCurrentThread());

   //键盘输入和鼠标输入线程是一种实时性较高的内核线程,比普通应用程序的线程的优先级高

   KeSetPriorityThread(&PsGetCurrentThread()->Tcb,LOW_REALTIME_PRIORITY + 3);

   //获取键盘初始的LED指示灯状态

   IntKeyboardGetIndicatorTrans(KeyboardDeviceHandle,&IndicatorTrans);

   for
(;;)

   {

      Status
= KeWaitForSingleObject(&InputThreadsStart,0,KernelMode,TRUE,NULL);

      //每轮循环读取、处理一个键或者两个键(复合键:修正键+普通键)

      while
(InputThreadsRunning)// InputThreadsRunning一般总是TRUE

      {

         BOOLEAN
NumKeys = 1;

         BOOLEAN
bLeftAlt;

         KEYBOARD_INPUT_DATA
KeyInput;

         KEYBOARD_INPUT_DATA
NextKeyInput;

         LPARAM
lParam = 0;

         UINT
fsModifiers, fsNextModifiers;

         struct
_ETHREAD *Thread;

         HWND
hWnd;

         int
id;

         //先读一个键

         Status
= NtReadFile (KeyboardDeviceHandle,NULL,NULL,NULL,&Iosb,

                              &KeyInput,sizeof(KEYBOARD_INPUT_DATA),NULL,NULL);

         if(Status == STATUS_ALERTED
&& !InputThreadsRunning) break;

         if(Status == STATUS_PENDING)

         {

            NtWaitForSingleObject(KeyboardDeviceHandle, FALSE,
NULL);

            Status
= Iosb.Status;

         }

         if
(Status == STATUS_ALERTED
&& !InputThreadsRunning)  break;

         IntLastInputTick(TRUE);//记录上次读得输入的时间

         //判断这个键是否是个修正键,若是,返回修正键ID

         fsModifiers
= IntKeyboardGetModifiers(&KeyInput);

         if
(fsModifiers)//if 是个修正键

         {

            if
(KeyInput.Flags
& KEY_BREAK)//if 弹起,修改状态

            {

               ModifierState &= ~fsModifiers;//去掉这个键

               if(fsModifiers == MOD_ALT)

               {

                   if(KeyInput.Flags & KEY_E0)//if
弹起的是右边alt

                      gQueueKeyStateTable[VK_RMENU]
= 0;//修改右alt键状态

                   else

                      gQueueKeyStateTable[VK_LMENU] = 0;

                   //修改alt键状态

                   if (gQueueKeyStateTable[VK_RMENU] == 0 &&gQueueKeyStateTable[VK_LMENU] == 0)

                      gQueueKeyStateTable[VK_MENU]
= 0;

               }

            }

            Else //if 按下

            {

               ModifierState |= fsModifiers;//加上这个修正键

               //if 当前仅仅按着ALTWIN键,特殊处理

               if (ModifierState ==
fsModifiers &&

                                    (fsModifiers == MOD_ALT
|| fsModifiers == MOD_WIN))

               {

                   bLeftAlt = FALSE;

                   if(fsModifiers == MOD_ALT)//if alt键,修改alt键的状态

                   {

                      if(KeyInput.Flags & KEY_E0)

                         gQueueKeyStateTable[VK_RMENU]
= 0x80;//最高位为1表示处于按着状态

                      else

                      {

                         gQueueKeyStateTable[VK_LMENU]
= 0x80;

                         bLeftAlt = TRUE;

                      }

                      gQueueKeyStateTable[VK_MENU]
= 0x80;//标记有个alt键按着

                   }

                  do

                  {

                     Status = NtReadFile
(KeyboardDeviceHandle,NULL,NULL,NULL,&Iosb,

                                         
&NextKeyInput,sizeof(KEYBOARD_INPUT_DATA),

                                          NULL,NULL);

                     if (Status == STATUS_ALERTED && !InputThreadsRunning)

                        goto KeyboardEscape;

 

                  }

                  while ((!(NextKeyInput.Flags & KEY_BREAK))
&&

                                                NextKeyInput.MakeCode == KeyInput.MakeCode);

                  //上面的循环,跳过因按住altwin不放产生的那些重复按键,直到读到一个原按键弹起输入或者另外一个键为止

                  //判断读到的下一个键是否为修正键

                  fsNextModifiers = IntKeyboardGetModifiers(&NextKeyInput);

                  if (fsNextModifiers)

                     ModifierState ^= fsNextModifiers;//状态切换

                  if (ModifierState ==
0)//也即if读到的下个键是原按键的弹起

                  {

                     if (fsModifiers == MOD_WIN)

                        IntKeyboardSendWinKeyMsg();//生成单个win键单击消息

                     else if (fsModifiers == MOD_ALT)

                     {

                        gQueueKeyStateTable[VK_MENU]
= 0;

                        if(bLeftAlt)

                           gQueueKeyStateTable[VK_LMENU] = 0;

                        else

                           gQueueKeyStateTable[VK_RMENU]
= 0;

                        co_IntKeyboardSendAltKeyMsg();//生成单个alt键单击消息

                     }

                     continue;//继续读取下个输入

                  }

                  //若下个输入是另外某个键按下

                  NumKeys = 2;//表示alt+xwin+x的复合键

               }

            }

         }

         //处理本轮循环读到的键(一个键或者复合键)

         for
(;NumKeys;memcpy(&KeyInput, &NextKeyInput,
sizeof(KeyInput)),NumKeys–)

         {

            PKBL
keyboardLayout = NULL;

            lParam
= 0;

            //修正对应的指示灯

            IntKeyboardUpdateLeds(KeyboardDeviceHandle,&KeyInput,IndicatorTrans);

            下面构造该KeyDown/KeyUp消息的lparam,其格式如下:

             *  0-15: 重复计数

             * 16-23: 扫描码

             *    24: 修正键标志

             *    29: alt键是否按着标志

             *    30: 标志是否与上次的按键状态相同

             *    31: 标志当前按键状态是按下还是弹起

            if
(!(KeyInput.Flags
& KEY_BREAK))//if 按下

            {

               //也即是同一按键后续的重复KeyDown

               if (((KeyInput.Flags & (KEY_E0
| KEY_E1)) == LastFlags)
&&

                                                   (KeyInput.MakeCode
== LastMakeCode))

               {

                  RepeatCount++;//递增重复按键计数

                  lParam |=
(1 << 30);//bit30标记为与上次的按键状态的相同

               }

               Else//首次按下某键

               {

                  RepeatCount = 1;

                  LastFlags = KeyInput.Flags & (KEY_E0
| KEY_E1);

                  LastMakeCode = KeyInput.MakeCode;

               }

            }

            Else
//if 弹起

            {

               LastFlags = 0;

               LastMakeCode = 0;

               lParam |= (1 << 30) | (1 << 31);

            }

            lParam
|= RepeatCount;

            lParam
|= (KeyInput.MakeCode
& 0xff) << 16;

            if
(KeyInput.Flags
& KEY_E0)

               lParam |= (1 << 24);

            if
(ModifierState & MOD_ALT)//如果按着alt键,那就是WM_SYSKEYDOWNWM_SYSKEYUP

            {

               lParam |= (1 << 29);

               if (!(KeyInput.Flags
& KEY_BREAK))

                  msg.message = WM_SYSKEYDOWN;

               else

                  msg.message = WM_SYSKEYUP;

            }

            else

            {

               if (!(KeyInput.Flags & KEY_BREAK))

                  msg.message = WM_KEYDOWN;

               else

                  msg.message = WM_KEYUP;

            }

            //获得当前的‘键盘焦点线程’(注意不是焦点窗口)

            FocusQueue
= IntGetFocusMessageQueue();

            if
(FocusQueue)

            {

                msg.hwnd = FocusQueue->FocusWindow;

                FocusThread = FocusQueue->Thread;

                if (FocusThread
&& FocusThread->Tcb.Win32Thread)

                    keyboardLayout = (FocusThread->Tcb.Win32Thread)->KeyboardLayout;

            }

msg.lParam
= lParam;

            if
(!keyboardLayout)

                keyboardLayout = W32kGetDefaultKeyLayout();

            //将扫描码转为虚拟码保存在wparam

            W32kKeyProcessMessage(&msg,keyboardLayout->KBTables,

                                  KeyInput.Flags
& KEY_E0 ? 0xE0 :

                                 (KeyInput.Flags
& KEY_E1 ? 0xE1 : 0));

 

            //检查是否这个键被注册为了热键

            if
(GetHotKey(ModifierState,msg.wParam,&Thread,&hWnd,&id))

            {

               if (!(KeyInput.Flags & KEY_BREAK))

               {

                  MsqPostHotKeyMessage (Thread,hWnd, (WPARAM)id,

                                        MAKELPARAM((WORD)ModifierState,
(WORD)msg.wParam));

               }

               continue;//看到没,热键会阻断正常的按键分派流程

            }

            if (!FocusQueue)

                continue;

            //关键。将消息发送给当前键盘焦点线程

            co_MsqPostKeyboardMessage(msg.message,msg.wParam,msg.lParam);

         }

      }

   }

}

 

如上,这个函数会循环从键盘驱动读取键盘输入,然后生成WM_HotKey/KeyDown/KeyUp消息,发给应用线程。读到的每个输入是一个结构,是由键盘驱动提交上来的。该结构定义如下:

typedef struct _KEYBOARD_INPUT_DATA {

  USHORT
UnitId;//键盘ID,一台机器可以安装多个键盘

  USHORT
MakeCode;//扫描码

  USHORT
Flags;//E0、E1Up等标志

  USHORT
Reserved;

  ULONG
ExtraInformation;

} KEYBOARD_INPUT_DATA,
*PKEYBOARD_INPUT_DATA;

其中Flags包含的常见三个标志位:

#define KEY_MAKE                       0x000   //表示按下

#define KEY_BREAK                      0x001   //表示弹起

#define KEY_E0                         0x010   //表示win键或右边的ctrllt

#define KEY_E1                         0x100   //表示普通键(非修正键

 

UINT  IntKeyboardGetModifiers(KEYBOARD_INPUT_DATA *InputData)

{

   if
(InputData->Flags
& KEY_E1)//普通键

      return
0;//返回FALSE

   if
(!(InputData->Flags
& KEY_E0))//if 不是win键和右边的ctrllt

   {

      switch
(InputData->MakeCode)

      {

         case
0x2a: /* left shift */

         case 0x36: /* right shift */

            return
MOD_SHIFT;

         case
0x1d: /* left control */

            return
MOD_CONTROL;

         case
0x38: /* left alt */

            return
MOD_ALT;

         default:

            return
0;

      }

   }

   else

   {

      switch
(InputData->MakeCode)

      {

         case
0x1d: /* right control */

            return
MOD_CONTROL;

         case
0x38: /* right alt */

            return
MOD_ALT;

         case
0x5b: /* left gui (windows) */

         case
0x5c: /* right gui (windows) */

            return
MOD_WIN;

         default:

            return
0;

      }

   }

}

 

用户可以调用RegisterHotkey
api
注册热键,它内部调用下面的系统服务:

BOOL

NtUserRegisterHotKey(HWND hWnd,int id,UINT fsModifiers,UINT vk)

{

   PHOT_KEY_ITEM
HotKeyItem;

   PWINDOW_OBJECT
Window;

   PETHREAD
HotKeyThread;

   DECLARE_RETURN(BOOL);

   if (IsHotKey (fsModifiers,
vk))//如果这个热键组合已被抢占了,注册失败

      RETURN(
FALSE);

   if
(hWnd == NULL)//可为NULL,热键消息发给当前线程

      HotKeyThread
= PsGetCurrentThread();

   else

   {

      if(!(Window = UserGetWindowObject(hWnd)))

         RETURN(
FALSE);

      HotKeyThread
= Window->pti->pEThread;//热键消息将发给的目标线程

   }

   HotKeyItem
= ExAllocatePoolWithTag (PagedPool, sizeof(HOT_KEY_ITEM), TAG_HOTKEY);

   HotKeyItem->Thread = HotKeyThread;//目标线程

   HotKeyItem->hWnd = hWnd;//目标窗口

   HotKeyItem->id = id;//热键id

   HotKeyItem->fsModifiers = fsModifiers;//修正键组合

   HotKeyItem->vk = vk;//虚拟码

   InsertHeadList
(&gHotkeyList, &HotKeyItem->ListEntry);//插入全局的热键表

   RETURN(
TRUE);

}

 

当读到的按键被发现注册为了热键的话,就发给热键注册的目标线程

BOOL FASTCALL //检测是不是热键,若是,再返回该热键的目标线程、窗口和id

GetHotKey (UINT fsModifiers,UINT vk,struct _ETHREAD **Thread,HWND *hWnd,int *id)

{

   PHOT_KEY_ITEM
HotKeyItem;

   LIST_FOR_EACH(HotKeyItem, &gHotkeyList,
HOT_KEY_ITEM, ListEntry)

   {

      if
(HotKeyItem->fsModifiers
== fsModifiers && HotKeyItem->vk
== vk)

      {

         if
(Thread != NULL)

            *Thread = HotKeyItem->Thread;

         if
(hWnd != NULL)

            *hWnd = HotKeyItem->hWnd;

         if
(id != NULL)

            *id = HotKeyItem->id;

         return TRUE;

      }

   }

   return
FALSE;

}

 

下面的函数将热键消息以Post方式发给目标线程

VOID FASTCALL

MsqPostHotKeyMessage(PVOID Thread, HWND hWnd, WPARAM wParam, LPARAM lParam)

{

   PWINDOW_OBJECT
Window;

   PTHREADINFO
Win32Thread;

   MSG
Mesg;

   LARGE_INTEGER
LargeTickCount;

   NTSTATUS
Status;

   Status
= ObReferenceObjectByPointer (Thread,THREAD_ALL_ACCESS,PsThreadType,KernelMode);

   Win32Thread
= ((PETHREAD)Thread)->Tcb.Win32Thread;

   Window
= IntGetWindowObject(hWnd);

   Mesg.hwnd = hWnd;

   Mesg.message = WM_HOTKEY;

   Mesg.wParam = wParam;

   Mesg.lParam = lParam;

   KeQueryTickCount(&LargeTickCount);

   Mesg.time = MsqCalculateMessageTime(&LargeTickCount);//消息的产生时间

   Mesg.pt = gpsi->ptCursor; //消息产生时的光标位置

   MsqPostMessage(Window->pti->MessageQueue, &Mesg,
FALSE, QS_HOTKEY);//发给目标线程

   UserDereferenceObject(Window);

   ObDereferenceObject
(Thread);

}

 

普通的按键消息都会发给当前键盘焦点线程(注意可能当前没有键盘焦点窗口)

VOID FASTCALL

co_MsqPostKeyboardMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)

{

   PUSER_MESSAGE_QUEUE
FocusMessageQueue;

   MSG
Msg;

   LARGE_INTEGER
LargeTickCount;

   KBDLLHOOKSTRUCT
KbdHookData;

   BOOLEAN
Entered = FALSE;

   FocusMessageQueue
= IntGetFocusMessageQueue();//当前焦点线程的消息队列

   Msg.hwnd = NULL;

   Msg.message = uMsg;

   Msg.wParam = wParam;

   Msg.lParam = lParam;

   KeQueryTickCount(&LargeTickCount);

   Msg.time = MsqCalculateMessageTime(&LargeTickCount);

   KbdHookData.vkCode = Msg.wParam;

   KbdHookData.scanCode = (Msg.lParam >> 16) & 0xff;

   KbdHookData.flags = (0 == (Msg.lParam & 0x01000000) ? 0 : LLKHF_EXTENDED) |

                       (0 == (Msg.lParam &
0x20000000) ? 0 : LLKHF_ALTDOWN) |

                       (0 == (Msg.lParam &
0x80000000) ? 0 : LLKHF_UP);

   KbdHookData.time = Msg.time;

   KbdHookData.dwExtraInfo = 0;

   //进队前,先检查、调用底层键盘钩子,若钩子处理结果返回非0值,则不再继续发送

   if
(co_HOOK_CallHooks(WH_KEYBOARD_LL,
HC_ACTION, Msg.message, (LPARAM)
&KbdHookData))

      return;

   if
(FocusMessageQueue->FocusWindow != NULL)

   {

         Msg.hwnd = FocusMessageQueue->FocusWindow;

         FocusMessageQueue->Desktop->pDeskInfo->LastInputWasKbd = TRUE;

         Msg.pt = gpsi->ptCursor;//光标位置

         MsqPostMessage(FocusMessageQueue, &Msg,
FALSE, QS_KEY);//发给焦点线程

   }

   return;

}

 

鼠标消息的产生、处理:

VOID  MouseThreadMain(PVOID StartContext)

{

   //鼠标类设备

   UNICODE_STRING
MouseDeviceName = RTL_CONSTANT_STRING(L”\\Device\\PointerClass0″);

   OBJECT_ATTRIBUTES
MouseObjectAttributes;

   IO_STATUS_BLOCK
Iosb;

   NTSTATUS
Status;

   MOUSE_ATTRIBUTES
MouseAttr;

 

   Status
= Win32kInitWin32Thread(PsGetCurrentThread());

   KeSetPriorityThread(&PsGetCurrentThread()->Tcb,LOW_REALTIME_PRIORITY + 3);

   InitializeObjectAttributes(&MouseObjectAttributes,&MouseDeviceName,0,NULL,NULL);

   do

   {

      LARGE_INTEGER
DueTime;

      KEVENT
Event;

      DueTime.QuadPart = (LONGLONG)(-10000000);

      KeInitializeEvent(&Event, NotificationEvent,
FALSE);

      Status
= KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE,
&DueTime);

      Status
= NtOpenFile(&MouseDeviceHandle,FILE_ALL_ACCESS,&MouseObjectAttributes,

                       &Iosb,0,FILE_SYNCHRONOUS_IO_ALERT);

   } while
(!NT_SUCCESS(Status));

   //上面的循环尝试打开鼠标类设备,直到成功

   for(;;)

   {

      Status
= KeWaitForSingleObject(&InputThreadsStart,0,KernelMode,TRUE,NULL);

      Status
= NtDeviceIoControlFile(MouseDeviceHandle,NULL,NULL,NULL,&Iosb,

                                    
IOCTL_MOUSE_QUERY_ATTRIBUTES,

                                    
&MouseAttr, sizeof(MOUSE_ATTRIBUTES),

                                    
NULL, 0);

      while(InputThreadsRunning)

      {

         MOUSE_INPUT_DATA
MouseInput;

         Status
= NtReadFile(MouseDeviceHandle,NULL,NULL,NULL,&Iosb,

                             &MouseInput,sizeof(MOUSE_INPUT_DATA),NULL,NULL);

         if(Status == STATUS_ALERTED
&& !InputThreadsRunning)  break;

         if(Status == STATUS_PENDING)

         {

            NtWaitForSingleObject(MouseDeviceHandle, FALSE,
NULL);

            Status
= Iosb.Status;

         }

         IntLastInputTick(TRUE);//记录上次得到输入的时间

         UserEnterExclusive();

         //处理得到的鼠标输入

         ProcessMouseInputData(&MouseInput, Iosb.Information / sizeof(MOUSE_INPUT_DATA));

         UserLeave();

      }

   }

}

 

上面这个线程,会不断的从鼠标驱动读取鼠标输入处理读取鼠标输入处理…

看看是怎么处理鼠标输入的

VOID FASTCALL

ProcessMouseInputData(PMOUSE_INPUT_DATA
Data, ULONG InputCount)

{

   PMOUSE_INPUT_DATA
mid;//当前鼠标输入

   MOUSEINPUT
mi;

   ULONG
i;

   ClearMouseInput(mi);//全部清0

   mi.time = 0;

   mi.dwExtraInfo = 0;

   for(i = 0; i < InputCount; i++)//遍历每个鼠标输入

   {

      mid
= (Data + i);

      mi.dx += mid->LastX;//X方向移动距离

      mi.dy += mid->LastY;//Y方向移动距离

      if
(mid->Flags
== MOUSE_MOVE_ABSOLUTE)//指LastXLastY是绝对值

         mi.dwFlags |= MOUSEEVENTF_ABSOLUTE;

      if(mid->ButtonFlags)

      {

         if(mid->ButtonFlags
& MOUSE_LEFT_BUTTON_DOWN)

         {

            mi.dwFlags |= MOUSEEVENTF_LEFTDOWN;

            SendMouseEvent(mi);

         }

         if(mid->ButtonFlags
& MOUSE_LEFT_BUTTON_UP)

         {

            mi.dwFlags |= MOUSEEVENTF_LEFTUP;

            SendMouseEvent(mi);

         }

         if(mid->ButtonFlags
& MOUSE_MIDDLE_BUTTON_DOWN)

         {

            mi.dwFlags |= MOUSEEVENTF_MIDDLEDOWN;

            SendMouseEvent(mi);

         }

         if(mid->ButtonFlags
& MOUSE_MIDDLE_BUTTON_UP)

         {

            mi.dwFlags |= MOUSEEVENTF_MIDDLEUP;

            SendMouseEvent(mi);

         }

         if(mid->ButtonFlags
& MOUSE_RIGHT_BUTTON_DOWN)

         {

            mi.dwFlags |= MOUSEEVENTF_RIGHTDOWN;

            SendMouseEvent(mi);

         }

         if(mid->ButtonFlags
& MOUSE_RIGHT_BUTTON_UP)

         {

            mi.dwFlags |= MOUSEEVENTF_RIGHTUP;

            SendMouseEvent(mi);

         }

         if(mid->ButtonFlags
& MOUSE_WHEEL)

         {

            mi.mouseData = mid->ButtonData;

            mi.dwFlags |= MOUSEEVENTF_WHEEL;

            SendMouseEvent(mi);

         }

      }

   }// end for

   SendMouseEvent(mi);

}

SendMouseEvent是个宏

#define SendMouseEvent(mi) \

  if(mi.dx != 0 || mi.dy != 0) \      //if鼠标有移动

    mi.dwFlags |= MOUSEEVENTF_MOVE;
\

  if(mi.dwFlags) \

    IntMouseInput(&mi); \  //生成相应的鼠标消息,放入系统的环形缓冲队列

  ClearMouseInput(mi);//又清0

 

可以看出,第一个满足的if条件语句,在调用SendMouseEvent时,才可能含有标志MOUSEEVENTF_MOVE

BOOL FASTCALL

IntMouseInput(MOUSEINPUT *mi)

{

   //鼠标的左右按键可以互换

   const
UINT SwapBtnMsg[2][2]
=

               { 
{WM_LBUTTONDOWN, WM_RBUTTONDOWN},{WM_LBUTTONUP,
WM_RBUTTONUP} 
};

   const
WPARAM SwapBtn[2]
={MK_LBUTTON, MK_RBUTTON};

   POINT
MousePos;

   PSYSTEM_CURSORINFO
CurInfo;

   BOOL
SwapButtons;

   MSG
Msg;

   CurInfo
= IntGetSysCursorInfo();//光标的一些属性

   SwapButtons
= gspv.bMouseBtnSwap;//全局变量,表示鼠标左右按键是否互换

   MousePos
= gpsi->ptCursor;
//当前光标的位置

   if(mi->dwFlags
& MOUSEEVENTF_MOVE)//前面讲了,第一个满足条件的if块才可能带有这个标志

   {

      if(mi->dwFlags
& MOUSEEVENTF_ABSOLUTE)

      {

         MousePos.x = mi->dx * UserGetSystemMetrics(SM_CXVIRTUALSCREEN) >> 16;

         MousePos.y = mi->dy * UserGetSystemMetrics(SM_CYVIRTUALSCREEN) >> 16;

      }

      Else  //计算新的光标位置

      {

         MousePos.x += mi->dx;

         MousePos.y += mi->dy;

      }

   }

   if(mi->dwFlags
& MOUSEEVENTF_MOVE)

      UserSetCursorPos(MousePos.x, MousePos.y, TRUE);//系统会自动物理移动桌面上的光标位置

   Msg.wParam = 0;

   Msg.lParam = MAKELPARAM(MousePos.x, MousePos.y);

   Msg.pt = MousePos;

   //上面构造了一个鼠标消息(注意这个消息尚未确定目标窗口)

   if
(gQueueKeyStateTable[VK_SHIFT]
& 0xc0)

      Msg.wParam |= MK_SHIFT;

   if
(gQueueKeyStateTable[VK_CONTROL]
& 0xc0)

      Msg.wParam |= MK_CONTROL;

   if(mi->dwFlags
& MOUSEEVENTF_LEFTDOWN)

   {

      gQueueKeyStateTable[VK_LBUTTON] |= 0xc0;

      Msg.message = SwapBtnMsg[0][SwapButtons];

      CurInfo->ButtonsDown |= SwapBtn[SwapButtons];

      Msg.wParam |= CurInfo->ButtonsDown;

      MsqInsertSystemMessage(&Msg);//关键。系统生成鼠标消息后,插入到系统的环形缓冲消息队列

   }

   else
if(mi->dwFlags & MOUSEEVENTF_LEFTUP)

   {

      gQueueKeyStateTable[VK_LBUTTON] &= ~0x80;

      Msg.message = SwapBtnMsg[1][SwapButtons];

      CurInfo->ButtonsDown &= ~SwapBtn[SwapButtons];

      Msg.wParam |= CurInfo->ButtonsDown;

      MsqInsertSystemMessage(&Msg);

   }

   if(mi->dwFlags
& MOUSEEVENTF_MIDDLEDOWN)

   {

      gQueueKeyStateTable[VK_MBUTTON] |= 0xc0;

      Msg.message = WM_MBUTTONDOWN;

     
CurInfo->ButtonsDown
|= MK_MBUTTON;

      Msg.wParam |= CurInfo->ButtonsDown;

      MsqInsertSystemMessage(&Msg);

   }

   else
if(mi->dwFlags & MOUSEEVENTF_MIDDLEUP)

   {

      gQueueKeyStateTable[VK_MBUTTON] &= ~0x80;

      Msg.message = WM_MBUTTONUP;

      CurInfo->ButtonsDown &= ~MK_MBUTTON;

      Msg.wParam |= CurInfo->ButtonsDown;

      MsqInsertSystemMessage(&Msg);

   }

   if(mi->dwFlags
& MOUSEEVENTF_RIGHTDOWN)

   {

      gQueueKeyStateTable[VK_RBUTTON] |= 0xc0;

      Msg.message = SwapBtnMsg[0][!SwapButtons];

      CurInfo->ButtonsDown |= SwapBtn[!SwapButtons];

      Msg.wParam |= CurInfo->ButtonsDown;

      MsqInsertSystemMessage(&Msg);

   }

   else
if(mi->dwFlags & MOUSEEVENTF_RIGHTUP)

   {

      gQueueKeyStateTable[VK_RBUTTON] &= ~0x80;

      Msg.message = SwapBtnMsg[1][!SwapButtons];

      CurInfo->ButtonsDown &= ~SwapBtn[!SwapButtons];

      Msg.wParam |= CurInfo->ButtonsDown;

      MsqInsertSystemMessage(&Msg);

   }

   if((mi->dwFlags
& (MOUSEEVENTF_XDOWN | MOUSEEVENTF_XUP)) &&

                                          (mi->dwFlags
& MOUSEEVENTF_WHEEL))

   {

      /*
fail because both types of events use the mouseData field */

      return
FALSE;//两种消息的mouseData字段意思不一样

   }

   if(mi->dwFlags
& MOUSEEVENTF_WHEEL)

   {

      Msg.message = WM_MOUSEWHEEL;

      Msg.wParam = MAKEWPARAM(CurInfo->ButtonsDown,
mi->mouseData);

      MsqInsertSystemMessage(&Msg);

   }

 

   return
TRUE;

}

 

如上,这个函数就是把鼠标输入转为一个鼠标消息,插入到系统的环形缓冲消息队列中。

VOID FASTCALL

MsqInsertSystemMessage(MSG* Msg)

{

   LARGE_INTEGER
LargeTickCount;

   KIRQL
OldIrql;

  
ULONG Prev;

   MSLLHOOKSTRUCT
MouseHookData;

   KeQueryTickCount(&LargeTickCount);

   Msg->time = MsqCalculateMessageTime(&LargeTickCount);//计算消息的进队时间

   MouseHookData.pt.x = LOWORD(Msg->lParam);

   MouseHookData.pt.y = HIWORD(Msg->lParam);

   switch(Msg->message)

   {

      case
WM_MOUSEWHEEL:

         MouseHookData.mouseData = MAKELONG(0,
GET_WHEEL_DELTA_WPARAM(Msg->wParam));

         break;

      case
WM_XBUTTONDOWN:

      case
WM_XBUTTONUP:

      case
WM_XBUTTONDBLCLK:

      case
WM_NCXBUTTONDOWN:

      case WM_NCXBUTTONUP:

      case
WM_NCXBUTTONDBLCLK:

         MouseHookData.mouseData = MAKELONG(0,
HIWORD(Msg->wParam));

         break;

      default:

         MouseHookData.mouseData = 0;

         break;

   }

   MouseHookData.flags = 0;

   MouseHookData.time = Msg->time;//消息的进队时间

   MouseHookData.dwExtraInfo = 0;

 

   //检查、调用底层鼠标钩子,如果钩子过程返回非0值,那么不再处理该鼠标消息

   if
(co_HOOK_CallHooks(WH_MOUSE_LL,
HC_ACTION, Msg->message, (LPARAM)
&MouseHookData))

      return;

 

   IntLockSystemMessageQueue(OldIrql);

   //由此可以看出,鼠标消息可能会因缓冲区满而丢失

   if
(SystemMessageQueueCount == SYSTEM_MESSAGE_QUEUE_SIZE)//缓冲容量:256个消息

   {

      IntUnLockSystemMessageQueue(OldIrql);

      return;

   }

  

   if
(Msg->message
== WM_MOUSEMOVE && SystemMessageQueueCount !=0)

   {

      if
(SystemMessageQueueTail == 0)

         Prev
= SYSTEM_MESSAGE_QUEUE_SIZE – 1;

      else

         Prev
= SystemMessageQueueTail – 1;

//若环形缓冲队列中的上一个消息也是WM_MOUSEMOVE,就更新替换(相当于合并MouseMove消息)

      if
(SystemMessageQueue[Prev].message == WM_MOUSEMOVE)

      {

         SystemMessageQueueTail
= Prev;

         SystemMessageQueueCount–;

      }

   }

   SystemMessageQueue[SystemMessageQueueTail] = *Msg;
//关键。插入到环形缓冲队列尾部

   SystemMessageQueueTail
= (SystemMessageQueueTail + 1) % SYSTEM_MESSAGE_QUEUE_SIZE;

   SystemMessageQueueCount++;

   IntUnLockSystemMessageQueue(OldIrql);

   KeSetEvent(&HardwareMessageEvent, IO_NO_INCREMENT,
FALSE);//触发事件,唤醒其他线程

}

当键盘按键消息插入到系统环形缓冲队列中后,会触发事件HardwareMessageEventGetMessagePeekMessage内部会等待这个事件。具体的,ntUserGetMessage我们看过,内部会调用co_IntPeekMessage接收消息,如果找不到,就调用co_IntWaitMessage等待。我们看这个函数的实现。

BOOL FASTCALL

co_IntWaitMessage( PWINDOW_OBJECT Window,UINT MsgFilterMin,UINT MsgFilterMax )

{

   PTHREADINFO
pti;

   PUSER_MESSAGE_QUEUE
ThreadQueue;

   NTSTATUS
Status = STATUS_SUCCESS;

   USER_MESSAGE
Msg;

  
pti = PsGetCurrentThreadWin32Thread();

   ThreadQueue
= pti->MessageQueue;

   do

   {

      //if 有消息,返回

      if
( co_IntPeekMessage( &Msg,Window,MsgFilterMin,MsgFilterMax,PM_NOREMOVE))//不移除

         return
TRUE;

      //否则,等待

 Status = co_MsqWaitForNewMessages(
ThreadQueue,Window,MsgFilterMin,MsgFilterMax);

   }while
( (STATUS_WAIT_0 <= Status && Status <= STATUS_WAIT_63));

   //上面的循环,每次一个新消息进队后,就回头去检测队列中是否有需要的消息

   if
(!NT_SUCCESS(Status))

      SetLastNtError(Status);

   return
FALSE;

}

 

NTSTATUS FASTCALL

co_MsqWaitForNewMessages(PUSER_MESSAGE_QUEUE
MessageQueue, PWINDOW_OBJECT
WndFilter,

                                               UINT
MsgFilterMin, UINT
MsgFilterMax)

{

   //等待线程的消息队列,全局的硬件消息队列两个队列的新消息事件

   PVOID
WaitObjects[2] = {MessageQueue->NewMessages, &HardwareMessageEvent};

   NTSTATUS
ret;

   ret
= KeWaitForMultipleObjects(2,WaitObjects,WaitAny,Executive,UserMode,FALSE,

                                  NULL,NULL);

   return
ret;

}

每当有新消息进队时,就会唤醒,回到NtUserGetMessage内部继续执行co_IntPeekMessage函数。这个函数前面看过,它会两次调用co_MsqFindMessage,先查找线程post队列,然后查找硬件消息队列。当时因为硬件消息处理特殊,省略没看,现在我们可以看他是如何查找硬件消息队列的。

BOOL

co_MsqPeekHardwareMessage(PUSER_MESSAGE_QUEUE
MessageQueue, PWINDOW_OBJECT
Window,

                          UINT FilterLow, UINT FilterHigh, BOOL Remove,PUSER_MESSAGE* Message)

{

   PWINDOW_OBJECT
DesktopWindow = NULL;

   PDESKTOPINFO
Desk = NULL;

WaitObjects[0] = &HardwareMessageQueueLock;

   WaitObjects[1]
= MessageQueue->NewMessages;//主要是等待这个事件

  

   do

   {

      IdlePing();

      UserLeaveCo();

      WaitStatus
= KeWaitForMultipleObjects(2, WaitObjects, WaitAny,
UserRequest,

                                           
UserMode, FALSE,
NULL, NULL);

      UserEnterCo();

   }

   while
(NT_SUCCESS(WaitStatus)
&& STATUS_WAIT_0 != WaitStatus);

   //上面的循环一直等到NewMessages事件触发后,才来到这里

   DesktopWindow
= UserGetWindowObject(IntGetDesktopWindow());

 

   if
(DesktopWindow)

   {

       UserRefObjectCo(DesktopWindow, &Ref);

       Desk
= DesktopWindow->pti->pDeskInfo;

   }

   IntLockHardwareMessageQueue(MessageQueue);//锁定线程的硬件消息队列

   CurrentEntry
= MessageQueue->HardwareMessagesListHead.Flink;

   while
(CurrentEntry != &MessageQueue->HardwareMessagesListHead)//遍历线程的硬件消息队列

   {

      PUSER_MESSAGE
Current = CONTAINING_RECORD(CurrentEntry, USER_MESSAGE,
ListEntry);

      CurrentEntry
= CurrentEntry->Flink;

      //我们只要鼠标消息

      if
(Current->Msg.message >= WM_MOUSEFIRST
&& Current->Msg.message <= WM_MOUSELAST)

      {

         //判断这个鼠标消息是不是我们需要的消息(即是否满足3个过滤条件)

         Accept
= co_MsqTranslateMouseMessage(MessageQueue, Window,
FilterLow, FilterHigh,

                                              Current, Remove,
&Freed,DesktopWindow,
&ScreenPoint, FALSE,
&CurrentEntry);

         if
(Accept)//如果是,也即如果找到了一个需要的消息

         {

            if
(Remove)  
RemoveEntryList(&Current->ListEntry);

            IntUnLockHardwareMessageQueue(MessageQueue);

            IntUnLockSystemHardwareMessageQueueLock(FALSE);

            *Message = Current;//返回找到的消息

            if
(Desk)  Desk->LastInputWasKbd
= FALSE;

            RETURN(TRUE);

         }

      }

   }

   IntUnLockHardwareMessageQueue(MessageQueue);

   IntLockSystemMessageQueue(OldIrql);//锁定系统的环形缓冲消息队列

   while
(SystemMessageQueueCount > 0)//将环形缓冲消息队列中的消息全部转入系统硬件消息队列

   {

      PUSER_MESSAGE
UserMsg;

      MSG
Msg;

      Msg
= SystemMessageQueue[SystemMessageQueueHead];

      SystemMessageQueueHead
= (SystemMessageQueueHead + 1) % SYSTEM_MESSAGE_QUEUE_SIZE;

      SystemMessageQueueCount–;

      IntUnLockSystemMessageQueue(OldIrql);

      //系统硬件消息队列中的消息结构 与 环形缓冲消息队列中的消息结构不相同,要转换

      UserMsg
= ExAllocateFromPagedLookasideList(&MessageLookasideList);

      UserMsg->FreeLParam = FALSE;

      UserMsg->Msg = Msg;

      InsertTailList(&HardwareMessageQueueHead, &UserMsg->ListEntry);//转入系统硬件消息队列

 

      IntLockSystemMessageQueue(OldIrql);

   }

   HardwareMessageQueueStamp++;

   IntUnLockSystemMessageQueue(OldIrql);

 

   //转移所有缓冲消息到系统硬件消息队列后,现在开始扫描查找

   CurrentEntry
= HardwareMessageQueueHead.Flink;

   while
(CurrentEntry != &HardwareMessageQueueHead)

   {

      PUSER_MESSAGE
Current = CONTAINING_RECORD(CurrentEntry, USER_MESSAGE,
ListEntry);

      CurrentEntry
= CurrentEntry->Flink;

      RemoveEntryList(&Current->ListEntry);//不管如何,先从系统硬件消息队列中取下来

      HardwareMessageQueueStamp++;

      if
(Current->Msg.message >= WM_MOUSEFIRST
&& Current->Msg.message <= WM_MOUSELAST)

      {

         const
ULONG ActiveStamp
= HardwareMessageQueueStamp;

         //判断是不是我们需要的消息(根据三个过滤条件)

         Accept
= co_MsqTranslateMouseMessage(MessageQueue, Window,
FilterLow, FilterHigh,

                                           
  Current,
Remove, &Freed,

                                              DesktopWindow, &ScreenPoint,
TRUE, NULL);

         if
(Accept)//如果是我们需要的消息,也即如果找到了

         {      

            IntLockSystemMessageQueue(OldIrql);

            if
(SystemMessageQueueCount == 0 && IsListEmpty(&HardwareMessageQueueHead))

               KeClearEvent(&HardwareMessageEvent);//若两个队列都空了,复位事件

            IntUnLockSystemMessageQueue(OldIrql);

            if
(!Remove)//将取下来的消息插回线程的硬件消息队列中

            {

               IntLockHardwareMessageQueue(MessageQueue);

               if(Current->Msg.message == WM_MOUSEMOVE)//不过MouseMove特殊处理

               {

                  if(MessageQueue->MouseMoveMsg)

                  {

                     RemoveEntryList(&MessageQueue->MouseMoveMsg->ListEntry);

                     ExFreePool(MessageQueue->MouseMoveMsg);//销毁原来的MouseMove消息

                  }

                  MessageQueue->MouseMoveMsg
= Current;//修改替换成现在刚取下的MouseMove消息

               }

               //插入到线程的硬件消息队列中,体现为NoRemove

               InsertTailList(&MessageQueue->HardwareMessagesListHead,&Current->ListEntry);

               IntUnLockHardwareMessageQueue(MessageQueue);

            }

            IntUnLockSystemHardwareMessageQueueLock(FALSE);

            *Message = Current;//返回找到的消息

 

            RETURN(TRUE);

         }

         //if
队列的内容改变了,重新从头开始处理

         if
(HardwareMessageQueueStamp != ActiveStamp)

         {

            CurrentEntry
= HardwareMessageQueueHead.Flink;

            continue;

         }

      }

   }

   IntLockSystemMessageQueue(OldIrql);

   if
(SystemMessageQueueCount == 0 && IsListEmpty(&HardwareMessageQueueHead))

      KeClearEvent(&HardwareMessageEvent);

   IntUnLockSystemMessageQueue(OldIrql);

   IntUnLockSystemHardwareMessageQueueLock(FALSE);

   RETURN(FALSE);

CLEANUP:

   if
(DesktopWindow) UserDerefObjectCo(DesktopWindow);

   END_CLEANUP;

}

 

 

 

如上,这个函数先在本地线程的硬件消息队列中查找,找不到就再去全局的系统硬件消息队列中查找,具体的查找判定函数看下面:(这个函数专用于判定鼠标消息是否符合过滤条件)

BOOL

co_MsqTranslateMouseMessage(PUSER_MESSAGE_QUEUE
MessageQueue,

 PWINDOW_OBJECT Window,
UINT FilterLow,
UINT FilterHigh,//过滤条件

                            PUSER_MESSAGE Message,//IN\OUT,返回该消息的目标窗口

 BOOL Remove,//没用

 PBOOL Freed,//返回是否需要释放L附件包

                            PWINDOW_OBJECT
ScopeWin, //一般就是桌面窗口

PPOINT ScreenPoint,//返回鼠标消息的产生位置

 BOOL FromGlobalQueue)//指Message参数是否是全局硬件消息队列中的

{

   USHORT
Msg = Message->Msg.message;

   PWINDOW_OBJECT
CaptureWindow = NULL; //鼠标消息发往的目标窗口

HWND hCaptureWin;//鼠标消息发往的目标窗口

   hCaptureWin
= IntGetCaptureWindow();//当前的鼠标焦点窗口(很少有鼠标焦点)

   if
(hCaptureWin == NULL)//一般总是NULL

   {

      if
(Msg == WM_MOUSEWHEEL)//滚轮消息总是发给键盘焦点窗口的

      {

         CaptureWindow
= UserGetWindowObject(IntGetFocusWindow());//键盘焦点窗口

         if
(CaptureWindow) UserReferenceObject(CaptureWindow);

      }

      else

      {

         //关键。计算鼠标消息产生时,当时光标处的窗口

         co_WinPosWindowFromPoint(ScopeWin, NULL,
&Message->Msg.pt, &CaptureWindow);

         if(CaptureWindow == NULL)

         {

            CaptureWindow
= ScopeWin;//如果光标处没有窗口,就是桌面窗口

            if
(CaptureWindow) UserReferenceObject(CaptureWindow);

         }

      }

   }

   Else //很少见。若有鼠标焦点窗口,所有鼠标消息都发给当前鼠标焦点窗口

   {

      CaptureWindow
= UserGetWindowObject(hCaptureWin);

      if
(CaptureWindow) UserReferenceObject(CaptureWindow);

   }

   if
(CaptureWindow == NULL)

 

   //如果现在的光标处窗口 与 当初鼠标消息刚产生时的光标处窗口不同了,将消息从消息队列中移除,转发给现在的光标处窗口的所属线程

   if
(CaptureWindow->pti->MessageQueue != MessageQueue)

   {

      if
(! FromGlobalQueue)

      {

         RemoveEntryList(&Message->ListEntry);

         if(MessageQueue->MouseMoveMsg
== Message)

            MessageQueue->MouseMoveMsg = NULL;

      }

      IntLockHardwareMessageQueue(CaptureWindow->pti->MessageQueue);

      //转入新的目标线程

      InsertTailList(&CaptureWindow->pti->MessageQueue->HardwareMessagesListHead,

                     &Message->ListEntry);

      if(Message->Msg.message == WM_MOUSEMOVE)
// WM_MOUSEMOV需要特殊处理

      {

         if(CaptureWindow->pti->MessageQueue->MouseMoveMsg)

         {

            RemoveEntryList(&CaptureWindow->pti->MessageQueue->MouseMoveMsg->ListEntry);

            ExFreePool(CaptureWindow->pti->MessageQueue->MouseMoveMsg);

         }

         CaptureWindow->pti->MessageQueue->MouseMoveMsg = Message;

 

         CaptureWindow->pti->MessageQueue->QueueBits |= QS_MOUSEMOVE;

         CaptureWindow->pti->MessageQueue->ChangedBits |= QS_MOUSEMOVE;

         if
(CaptureWindow->pti->MessageQueue->WakeMask
& QS_MOUSEMOVE)

            KeSetEvent(CaptureWindow->pti->MessageQueue->NewMessages,
0, FALSE);

      }

      else

      {

         CaptureWindow->pti->MessageQueue->QueueBits |= QS_MOUSEBUTTON;

         CaptureWindow->pti->MessageQueue->ChangedBits |= QS_MOUSEBUTTON;

         if
(CaptureWindow->pti->MessageQueue->WakeMask
& QS_MOUSEBUTTON)

            KeSetEvent(CaptureWindow->pti->MessageQueue->NewMessages,
0, FALSE);

      }

      IntUnLockHardwareMessageQueue(CaptureWindow->pti->MessageQueue);

      *Freed
= FALSE;

      UserDereferenceObject(CaptureWindow);

      return(FALSE);

   }

 

 

   //否则为同一个消息队列(也即光标处的窗口没发生变化)

   *ScreenPoint
= Message->Msg.pt;//返回鼠标消息产生时的光标位置

 

   //关键。当计算出该鼠标消息发往的目标窗口后,现在比较判断这条消息是不是我们想要的消息

   //if 不是我们想要的消息,简单转入线程的硬件消息队列

   if((Window != NULL
&& PtrToInt(Window)
!= 1 && CaptureWindow->hSelf != Window->hSelf) ||

         ((FilterLow
!= 0 || FilterHigh != 0) && (Msg < FilterLow
|| Msg > FilterHigh)))

   {

      if(FromGlobalQueue)//if 是系统硬件消息队列中的消息

      {

         IntLockHardwareMessageQueue(CaptureWindow->pti->MessageQueue);

         //转入线程的硬件消息队列

         InsertTailList(&CaptureWindow->pti->MessageQueue->HardwareMessagesListHead,

                        &Message->ListEntry);

      }

      if
(Message->Msg.message == WM_MOUSEMOVE)
//特殊处理

      {

         if(CaptureWindow->pti->MessageQueue->MouseMoveMsg
&&

               (CaptureWindow->pti->MessageQueue->MouseMoveMsg
!= Message))

         {

            RemoveEntryList(&CaptureWindow->pti->MessageQueue->MouseMoveMsg->ListEntry);

            ExFreePool(CaptureWindow->pti->MessageQueue->MouseMoveMsg);

         }

         CaptureWindow->pti->MessageQueue->MouseMoveMsg = Message;

      }

      if(FromGlobalQueue)

         IntUnLockHardwareMessageQueue(CaptureWindow->pti->MessageQueue);

      UserDereferenceObject(CaptureWindow);

      *Freed
= FALSE;

      return(FALSE);

   }

   //关键。If是我们想要的消息,返回这条消息

   Message->Msg.hwnd = CaptureWindow->hSelf;//返回目标窗口

  
Message->Msg.lParam = MAKELONG(Message->Msg.pt.x, Message->Msg.pt.y);//构造好l参数

   if
(Message->Msg.message == WM_MOUSEMOVE
|| Message->Msg.message == WM_NCMOUSEMOVE)

   {

      if(FromGlobalQueue) //销毁原MouseMove消息

      {

         IntLockHardwareMessageQueue(CaptureWindow->pti->MessageQueue);

         if(CaptureWindow->pti->MessageQueue->MouseMoveMsg)

         {

            RemoveEntryList(&CaptureWindow->pti->MessageQueue->MouseMoveMsg->ListEntry);

            ExFreePool(CaptureWindow->pti->MessageQueue->MouseMoveMsg);

            CaptureWindow->pti->MessageQueue->MouseMoveMsg = NULL;

         }

         IntUnLockHardwareMessageQueue(CaptureWindow->pti->MessageQueue);

      }

      else
if (CaptureWindow->pti->MessageQueue->MouseMoveMsg == Message)

           CaptureWindow->pti->MessageQueue->MouseMoveMsg
= NULL;

   }

   UserDereferenceObject(CaptureWindow);

   *Freed
= FALSE;

   return(TRUE);

}

如上,关键的判断比较操作就是比较光标处窗口是不是我们的窗口。

另外从以上可以看出,鼠标消息的流动方向是:系统环形缓冲消息队列->系统硬件消息队列->目标线程硬件消息队列

 

 

除了由真实的硬件(键盘、鼠标)产生键盘消息外,用户也可以模拟键盘鼠标动作。

keybd_event、mouse_eventSendInput这几个API都是用来模拟鼠标键盘的,如下:

VOID

keybd_event(BYTE bVk,BYTE bScan,DWORD dwFlags,ULONG_PTR dwExtraInfo)

{

  INPUT
Input;

  Input.type = INPUT_KEYBOARD;

  Input.ki.wVk = bVk;

  Input.ki.wScan = bScan;

  Input.ki.dwFlags = dwFlags;

  Input.ki.time = 0;

  Input.ki.dwExtraInfo = dwExtraInfo;

  NtUserSendInput(1,
&Input, sizeof(INPUT));

}

 

VOID

mouse_event(DWORD dwFlags,DWORD dx,DWORD dy,DWORD dwData,ULONG_PTR dwExtraInfo)

{

  INPUT
Input;

  Input.type = INPUT_MOUSE;

  Input.mi.dx = dx;

  Input.mi.dy = dy;

  Input.mi.mouseData = dwData;

  Input.mi.dwFlags = dwFlags;

  Input.mi.time = 0;

  Input.mi.dwExtraInfo = dwExtraInfo;

  NtUserSendInput(1,
&Input, sizeof(INPUT));

}

 

UINT SendInput(UINT nInputs, LPINPUT pInputs, int cbSize)

{

    return
NtUserSendInput(nInputs,
pInputs, cbSize);

}

 

这几个API都是通过调用NtUserSendInput这个系统服务实现的。

UINT NtUserSendInput(UINT nInputs,LPINPUT pInput,INT cbSize)

{

   PTHREADINFO
W32Thread;

   UINT
cnt;

   DECLARE_RETURN(UINT);

   UserEnterExclusive();

   W32Thread
= PsGetCurrentThreadWin32Thread();

   if(!W32Thread->rpdesk)

      RETURN(
0);

   if(!nInputs || !pInput
|| (cbSize != sizeof(INPUT)))

   {

      SetLastWin32Error(ERROR_INVALID_PARAMETER);

      RETURN(
0);

   }

   //检查权限和活动桌面

   if(!ThreadHasInputAccess(W32Thread)
|| !IntIsActiveDesktop(W32Thread->rpdesk))

   {

      SetLastWin32Error(ERROR_ACCESS_DENIED);

      RETURN(
0);

   }

   cnt
= 0;

   while(nInputs–)//处理每个输入

   {

      INPUT
SafeInput;

      NTSTATUS
Status;

      Status
= MmCopyFromCaller(&SafeInput, pInput++,
sizeof(INPUT));

      switch(SafeInput.type)

      {

         case
INPUT_MOUSE:

            if(IntMouseInput(&SafeInput.mi))//将模拟的鼠标消息插入系统的环形缓冲队列

               cnt++;

            break;

         case
INPUT_KEYBOARD://将模拟的键盘消息发给当前键盘焦点线程的post队列

            if(IntKeyboardInput(&SafeInput.ki))

               cnt++;

            break;

         case
INPUT_HARDWARE:

            break;

      }

   }

   RETURN(
cnt);

CLEANUP:

   UserLeave();

   END_CLEANUP;

}

 

可以看出,要反按键的话,hook
NtUserSendInput
函数是一个简单可行的方案,不过不能防止直接操作键盘鼠标的程序。

 

打赏