Chinaunix首页 | 论坛 | 博客
  • 博客访问: 9548610
  • 博文数量: 1227
  • 博客积分: 10026
  • 博客等级: 上将
  • 技术积分: 20273
  • 用 户 组: 普通用户
  • 注册时间: 2008-01-16 12:40
文章分类

全部博文(1227)

文章存档

2010年(1)

2008年(1226)

我的朋友

分类: C/C++

2008-04-23 21:46:04

VC初学者入门系列之二:消息循环


作者:


适用读者:VC初学者并有C 基础。

VC初学者入门系列之一:窗口类的诞生

一、传统SDK程序的消息循环

在传统的SDK程序中,消息循环是很简单的,也许你不信,那我们就看看下面这段代码吧:

#include 

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

                    PSTR szCmdLine, int iCmdShow)

{

	static TCHAR szAppName[] = TEXT ("HelloWin") ;

	WNDCLAS	wndclass ;



 	wndclass.style	  = CS_HREDRAW | CS_VREDRAW ;

 	wndclass.lpfnWndProc  = WndProc ;

	wndclass.lpszClassName	= szAppName ;



	RegisterClass (&wndclass);



	hwnd = CreateWindow( szAppName,……,NULL);

     

	ShowWindow (hwnd, iCmdShow) ;

	UpdateWindow (hwnd) ;

     

	while (GetMessage (&msg, NULL, 0, 0))

       {

		TranslateMessage (&msg) ;

  		DispatchMessage (&msg) ;

          }

	return msg.wParam ;

}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)

{    

	switch (message)

          {

case WM_CREATE:

                     ………

	case WM_PAINT:

                     ………

	case WM_DESTROY:

		PostQuitMessage (0) ;

		return 0 ;

          }

   return DefWindowProc (hwnd, message, wParam, lParam) ;

}

  在WinMain 中 CreateWindow通过一个参数将创建的窗口和窗口类(见"窗口类的诞生"一文)联系起来,这样该窗口的所有消息都将发送到该窗口类的窗口函数WndProc,其后WndProc根据不同的消息给予不同的动作。

二、MFC期望的消息循环
  在传统的SDK程序中消息循环是非常简单的,并且将窗口和窗口函数绑定在一起。而在MFC中就出现了问题,比如CDocument类,不是窗口,所以没有窗口类,但是我也想让它响应消息,怎办?问题不仅仅如此,我们再看看MFC的消息,就会发现更多问题。
  MFC将消息分为三大类:1.标准消息,即除WM_COMMAND之外的任何WM_开头的消息,任何派生自CWnd的类都可以接受该消息,并按照继承关系接受(如从CScrollView到CView再到CWnd)。2.命令消息,即WM_COMMAND,任何派生自CCmdTarget的类,兼可接受该消息,接受顺序如下图所示,其中标号标注了接受消息的顺序,箭头代表调用顺序 :


图1 消息的拐弯流动

3.Control Notification,通知类消息,也以WM_COMMAND形式出现,由控件产生,通知其父窗口。

三、消息宏背后的秘密
  知道了MFC消息流动的要求,那MFC是怎样实现的呢?当一个消息出现时,Application FrameWork怎么知道将该消息发送给哪个对象的呢?其实都是CCmdTarget类在作怪,所有能够接受消息的类都必须继承于CCmdTarget类,因为这些类都一个共同的特征:含有DECLARE_MESSAGE_MAP、BEGIN_MESSAGE_MAP、END_MESSAGE_MAP三个宏。啊!就这三个宏组织了一张庞大的消息映射网,也许你不信,那我们就看看这三个宏是怎样定义的:
#define DECLARE_MESSAGE_MAP()\

private:\

     static const AFX_MSGMAP_ENTRY _messageEntries[];\

protected:

     static AFX_DATA const AFX_MSGMAP messageMap;\

     virtual const AFX_MSGMAP* GetMessageMap() const;\



#define BEGIN_MESSAGE_MAP(theClass, baseClass)\

     const AFX_MSGMAP* theClass::GetMessageMap() const\

{return &theClass::messageMap;}\

AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \

{&baseClass::messageMap, &theClass::_messageEntries[0]};\

const AFX_MSGMAP_ENTRY theClass::_messageEntries[]=\

{\



#define END_MESSAGE_MAP()\

{0,0,0,0,AfxSig_end,(AFX_pMSG)0}\

};\



typedef void (AFX_MSG_CALL CCmdTarget::*AFX_PMSG)(void);

struct AFX_MSGMAP_ENTRY

{

     UINT  nMessage;

     UINT  nCode;

     UINT  nID;

     UINT  nLastID;

     UINT  nSig;

     AFX_PMSG pfn;

};

struct AFX_MSGMAP

{

    const AFX_MSGMAP* pBaseMap;

    const AFX_MSGMAP_ENTRY* lpEntries;

};

  可以看出DECLARE_MESSAGE_MAP宏在其类中申请了一个全局结构和获得该结构的函数,而在BEGIN_MESSAGE_MAP和END_MESSAGE_MAP之间填写刚才的全局结构,将消息和对应的处理函数联系起来,并通过AFX_MSGMAP中的pBaseMap指针,将各类按继承顺序连接起来,从而提供消息流动的道路(即消息的直流,满足标准消息流动的要求)。

下面我们举个例子:
CMyWnd : public CWnd

{

   ……

   DECLARE_MESSAGE_MAP()

}

BEGIN_MESSAGE_MAP(CMyWnd,CWnd)

     ON_WM_CREATE()

     ON_WM_PAINT()

END_MESSAGE_MAP()

被展开后,代码如下:
CMyWnd:public CWnd

{

    ……

private:

    static const AFX_MSGMAP_ENTRY _messageEntries[];

protected:

    static AFX_DATA const AFX_MSGMAP messageMap;

    virtual const AFX_MSGMAP* GetMessageMap() const;

}



const AFX_MSGMAP* CMyWnd::GetMessageMap() const

{  return &CMyWnd::messageMap;}

AFX_DATADEF const AFX_MSGMAP CMyWnd::messageMap=

{&CWnd::messageMap, &CMyWnd::_messageEntries[0]};

const AFX_MSGMAP_ENTRY CMyWnd::_messageEntries[]=

{

{WM_CREATE,0,0,0,AfxSig_is,

(AFX_PMSG)(AFX_PMSGW)(int(AFX_MSG_CALL CWnd::*)(LPCREATESTRUCT))OnCreate},

{WM_PAINT,0,0,0,AfxSig_vv,

(AFX_PMSG)(AFX_PMSGW)(void (AFX_MSG_CALL CWnd::*)(void))OnPaint},

{0,0,0,0,AfxSig_end,(AFX_PMSG)0}

};

  这样 WM_CREATE,WM_PAINT 在消息网中流动,当流到CMyWnd类的 messageMap 结构时,发现有该消息的记录,则调用记录中记载的 OnCreate 和 OnPaint 函数,进行响应消息,从而完成了 Windows 消息驱动机制。

四、MFC消息的起点
  我们已经建立了一张消息流动网络,但是消息是怎样从产生到响应函数收到该消息,而且标准消息需要直流,命令消息还有许多拐弯(在标题二中可以看到)。不要紧张,我们只需要看看MFC是怎样实现的。
  不管怎么说,对 Windows 系统来说都是一样的,它都是不断地用GetMessage(或者其它)从消息队列中取出消息,然后用DispatchMessage将消息发送到窗口函数中去。在"窗口类的诞生"中知道,MFC将所有的窗口处理函数都注册成DefWndProc,那是不是MFC将所有的消息都发送到DefWndProc中去了呢?很抱歉不是,而是都发送到了AfxWndProc函数去了。你可能要问为什么,这也是我想知道的,那我们就看看MFC代码吧:
BOOL CWnd::CreateEx(……)

{

……

PreCreateWindow(cs);

AfxHookWindowCreate(this);

HWND hWnd = ::CreateWindowEx(……);

……

}

void AFXAPI AfxHookWindowCreate(CWnd *pWnd)

{

……

pThreadState->m_hHookOldCbtFilter = 

::SetWindowsHookEx(WH_CBT,_AfxCbtFilterHook,NULL,::GetCurrentThreadId());

……

}

_AfxCbtFilterHook(int code, WPARAM wParam, LPARAM lParam)

{

……

if(!afxData.bWin31)

{

_AfxStandardSubclass((HWND)wParam);

}

……

}

void AFXAPI _AfxStandardSubclass(HWND hWnd)

{

……

oldWndProc =

 (WNDPROC)SetWindowLong(hWnd,GWL_WNDPROC,(DWORD)AfxGetAfxWndProc());

}

WNDPROC AFXAPI AfxGetAfxWndProc()

{

……

return &AfxWndProc;

}

  看了上面的代码,不知你有没有了然于胸的感觉"啊,原来是这样呀!"其实MFC在PreCreateWindow注册窗口类之后,在创建窗口之前,调用了AfxHookWindowCreate函数,该函数设置了钩子(钩子用SetWinowsHook或者SetWindowsHookEx设置,这样消息有满足设置的消息时,系统就发送给你设置的函数,这里是_AfxCbtFilterHook函数),这样每次创建窗口的时候,该函数就将窗口函数修改成AfxWndProc。至于为什么这样做吗?那是为了包容新的3D控件而又同MFC2.5兼容。

五、MFC消息的流动
  消息的起点是AfxWndProc函数,所有的消息都被发送到AfxWndProc,也从AfxWndProc再次流向各自的消息响应函数的,怎么流的呢?那只有MFC知道:
LRESULT CALLBACK AfxWndProc(…….)

{

……

return AfxCallWndProc(pWnd,hWnd,nMsg,wParam,lParam);

}

LRESULT AFXAPI AfxCallWndProc(……)

{

……

lResult = pWnd->WindowProc(nMsg,wParam,lParam);

……

}

LRESULT CWnd::WindowProc(……)

{

……

if(!OnWndMsg(message,wParam,lParam,&lResult))

lResult = DefWindowProc(message,wParam,lParam);

……

}

BOOL CWnd::OnWndMsg(……)//该函数原来太过庞大,被我改造了一下,只反映意思,不能执行

{

    ……

    if(message == WM_COMMAND)

         OnCommand(wParam,lParam);

    if(message == WM_NOTIFY)

         OnNotify(wParam,lParam,&lResult);

    pMessage = GetMessageMap();

    for(; pMessageMap!=NULL; pMessageMap = pMessageMap->pBaseMap)

    {

        if((lpEntry=AfxFindMessageEntry(pMessageMap->lpEntries,

message,0,0))!=NULL) 

break;

    }

    

    (this->*(lpEntry->pnf))(……);//调用消息响应函数

}

AFX_MSGMAP_ENTRY AfxFindMessageEntry(……)

{

    ……

    while(lpEntry->nSign!=AfxSig_end)

    {

       if(lpEntry->nMessage==nMsg&&lpEntry->nCode==nCode&&nID>=lpEntry->nID

              &&nID<=lpEntry->nLastID)

       {

             return lpEntry;

       }

       lpEntry  ;

    }

……

}

  消息被发送到对应窗口的OnWndMsg后,然后根据消息的类型采取相应动作:如果是标准消息,则检查但前类中有无处理函数(由AfxFindMessageEntry实现),若没有,就在其父亲类中找(通过pMessageMap->pBaseMap实现),这样望上顺序搜索消息网,搜索结束也找不到处理函数,那么回到WindowProc函数调用默认DefWindowProc函数;如果是命令消息或通知消息则发送到OnCommand或者OnNotify函数中去处理,来实现消息的拐弯流动:
BOOL CWnd::OnCommand(WPARAM wParam,LPARAM lParam)

{

    ……

    return OnCmdMsg(nID,nCode,NULL,NULL);

}

BOOL CFrameWnd::OnCmdMsg(……)

{

    CView* pView = GetActiveView();

    if(pView!=NULL&&pView->OnCmdMsg(……))   //相当于图1中Frame指向View的箭头

return TRUE;

     if(CWnd::OnCmdMsg(……))                    //图1中Frame自身

          return TRUE;

     CWinApp *pApp = AfxGetApp();

     if(pApp != NULL && pApp->OnCmdMsg(……)) //图1中CWinApp对象

          return TRUE;

     return FALSE;

}

BOOL CView::OnCmdMsg(……)

{

     if(CWnd::OnCmdMsg(……))                    //图1中View本身

          return TRUE;

     if(m_pDocument!=NULL) m_pDocument->OnCmdMsg(……);//图1中View到Doc箭头

     ……

}

BOOL CDocument::OnCmdMsg(……)

{

     if(CCmdTarget::OnCmdMsg(……))              //图1中Doc本身

return TRUE;

     if(m_pDocTemplate!=NULL&&m_pDocTemplate->OnCmdMsg(……))//图1中Doc Template

return TRUE;

     return FALSE;

}

BOOL CCmdTarget::OnCmdMsg(……)//注:CWnd没有重载CCmdTarget的OnCmdMsg

{

     ……

     for(pMessageMap=GetMessageMap();pMessageMap!=NULL;

pMessageMap=pMessageMap->pBaseMap)

     {

         lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries,……);

         if(lpEntry!=NULL)

             return DispatchCmdMsg(……lpEntry->pfn,……);

     }

return FALSE;

}

  从代码中可以看出,OnCmdMsg各自调用的顺序刚好就是图1中所要求的顺序,这样也就实现了消息的拐弯流动,最后DispatchCmdMsg 函数是调用找到的消息处理函数处理消息。至此消息从出现到找到处理函数已经完成!

阅读(278) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~