AutoCAD 3DMAX C语言 Pro/E UG JAVA编程 PHP编程 Maya动画 Matlab应用 Android
Photoshop Word Excel flash VB编程 VC编程 Coreldraw SolidWorks A Designer Unity3D
 首页 > VC编程

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

51自学网 2015-08-30 http://www.wanshiok.com

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

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

#include <windows.h>
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兼容。

<

 

 

 
说明
:本教程来源互联网或网友上传或出版商,仅为学习研究或媒体推广,wanshiok.com不保证资料的完整性。
上一篇:VC初学者入门系列之一:窗口类的诞生  下一篇:浅谈C++递归的思想实现以及和循环的关系