消息循环是事件驱动的 GUI 编程基础。在循环中,程序从外部输入设备获取某些事件,比如按键或鼠标移动。然后根据这些事情做出某种响应,并完成一定的功能。这个循环直到程序接受到某个消息为止。窗口是 MiniGUI 当中最基本的 GUI 元素,一旦窗口建立之后,窗口就会从消息队列当中获取属于自己的消息,然后交由它的窗口过程进行处理。这些消息当中,有一些是基本的输入设备事件,而有一些则是与窗口管理相关的逻辑消息。
MSG
●PROTOTYPE
typedef struct _MSG
{
/** The handle to the window which receives this message. */
HWND hwnd;
/** The message identifier. */
int message;
/** The first parameter of the message (32-bit integer). */
WPARAM wParam;
/** The second parameter of the message (32-bit integer). */
LPARAM lParam;
/** Time*/
unsigned int time;
#ifdef _MGRM_THREADS
/** Addtional data*/
void* pAdd;
#endif
} MSG;
●MEMBERS
hwnd : 接受消息的窗口句柄
message : 消息编号
wParam : 第一个消息参数
lParam : 第二个消息参数
time : 时间
pAdd : 附加数据
●DESCRIPTION
_MSG用于描述消息信息
GetMessage:从自己的消息队列中取出一条消息,其关键代码如下,
GetMessage(PMSG pMsg, HWND hWnd)
PeekMessageEx(pMsg, hWnd, 0, 0, TRUE, PM_REMOVE)
PMSGQUEUE pMsgQueue
PQMSG phead
pMsgQueue = GetMsgQueueThisThread()
MSGQUEUE* pMsgQueue
pMsgQueue = (MSGQUEUE*) pthread_getspecific(__mg_threadinfo_key) //get the key
return pMsgQueue
memset(pMsg, 0, sizeof(MSG))
if((pMsgQueue->dwState & QS_QUIT)) //QUIT msg
{
hwnd:hWnd
message:MSG_QUIT
wParam:0
lParam:0
pAdd:NULL
if (uRemoveMsg == PM_REMOVE)
{
pMsgQueue->loop_depth--;
if (pMsgQueue->loop_depth == 0)
{
pMsgQueue->dwState &= ~QS_QUIT;
}
}
}
if(pMsgQueue->dwState & QS_SYNCMSG) //SYNC msg
{
if(pMsgQueue->pFirstSyncMsg)
{
*pMsg = pMsgQueue->pFirstSyncMsg->Msg
pMsg->pAdd = pMsgQueue->pFirstSyncMsg
if(uRemoveMsg == PM_REMOVE)
{
pMsgQueue->pFirstSyncMsg = pMsgQueue->pFirstSyncMsg->pNext
}
}
else
{
pMsgQueue->dwState &= ~QS_SYNCMSG
}
}
if(pMsgQueue->dwState & QS_NOTIFYMSG) //NOTIFY MSG
{
if(pMsgQueue->pFirstNotifyMsg)
{
phead = pMsgQueue->pFirstNotifyMsg
*pMsg = phead->Msg
pMsg->pAdd = NULL
if (IS_MSG_WANTED(pMsg->message))
{
if (uRemoveMsg == PM_REMOVE)
{
pMsgQueue->pFirstNotifyMsg = phead->next;
FreeQMSG (phead);
}
}
}
else
pMsgQueue->dwState &= ~QS_NOTIFYMSG
}
if(pMsgQueue->dwState & QS_POSTMSG) //POST MSG
{
if(pMsgQueue->readpos != pMsgQueue->writepos)
{
*pMsg = pMsgQueue->msg[pMsgQueue->readpos]
pMsg->pAdd = NULL
if(IS_MSG_WANTED(pMsg->message))
{
CheckCapturedMouseMessage(pMsg)
if(uRemoveMsg == PM_REMOVE)
{
pMsgQueue->readpos++
pMsgQueue->readpos %= pMsgQueue->len
}
}
}
else
pMsgQueue->dwState &= ~QS_POSTMSG
}
/* check invalidate region of the windows */
if(pMsgQueue->dwState & QS_PAINT && IS_MSG_WANTED(MSG_PAINT))
{
if(hWnd == HWND_DESKTOP)
{
pMsg->hwnd = hWnd;
pMsg->message = MSG_PAINT;
pMsg->wParam = 0;
pMsg->lParam = 0;
pMsg->pAdd = NULL;
if(uRemoveMsg == PM_REMOVE)
{
pMsgQueue->dwState &= ~QS_PAINT;
}
}
pMsg->message = MSG_PAINT;
pMsg->wParam = 0;
pMsg->lParam = 0;
pMsg->pAdd = NULL;
pHostingRoot = pMsgQueue->pRootMainWin
if((hNeedPaint = msgCheckHostedTree(pHostingRoot)))
{
pMsg->hwnd = hNeedPaint
pWin = (PMAINWIN) hNeedPaint
pMsg->lParam = (LPARAM)(&pWin->InvRgn.rgn)
return TRUE
}
pMsgQueue->dwState &= ~QS_PAINT
}
if(pMsgQueue->TimerMask && IS_MSG_WANTED(MSG_TIMER))
{
if(hWnd == HWND_DESKTOP)
{
pMsg->hwnd = hWnd
pMsg->message = MSG_TIMER
pMsg->wParam = 0
pMsg->lParam = 0
pMsg->pAdd = NULL
if(uRemoveMsg == PM_REMOVE)
{
pMsgQueue->TimerMask = 0
}
}
/* get the first expired timer slot */
slot = pMsgQueue->FirstTimerSlot
do
{
if(pMsgQueue->TimerMask & (0x01 << slot))
break;
slot ++;
slot %= DEF_NR_TIMERS;
if(slot == pMsgQueue->FirstTimerSlot)
{
slot = -1;
break;
}
}while (TRUE)
pMsgQueue->FirstTimerSlot++
pMsgQueue->FirstTimerSlot %= DEF_NR_TIMERS
if((timer = __mg_get_timer(slot)))
{
...
if(timer->proc)
{
/* calling the timer callback procedure */
ret_timer_proc = timer->proc(timer->hWnd, timer->id, tick_count);
...
}
else
{
pMsg->message = MSG_TIMER;
pMsg->hwnd = timer->hWnd;
pMsg->wParam = timer->id;
pMsg->lParam = tick_count;
pMsg->pAdd = NULL;
}
}
}
if(bWait) //TRUE
{
/* no message, wait again. */
sem_wait(&pMsgQueue->wait) //P2
}
return FALSE
TranslateMessage:把击键消息转换为 MSG_CHAR 消息,然后直接发送到窗口过程函数。其关键代码如下,
TranslateMessage(PMSG pMsg)
if((pMsg->hwnd != HWND_DESKTOP))
{
...
handle_scancode_on_keydown
...
handle_scancode_on_keyup
}
...
SendNotifyMessage(pMsg->hwnd, MSG_CHAR, kinfo.buff[0], pMsg->lParam)
...
DispatchMessage:最终把消息发往消息的目标窗口的窗口过程,让窗口过程进行处理。其关键代码如下,
WndProc = GetWndProc(pMsg->hwnd)
(*WndProc)(pMsg->hwnd, pMsg->message, pMsg->wParam, pMsg->lParam) //call the callback func
if(pMsg->pAdd)
{
pSyncMsg = (PSYNCMSG)pMsg->pAdd
pSyncMsg->retval = iRet
sem_post(pSyncMsg->sem_handle) //V3
}
2.4.2 Message Loop
在这个循环体中,程序利用 GetMessage 函数不停地从消息队列中获得消息,然后利用 DispatchMessage 函数将消息发送到指定的窗口,也就是调用指定窗口的窗口过程,并传递消息及其参数。程序中一般使用下列语句进行消息循环:
while (GetMessage (&Msg, hMainWnd)) {
TranslateMessage (&Msg);
DispatchMessage (&Msg);
}
GetMessage 函数从 hMainWnd 窗口所属的消息队列当中获得消息,然后调用 TranslateMessage 函数将 MSG_KEYDOWN 和 MSG_KEYUP 消息翻译成 MSG_CHAR 消息,最后调用 DispatchMessage 函数将消息发送到指定的窗口。
在MiniGUI消息机制中,主要提供了以下几个消息处理函数。
PostMessage:字面理解为邮寄消息。将消息放到指定窗口的消息队列后立即返回。此消息在消息队列的存储形式为消息缓冲区。存在缓冲区满的情况。PostMessage 一般用于发送一些非关键性的消息。例如鼠标和键盘消息。其关键代码如下,
PostMessage(HWND hWnd, int iMsg, WPARAM wParam, LPARAM lParam)
pMsgQueue = kernel_GetMsgQueue(hWnd)
if(iMsg == MSG_PAINT)
{
pMsgQueue->dwState |= QS_PAINT
if(!BE_THIS_THREAD(hWnd))
POST_MSGQ(pMsgQueue)
sem_post(&(pMsgQueue)->wait) //V1
}
msg.hwnd = hWnd
msg.message = iMsg
msg.wParam = wParam
msg.lParam = lParam
kernel_QueueMessage(pMsgQueue, &msg)
...
/* Write the data and advance write pointer */
msg_que->msg [msg_que->writepos] = *msg
msg_que->writepos++
msg_que->writepos %= msg_que->len
msg_que->dwState |= QS_POSTMSG
if (!BE_THIS_THREAD (msg->hwnd))
POST_MSGQ(msg_que)
SendNotifyMessage:和PostMessage类似,发送即返回,不等待消息被处理。两者区别是SendNotifyMessage发送的消息在消息队列的存储形式是链表,不存在消息队列满而丢失消息的情况。一般用来从控件向其父窗口发送通知。其关键代码如下,
SendNotifyMessage(HWND hWnd, int iMsg, WPARAM wParam, LPARAM lParam)
pMsgQueue = kernel_GetMsgQueue(hWnd)
/* queue the notification message. */
pqmsg->Msg.hwnd = hWnd
pqmsg->Msg.message = iMsg
pqmsg->Msg.wParam = wParam
pqmsg->Msg.lParam = lParam
pqmsg->next = NULL
if(pMsgQueue->pFirstNotifyMsg == NULL) //added to the list
{
pMsgQueue->pFirstNotifyMsg = pMsgQueue->pLastNotifyMsg = pqmsg;
}
else
{
pMsgQueue->pLastNotifyMsg->next = pqmsg;
pMsgQueue->pLastNotifyMsg = pqmsg;
}
pMsgQueue->dwState |= QS_NOTIFYMSG
if( !BE_THIS_THREAD(hWnd) )
POST_MSGQ(pMsgQueue)
sem_post(&(pMsgQueue)->wait) //V2
SendSyncMessage:发送同步消息,即将要发送的同步消息加入消息队列后,等待消息被处理之后才返回。其关键代码如下,
SendSyncMessage(HWND hWnd, int msg, WPARAM wParam, LPARAM lParam)
pMsgQueue = kernel_GetMsgQueue(hWnd)
if((thinfo = GetMsgQueueThisThread()))
{
/* avoid to create a new semaphore object */
SyncMsg.sem_handle = &thinfo->sync_msg
}
else
{
/* this is not a GUI thread */
sem_init (&sync_msg, 0, 0)
SyncMsg.sem_handle = &sync_msg
}
/* queue the sync message. */
SyncMsg.Msg.hwnd = hWnd
SyncMsg.Msg.message = msg
SyncMsg.Msg.wParam = wParam
SyncMsg.Msg.lParam = lParam
SyncMsg.retval = ERR_MSG_CANCELED
SyncMsg.pNext = NULL
if(thinfo)
{
MSG msg
PMSGQUEUE pMsgQueue = thinfo
while(TRUE)
{
if(pMsgQueue->dwState & QS_SYNCMSG)
{
if(pMsgQueue->pFirstSyncMsg)
{
msg = pMsgQueue->pFirstSyncMsg->Msg
msg.pAdd = (pMsgQueue->pFirstSyncMsg)
pMsgQueue->pFirstSyncMsg = pMsgQueue->pFirstSyncMsg->pNext
TranslateMessage(&msg)
SendNotifyMessage(pMsg->hwnd, MSG_CHAR, kinfo.buff[0], pMsg->lParam)
DispatchMessage(&msg)
sem_post(pSyncMsg->sem_handle) //V3
}
else
{
pMsgQueue->dwState &= ~QS_SYNCMSG
break;
}
}
else
break;
}
}
if(pMsgQueue->pFirstSyncMsg == NULL)
{
pMsgQueue->pFirstSyncMsg = pMsgQueue->pLastSyncMsg = &SyncMsg;
}
else
{
pMsgQueue->pLastSyncMsg->pNext = &SyncMsg;
pMsgQueue->pLastSyncMsg = &SyncMsg;
}
pMsgQueue->dwState |= QS_SYNCMSG
POST_MSGQ(pMsgQueue)
sem_post(&(pMsgQueue)->wait) //V2
sem_wait(SyncMsg.sem_handle) //P3
if(thinfo == NULL)
sem_destroy(&sync_msg)
SendMessage:与PostMessage不同,它在发送一条消息给指定窗口时,将等待该消息被处理之后才会返回。当需要知道某个消息的处理结果时,使用该函数发送消息,然后根据其返回值进行处理。在MG-T模式中,如果发送消息的线程和接收消息的线程不是同一个线程,发送消息的线程将阻塞并等待另一个线程的处理结果,然后继续运行;否则,SendMessage 函数将直接调用接收消息窗口的窗口过程函数。其关键代码如下,
SendMessage(HWND hWnd, int iMsg, WPARAM wParam, LPARAM lParam)
if(!BE_THIS_THREAD(hWnd))
return SendSyncMessage(hWnd, iMsg, wParam, lParam)
WndProc = GetWndProc(hWnd)
return (*WndProc)(hWnd, iMsg, wParam, lParam)
SendAsyncMessage:发送异步消息,如果窗口处理函数存在,则直接调用窗口处理函数。其关键代码如下,
SendAsyncMessage(HWND hWnd, int iMsg, WPARAM wParam, LPARAM lParam)
WndProc = GetWndProc(hWnd)
return (*WndProc)(hWnd, iMsg, wParam, lParam)
PostSyncMessage:如果发送消息的线程和接收消息的线程是同一个线程,则不做处理。不同线程则调用SendSyncMessage发送同步消息。其关键代码如下,
PostSyncMessage(HWND hWnd, int msg, WPARAM wParam, LPARAM lParam)
if(BE_THIS_THREAD(hWnd))
return -1
return SendSyncMessage(hWnd, msg, wParam, lParam)
PostQuitMessage:该消息在消息队列中设置一个 QS_QUIT 标志。GetMessage 在从指定消息队列中获取消息时,会检查该标志,如果有 QS_QUIT 标志,GetMessage 消息将返回 FALSE,从而可以利用该返回值终止消息循环。其关键代码如下,
if(hWnd && hWnd != HWND_INVALID)
{
PMAINWIN pWin = (PMAINWIN)hWnd
if (pWin->DataType == TYPE_HWND || pWin->DataType == TYPE_WINTODEL)
{
pMsgQueue = pWin->pMainWin->pMessages
}
}
if(!(pMsgQueue->dwState & QS_QUIT))
{
pMsgQueue->loop_depth ++
pMsgQueue->dwState |= QS_QUIT
}
if(!BE_THIS_THREAD(hWnd))
POST_MSGQ(pMsgQueue)
sem_post(&(pMsgQueue)->wait) //V1
ThrowAwayMessages:丢弃和指定窗口相关的消息队列中的所有消息,并返回所丢弃的消息个数。其关键代码如下,
ThrowAwayMessages(HWND hWnd)
if(pMsgQueue->pFirstNotifyMsg) //notification messages thrown
{
pQMsg = pMsgQueue->pFirstNotifyMsg;
while(pQMsg)
{
pMsg = &pQMsg->Msg;
if(pMsg->hwnd == hWnd || gui_GetMainWindowPtrOfControl(pMsg->hwnd) == (PMAINWIN)hWnd)
{
pMsg->hwnd = HWND_INVALID
nCountN ++
}
pQMsg = pQMsg->next
}
}
if(pMsgQueue->pFirstSyncMsg) //sync messages thrown
{
pSyncMsg = pMsgQueue->pFirstSyncMsg
while(pSyncMsg)
{
pMsg = &pSyncMsg->Msg
if(pMsg->hwnd == hWnd || gui_GetMainWindowPtrOfControl(pMsg->hwnd) == (PMAINWIN)hWnd)
{
pMsg->hwnd = HWND_INVALID
nCountS ++
pSyncMsg->retval = ERR_MSG_CANCELED
if(pSyncPrev)
{
pSyncPrev->pNext = pSyncMsg->pNext ? pSyncMsg->pNext : NULL;
}
else
{
pSyncPrev = pSyncMsg;
pSyncMsg = pSyncMsg->pNext;
pMsgQueue->pFirstSyncMsg = pSyncMsg;
sem_post(pSyncPrev->sem_handle); //V3
pSyncPrev = NULL;
continue;
}
sem_post(pSyncMsg->sem_handle) //V3
}
pSyncPrev = pSyncMsg
pSyncMsg = pSyncMsg->pNext
}
}
readpos = pMsgQueue->readpos
while(readpos != pMsgQueue->writepos) //post messages thrown
{
pMsg = pMsgQueue->msg + readpos
if(pMsg->hwnd == hWnd || gui_GetMainWindowPtrOfControl (pMsg->hwnd) == (PMAINWIN)hWnd)
{
pMsg->hwnd = HWND_INVALID
nCountP ++
}
readpos++
readpos %= pMsgQueue->len
}
/* clear timer message flags of this window */
for(slot = 0; slot < DEF_NR_TIMERS; slot++)
{
if(pMsgQueue->TimerMask & (0x01 << slot))
{
HWND timer_wnd = __mg_get_timer_hwnd(slot)
if(timer_wnd == hWnd || gui_GetMainWindowPtrOfControl(timer_wnd) == (PMAINWIN)hWnd)
{
RemoveMsgQueueTimerFlag(pMsgQueue, slot)
}
}
}
return nCountN + nCountS + nCountP
BroadcastMessage:调用SendMessage将指定消息广播给桌面上的所有主窗口。其关键代码如下,
BroadcastMessage(int iMsg, WPARAM wParam, LPARAM lParam)
SendMessage(HWND_DESKTOP, MSG_BROADCASTMSG, 0, (LPARAM)(&msg))