MiniGUI源码分析--Helloworld(3):消息概览

上一篇:MiniGUI源码分析--hellowworld(2):主窗口诞生的秘密


这本篇中,将重点介绍MiniGUI的消息。


在MiniGUI中,有以下几种消息:

1. 同步消息,由SendMessage、SendSyncMessage发送的同步消息,消息将被立即处理,消息的返回值将通过函数的返回值返回;

2. Notify消息,通过SendNotifyMessage发送 。这是一个异步的消息,但是一定能够被处理

3. Post消息,通过PostMessage发送,这也是一个异步消息,可能会丢失

4. 特殊的消息,包括MSG_PAINT和MSG_TIMER,MSG_QUIT,这三个消息是系统消息,在消息队列中,实际上以标志位的方式存在。MSG_PAINT消息可以由InvalidateRect和 UpdateRect两个函数引起。而MSG_TIMER消息,则必须由系统产生。MSG_QUIT只能通过PostQuitMessage产生


消息的优先级是这样定义的:MSG_QUIT > 同步消息 > Notify消息 > Post消息 > MSG_PAINT > MSG_TIMER消息。


在上一篇中,在创建一个窗口时,我们使用了函数 InitMsgQueueThisThread(3.0版本的为mg_InitMsgQueueThisThread)和GetMsgQueueThisThread,前者为当前线程创建一个消息队列(仅限线程版,进程版和standalone版都只有一个消息队列),后者是获取当前线程的消息队列。

InitMsgQueueThisThread函数本身很简单。它主要是创建和初始化MSGQUEUE结构体。(_LITE_VERSION代表的是进程版和standalone版。在3.0中,这个宏已经被规范为_MGRM_PROCESSES 和_MGRM_STANDALONE。)

struct _MSGQUEUE
{
    DWORD dwState;              // message queue states

#ifndef _LITE_VERSION
    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

#ifndef _LITE_VERSION
    PSYNCMSG pFirstSyncMsg;     // head of the sync message queue
    PSYNCMSG pLastSyncMsg;      // tail of the sync message queue
#else
    IDLEHANDLER OnIdle;         // Idle handler
#endif

#ifndef _LITE_VERSION
    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. */
};


dwState是一个标志位,它的定义有:

#define QS_NOTIFYMSG        0x10000000
#ifndef _LITE_VERSION
  #define QS_SYNCMSG        0x20000000
#else
  #define QS_DESKTIMER      0x20000000
#endif
#define QS_POSTMSG          0x40000000
#define QS_QUIT             0x80000000
#define QS_INPUT            0x01000000
#define QS_PAINT            0x02000000
#define QS_TIMER            0x0000FFFF
#define QS_EMPTY            0x00000000

QS_NOTIFYMSG 标志表示消息队列中有待处理的notify消息。同理,QS_SYNCMSG表示有待处理的同步消息;QS_POSTMSG表示有待处理的post消息。

QS_QUIT、QS_PAINT和QS_TIMER分别对应MSG_QUIT, MSG_PAINT和MSG_TIMER。


同步消息在线程版,分成两种情况来实现:1)如果消息发送者和消息接受者在同一个线程,则直接调用窗口的窗口过程;2)如果不在同一个线程,则使用同步消息,来传递。

在非线程版,则统一使用第一种方式。

所以,在线程版的MSGQUEUE结构体中,增加了sync_msg, pFirstSyncMsg和pLastSyncMsg。其中,wait变量的作用是为了唤醒消息循环。

请看SendMessage代码

int GUIAPI SendMessage (HWND hWnd, int iMsg, WPARAM wParam, LPARAM lParam)
{
    WNDPROC WndProc;

    MG_CHECK_RET (MG_IS_WINDOW(hWnd), -1);

#ifndef _LITE_VERSION
    if (!BE_THIS_THREAD(hWnd))
        return SendSyncMessage (hWnd, iMsg, wParam, lParam);
#endif /* !_LITE_VERSION */
    
    if ( !(WndProc = GetWndProc(hWnd)) )
        return ERR_INV_HWND;

    return (*WndProc)(hWnd, iMsg, wParam, lParam);


BE_IS_THREAD宏通过hWnd的住窗口内保持的线程句柄来判断的。


当不是一个线程时,就调用SendSyncMessage。这个函数

int SendSyncMessage (HWND hWnd, int msg, WPARAM wParam, LPARAM lParam)
{
    PMSGQUEUE pMsgQueue, thinfo = NULL;
    SYNCMSG SyncMsg;
    sem_t sync_msg;

    if (!(pMsgQueue = GetMsgQueue(hWnd)))
        return ERR_INV_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;

    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, 
            "SendSyncMessage: thread is interrupted abnormally!\n");
    }

    if (thinfo == NULL)
        sem_destroy (&sync_msg);

    return SyncMsg.retval;
}


注意中间几行处理pMsgQueue->pFirstSyncMsg的语句。由于是跨线程同步处理,所以,该函数直接使用栈上变量,这是一个简单的技巧。


当把消息添加到队里中后,通过POST_MSGQ宏,唤醒消息循环,该宏的定义是:

  #define POST_MSGQ(pMsgQueue) \
  { \
    int sem_value; \
    /* Signal that the msg queue contains one more element for reading */ \
    sem_getvalue (&(pMsgQueue)->wait, &sem_value); \
    if (sem_value <= 0) \
        sem_post(&(pMsgQueue)->wait); \
  }

可以看到wait信号量被消息循环线程等待。

最后,该函数等待消息循环处理该消息并处罚事先设置的sem_handle信号量。

由于两个线程存在相互等待的情况,所以,需要非常小心的避免引起死锁。

SendSyncMessage函数时不能在一个线程中重入的。否则就会引起死锁。但是不同的线程可以同时调用该函数。


同步消息应用非常广泛,是用到最多的一种消息。但是,在一些方面,却不太适合。如,从底层发送的一些事件,特别是硬件事件;当窗口要再一个很深的消息中删除自己时,如果直接用同步消息,会导致该窗口后续的消息使用了非法指针等等。

所以,这个时候,就应该使用Notify消息。

Notify消息使用SendNotifyMessage发送,它的实现是:

int GUIAPI SendNotifyMessage (HWND hWnd, int iMsg, WPARAM wParam, LPARAM lParam)
{
    PMSGQUEUE pMsgQueue;
    PQMSG pqmsg;

    MG_CHECK_RET (MG_IS_WINDOW(hWnd), ERR_INV_HWND);

    if (!(pMsgQueue = GetMsgQueue(hWnd)))
        return ERR_INV_HWND;
  
    pqmsg = QMSGAlloc();

    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);
#ifndef _LITE_VERSION
    if ( !BE_THIS_THREAD(hWnd) )
        POST_MSGQ(pMsgQueue);
#endif

    return ERR_OK;
}

不同于SendSyncMessage,该消息时异步的。把消息放入队列后,就立即退出。所以,它是通过QMSGAlloc分配的消息。这个时候, 如果在消息的wParam和lParam放入指针,就需要非常小心的在合适时机释放内存,以避免内存泄露的产生


PostMessage在应用层使用不是很多,不过,在系统内部常常用来发送键盘和鼠标消息,这一点,会在后面详细谈到。因为键盘和鼠标消息如果不能被及时处理,就需要丢掉了。

int GUIAPI PostMessage (HWND hWnd, int iMsg, WPARAM wParam, LPARAM lParam)
{
    PMSGQUEUE pMsgQueue;
    MSG msg;

    if (!(pMsgQueue = GetMsgQueue(hWnd)))
        return ERR_INV_HWND;

    if (iMsg == MSG_PAINT) {
        LOCK_MSGQ (pMsgQueue);
        pMsgQueue->dwState |= QS_PAINT;
        UNLOCK_MSGQ (pMsgQueue);
#ifndef _LITE_VERSION
        if ( !BE_THIS_THREAD(hWnd) )
            POST_MSGQ(pMsgQueue);
#endif
        return ERR_OK;
    }

    msg.hwnd = hWnd;
    msg.message = iMsg;
    msg.wParam = wParam;
    msg.lParam = lParam;

    if (!QueueMessage(pMsgQueue, &msg))
        return ERR_QUEUE_FULL;

    return ERR_OK;
}

它与SendNotifyMessage的区别主要在于:

对MSG_PAINT消息做了特殊处理,piant消息仅仅是增加了标志位

QueueMessage函数的是从固定大小的队列中获取消息,然后处理的:

BOOL QueueMessage (PMSGQUEUE msg_que, PMSG msg)
{
    LOCK_MSGQ(msg_que);

    /* check whether the last message is MSG_MOUSEMOVE */
    if (msg->message == MSG_MOUSEMOVE && msg->hwnd == HWND_DESKTOP
                    && msg_que->readpos != msg_que->writepos) {
        PMSG last_msg;

        if (msg_que->writepos == 0)
            last_msg = msg_que->msg + msg_que->len - 1;
        else
            last_msg = msg_que->msg + msg_que->writepos - 1;

        if (last_msg->message == MSG_MOUSEMOVE
                        && last_msg->wParam == msg->wParam
                        && last_msg->hwnd == msg->hwnd) {
            last_msg->lParam = msg->lParam;
            last_msg->time = msg->time;
            goto ret;
        }
    }

    if ((msg_que->writepos + 1) % msg_que->len == msg_que->readpos) {
        UNLOCK_MSGQ(msg_que);
        return FALSE;
    }

    /* Write the data and advance write pointer */
    msg_que->msg [msg_que->writepos] = *msg;

    msg_que->writepos++;
    if (msg_que->writepos >= msg_que->len) msg_que->writepos = 0;

ret:
    msg_que->dwState |= QS_POSTMSG;

    UNLOCK_MSGQ (msg_que);

#ifndef _LITE_VERSION
    if (!BE_THIS_THREAD (msg->hwnd))
        POST_MSGQ (msg_que);
#endif

    return TRUE;
}

readpos和writepos一个是队列头一个是队列尾,MSGQUEUE中的msg是一个数组,在InitMsgQueue分配了指定长度的数组。



我们知道,消息循环都是这么写的:

 while (GetMessage(&Msg, hMainWnd)) {  
        TranslateMessage(&Msg);  
        DispatchMessage(&Msg);  
}  

GetMessage函数,它的实现是

static inline BOOL GUIAPI GetMessage (PMSG pMsg, HWND hWnd)
{
    return PeekMessageEx (pMsg, hWnd, 0, 0, TRUE, PM_REMOVE);
}

PeekMessageEx函数时从消息队列中检出消息。它的实现

BOOL PeekMessageEx (PMSG pMsg, HWND hWnd, int iMsgFilterMin, int iMsgFilterMax, 
                          BOOL bWait, UINT uRemoveMsg)
{
    PMSGQUEUE pMsgQueue;
    PQMSG phead;

    if (!pMsg || (hWnd != HWND_DESKTOP && !MG_IS_MAIN_WINDOW(hWnd)))
        return FALSE;

#ifndef _LITE_VERSION
    if (!(pMsgQueue = GetMsgQueueThisThread ()))
            return FALSE;
#else
    pMsgQueue = __mg_dsk_msg_queue;
#endif

    memset (pMsg, 0, sizeof(MSG));

checkagain:

    LOCK_MSGQ (pMsgQueue);

    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;
    }

    /* Dealing with sync messages before notify messages is better ? */
#ifndef _LITE_VERSION
    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
            pMsgQueue->dwState &= ~QS_SYNCMSG;
    }
#endif

    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;
    }

    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 (pMsgQueue->readpos >= pMsgQueue->len)
                        pMsgQueue->readpos = 0;
                }

                UNLOCK_MSGQ (pMsgQueue);
                return TRUE;
            }
        }
        else
            pMsgQueue->dwState &= ~QS_POSTMSG;
    }

    /*
     * check invalidate region of the windows
     */

    if (pMsgQueue->dwState & QS_PAINT && IS_MSG_WANTED(MSG_PAINT)) {
        PMAINWIN pHostingRoot;
        HWND hNeedPaint;
        PMAINWIN pWin;
 
#ifndef _LITE_VERSION
        /* REMIND this */
        if (hWnd == HWND_DESKTOP) {
            pMsg->hwnd = hWnd;
            pMsg->message = MSG_PAINT;
            pMsg->wParam = 0;
            pMsg->lParam = 0;
            SET_PADD (NULL);

            if (uRemoveMsg == PM_REMOVE) {
                pMsgQueue->dwState &= ~QS_PAINT;
            }
            UNLOCK_MSGQ (pMsgQueue);
            return TRUE;
        }
#endif

        pMsg->message = MSG_PAINT;
        pMsg->wParam = 0;
        pMsg->lParam = 0;
        SET_PADD (NULL);

#ifdef _LITE_VERSION
        pHostingRoot = __mg_dsk_win;
#else
        pHostingRoot = pMsgQueue->pRootMainWin;
#endif

        if ( (hNeedPaint = msgCheckHostedTree (pHostingRoot)) ) {
            pMsg->hwnd = hNeedPaint;
            pWin = (PMAINWIN) hNeedPaint;
            pMsg->lParam = (LPARAM)(&pWin->InvRgn.rgn);
            UNLOCK_MSGQ (pMsgQueue);
            return TRUE;
        }
 
        /* no paint message */
        pMsgQueue->dwState &= ~QS_PAINT;
    }

    /*
     * handle timer here
     */
#ifdef _LITE_VERSION
    if (pMsgQueue->dwState & QS_DESKTIMER) {
        pMsg->hwnd = HWND_DESKTOP;
        pMsg->message = MSG_TIMER;
        pMsg->wParam = 0;
        pMsg->lParam = 0;

        if (uRemoveMsg == PM_REMOVE) {
            pMsgQueue->dwState &= ~QS_DESKTIMER;
        }
        return TRUE;
    }
#endif

    if (pMsgQueue->TimerMask && IS_MSG_WANTED(MSG_TIMER)) {
        int slot;
        TIMER* timer;

#ifndef _LITE_VERSION
        if (hWnd == HWND_DESKTOP) {
            pMsg->hwnd = hWnd;
            pMsg->message = MSG_TIMER;
            pMsg->wParam = 0;
            pMsg->lParam = 0;
            SET_PADD (NULL);

            if (uRemoveMsg == PM_REMOVE) {
                pMsgQueue->TimerMask = 0;
            }
            UNLOCK_MSGQ (pMsgQueue);
            return TRUE;
        }
#endif

        /* 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))) {

            unsigned int tick_count = timer->tick_count;

            timer->tick_count = 0;
            pMsgQueue->TimerMask &= ~(0x01 << slot);

            if (timer->proc) {
                BOOL ret_timer_proc;

                /* unlock the message queue when calling timer proc */
                UNLOCK_MSGQ (pMsgQueue);

                /* calling the timer callback procedure */
                ret_timer_proc = timer->proc (timer->hWnd, 
                        timer->id, tick_count);

                /* lock the message queue again */
                LOCK_MSGQ (pMsgQueue);

                if (!ret_timer_proc) {
                    /* remove the timer */
                    __mg_remove_timer (timer, slot);
                }
            }
            else {
                pMsg->message = MSG_TIMER;
                pMsg->hwnd = timer->hWnd;
                pMsg->wParam = timer->id;
                pMsg->lParam = tick_count;
                SET_PADD (NULL);

                UNLOCK_MSGQ (pMsgQueue);
                return TRUE;
            }
        }
    }

    UNLOCK_MSGQ (pMsgQueue);

#ifndef _LITE_VERSION
    if (bWait) {
        /* no message, wait again. */
        sem_wait (&pMsgQueue->wait);
        goto checkagain;
    }
#else
    /* no message, idle */
    if (bWait) {
         pMsgQueue->OnIdle (pMsgQueue);
         goto checkagain;
    }
#endif

    /* no message */
    return FALSE;
}

内容很长,但是可以分成两部分看:第一步部分,是获取消息的部分;第二部分,是等待消息循环的部分。

第二部分,请看函数最后部分,if(bWait)的代码。对于线程版,它就是通过wait信号量,让自己进入休眠。对于进程版和standalone版,它调用OnIdle回调。OnIdle回调在进程版中和线程版转化为对端口的select方法调用,从而导致一个较短时间的休眠。


重点看第一部分,它按照优先级,依次取MSG_QUIT消息,同步消息,notify消息,post消息,和MSG_PAINT消息和MSG_TIMER消息。

普通的消息都比较简单。所以我们重点介绍MSG_PAINT消息和MSG_TIMER消息。



MSG_PAINT消息,其重点是检查了QS_PAINT标志。当有QS_PAINT标志的时候,它实际上通过 msgCheckHostedTree函数,来检查那些窗口是需要重绘的。那些需要重绘的窗口,就会产生MSG_PAINT消息。

msgCheckHostedTree函数,其实现是:

static HWND msgCheckHostedTree (PMAINWIN pHosting)
{
    HWND hNeedPaint;
    PMAINWIN pHosted;

    if ( (hNeedPaint = msgCheckInvalidRegion (pHosting)) )
        return hNeedPaint;

    pHosted = pHosting->pFirstHosted;
    while (pHosted) {
        if ( (hNeedPaint = msgCheckHostedTree (pHosted)) )
            return hNeedPaint;

        pHosted = pHosted->pNextHosted;
    }

    return 0;
}
msgCheckInvalidRegion函数判断窗口是否存在无效区域,如果存在,则是需要重绘,否则,它会继续查找下个hosted窗口。


这里面涉及很多点:

  1. MiniGUI窗口的管理方式,是主窗口和普通窗口分离的。主窗口采用hosting的链表树方式管理,可以遍历到所有的主窗口。而每个主窗口则是一个树的根节点,通过parent-child关系管理所有的子窗口。所以,msgCheckHostedTree是遍历主窗口用的,而msgCheckInvalidRegion是遍历子窗口用的。注意这两个函数的返回值,都是需要重绘的窗口句柄。
  2. 当找到一个需要重绘的窗口句柄的时候,它就返回该窗口句柄。返回后,消息循环接下来回调用DispatchMessage,该函数会调用到窗口过程。在窗口过程中,处理MSG_PAINT消息时,我们必须用BeginPaint和EndPaint来获取绘制的DC。这一点非常重要,因为在BeginPaint中,会清除无效区域。当无效区域被清除后,下次在此调用msgCheckHostedTree和msgCheckInvalidRegion的时候,该窗口的子窗口或者兄弟窗口就会被检测。一直等到所有窗口被检测完毕后,整个绘制过程才算完成。在此过程中,MiniGUI会多次遍历所有的窗口
  3. msgCheckHostedTree和msgCheckInvalidRegion相结合使用,是通过递归的方法进行的一个先根遍历法,这个方法,总是能够保证父窗口先于子窗口被绘制,从而保证了窗口之间以正确的次序被绘制。
msgCheckInvalidRegion的函数定义如下:

tatic HWND msgCheckInvalidRegion (PMAINWIN pWin)
{
    PCONTROL pCtrl = (PCONTROL)pWin;
    HWND hwnd;

    if (pCtrl->InvRgn.rgn.head)
        return (HWND)pCtrl;

    pCtrl = pCtrl->children;
    while (pCtrl) {

        if ((hwnd = msgCheckInvalidRegion ((PMAINWIN) pCtrl)))
            return hwnd;

        pCtrl = pCtrl->next;
    }

    return 0;
}

他是通过判断窗口结构体中的 InvRgn的区域是否为空来实现的。它的实现,是一个典型的先根遍历法,可以保证窗口的绘制次序。



MSG_TIMER消息,在MiniGUI中是一个很特别的消息。它是通过一个Timer服务来支持的。

我们知道,MiniGUI的Timer消息,通过SetTimer和SetTimerEx来启动的。SetTimerEx 函数是主要实现Timer安装的。


在MiniGUI中,Timer的相关信息,通过一个Timer slot来保存的。timer slot实际上一一组数组,数组内的元素可以被重复利用。通过MSGQUEUE中的TimerMark变量,保存那些数组元素被使用了。 TimerMark是用掩码来表示 timerslot的索引的。所以,在MiniGUI最多能够安装32个timer,就是因为TimerMark变量时32位的。

在src/kernel/timer.c文件中,定义了静态变量

static TIMER *timerstr[DEF_NR_TIMERS];
其中DEF_NR_TIMERS被定义为32. TIMER结构体定义是:

typedef struct _timer {
    HWND    hWnd;
    int     id;
    unsigned int speed;
    unsigned int count;

    TIMERPROC proc;
    unsigned int tick_count;
    PMSGQUEUE msg_queue;
} TIMER;
一个Timer会和指定的窗口、和窗口依赖的消息队列相关。


当调用SetTimerEx时,实际上,就是使用这样的timer的slot:

BOOL GUIAPI SetTimerEx (HWND hWnd, int id, unsigned int speed, 
                TIMERPROC timer_proc)
{
    int i;
    PMSGQUEUE pMsgQueue;
    int slot = -1;

#ifndef _LITE_VERSION
    if (!(pMsgQueue = GetMsgQueueThisThread ()))
        return FALSE;
#else
    pMsgQueue = __mg_dsk_msg_queue;
#endif

    TIMER_LOCK ();

    /* Is there an empty timer slot? */
    for (i=0; ihWnd == hWnd && timerstr[i]->id == id) {
            goto badret;
        }
    }

    if (slot < 0 || slot == DEF_NR_TIMERS)
        goto badret ;

    timerstr[slot] = malloc (sizeof (TIMER));

    timerstr[slot]->speed = speed;
    timerstr[slot]->hWnd = hWnd;
    timerstr[slot]->id = id;
    timerstr[slot]->count = 0;
    timerstr[slot]->proc = timer_proc;
    timerstr[slot]->tick_count = 0;
    timerstr[slot]->msg_queue = pMsgQueue;

#if defined(_LITE_VERSION) && !defined(_STAND_ALONE)
    if (!mgIsServer)
        __mg_set_select_timeout (USEC_10MS * speed);
#endif

    TIMER_UNLOCK ();
    return TRUE;
    
badret:
    TIMER_UNLOCK ();
    return FALSE;
}



前面的章节中,在讲到MiniGUI初始化的时候,我们提到过,线程版本中,timer会单独使用一个线程,来定时发出消息。该线程入口是TimerEntry,它通过_os_timer_loop来处理线程:

static inline void _os_timer_loop (void)
{
    while (1) {
        __mg_os_time_delay (10);
        __mg_timer_action (NULL);
    }
}

static void* TimerEntry (void* data)
{
    if (!InitTimer ()) {
        fprintf (stderr, "TIMER: Init Timer failure, exit!\n");
#ifndef __NOUNIX__
        exit (1);
#endif
        return NULL;
    }
    sem_post ((sem_t*)data);

    _os_timer_loop ();

    return NULL;
}


_os_timer_loop是每10毫秒发出一次消息,通过__mg_timer_action函数:

static void __mg_timer_action (void *data)
{
#if defined(_LITE_VERSION) && !defined(_STAND_ALONE)
    SHAREDRES_TIMER_COUNTER += 1;
#else

#if defined(__uClinux__) && defined(_STAND_ALONE)
    __mg_timer_counter += 10;
#else
    __mg_timer_counter ++;
#endif

#endif

#ifndef _LITE_VERSION
    /* alert desktop */
    AlertDesktopTimerEvent ();
#endif
}

关键函数是AlertDesktopTimerEvent:

static inline void 
AlertDesktopTimerEvent (void)
{
    __mg_dsk_msg_queue->TimerMask = 1;
    POST_MSGQ(__mg_dsk_msg_queue);
}
我们知道,Desktop是单独一个线程运行的,其入口是DesktopMain,其核心也是一个消息循环。关键还在于PeekMessageEx中,请注意其中的代码:

    if (pMsgQueue->TimerMask && IS_MSG_WANTED(MSG_TIMER)) {
        int slot;
        TIMER* timer;

#ifndef _LITE_VERSION
        if (hWnd == HWND_DESKTOP) {
            pMsg->hwnd = hWnd;
            pMsg->message = MSG_TIMER;
            pMsg->wParam = 0;
            pMsg->lParam = 0;
            SET_PADD (NULL);

            if (uRemoveMsg == PM_REMOVE) {
                pMsgQueue->TimerMask = 0;
            }
            UNLOCK_MSGQ (pMsgQueue);
            return TRUE;
        }
#endif
实际上,它发给Desktop的窗口过程的MSG_TIMER处理。Desktop的窗口过程是DesktopWnProc函数,在该函数中主要调用了DispatchTimerMessage函数。该函数的实现是

void DispatchTimerMessage (unsigned int inter)
{
    int i;

    TIMER_LOCK ();

    for (i=0; imsg_queue) {
            timerstr[i]->count += inter;
            if (timerstr[i]->count >= timerstr[i]->speed) {
                if (timerstr[i]->tick_count == 0)
#if defined(_LITE_VERSION) && !defined(_STAND_ALONE)
                    timerstr[i]->tick_count = SHAREDRES_TIMER_COUNTER;
#else
                    timerstr[i]->tick_count = __mg_timer_counter;
#endif
                /* setting timer flag is simple, we do not need to lock msgq,
                   or else we may encounter dead lock here */ 
                SetMsgQueueTimerFlag (timerstr[i]->msg_queue, i);
                
                timerstr[i]->count -= timerstr[i]->speed;
            }
        }
    }

    TIMER_UNLOCK ();
}

很明显,该函数最重要的地方,是调用SetMsgQueueTimerFlag,把MSGQUEUE的TimerMark变量打上掩码标记,并唤醒消息队列。


消息队列的PeekMessageEx函数,就会从特定的timerstr中取得对应的信息,并形成MSG_TIMER消息。


下面的一个序列图简要说明了MSG_TIMER的产生过过程:


MiniGUI源码分析--Helloworld(3):消息概览_第1张图片


你可能感兴趣的:(UI,MiniGUI,嵌入式)