miniGUI源码分析:消息机制

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有三个主要的发送消息函数

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

  1. 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,下次读取时则从缓冲区开始位置。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值