应用层发消息: 发送消息过程 SendMessage(user32.dll)->SendMessageWorker,先检查有没有hook消息钩子,有的话调用CsSendMessage,进入消息钩子过滤函数。
没有的话,看是不是系统消息,是的话在Message表中找到对应msg id的索引值,通过索引值在在gapfnScSendMessage数组中找到对应的消息处理函数
如果是NtUserMessageCall的话,则进驱动处理消息。
进驱动层发消息: 进入NtUserMessageCall后,如果是系统消息,同上,在gapfnMessageCall数组中找到对应的处理函数。比如msg id为0x112,最小化窗口消息,
调用过程为NtUserfnNCDESTORY->xxxWrapSendMessage->xxxSendMessageTimeout->xxxInterSendMsgEx,在这个函数中,先通过AllocSMS分配一个消息结构体,
在将消息加入到目标线程的pSMSReceiveList链表中,代表目标线程有要接受的消息,然后调用SetWakeBit唤醒目标线程,激发目标线程的EventQueueServer,然后等待
自己的EventQueueServer被激发。然后调用xxxSleepThread是自己处于等待状态。在等待前,调用xxxReceiveMessage检查是否有hook消息钩子,如果有的话,回调CallClientProc
->xxxHkCallHook 分发处理消息钩子函数。 自己会setevent,while死循环阻塞在KeWaitForSingleObject等待。在此函数中,先检查是哪种互斥对象,如果对象被激活,则调用处理函数。否则KiSwapThread通知cpu切换为别的线程运行。调用KiComputeWaitInterval继续等待。
安装钩子过程:进入驱动调用NtUserSetWindowsHookEx->zzzSetWindowsHookEx修改目标线程的ptiThread->aphkStart[nFilterType + 1]钩子数组。
键盘按键:ioapic寄存器接收,读出index和哪个cpu处理,local apic发给键盘中断处理例程,再传给键盘端口驱动,端口类驱动。==win32k的RawInputThread读顶层类设备状态,读出按键扫描码,经过底层键盘钩子处理,匹配是win,alt+tab键等对应处理,再匹配普通字符,发给顶层窗口的线程,进入wm_char处理
消息优先级:QS_SENDMESSAGE > QS_POSTMESSAGE > QS_QUIT > QS_INPUT > QS_PAINT > QS_TIMER
msg: 112
user消息用的是win32k自己的一套通信方式,最多就用了个event来同步
跨线程的WM_COPYDATA没有使用共享内存,反而复制了两次数据
发送者SendMessage->xxxSendMessageTimeout->xxxInterSendMsgEx(UserAllocPoolWithQuota分配内核内存,将用户数据复制到内核空间)->SetWakeBit唤醒接受者->SetWakeBit等待应答
接受者xxxReceiveMessage->XXXSENDMESSAGETOCLIENT(宏)->ScSendMessageSMS(也是宏)->SfnCOPYDATA(sender side)->CaptureCallbackData(把数据从内核空间复制到用户空间)->KeUserModeCallback(转到用户模式)->SfnCOPYDATA(receiver side)->窗口过程->回到内核模式,应答发送者
回调函数什么时候被调用, 每个回调函数的情况都不一样, 就如你老板说的, 是系统定义好的, 对方(系统/库)对这个回调函数的文档说明一般只讲个大概不会讲细节, 你想弄清楚"触发机制", 那就去看对方的源码, 具体在哪些情况下哪些代码会call你的callback函数. 如果对方文档不详源码不给, 那就要靠自己的经验去猜去反汇编.
1) comp
comp可算是最简单的callback, 你调用qsort时把comp(你/crt库之间的callback)作为参数, 告诉对方(crt库)比较大小时call你这个comp. 接下去qsort立即触发comp, 反复调用comp直至qsort返回.
[] Vc7\crt\src\qsort.c
void __cdecl qsort (
void *base,
size_t num,
size_t width,
int (__cdecl *comp)(const void *, const void *)
)
{
for (;;) {
if (mid > loguy) {
do {
loguy += width;
} while (loguy < mid && comp(loguy, mid) <= 0);
}
if (mid <= loguy) {
do {
loguy += width;
} while (loguy <= hi && comp(loguy, mid) <= 0);
}
}
}
2) ThreadProc
你调用AfxBeginThread时把ThreadProc(你/mfc库之间的callback)作为参数, 告诉对方(mfc库)线程开始执行时call你这个ThreadProc.
AfxBeginThread把ThreadProc作为CWinThread内部参数保存起来, 调用成员函数CWinThread::CreateThread. CWinThread::CreateThread调用_beginthreadex时把_AfxThreadEntry(mfc库/crt库之间的callback)作为参数.
_beginthreadex把_AfxThreadEntry放在_ptiddata ptd, 调用Windows API CreateThread时把_threadstartex(crt库/系统之间的callback)作为参数.
CreateThread按原参数(加上hProcess)调用CreateRemoteThread. CreateRemoteThread把lpStartAddress(AfxBeginThread是_threadstartex, 如果你直接用Windows API CreateThread, 则是你的ThreadProc)放在CONTEXT ThreadContext. 并把ThreadContext.Eip设置为BaseThreadStartThunk(子系统/子系统之间的callback).
AfxBeginThread (pfnThreadProc = ThreadProc, pParam = Param)
CWinThread::CWinThread (m_pfnThreadProc = pfnThreadProc = ThreadProc, m_pThreadParams = pParam = Param)
CWinThread::CreateThread (startup.pThread = this)
_beginthreadex (ptd->_initaddr = initialcode = _AfxThreadEntry, ptd->_initarg = argument = startup, CREATE_SUSPENDED)
CreateThread (lpStartAddress = _threadstartex, lpParameter = ptd) (user mode/kernel mode切换)
CreateRemoteThread (ThreadContext.Eip = BaseThreadStartThunk, ThreadContext.Eax = lpStartAddress, ThreadContext.Ebx = lpParameter)
BaseCreateStack
BaseInitializeContext
NtCreateThread (ThreadContext = &ThreadContext, CreateSuspended = TRUE)
PspCreateThread (ThreadContext = ThreadContext, StartRoutine = NULL, StartContext = NULL, Thread->StartAddress = ThreadContext->Eip)
ObCreateObject 创建线程对象
MmCreateKernelStack
KeInitializeThread (SystemRoutine = PspUserThreadStartup, StartRoutine = NULL, StartContext = ThreadContext->Eip, ContextFrame = ThreadContext)
KeInitializeApc (SuspendApc, KernelRoutine = KiSuspendNop, NormalRoutine = KiSuspendThread, NormalContext = NULL, ApcMode = KernelMode)
KeInitializeSemaphore (SuspendSemaphore, Semaphore->Header.SignalState = 0, Semaphore->Limit = 2)
KiInitializeContextThread (Thread->PreviousMode = TrFrame->PreviousPreviousMode = UserMode, SwitchFrame->RetAddr = KiThreadStartup)
KeContextToKframes
KeSuspendThread (if CreateSuspended)
KiInsertQueueApc (SuspendApc)
KeReadyThread
KiReadyThread 把线程对象转到就绪状态
CsrClientCallServer
NtResumeThread (if !CREATE_SUSPENDED)
KeResumeThread (Thread->SuspendSemaphore.Header.SignalState += 1)
KiWaitTest (if Thread->SuspendSemaphore.Header.WaitListHead not empty)
KiWaitSatisfyAny (Thread->SuspendSemaphore.Header.SignalState -= 1)
KiUnwaitThread
KiReadyThread (Thread->State = Ready)
ResumeThread
WaitForSingleObject (startup.hEvent)
SuspendThread (if CREATE_SUSPENDED)
SetEvent (startup.hEvent2)
|_
| SwitchFrame
|_
| PSystemRoutine (PspUserThreadStartup)
|_
| PStartRoutine (NULL)
|_
| PStartContext (BaseThreadStartThunk)
|_
| PUserContextFlag (1)
|_
| TrFrame (copy from ThreadContext)
|_
| NpxFrame
|_
Thread->InitialStack
AfxBeginThread到达KiReadyThread之后就逐级返回. 线程切换轮到新线程, 新线程先处理SuspendApc, 等待SuspendSemaphore.
如果在处理SuspendApc之前, CreateRemoteThread已调用NtResumeThread, 那么SuspendSemaphore立即满足, 无需等待, 立即执行KiThreadStartup.
反之, 则转到等待状态, 直到CreateRemoteThread调用NtResumeThread之后, 又转到就绪状态. 线程切换又轮到新线程, 开始执行KiThreadStartup.
AfxBeginThread创建的线程总是CREATE_SUSPENDED, 在_beginthreadex返回后再ResumeThread, 在_AfxThreadEntry callback后再SuspendThread(如果你给AfxBeginThread参数有CREATE_SUSPENDED).
KiSuspendNop
KiSuspendThread
KeWaitForSingleObject (SuspendSemaphore, WaitMode = KernelMode, Alertable = FALSE, Timeout = NULL)
KiWaitSatisfyOther (if Semaphore->Header.SignalState > 0, Semaphore->Header.SignalState -= 1)
KiSwapThread (else, Thread->State = Waiting)
SwapContext
KiThreadStartup
PspUserThreadStartup
KeInitializeApc (StartApc, KernelRoutine = PspNullSpecialApc, NormalRoutine = PspSystemDll.LoaderInitRoutine, NormalContext = NULL, ApcMode = UserMode)
KeInsertQueueApc (StartApc, SystemArgument1 = PspSystemDll.DllBase)
KiInsertQueueApc (StartApc)
DbgkCreateThread
KiServiceExit2 (TFrame = &TrFrame, Thread->Tcb.Alerted = 0)
KiDeliverApc (StartApc, PreviousMode = UserMode, ExceptionFrame = NULL, TrapFrame = TFrame, Thread->TrapFrame = TFrame)
PspNullSpecialApc
ExFreePool
KiInitializeUserApc (TrapFrame = TFrame, TFrame->Eip = (ULONG)KeUserApcDispatcher, TFrame->HardwareEsp = UserStack)
KeContextFromKframes
iretd (esp = &TFrame->Eip)
|_UserStack
| NormalRoutine (PspSystemDll.LoaderInitRoutine)
|_
| NormalContext (NULL)
|_
| SystemArgument1 (PspSystemDll.DllBase)
|_
| SystemArgument2 (NULL)
|_
| ContextFrame (copy from ThreadContext)
|_
ThreadContext.Esp
KeUserApcDispatcher (UserStartApc)
KiUserApcDispatcher
PspSystemDll.LoaderInitRoutine
LdrInitializeThunk (NormalContext = &ContextFrame)
LdrpInitialize (Context = NormalContext)
LdrpInitializeThread
LdrpAllocateTls
LdrpCallInitRoutine (DLL_THREAD_ATTACH)
LdrpCallTlsInitializers (DLL_THREAD_ATTACH)
NtContinue (ContextRecord = &ContextFrame, TestAlert = TRUE) (user mode/kernel mode切换)
KiContinue (TrFrame->Eip = ContextRecord->Eip)
KiContinuePreviousModeUser
KeContextToKframes
KeTestAlertThread
KiServiceExit2
BaseThreadStartThunk (eax = ThreadContext.Eax = lpStartAddress = _threadstartex, ebx = ThreadContext.Ebx = lpParameter = ptd)
BaseThreadStart (lpStartAddress = _threadstartex, lpParameter = ptd)
CsrNewThread
NtRegisterThreadTerminatePort
_threadstartex (ptd = ptd)
_callthreadstartex (ptd->_initaddr = _AfxThreadEntry, ptd->_initarg = startup)
_AfxThreadEntry (pStartup = startup, pThread = pStartup->pThread, pThread->m_pfnThreadProc = ThreadProc, pThread->m_pThreadParams = Param)
AfxInitThread
SetWindowsHookEx (WH_MSGFILTER)
SetEvent (pStartup->hEvent)
WaitForSingleObject (pStartup->hEvent2)
ThreadProc (lpParameter = Param) 你/mfc库之间的callback绕了一大圈终于被调用
AfxEndThread
_endthreadex
ExitThread
3) WndProc
WndProc的情况就复杂多了, 前几天我只是想弄清楚窗口移动时几个消息的先后顺序和作用, 花了好几天时间才管见一斑. 要是把WndProc被调用(几百个消息各种情况下)的"触发机制"全弄清楚了, 也就是对Windows的窗口子系统了如指掌了.
xxxMoveWindow/xxxSetWindowPlacement
xxxSetWindowPos
xxxSetWindowPos/xxxMinMaximize
InternalBeginDeferWindowPos
_DeferWindowPos
xxxEndDeferWindowPosEx
xxxCalcValidRects
WM_WINDOWPOSCHANGING (if !SWP_NOSENDCHANGING)
xxxDefWindowProc
xxxAdjustSize (if !SWP_NOSIZE)
xxxInitSendValidateMinMaxInfo
WM_GETMINMAXINFO
WM_NCCALCSIZE (if !SWP_NOSIZE || SWP_FRAMECHANGED)
xxxDefWindowProc
xxxCalcClientRect
GetCaptionHeight
GetWindowBorders
xxxMenuBarCompute
xxxDoSyncPaint
xxxSendChangedMsgs
WM_WINDOWPOSCHANGED (if !SWP_NOCHANGE)
xxxDefWindowProc
xxxHandleWindowPosChanged
WM_MOVE (if !SWP_NOCLIENTMOVE)
xxxSendSizeMessage (if !SWP_NOCLIENTSIZE)
WM_SIZE
WM_NCLBUTTONDOWN
xxxDefWindowProc
xxxDWP_NCMouse
WM_SYSCOMMAND (SC_SIZE + ht - HTSIZEFIRST + WMSZ_SIZEFIRST)
WM_NCLBUTTONUP
xxxDefWindowProc
xxxDWP_NCMouse
xxxHandleNCMouseGuys
WM_SYSCOMMAND
WM_SYSCOMMAND (SC_SIZE + ht - HTSIZEFIRST + WMSZ_SIZEFIRST)
xxxDefWindowProc
xxxSysCommand
xxxSysCommand (SC_RESTORE/SC_MINIMIZE/SC_MAXIMIZE)
xxxShowWindow
xxxMinMaximize
CkptRestore
InternalBeginDeferWindowPos
_DeferWindowPos
xxxEndDeferWindowPosEx (SWP_DRAWFRAME | SWP_NOCOPYBITS | SWP_STATECHANGE)
xxxSysCommand (SC_SIZE/SC_MOVE)
xxxMoveSize
xxxInitSendValidateMinMaxInfo
WM_GETMINMAXINFO
xxxMS_FlushWigglies
bSetDevDragWidth
xxxDrawDragRect
WM_ENTERSIZEMOVE
xxxCapture
xxxMS_TrackMove
xxxMS_TrackMove (WM_MOUSEMOVE)
xxxTM_MoveDragRect
WM_SIZING
xxxDrawDragRect
xxxMS_TrackMove (WM_LBUTTONUP)
xxxTM_MoveDragRect
xxxDrawDragRect
xxxReleaseCapture
xxxSetWindowPos
WM_EXITSIZEMOVE
3.1) SendMessage
SendMessage的消息SDK文档称之为nonqueued messages, 没放到消息队列(mlPost/mlInput)里面, GetMessage不会得到SendMessage的消息.
mlPost是PostMessage的消息队列, 比如WM_HOTKEY, WM_TIMER, WM_LOGONNOTIFY. WM_QUIT一般没存放在队列(Desktop/console窗口例外), 是直接放到xxxReadPostMessage的参数.
mlInput是键盘鼠标和系统事件的消息队列. psmsReceiveList是异线程SendMessage的SMS(SendMessage Structure)队列. GetMessage只取回mlPost/mlInput的消息, SMS是另外处理的.
xxxInternalGetMessage
while (TRUE) {
while (QS_SENDMESSAGE) xxxReceiveMessage (pti->psmsReceiveList)
if (QS_POSTMESSAGE) xxxReadPostMessage (pti->mlPost) break
if (QS_INPUT|QS_EVENT) xxxScanSysQueue (pti->pq->mlInput) break
xxxSleepThread
}
#define QS_ALLINPUT (QS_INPUT | QS_POSTMESSAGE | QS_TIMER | QS_PAINT | QS_HOTKEY | QS_SENDMESSAGE)
typedef struct tagTHREADINFO { ...
PQ pq; // keyboard and mouse input queue
PSMSpsmsReceiveList; // SMSs to be processed
MLISTmlPost; // posted message list.
} THREADINFO;
* Message Queue structure.
typedef struct tagQ { ...
MLIST mlInput;// raw mouse and key message list.
} Q, *PQ;
SendMessage如果同线程, 是直接call WndProc. 如果异线程, 一般是调用NtUserMessageCall(特殊的消息是调用fnCOPYGLOBALDATA/fnEMGETSEL这些系统函数). NtUserMessageCall又根据不同的消息分别调用45个NtUserfn*系统函数. 比如WM_SETTEXT是调用NtUserfnINSTRINGNULL, 进而调用xxxInterSendMsgEx.
xxxInterSendMsgEx构建一个sms(SendMessage Structure), 把消息复制到sms, 如果消息有指针参数, 比如WM_SETTEXT的lParam是字符指针, 就分配系统内存把指针所指的内容复制过来, 把sms加到系统gpsmsList队列首, 再把sms加到ptiReceiver->psmsReceiveList队列尾. 接着设置目标线程的QS_SENDMESSAGE标志, 激发目标线程的EventQueueServer, 然后等待自己的EventQueueServer.
目标线程一般多在消息循环的GetMessage中等待自己的EventQueueServer(如果正在处理消息, 处理完仍是回到GetMessage), EventQueueServer激发, 导致KeWaitForSingleObject返回, xxxSleepThread返回, 进入xxxReceiveMessage, 把sms从ptiReceiver->psmsReceiveList队列(从头到尾逐个处理)移除, 调用xxxSendMessageToClient进入usermode call WndProc再返回kernel mode, 接着设置源线程的QS_SMSREPLY标志, 激发源线程的EventQueueServer, 然后继续自己的消息循环.
源线程的EventQueueServer激发, 导致KeWaitForSingleObject返回, xxxSleepThread返回到xxxInterSendMsgEx, 把sms从系统gpsmsList队列移除, 释放系统内存和sms, 逐级返回到SendMessage.
SendMessage (同线程)
SendMessageWorker
UserCallWinProcCheckWow
InternalCallWinProc
WndProc
SendMessage (异线程)
SendMessageWorker
NtUserMessageCall (user mode/kernel mode切换)
EnterCrit
NtUserfnINSTRINGNULL (WM_SETTEXT)
RtlInitLargeUnicodeString
xxxWrapSendMessage (xParam = 0)
xxxSendMessageTimeout (fuFlags = SMTO_NORMAL, uTimeout = 0, lpdwResult = NULL)
xxxInterSendMsgEx (pism = 0, lRet = psms->lRet)
AllocSMS (psms->spwnd = pwnd, psms->ptiCallBackSender = NULL, psms->flags = 0)
HeavyAllocPool (dwFlags = DAP_USEQUOTA)
HMAssignmentLock (psms->spwnd)
SetWakeBit (pti = ptiReceiver, wWakeBit = QS_SENDMESSAGE)
KeSetEvent (pti->pEventQueueServer, PriorityIncrement = 2, WaitImmediatelyFollowed = FALSE)
KeSetKernelStackSwapEnable (Enable = FALSE)
xxxSleepThread (fsWakeMask = QS_SMSREPLY, Timeout = 0, fInputIdle = FALSE)
ClearQueueServerEvent
KeClearEvent(ptiCurrent->pEventQueueServer)
LeaveCrit
KeWaitForSingleObject (ptiCurrent->pEventQueueServer)
EnterCrit
KeSetKernelStackSwapEnable (Enable = OldState)
SetWakeBit (pti = ptiSender, wWakeBit = QS_SMSREPLY)
UnlinkSendListSms (ppsmsUnlink = NULL)
HMAssignmentUnlock (psms->spwnd)
UserFreePool
FreeSMS
LeaveCrit
xxxInternalGetMessage
xxxSleepThread (fsWakeMask = QS_ALLINPUT | QS_EVENT | QS_ALLPOSTMESSAGE, Timeout = 0, fInputIdle = TRUE)
ClearQueueServerEvent
KeClearEvent(ptiCurrent->pEventQueueServer)
LeaveCrit
KeWaitForSingleObject (ptiCurrent->pEventQueueServer, WaitReason = WrUserRequest, WaitMode = UserMode, Alertable = FALSE, Timeout = NULL)
EnterCrit
xxxReceiveMessage
xxxSendMessageToClient
SfnINSTRINGNULL
AllocCallbackMessage
CaptureCallbackData
LeaveCrit
KeUserModeCallback (ApiNumber = 0x1B)
KiGetUserModeStackAddress
KiCallUserMode ([esp].TsEip = KiUserCallbackDispatcher)
KiServiceExit
KiSystemCallExitBranch
KiSystemCallExit2
sysexit (kernel mode进入user mode)
KiUserCallbackDispatcher
__fnINSTRING
FixupCallbackPointers
DispatchClientMessage
UserCallWinProcCheckWow
InternalCallWinProc
WndProc
XyCallbackReturn
int 2b (user mode返回kernel mode)
KiCallbackReturn
EnterCrit
SetWakeBit(pti = ptiSender, wWakeBit = QS_SMSREPLY)
KeSetEvent (pti->pEventQueueServer)
----写于-11-3
如果觉得《windows消息处理过程及消息钩子》对你有帮助,请点赞、收藏,并留下你的观点哦!