4.2.8 线程的消息队列
消息队列是一个由数组结构构成的循环队列,即核心线程对象(__KERNEL_THREAD_OBJECT)定义的KernelThreadMsg数组,为方便阅读,把核心线程对象定义中关于线程消息队列的部分代码列举如下。
… … … __KERNEL_THREAD_MESSAGE KernelThreadMsg[MAX_KTHREAD_MSG_NUM]; UCHAR ucMsgQueueHeader; UCHAR ucMsgQueueTail; UCHAR ucCurrentMsgNum; UCHAR ucAligment; __EVENT* lpMsgEvent; … … …
KernelThreadMsg数组是一个类型为__KERNEL_THREAD_MESSAGE结构的数组,根据目前的定义,该数组大小是32(MAX_KTHREAD_MSG_NUM=32)。ucAligment是为了实现数据对齐(32 bit对齐),ucQueueHeader和ucQueueTail分别指向队列的头部和尾部,其中,ucQueueHeader指向队列的第一个非空元素(若队列非空的话),而ucQueueTail指向了消息队列中第一个空元素(若队列不满的话)。ucCurrentMsgNum则指出了当前队列中消息的个数,如图4-8所示。
图4-8 线程的消息队列
系统中的核心线程可以通过SendMessage函数调用向队列中发送消息,如果队列不满,则消息被存储在ucQueueTail所指向的位置,同时ucQueueTail后移一个元素(指向下一个非空位置),ucCurrentMsgNum增加1,如图4-9所示。
图4-9 线程消息队列的添加操作
线程本身可以调用GetMessage函数,从自己的消息队列中获取消息,若当前消息队列为空,则GetMessage函数阻塞(通过等待一个EVENT核心对象),直到有其他线程向本线程的消息队列中发送消息。若消息队列非空,则GetMessage函数取走ucQueueHeader所指位置的消息,然后ucQueueHeader向后移动一个位置,ucCurrentMsgNum减1,如图4-10所示。
图4-10 线程消息队列的删除操作
队列当前的状态(空或满)可以通过判断ucCurrentMsgNum的大小获得。
lpMsgEvent是一个__EVENT内核对象,该对象用来完成消息操作的同步。在当前Hello China的实现中,GetMessage函数是按照同步操作实现的,即若队列中有消息,则该函数立即返回,并把队列中的消息返回给用户程序,若队列中没有消息,则该函数阻塞,直到有消息到达。阻塞操作就是通过等待该事件对象实现的,下面是GetMessage函数的相关代码。
static BOOL MgrGetMessage(__COMMON_OBJECT* lpThread,__KERNEL_THREAD_MESSAGE* lpMsg) { __KERNEL_THREAD_OBJECT* lpKernelThread=NULL; DWORD dwFlags =0L; if((NULL==lpThread) || (NULL==lpMsg)) //Parameters check. return FALSE; lpKernelThread=(__KERNEL_THREAD_OBJECT*)lpThread; if(MsgQueueEmpty(lpThread)) { lpKernelThread->lpMsgEvent->WaitForThisObject( (__COMMON_OBJECT*)(lpKernelThread->lpMsgEvent)); //Block the current thread. } … … … return TRUE; }
在上述实现中,GetMessage函数首先判断线程的消息队列是否为空,若为空,则调用lpMsgEvent对象的WaitForThisObject函数等待lpMsgEvent对象。
而lpMsgEvent对象是被SendMessage函数唤醒的,SendMessage函数的相关实现代码如下。
static BOOL MgrSendMessage(__COMMON_OBJECT* lpThread,__KERNEL_THREAD_MESSAGE* lpMsg) { __KERNEL_THREAD_OBJECT* lpKernelThread=NULL; BOOL bResult =FALSE; DWORD dwFlags =0L; if((NULL==lpThread) || (NULL==lpMsg)) //Parameters check. return bResult; if(MsgQueueFull(lpThread)) //If the queue is full. return bResult; lpKernelThread=(__KERNEL_THREAD_OBJECT*)lpThread; … … …//Put the message into kernel thread's message queue. // //Set the signal to indicate there is one message to be get at least. // lpKernelThread->lpMsgEvent->SetEvent((__COMMON_OBJECT*)(lpKerne lThread->lpMsgEvent)); return bResult; }
在上述实现中,每向线程队列发送一个消息,就会调用SetEvent函数设置事件对象的状态,这样若当前线程因为调用GetMessage函数阻塞,在此时刻就会被唤醒。
对于线程的消息队列,最后需要解释的就是__KERNEL_THREAD_MESSAGE结构本身了,顾名思义,该结构用来装载具体的消息,定义如下。
BEGIN_DEFINE_OBJECT(__KERNEL_THREAD_MESSAGE) WORD wCommand; WORD wParam; DWORD dwParam; //DWORD (*MsgAssocRoutine)(__KERNEL_THREAD_MESSAGE*); END_DEFINE_OBJECT()
wCommand是一个命令字,指出具体的消息类型,比如键盘按下、鼠标按下等,也可以由用户自己定义。wParam和dwParam是两个跟wCommand关联的参数,比如,跟“键盘按下”这样一个消息相关联,可以是具体被按下的键的ASCII码(可以通过wParam设置)。
消息队列机制的应用十分广泛,也十分灵活,从理论上说,任何基于多线程通信的应用模型都可以使用消息队列来实现。