miniGUI消息隊列的定義
miniGUI(版本:libminigui-gpl-3.0.12)的消息隊列支持通知消息、同步消息和普通消息三種消息類型,_MSGQUEUE消息隊列結構容納了這三種消息隊列,也就是說真正的消息隊列在_MSGQUEUE內部才實現,_MSGQUEUE結構只是將這三種消息隊列做了一層封裝。其中普通消息的內存使用方式為“動態數組”方式,即通過malloc方式申請固定大小的連續內存;同步消息和通知消息的內存使用方式為“鏈表”方式。
// the MSGQUEUE struct is a internal struct.
// using semaphores to implement message queue.
struct _MSGQUEUE
{
DWORD dwState; // message queue states
#ifdef _MGRM_THREADS
pthread_mutex_t lock; // lock
sem_t wait; // the semaphore for wait message
sem_t sync_msg; // the semaphore for sync message
#endif
PQMSG pFirstNotifyMsg; // head of the notify message queue
PQMSG pLastNotifyMsg; // tail of the notify message queue
#ifdef _MGRM_THREADS
PSYNCMSG pFirstSyncMsg; // head of the sync message queue
PSYNCMSG pLastSyncMsg; // tail of the sync message queue
#else
IDLEHANDLER OnIdle; // Idle handler
#endif
#ifdef _MGRM_THREADS
PMAINWIN pRootMainWin; // The root main window of this message queue.
#endif
MSG* msg; /* post message buffer */
int len; /* buffer len */
int readpos, writepos; /* positions for reading and writing */
int FirstTimerSlot; /* the first timer slot to be checked */
DWORD TimerMask; /* timer slots mask */
int loop_depth; /* message loop depth, for dialog boxes. */
};
miniGUI消息隊列的接口
消息隊列作為一種抽象數據類型(ADT),是有和它自身相關的一套接口的,常見的隊列接口的功能如下:
- 創建一個隊列;
- 銷毀一個隊列;
- 向隊列中插入一個元素;
- 從隊列中刪除一個元素;
- 返回隊列頭部的第一個元素;
- 判斷隊列是否為空;
- 判斷隊列是否為滿;
- 返回隊列的size;
- 返回隊列中元素的個數;
- ...
創建消息隊列
首先,看一下miniGUI中創建隊列的函數定義,位于src/kernel/message.c文件中,該函數的原型為:
BOOL mg_InitMsgQueue (PMSGQUEUE pMsgQueue, int iBufferLen);
從其接口中可以看出來,該函數接收一個_MSGQUEUE類型的指針(所以在此函數之前,首先要申請一個_MSGQUEUE結構體或定義一個_MSGQUEUE指針),和一個整數iBufferLen(用于作為普通消息的消息隊列的size),返回消息隊列是否創建成功。下面為該函數的定義。
BOOL mg_InitMsgQueue (PMSGQUEUE pMsgQueue, int iBufferLen)
{
memset (pMsgQueue, 0, sizeof(MSGQUEUE));
pMsgQueue->dwState = QS_EMPTY;
#ifdef _MGRM_THREADS
pthread_mutex_init (&pMsgQueue->lock, NULL);
sem_init (&pMsgQueue->wait, 0, 0);
sem_init (&pMsgQueue->sync_msg, 0, 0);
#endif
if (iBufferLen <= 0)
iBufferLen = DEF_MSGQUEUE_LEN;
/* 為消息隊列申請動態內存數組 */
pMsgQueue->msg = malloc (sizeof (MSG) * iBufferLen);
if (!pMsgQueue->msg) {
#ifdef _MGRM_THREADS
pthread_mutex_destroy (&pMsgQueue->lock);
sem_destroy (&pMsgQueue->wait);
sem_destroy (&pMsgQueue->sync_msg);
return FALSE;
#endif
}
pMsgQueue->len = iBufferLen; //指定普通消息的消息隊列長度
pMsgQueue->FirstTimerSlot = 0;
pMsgQueue->TimerMask = 0;
return TRUE;
}
由于只有普通消息的消息隊列的內存使用方式采用“動態數組”,所以創建或初始化miniGUI的消息隊列時,只對普通消息隊列及其它數據成員做初始化,不對采用鏈表的同步消息和通知消息隊列初始化。那同步消息和通知消息隊列的初始化放在哪了呢?
由于同步消息和通知消息采用的是鏈表的內存組織方式,每個節點的獲取和釋放都是動態的,在初始化miniGUI的消息隊列時,由于還沒有分配消息,所以通知消息和同步消息的隊列指針沒有指向實際的內存位置。通過mg_InitMsgQueue函數中的“memset (pMsgQueue, 0, sizeof(MSGQUEUE));”已將這兩類消息的指針設為NULL。
銷毀消息隊列
銷毀miniGUI的消息隊列是由mg_DestroyMsgQueue函數完成的。該函數對通知消息和普通消息的消息隊列做了銷毀。同步消息由于它的特性,其釋放或銷毀不必放在該函數內進行。
void mg_DestroyMsgQueue (PMSGQUEUE pMsgQueue)
{
PQMSG head;
PQMSG next;
head = next = pMsgQueue->pFirstNotifyMsg;
while (head) {
next = head->next;
FreeQMSG (head);
head = next;
}
#ifdef _MGRM_THREADS
pthread_mutex_destroy (&pMsgQueue->lock);
sem_destroy (&pMsgQueue->wait);
sem_destroy (&pMsgQueue->sync_msg);
#endif
mg_remove_timers_by_msg_queue (pMsgQueue);
/* 普通消息隊列的銷毀 */
if (pMsgQueue->msg)
free (pMsgQueue->msg);
pMsgQueue->msg = NULL;
}
向消息隊列中插入消息
普通消息的插入
普通消息的插入是通過PostMessage()函數和kernel_QueueMessage()函數完成的。PostMessage()完成對消息的封裝,kernel_QueueMessage()將普通消息投遞到消息隊列中。
- PostMessage()
- 根據窗口句柄獲取消息隊列句柄;
- 根據PostMessage()參數封裝消息體;
- 調用kernel_QueueMessage()向消息隊列插入消息;
- kernel_QueueMessage()
- 首先是檢查幾種特殊消息:MSG_MOUSEMOVE、MSG_NCMOUSEMOVE、MSG_DT_MOUSEMOVE、MSG_TIMEOUT、MSG_IDLE、MSG_CARETBLINK,如果要插入的消息是這幾種的話,先做特殊處理后,再變相插入到消息隊列中;
- 平常消息的插入:
/* Write the data and advance write pointer */
msg_que->msg [msg_que->writepos] = *msg;
msg_que->writepos++;
#if 0
//此語句無效
if (msg_que->writepos >= msg_que->len) msg_que->writepos = 0;
#else
msg_que->writepos %= msg_que->len;
#endif
**
通過這兩個函數可以看到,對于miniGUI消息隊列中的普通消息,miniGUI是采用動態數組(malloc分配內存)的方式實現循環隊列,讀指針指向循環隊列中的頭部第一個元素,寫指針指向循環隊列中尾部的最后一個元素的下一個寫位置,并且循環隊列采用“空閑一個位置”的方式避免“判斷隊列為空或為滿時讀寫指針相同的情況”。**
同步消息的插入
同步消息的插入由SendSyncMessage()函數完成,該函數向另一個線程中的窗口發送消息,該函數的處理流程:一先將自己同步消息隊列中所有的同步消息都處理完,這樣是為了避免死鎖,如果兩個線程以相差較短的時間互給對方發送同步消息,線程一等待線程二處理消息而阻塞,而線程二也因等待線程一處理消息而阻塞;二是將同步消息插入到另一個線程的消息隊列中,這一部分的代碼如下。
LOCK_MSGQ (pMsgQueue);
if (pMsgQueue->pFirstSyncMsg == NULL) {
pMsgQueue->pFirstSyncMsg = pMsgQueue->pLastSyncMsg = &SyncMsg;
}
else {
pMsgQueue->pLastSyncMsg->pNext = &SyncMsg;
pMsgQueue->pLastSyncMsg = &SyncMsg;
}
pMsgQueue->dwState |= QS_SYNCMSG;
UNLOCK_MSGQ (pMsgQueue);
POST_MSGQ (pMsgQueue);
/* suspend until the message has been handled. */
if (sem_wait (SyncMsg.sem_handle) < 0) {
fprintf (stderr,
"KERNEL>SendSyncMessage: thread is interrupted abnormally!\n");
}
通知消息的插入
通知消息的插入由SendNotifyMessage()和SendTopNotifyMessage()函數完成,SendTopNotifyMessage()函數主要是對于緊急的消息,亟需被處理,將其插在通知消息的頭部。
- SendNotifyMessage()
LOCK_MSGQ (pMsgQueue);
/* 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) {
pMsgQueue->pFirstNotifyMsg = pMsgQueue->pLastNotifyMsg = pqmsg;
}
else {
pMsgQueue->pLastNotifyMsg->next = pqmsg;
pMsgQueue->pLastNotifyMsg = pqmsg;
}
pMsgQueue->dwState |= QS_NOTIFYMSG;
UNLOCK_MSGQ (pMsgQueue);
- SendTopNotifyMessage()
SendTopNotifyMessage()代碼與SendNotifyMessage()類似,此處不再列出。
從消息隊列中刪除消息
在miniGUI中是從消息隊列中獲取消息,這部分工作由GetMessage()和PeekMessageEx()函數完成。GetMessage()只是對PeekMessageEx()做了一層封裝,內部是直接調用PeekMessageEx()。GetMessage函數的定義如下:
/**
* \fn BOOL GetMessage (PMSG pMsg, HWND hMainWnd)
* \brief Gets a message from the message queue of a main window.
*
* This function gets a message from the message queue of the main window
* \a hMainWnd, and returns until there is a message in the message queue.
*
* \param pMsg Pointer to the result message.
* \param hMainWnd Handle to the window.
*
* \return FALSE on MSG_QUIT have been found or on error, else gets a message.
*
* \sa HavePendingMessage, PostQuitMessage, MSG
*
* Example:
*
* \include getmessage.c
*/
static inline BOOL GUIAPI GetMessage (PMSG pMsg, HWND hWnd)
{
return PeekMessageEx (pMsg, hWnd, 0, 0, TRUE, PM_REMOVE);
}
GetMessage()只是傳遞兩個參數,一個是指向消息緩沖的指針,一個是要獲取消息的來源窗體,內部調用PeekMessageEx()又默認添加了諸多參數。PeekMessageEx()函數的原型為:
//位置:include/window.h - line: 2218
/**
* \fn BOOL PeekMessageEx (PMSG pMsg, HWND hWnd, \
* int iMsgFilterMin, int iMsgFilterMax, \
* BOOL bWait, UINT uRemoveMsg)
* \brief Peeks a message from the message queue of a main window.
*
* This functions peek a message from the message queue of the window \a hWnd;
* if \a bWait is TRUE, it will wait for the message, else return immediatly.
*
* \param pMsg Pointer to the result message.
* \param hWnd The handle to the window.
* \param iMsgFilterMin The min identifier of the message that should be peeked.
* \param iMsgFilterMax The max identifier of the message that should be peeked.
* \param bWait Whether to wait for a message.
* \param uRemoveMsg Whether remove the message from the message queue.
* Should be the following values:
* - PM_NOREMOVE\n
* Leave it in the message queue.
* - PM_REMOVE
* Remove it from the message queue.
* - PM_NOYIELD
* Nouse now.
*
* \return TRUE if there is a message peeked, or FALSE.
*
* \sa GetMessage, PeekPostMessage, HavePendingMessage, PostMessage
*/
MG_EXPORT BOOL GUIAPI PeekMessageEx (PMSG pMsg, HWND hWnd,
int iMsgFilterMin, int iMsgFilterMax,
BOOL bWait, UINT uRemoveMsg);
- 參數iMsgFilterMin和iMsgFilterMax
iMsgFilterMin和iMsgFilterMax用于限定想要獲取的消息范圍,在PeekMessageEx()中由宏 IS_MSG_WANTED(message)使用,判斷當前獲取的消息是否是想要的。該宏的定義 如下:
#define IS_MSG_WANTED(message) \
( (iMsgFilterMin <= 0 && iMsgFilterMax <= 0) || \
(iMsgFilterMin > 0 && iMsgFilterMax >= iMsgFilterMin && \
message >= iMsgFilterMin && message <= iMsgFilterMax) )
由該宏的定義可知,GetMessage()獲取消息時,獲取到的消息總是想要的。
- 參數bWait
bWait參數用于定義當消息隊列中沒有消息時,是否等待;GetMessage()將該值置為TRUE,表示在消息隊列中暫時沒有消息時,愿意等待。 - 參數uRemoveMsg
uRemoveMsg參數用于表征在獲取到一個消息時,是否將該消息從消息隊列中刪除。在隊列的接口中,有“從隊列中刪除一個元素”和“返回隊列頭部的第一個元素”兩種接口,對應兩種不同的應用場景。該參數正是這兩種接口的另一種解決方式。
由于miniGUI消息隊列設計的特性,除了普通消息、通知消息和同步消息之外,另有幾種消息,比如MSG_QUIT、MSG_TIMER、MSG_PAINT等,這些消息不插入到三種消息隊列中,而是通過消息隊列的標志位dwState提現。所以,這些消息不涉及從消息隊列中刪除消息這一說,而是如果相應的標志被置位,pMsg再被重新封裝。
PeekMessageEx()函數內部的其它部分基本就是普通消息、通知消息和同步消息從消息隊列中的獲取。
- 同步消息的獲取
if (pMsgQueue->dwState & QS_SYNCMSG) {
/* 檢查隊列是否為空 */
if (pMsgQueue->pFirstSyncMsg) {
*pMsg = pMsgQueue->pFirstSyncMsg->Msg;
SET_PADD (pMsgQueue->pFirstSyncMsg);
if (IS_MSG_WANTED(pMsg->message)) {
if (uRemoveMsg == PM_REMOVE) {
/* 從消息隊列中刪除該消息 */
pMsgQueue->pFirstSyncMsg = pMsgQueue->pFirstSyncMsg->pNext;
}
UNLOCK_MSGQ (pMsgQueue);
return TRUE;
}
}
else /* 隊列為空則去掉QS_SYNCMSG的置位 */
pMsgQueue->dwState &= ~QS_SYNCMSG;
}
- 通知消息的獲取
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 /* 隊列為空則去掉QS_NOTIFYMSG的置位 */
pMsgQueue->dwState &= ~QS_NOTIFYMSG;
}
- 普通消息的獲取
if (pMsgQueue->dwState & QS_POSTMSG) {
/* 檢查隊列是否為空 */
if (pMsgQueue->readpos != pMsgQueue->writepos) {
*pMsg = pMsgQueue->msg[pMsgQueue->readpos];
SET_PADD (NULL);
if (IS_MSG_WANTED(pMsg->message)) {
CheckCapturedMouseMessage (pMsg);
/* 從消息隊列中刪除該消息 */
if (uRemoveMsg == PM_REMOVE) {
pMsgQueue->readpos++;
/* #if 0 中對循環隊列的兩種處理方式都可行 */
#if 0
if (pMsgQueue->readpos >= pMsgQueue->len)
pMsgQueue->readpos = 0;
#else
pMsgQueue->readpos %= pMsgQueue->len;
#endif
}
UNLOCK_MSGQ (pMsgQueue);
return TRUE;
}
}
else /* 隊列為空則去掉QS_POSTMSG的置位 */
pMsgQueue->dwState &= ~QS_POSTMSG;
}
Notes
隊列的實現有多種方式,按內存的申請方式包括靜態數組、動態數組、鏈表。通過靜態數組和動態數組實現的隊列,通常為了避免“判斷隊列為空或為滿的隊尾、對頭指針指向相同”的問題,又將隊列實現為循環隊列。以下為各種隊列的實現方式:
- 來源:CSDN
作者:乞力馬扎羅的雪雪
文章:C語言實現使用靜態數組實現循環隊列 - 來源:CSDN
作者:乞力馬扎羅的雪雪
文章:C語言實現使用動態數組實現循環隊列 - 來源:網易博客
作者:Dr
文章:用鏈表實現隊列
Others
- miniGUI私有塊數據堆(Private Block Data Heap)
miniGUI為了提高性能,不過于頻繁地調用內存管理函數(malloc和free),自身維護一些用于分配數據塊的塊數據堆,這些數據塊有一個特性就是大小固定,比如一個區域中的剪切矩形。塊數據堆的定義如下。
typedef struct _BLOCKHEAP
{
#ifdef _MGRM_THREADS
pthread_mutex_t lock;
#endif
/**
* Size of one block element.
*/
size_t bd_size;
/**
* Size of the heap in blocks.
*/
size_t heap_size;
/**
* The first free element in the heap.
* 用于跟蹤分配的堆中第一個可用的塊--空塊
*/
int free;
/**
* Pointer to the pre-allocated heap.
* 通過malloc得到的內存模擬堆的起始地址
*/
void* heap;
} BLOCKHEAP;
typedef BLOCKHEAP* PBLOCKHEAP;
通知消息的分配方式就是從塊數據堆中分配,miniGUI將QMSGHeap定義為全局靜態變量,該通知消息(有待考證)堆的初始化和銷毀通過mg_InitFreeQMSGList和mg_DestroyFreeQMSGList完成。
static BLOCKHEAP QMSGHeap;
//Interface
BOOL mg_InitFreeQMSGList (void);
void mg_DestroyFreeQMSGList (void);
inline static PQMSG QMSGAlloc (void);
inline static void FreeQMSG (PQMSG pqmsg);