miniGUI通过接收消息来和外界交互。消息由系统或应用程序产生,系统对输入事件产生消息,系统对应用程序的响应也会产生消息,应用程序可以通过产生消息来完成某个任务,或者与其它应用程序的窗口进行通讯。总而言之,miniGUI 是消息驱动的系统,一切运作都围绕着消息进行。
MSG 消息结构
MSG 消息结构的成员包括该消息所属的窗口(hwnd)、消息标识(message)、消息的WPARAM 型参数(wParam)、消息的 LPARAM 型参数(lParam)以及消息发生的时间。
typedef struct _MSG
{
HWND hwnd;
int message;
WPARAM wParam;
LPARAM lParam;
unsigned int time;
}MSG;
typedef MSG* PMSG;
窗口句柄决定消息所发送的目标窗口。消息标识是一个整数常量,由它来标明消息的类型,每一个消息均有一个对应的预定义标识符。消息的参数对消息的内容作进一步的说明,它的意义通常取决于消息本身,可以是一个整数、位标志或数据结构指针等。
MSGQUEUE消息队列
miniGUI 是消息驱动的系统,消息驱动的含义就是,程序的流程不再是只有一个入口和若干个出口的串行执行线路;相反,程序会一直处于一个循环状态,在这个循环当中,程序不断从外部或内部获取某些事件,比如用户的按键或者鼠标的移动,然后根据这些事件作出某种响应,并完成一定的功能,这个循环直到程序接收到某个消息为止。它的底层实现就是靠消息队列。
typedef struct _QMSG
{
MSG Msg;
struct _QMSG* next;
}QMSG;
typedef QMSG* PQMSG
struct _MSGQUEUE
{
LWORD dwState;
PQMSG pFirstNotifyMsg;
PQMSG pLastNotifyMsg;
MSG* msg;
int len;
int readpos, writepos;
WORD TimerMask;
int loop_depth;
}
消息队列结构_MSGQUEUE的成员包含消息队列类型(dwState)、NOTIFY消息链表表头(pFirstNotifyMsg)、NOTIFY消息链表表尾(pLastNotifyMsg)、POST消息缓冲区(msg)、POST消息缓冲区大小(len)、消息队列读写标志位(readpos, writepos)。
初始化消息队列
BOOL InitMsgQueue (PMSGQUEUE pMsgQueue, int iBufferLen)
{
memset (pMsgQueue, 0, sizeof(MSGQUEUE));
pMsgQueue->dwState = QS_EMPTY;
if (iBufferLen <= 0)
iBufferLen = DEF_MSGQUEUE_LEN;
pMsgQueue->msg = malloc (sizeof (MSG) * iBufferLen);
if (pMsgQueue->msg)
{
memset(pMsgQueue->msg, 0, sizeof (MSG) * iBufferLen);
}
pMsgQueue->len = iBufferLen;
pMsgQueue->TimerMask = 0;
return TRUE;
}
消息队列的初始化主要将dwState赋值为QS_EMPTY类型,并开辟iBufferLen大小的缓冲区,DEF_MSGQUEUE_LEN默认值为16。
发送消息
miniGUI 有两种向窗口过程发送消息的办法:
- 把消息投递到一个先进先出的消息队列中,它是系统中用于存储消息的一块内存区域,每个消息存储在一个消息结构中。
- 或是把消息直接发送给窗口过程,也就是通过消息发送函数直接调用窗口过程函数。
miniGUI有三个主要的发送消息函数
- PostMessage
该函数将消息放到指定窗口的消息队列后立即返回。这种发送方式称为“邮寄”消息。如果消息队列中的邮寄消息缓冲区已满,则该函数返回错误值。PostMessage 一般用于发送一些非关键性的消息。比如在 MiniGUI 中,鼠标和键盘消息就是通过PostMessage 函数发送的。
PostMessage通过调用GetMsgQueue获取消息队列,并调用QueueMessage将消息写入消息队列。消息队列的读取会记录readpos和writepos标志位,当写入一个消息writepos就加1,读了一个消息readpos就加1,如果readpos和writepos相等就表示没有新消息,默认消息队列一次最多存msg_que->len的消息,通常为默认值DEF_MSGQUEUE_LEN,消息满了的话就会丢弃。QueueMessage函数把消息写入队列中,writepos加1,超过最大数之后就归零。
int GUIAPI PostMessage (HWND hWnd, int iMsg, WPARAM wParam, LPARAM lParam)
{
PMSGQUEUE pMsgQueue = NULL;
MSG msg;
if (!(pMsgQueue = GetMsgQueue(hWnd)))
{
return ERR_INV_HWND;
}
memset(&msg, 0, sizeof(msg));
msg.hwnd = hWnd;
msg.message = iMsg;
msg.wParam = wParam;
msg.lParam = lParam;
msg.time = 0;
if (!QueueMessage(pMsgQueue, &msg))
{
return ERR_QUEUE_FULL;
}
return ERR_OK;
}
BOOL QueueMessage (PMSGQUEUE msg_que, PMSG msg)
{
LOCK_MSGQ(msg_que);
if ((msg_que->writepos + 1) % msg_que->len == msg_que->readpos)
{
UNLOCK_MSGQ(msg_que);
return FALSE;
}
msg_que->msg [msg_que->writepos] = *msg;
msg_que->writepos++;
if (msg_que->writepos >= msg_que->len)
{
msg_que->writepos = 0;
}
msg_que->dwState |= QS_POSTMSG;
UNLOCK_MSGQ (msg_que);
return TRUE;
}
- SendNotifyMessage
该函数和 PostMessage 消息类似,也是不等待消息被处理即返回。但和 PostMessage 消息不同,通过该函数发送的消息不会因为缓冲区满而丢失,因为系统采用链表的形式处理这种消息。通过该函数发送的消息称为“通知消息”,一般用来从控件向其父窗口发送通知消息。
SendNotifyMessage 函数将消息写入消息队列中的链表QMSG中,发送Notify消息时会创建链表节点,并接入链表,通过维护链表表头pFirstNotifyMsg和表尾pLastNotifyMsg指针记录消息。
int GUIAPI SendNotifyMessage (HWND hWnd, int iMsg, WPARAM wParam, LPARAM lParam)
{
PMSGQUEUE pMsgQueue;
PQMSG pqmsg;
if (!(pMsgQueue = GetMsgQueue(hWnd)))
return ERR_INV_HWND;
pqmsg = QMSGAlloc();
if (pqmsg == NULL) {
return ERR_RES_ALLOCATION;
}
LOCK_MSGQ (pMsgQueue);
pqmsg->Msg.hwnd = hWnd;
pqmsg->Msg.message = iMsg;
pqmsg->Msg.wParam = wParam;
pqmsg->Msg.lParam = lParam;
pqmsg->next = NULL;
if (pMsgQueue->pFirstNotifyMsg == NULL) {
pMsgQueue->pFirstNotifyMsg = pMsgQueue->pLastNotifyMsg = pqmsg;
}
else {
if (pMsgQueue->pLastNotifyMsg == NULL) {
pMsgQueue->pLastNotifyMsg = pqmsg;
}
else {
pMsgQueue->pLastNotifyMsg->next = pqmsg;
pMsgQueue->pLastNotifyMsg = pqmsg;
}
}
pMsgQueue->dwState |= QS_NOTIFYMSG;
UNLOCK_MSGQ (pMsgQueue);
return ERR_OK;
}
- SendMessage
该函数和 PostMessage 函数不同,它把一条消息发送给指定窗口的窗口过程,而且等待该窗口过程完成消息的处理之后才会返回。当需要知道某个消息的处理结果时,使用该函数发送消息,然后根据其返回值进行处理。
SendMessage函数通过调用GetWndProc获取窗口过程函数,并直接调用窗口过程函数处理消息,处理结束后返回。
int GUIAPI SendMessage(HWND hWnd, int iMsg, WPARAM wParam, LPARAM lParam)
{
...//省略
WndProc = GetWndProc(hWnd) //获取消息处理函数
(*WndProc)(hWnd, iMsg, wParam, lParam) //直接处理消息并返回
...
}
处理消息
程序会一直处于一个循环状态,在这个循环当中,不断从消息队列中获取消息并处理,直到程序接收到退出消息QS_QUIT为止。GetMessage从消息队列中取出消息,主要调用PeekMessageEx函数处理,PeekMessageEx处理消息时会按以下优先级处理:QS_QUIT、QS_NOTIFYMSG、QS_POSTMSG、QS_PAINT、QS_DESKTIMER。TranslateMessage把击键消息转换为 MSG_CHAR 消息,然后直接发送到窗口过程函数。DispatchMessage获取窗口过程函数并直接调用处理消息。下面主要介绍PeekMessageEx函数如何处理消息队列中的NOTIFYMSG消息和POSTMSG消息。
while (GetMessage (&Msg, hWnd))
{
if( !TranslateMessage (&Msg) )
{
}
if( !DispatchMessage (&Msg) )
{
}
}
BOOL PeekMessageEx (PMSG pMsg, HWND hWnd, int iMsgFilterMin, int iMsgFilterMax,
BOOL bWait, UINT uRemoveMsg)
{
PMSGQUEUE pMsgQueue;
PQMSG phead;
int slot;
PMG_TDEINFO pstruTdeInfo = NULL;
#ifndef _LITE_VERSION
if (!(thi = GetThreadInfo ((pthread_t)HPR_Thread_GetSelfId())))
{
return FALSE;
}
pMsgQueue = thi->pMsgQueue;
#else
pMsgQueue = __mg_dsk_msg_queue;
#endif
memset (pMsg, 0, sizeof(MSG));
LOCK_MSGQ (pMsgQueue);
//QS_QUIT
if (pMsgQueue->dwState & QS_QUIT) {
pMsg->hwnd = hWnd;
pMsg->message = MSG_QUIT;
pMsg->wParam = 0;
pMsg->lParam = 0;
SET_PADD (NULL);
if (uRemoveMsg == PM_REMOVE) {
pMsgQueue->loop_depth --;
if (pMsgQueue->loop_depth == 0)
pMsgQueue->dwState &= ~QS_QUIT;
}
UNLOCK_MSGQ (pMsgQueue);
return FALSE;
}
//QS_NOTIFYMSG
if (pMsgQueue->dwState & QS_NOTIFYMSG) {
if (pMsgQueue->pFirstNotifyMsg) {
phead = pMsgQueue->pFirstNotifyMsg;
*pMsg = phead->Msg;
SET_PADD (NULL);
if (IS_MSG_WANTED(pMsg->message)) {
if (uRemoveMsg == PM_REMOVE) {
pMsgQueue->pFirstNotifyMsg = phead->next;
FreeQMSG (phead);
}
UNLOCK_MSGQ (pMsgQueue);
return TRUE;
}
}
else
pMsgQueue->dwState &= ~QS_NOTIFYMSG;
}
//QS_POSTMSG
if (pMsgQueue->dwState & QS_POSTMSG) {
if (pMsgQueue->readpos != pMsgQueue->writepos) {
*pMsg = pMsgQueue->msg[pMsgQueue->readpos];
SET_PADD (NULL);
if (IS_MSG_WANTED(pMsg->message)) {
if (uRemoveMsg == PM_REMOVE) {
pMsgQueue->readpos++;
if (pMsgQueue->readpos >= pMsgQueue->len)
pMsgQueue->readpos = 0;
}
UNLOCK_MSGQ (pMsgQueue);
return TRUE;
}
}
else
pMsgQueue->dwState &= ~QS_POSTMSG;
}
...
}
- 当获取的消息类型为QS_QUIT时,返回FALSE结束循环。
- 当获取的消息类型为QS_NOTIFYMSG时,则从NOTIFY消息链表表头取出消息,并将表头指针指向链表下一节点,释放表头节点空间。
- 当获取的消息类型为QS_POSTMSG时,首先通过判断readpos和writepos是否相等确认POST消息缓冲区中是否有新消息,从readpos位置读取消息后将readpos加1,若readpos超过默认大小则置为0,下次读取时则从缓冲区开始位置。