RTTI 与消息机制(VC_MFC)
目录
(本章节中例子都是用 VS2005 编译调试的)
RTTI 运行时类型识别(内容源自深入浅出MFC ,相关宏的知识点链接)
类的"类型识别录" CRuntimeClass 类:
在介绍动态识别技术时候首先要介绍一个结构,用于记录类的信息.(在 afx.h 头文件中定义)其成员定义与解释如下(MSDN参考链接)
Public Methods
Name Description
CRuntimeClass::CreateObject Creates an object during run time.
CRuntimeClass::FromName Creates an object during run time using the familiar class name.
CRuntimeClass::IsDerivedFrom Determines if the class is derived from the specified class.
Public Data Members
Name Description
CRuntimeClass::m_lpszClassName The name of the class.
CRuntimeClass::m_nObjectSize The size of the object in bytes.
CRuntimeClass::m_pBaseClass A pointer to the CRuntimeClass structure of the base class.
CRuntimeClass::m_pfnCreateObject A pointer to the function that dynamically creates the object.
CRuntimeClass::m_pfnGetBaseClass Returns the CRuntimeClass structure (only available when dynamically linked).
CRuntimeClass::m_wSchema The schema number of the class.
CRuntimeClass::m_pClassInit 暂时不用管它(MSDN中没有声明,这个是在 CRuntimeClass 类定义中看到的所以蛮写进来)
这样我们就可以通过在类中添加这个类的静态对象(即 CRuntimeClass 成员变量)记录的信息来完成"类型识别录",然后在开辟一个全局的 CRuntime 指针指向这个"类型识别录"的尾部,如下图所示:
在类中添加 CRuntimeClass 对象:
MFC为了实现把 CRuntimeClass 对象添加到类中,声明了两个类来完成这项工作(即 DECLARE_DYNAMIC , IMPLEMENT_DYNAMIC 与 IMPLEMENT_RUNTIMECLASS ,在头文件 afx.h 中定义)
// DECLARE_DYNAMIC 宏定义 #define DECLARE_DYNAMIC(class_name) \ protected: \ static CRuntimeClass* PASCAL _GetBaseClass(); \ public: \ static const CRuntimeClass class##class_name; \ static CRuntimeClass* PASCAL GetThisClass(); \ virtual CRuntimeClass* GetRuntimeClass() const; \ // IMPLEMENT_DYNAMIC 宏定义 #define IMPLEMENT_DYNAMIC(class_name, base_class_name) \ IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, NULL, NULL) // IMPLEMENT_RUNTIMECLASS 宏定义 #define IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, pfnNew, class_init) \ CRuntimeClass* PASCAL class_name::_GetBaseClass() \ { return RUNTIME_CLASS(base_class_name); } \ AFX_COMDAT const CRuntimeClass class_name::class##class_name = { \ #class_name, sizeof(class##class_name), wSchema, pfnNew, \ &class_name::_GetBaseClass, NULL, class_init }; \ CRuntimeClass* PASCAL class_name::GetThisClass() \ { return _RUNTIME_CLASS(class_name); } \ CRuntimeClass* class_name::GetRuntimeClass() const \ { return _RUNTIME_CLASS(class_name); } \
相当与在类中填写了这样的 CRuntimeClass 对象,大致如下所示:
但是链表头(即 CObject 类)便不能套用一般的链表行为方式,必须特别设计.在 MFC 中代码如下(在 afx.h 头文件中):
// in header file class AFX_NOVTABLE CObject { public: // Object model (types, destruction, allocation) virtual CRuntimeClass* GetRuntimeClass() const; ... // Implementation public: static const CRuntimeClass classCObject; ... } // in implementation file IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, NULL, NULL) AFX_COMDAT const CRuntimeClass class_name::classCObject = { classCObject, sizeof(classCObject), 0xFFFF, NULL, NULL, NULL, NULL }; CRuntimeClass* PASCAL class_name::GetThisClass() { return _RUNTIME_CLASS(class_name); }
填写后的数据内容如下图所示:
代码样例:
新建一个 MFC 的单文档程序,以 Frame 类来说明:
// CMainFrame.h class CMainFrame : public CFrameWnd { ... DECLARE_DYNAMIC(CMainFrame) ... }; // CMainFrame.cpp ... IMPLEMENT_DYNAMIC(CMainFrame, CFrameWnd) ...
宏展开后
// CMainFrame.h class CMainFrame : public CFrameWnd { ... protected: static CRuntimeClass* PASCAL _GetBaseClass(); public: static const CRuntimeClass classCMainFrame; static CRuntimeClass* PASCAL GetThisClass(); virtual CRuntimeClass* GetRuntimeClass() const; ... }; // CMainFrame.cpp ... CRuntimeClass* PASCAL CMainFrame::_GetBaseClass() { return RUNTIME_CLASS(CFrameWnd); } AFX_COMDAT const CRuntimeClass CMainFrame::classCMainFrame = { CMainFrame, sizeof(classCMainFrame), 0xFFFF, NULL, &CMainFrame::_GetBaseClass, NULL, NULL }; CRuntimeClass* PASCAL CMainFrame::GetThisClass() { return _RUNTIME_CLASS(CMainFrame); } CRuntimeClass* CMainFrame::GetRuntimeClass() const { return _RUNTIME_CLASS(CMainFrame); } ...
MFC消息映射机制(内容源自深入浅出MFC,相关宏的知识点链接)
总体思路:
实现的大致思路是在每个能接受和处理消息的类中定义一个消息和消息处理函数的静态对照表,即消息映射表,在消息映射表中,消息与对应的消息处理函数指针是成对出现的,当有消息需要处理的时候,查看静态对照表,如果对照表中有这个消息,便调用相关函数来处理消息,如果多个窗体响应了这个消息则,以消息路由的路径顺序来把消息分配给对象的窗口.
具体实现:
那具体的实现方法先定义一个结构体用来建立消息与消息处理函数的对应关系.在此之前为了消息能按照一定形式进行路由所以首先定义一个数据结构(头文件 afxwin.h 中):
struct AFX_MSGMAP { const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)(); //指向上一个消息映射表 const AFX_MSGMAP_ENTRY* lpEntries; //消息和响应函数的映射表 };
接着我们来看看消息映射表结构(头文件 afxwin.h 中):
struct AFX_MSGMAP_ENTRY { UINT nMessage; // windows message UINT nCode; // control code or WM_NOTIFY code UINT nID; // control ID (or 0 for windows messages) UINT nLastID; // used for entries specifying a range of control id's UINT_PTR nSig; // signature type (action) or pointer to message # AFX_PMSG pfn; // routine to call (or special value) };
然后两者的关系如下图所示:
其中消息的处理函数的返回类型为 AFX_PMSG 为一个函数指针的类型定义,具体定义如下:
typedef void (AFX_MSG_CALL CCmdTarget::*AFX_PMSG)(void);
接着 MFC 通过 BEGIN_MESSAGE_MAP,ON_COMMAND,END_MESSAGE_MAP 三个宏来完成对消息映射结构的填写.
/* BEGIN_MESSAGE_MAP 宏定义 *********************************/ #define BEGIN_MESSAGE_MAP(theClass, baseClass) \ PTM_WARNING_DISABLE \ const AFX_MSGMAP* theClass::GetMessageMap() const \ { return GetThisMessageMap(); } \ const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap() \ { \ typedef theClass ThisClass; \ typedef baseClass TheBaseClass; \ static const AFX_MSGMAP_ENTRY _messageEntries[] = \ { /* ON_COMMAND 宏定义*****************************************/ #define ON_COMMAND(id, memberFxn) \ { WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSigCmd_v, \ static_cast<AFX_PMSG> (memberFxn) }, //当然现在有些特定的消息有专门的消息映射宏,如 WM_CREATE 由 ON_WM_CREATE() 宏填写,如 WM_DESTROY 由 ON_WM_DESTROY() 宏填写等等
//这些都 afxmsg_.h 在头文件中定义,一般的系统消息映射由编译器来帮你填写完成,除非你要添加用户消息并填写对应的消息映射 /* END_MESSAGE_MAP 宏定义 *********************************/ #define END_MESSAGE_MAP() \ {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \ }; \ static const AFX_MSGMAP messageMap = \ { &TheBaseClass::GetThisMessageMap, &_messageEntries[0] }; \ return &messageMap; \ } \ PTM_WARNING_RESTORE
接着在用 DECLARE_MESSAGE_MAP() 宏,来在对应类中填充相对应的成员函数
#define DECLARE_MESSAGE_MAP() \ protected: \ static const AFX_MSGMAP* PASCAL GetThisMessageMap(); \ virtual const AFX_MSGMAP* GetMessageMap() const; \
因此 MFC 就可以根据类的继承关系形成对象的消息网
代码样例:
新建一个 MFC 的单文档程序,以 view 类来说明:
//childView.h ... class CChildView : public CWnd { ... protected: afx_msg void OnPaint(); DECLARE_MESSAGE_MAP() }; // ChildView.cpp ... BEGIN_MESSAGE_MAP(CChildView, CWnd) ON_WM_PAINT() END_MESSAGE_MAP() .... void CChildView::OnPaint() { CPaintDC dc(this); // 用于绘制的设备上下文 // TODO: 在此处添加消息处理程序代码 // 不要为绘制消息而调用 CWnd::OnPaint() }
宏展开为:
//childView.h ... class CChildView : public CWnd { ... protected: afx_msg void OnPaint(); protected: static const AFX_MSGMAP* PASCAL GetThisMessageMap(); virtual const AFX_MSGMAP* GetMessageMap() const; }; // ChildView.cpp ... __pragma(warning( push )) \ __pragma(warning( disable : 4867 )) const AFX_MSGMAP* CChildView::GetMessageMap() const { return GetThisMessageMap(); } const AFX_MSGMAP* PASCAL CChildView::GetThisMessageMap() { typedef CChildView ThisClass; typedef CWnd TheBaseClass; static const AFX_MSGMAP_ENTRY _messageEntries[] = { {WM_HELP, 0, 0, 0, AfxSig_bHELPINFO,(AFX_PMSG)(AFX_PMSGW)(static_cast< BOOL (AFX_MSG_CALL CWnd::*)(HELPINFO*) > ( &ThisClass :: OnHelpInfo))}, {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } }; static const AFX_MSGMAP messageMap = { &TheBaseClass::GetThisMessageMap, &_messageEntries[0] }; return &messageMap; } __pragma(warning( pop )) .... void CChildView::OnPaint() { CPaintDC dc(this); // 用于绘制的设备上下文 // TODO: 在此处添加消息处理程序代码 // 不要为绘制消息而调用 CWnd::OnPaint() }
消息结构如下图所示
MFC自定义消息:
- 第一步:定义消息(推荐用户自定义消息至少是WM_USER+100,因为很多新控件也要使用WM_USER消息).
#define WM_MY_MESSAGE (WM_USER+100)
- 第二步:声明成员函数(在类头文件的AFX_MSG块中说明消息处理函数):
afx_msg LRESULT OnMyMessage(WPARAM wParam, LPARAM lParam);
- 第三步:实现成员函数(实现处理消息的成员函数,该函数使用WPRAM和LPARAM参数并返回LPESULT).
LRESULT CMainFrame::OnMyMessage(WPARAM wParam, LPARAM lParam) { // TODO: 处理用户自定义消息 ... return 0; }
- 第四步:添加消息映射(在用户类的消息块中,使用ON_MESSAGE宏指令将消息映射到消息处理函数中).
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) //{{AFX_MSG_MAP(CMainFrame) ... //}}AFX_MSG_MAP ON_MESSAGE(WM_MY_MESSAGE ,OnMyMessage) END_MESSAGE_MAP()
消息路由(内容源自深入浅出MFC)
Windows消息分类
消息路由路径
- 从类对消息的传递看消息路由
一般的消息路由
WM_COMMAND 消息路由
- 从函数调用看消息路由