[8]windows内核情景分析--窗口消息
消息与钩子
众所周知,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结构,更改系统服务表描述符表。上面的PspW32ProcessCallout和PspW32ThreadCallout函数都是回调函数,分别指向win32k.sys模块中的Win32kProcessCallback、Win32kThreadCallback函数。
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 、SendMessage、PostMessage、PeekMessage
下面的函数从本线程的消息队列中取出一条符合指定过滤条件的消息
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队列、键盘鼠标消息、重绘消息、定时器消息【send、post、键鼠、重、定时】
注意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表示HookId,hwnd表示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);
}
上面的代码我想不用解释了。
由此可以看到:在GetMessage、PeekMessage函数内部,会一直等待到消息队列中出现符合指定条件的消息,这就是为什么GUI线程绝大多数时刻都处于睡眠状态的原因,因为它在等待消息。
另外,在GetMessage、PeekMessage内部,即使因为队列中一直没有符合指定条件的消息而等待,也为
在内部不断的处理别的线程通过SendMessage方式发来的消息,以尽快完成这种消息的处理。
Windows消息的产生来源(也即发送方的类型)
1、 一个线程调用SendMessage、PostMessage等函数给自己或别的线程发消息,它就是一个发送方线程
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队列中,然后一直等待得到处理。另外,可以看出:
不管是GetMessage、PeekMessage还是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、shift、alt、win四个修正键的按住状态
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 当前仅仅按着ALT或WIN键,特殊处理
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);
//上面的循环,跳过因按住alt、win不放产生的那些重复按键,直到读到一个原按键弹起输入或者另外一个键为止
//判断读到的下一个键是否为修正键
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+x或win+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_SYSKEYDOWN、WM_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、E1、Up等标志
USHORT Reserved;
ULONG ExtraInformation;
} KEYBOARD_INPUT_DATA, *PKEYBOARD_INPUT_DATA;
其中Flags包含的常见三个标志位:
#define KEY_MAKE 0x000 //表示按下
#define KEY_BREAK 0x001 //表示弹起
#define KEY_E0 0x010 //表示win键或右边的ctrl、lt
#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键和右边的ctrl、lt
{
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)//指LastX、LastY是绝对值
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);//触发事件,唤醒其他线程
}
当键盘按键消息插入到系统环形缓冲队列中后,会触发事件HardwareMessageEvent。GetMessage、PeekMessage内部会等待这个事件。具体的,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_event、SendInput这几个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函数是一个简单可行的方案,不过不能防止直接操作键盘鼠标的程序。
[8]windows内核情景分析--窗口消息的更多相关文章
- 几个常用内核函数(《Windows内核情景分析》)
参考:<Windows内核情景分析> 0x01 ObReferenceObjectByHandle 这个函数从句柄得到对应的内核对象,并递增其引用计数. NTSTATUS ObRefer ...
- [1]windows 内核情景分析---说明
本文说明:这一系列文章(笔记)是在看雪里面下载word文档,现转帖出来,希望更多的人能看到并分享,感谢原作者的分享精神. 说明 本文结合<Windows内核情景分析>(毛德操著).< ...
- windows内核情景分析之—— KeRaiseIrql函数与KeLowerIrql()函数
windows内核情景分析之—— KeRaiseIrql函数与KeLowerIrql()函数 1.KeRaiseIrql函数 这个 KeRaiseIrql() 只是简单地调用 hal 模块的 KfRa ...
- [4]Windows内核情景分析---内核对象
写过Windows应用程序的朋友都常常听说"内核对象"."句柄"等术语却无从得知他们的内核实现到底是怎样的, 本篇文章就揭开这些技术的神秘面纱. 常见的内核对象 ...
- [14]Windows内核情景分析 --- 文件系统
文件系统 一台机器上可以安装很多物理介质来存放资料(如磁盘.光盘.软盘.U盘等).各种物理介质千差万别,都配备有各自的驱动程序,为了统一地访问这些物理介质,windows设计了文件系统机制.应用程序要 ...
- [11]Windows内核情景分析---设备驱动
设备驱动 设备栈:从上层到下层的顺序依次是:过滤设备.类设备.过滤设备.小端口设备[过.类.过滤.小端口] 驱动栈:因设备堆栈原因而建立起来的一种堆栈 老式驱动:指不提供AddDevice的驱动,又叫 ...
- [7] Windows内核情景分析---线程同步
基于同步对象的等待.唤醒机制: 一个线程可以等待一个对象或多个对象而进入等待状态(也叫睡眠状态),另一个线程可以触发那个等待对象,唤醒在那个对象上等待的所有线程. 一个线程可以等待一个对象或多个对象, ...
- Linux内核情景分析之消息队列
早期的Unix通信只有管道与信号,管道的缺点: 所载送的信息是无格式的字节流,不知道分界线在哪,也没通信规范,另外缺乏控制手段,比如保温优先级,管道机制的大小只有1页,管道很容易写满而读取没有及时,发 ...
- [15]Windows内核情景分析 --- 权限管理
Windows系统是支持多用户的.每个文件可以设置一个访问控制表(即ACL),在ACL中规定每个用户.每个组对该文件的访问权限.不过,只有Ntfs文件系统中的文件才支持ACL. (Ntfs文件系统中, ...
随机推荐
- 使用UMDH查找内存泄露
参考文献: 1.http://blog.csdn.net/wcjy07220114/article/details/6962140 2.http://blog.csdn.net/chenyujing1 ...
- jQuery -- 监听input、textarea输入框值变化
$('textarea').bind('input propertychange', function(){ if($(".textareachange").val() != &q ...
- java 线程 (三)线程并发的安全性 同步代码块
package cn.sasa.demo1; import java.util.concurrent.ExecutionException; public class ThreadDemo { pub ...
- 【数据可视化-pyecharts】pyecharts快速入门
pyecharts快速开始 首先开始来绘制你的第一个图表 from pyecharts import Bar bar = Bar("我的第一个图表", "这里是副标题&q ...
- java Map 怎么遍历
java中遍历MAP的几种方法 Java代码 Map<String,String> map=new HashMap<String,String>(); map.put(& ...
- Loadrunner回放脚本时报错Action.c(41): Error -27979: Requested form not found [MsgId: MERR-27979]
解决方法 打开录制选项配置对话框进行设置,在“Recording Options”的“Internet Protocol”选项里的“Recording”中选择“Recording Level”为“HT ...
- 利用session防止表单重复提交
转自:http://www.cnblogs.com/xdp-gacl/p/3859416.html 利用Session防止表单重复提交 对于[场景二]和[场景三]导致表单重复提交的问题,既然客户端无法 ...
- 如何将wordpress所有文章批量改为已发布状态
用wordpress建站的一个好处就是bd站长工具平台上有数据结构插件,可以认为bd默认支持wp发展,另外一种建站程序是discuz.我们在用wordpress发布文章时,特别是那种多用户投稿的文章一 ...
- 我想要得那块牌—记烟台大学第一届"ACM讲堂"
版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/sr19930829/article/details/26812621 2014年 ...
- 后台返回路由的数组,然后根事先写好的路由比对如果相等就放到一个数组中https://www.cnblogs.com/zhengrunlin/p/8981017.html
https://www.cnblogs.com/zhengrunlin/p/8981017.html 首先,await fetchPermission()获取后台给的权限数组,格式大概如下 { &qu ...