(转) MFC的入口点与消息循环,消息映射
博文分析的很不错,尤其是替换默认窗口过程的这块,本人觉得,所有的这些都可以参阅侯杰的《深入浅出MFC 》。
来自:http://blog.csdn.net/sryan/article/details/7392814
1.MFC的入口点与简单的执行过程
MFC隐藏了windows程序的入口点winMain,其实是在appmodule.cpp文件下,该入口点调用MFC的全局函数AfxWinMain作为MFC的入口点
然后AfxWinMain会进行一些初始化操作,并执行全局CWinApp的InitInstance函数,即我们重写的一个虚函数
在CWinApp函数中,会初始化我们的窗口指针,调用了该窗口的构造函数,在构造函数中会有一个Create函数,该函数会注册一个窗口类,但是还没有类名和窗口过程,接着会执行一个PreCreateWindow的函数,处理好后,会给一个类名和一个DefWindowProc,并注册好这个窗口类,紧接着,会执行AfxHookWindowCreate函数,作用是将该窗口的窗口过程变为AfxWindowProc,即为一个全局的窗口过程函数,即MFC下所有窗口都共享一个窗口过程。窗口类注册好后,就用CreateWindow的api函数创建了一个窗口,窗口的创建就完成了
这是由入口点大概的执行流程,中间也忽略了不少函数,但是差不多就是windows sdk写程序的大概流程了,注册窗口类之类的全在里面了。
最主要的还是窗口的消息循环,由上述步骤可以发现,MFC的窗口的WindowProc是一个函数,即全局的AfxWindowProc,接下来记录下MFC的消息流向即消息映射。
AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
// special message which identifies the window as using AfxWndProc
if (nMsg == WM_QUERYAFXWNDPROC)
return ; // all other messages route through message map
CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
ASSERT(pWnd != NULL);
ASSERT(pWnd==NULL || pWnd->m_hWnd == hWnd);
if (pWnd == NULL || pWnd->m_hWnd != hWnd)
return ::DefWindowProc(hWnd, nMsg, wParam, lParam);
return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);
}
这是MFC窗体所共有的窗口过程,即任何窗体接受到的消息,最终都会流向这里,首先 该函数会由消息的接受句柄来得到它的窗体指针。
LRESULT AFXAPI AfxCallWndProc(CWnd* pWnd, HWND hWnd, UINT nMsg,
WPARAM wParam = , LPARAM lParam = )
{
_AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
MSG oldState = pThreadState->m_lastSentMsg; // save for nesting
pThreadState->m_lastSentMsg.hwnd = hWnd;
pThreadState->m_lastSentMsg.message = nMsg;
pThreadState->m_lastSentMsg.wParam = wParam;
pThreadState->m_lastSentMsg.lParam = lParam; #ifdef _DEBUG
_AfxTraceMsg(_T("WndProc"), &pThreadState->m_lastSentMsg);
#endif // Catch exceptions thrown outside the scope of a callback
// in debug builds and warn the user.
LRESULT lResult;
TRY
{
#ifndef _AFX_NO_OCC_SUPPORT
// special case for WM_DESTROY
if ((nMsg == WM_DESTROY) && (pWnd->m_pCtrlCont != NULL))
pWnd->m_pCtrlCont->OnUIActivate(NULL);
#endif // special case for WM_INITDIALOG
CRect rectOld;
DWORD dwStyle = ;
if (nMsg == WM_INITDIALOG)
_AfxPreInitDialog(pWnd, &rectOld, &dwStyle); // delegate to object's WindowProc
lResult = pWnd->WindowProc(nMsg, wParam, lParam); // more special case for WM_INITDIALOG
if (nMsg == WM_INITDIALOG)
_AfxPostInitDialog(pWnd, rectOld, dwStyle);
}
CATCH_ALL(e)
{
lResult = AfxProcessWndProcException(e, &pThreadState->m_lastSentMsg);
TRACE(traceAppMsg, , "Warning: Uncaught exception in WindowProc (returning %ld).\n",
lResult);
DELETE_EXCEPTION(e);
}
END_CATCH_ALL pThreadState->m_lastSentMsg = oldState;
return lResult;
}
最主要的是lResult = pWnd->WindowProc(nMsg, wParam, lParam)这句,将会执行由消息接收句柄对应窗体的窗口过程函数。
LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
// OnWndMsg does most of the work, except for DefWindowProc call
LRESULT lResult = ;
if (!OnWndMsg(message, wParam, lParam, &lResult))
lResult = DefWindowProc(message, wParam, lParam);
return lResult;
}
在这里又被转发到该窗体的OnWndMsg函数内。
该函数很长,实现在wincore.cpp内,由此我们可以所有的MFC窗体共享一个窗口过程,即AfxWindowProc,然后由这个中转站根据接收句柄来得到需要得到这个消息的对象,然后执行该对象内的窗口过程。
OnWndMsg又是MFC消息映射的实现函数,首先我们得看下消息映射的一系列宏定义。
#define DECLARE_MESSAGE_MAP() \
protected: \
static const AFX_MSGMAP* PASCAL GetThisMessageMap(); \
virtual const AFX_MSGMAP* GetMessageMap() const; \
struct AFX_MSGMAP
{
const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
const AFX_MSGMAP_ENTRY* lpEntries;
};
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)
};
这是在定义一个支持消息映射的类必须要写的宏定义。可见这个宏展开就是声明了两个protected权限的函数,一个为静态函数,一个为虚函数。其中两者返回的都是AFX_MSGMAP的结构体,结构体成员为1个函数指针和一个结构体,这个结构体保存了消息与对应执行函数的对应关系。要使用一个消息循环,得添加一个宏。
#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[] = \
{ #define END_MESSAGE_MAP() \
{, , , , AfxSig_end, (AFX_PMSG) } \
}; \
static const AFX_MSGMAP messageMap = \
{ &TheBaseClass::GetThisMessageMap, &_messageEntries[] }; \
return &messageMap; \
} \
PTM_WARNING_RESTORE
首先是BEGIN_MESSAGE_MAP这个宏,由于得对照这来,得喝END_MESSAGE_MAP()一起来看。这两个宏的主要目的就是为了实现在类声明时候加入的DECLARE_MESSAGEMAP这个宏的两个函数。实现的第一个虚函数即为调用了这个类的GetThisMessageMap这个静态函数。
下面来看这个静态函数的实现,在这个静态函数中有一个静态的数组,该数组保存着消息对应执行的消息函数,在譬如ON_WM_LBUTTONDOWN这些宏的帮助下把AFX_MSGMAP_ENTRY这个结构初始化。举个例子,ON_WM_LBUTTONDOWN展开是
#define ON_WM_LBUTTONDOWN() \
{ WM_LBUTTONDOWN, , , , AfxSig_vwp, \
(AFX_PMSG)(AFX_PMSGW) \
(static_cast< void (AFX_MSG_CALL CWnd::*)(UINT, CPoint) > ( &ThisClass :: OnLButtonDown)) },
即将WM_LBUTTONDOWN这个消息和这个类想要对应的OnLButtonDown这个函数绑定在一起了,消息映射表中就存在着了一个对应关系。其中的AfxSig代表了不同类型的消息。其中将派生类的OnLButton进行了三次强转,第一次是转为CWnd成员函数指针类型,参数不变,第二次转为了无参版本,第三次转为了CCmdTarget无参版本,无论怎么转换,它都是个成员函数,始终是派生类的成员函数地址。而AfxSig会记录下具体的参数,在OnWndMsg中会解析出来并还原执行。当住消息循环收到WM_LBUTTONDOWN这个消息时,会根据接收的句柄来调用本窗口对应的窗口过程,即OnWndMsg这个函数,当是下面子控件的通知消息时,就交给自身的OnCommand等函数处理。
if (message == WM_COMMAND)
{
if (OnCommand(wParam, lParam))
{
lResult = ;
goto LReturnTrue;
}
return FALSE;
} // special case for notifies
if (message == WM_NOTIFY)
{
NMHDR* pNMHDR = (NMHDR*)lParam;
if (pNMHDR->hwndFrom != NULL && OnNotify(wParam, lParam, &lResult))
goto LReturnTrue;
return FALSE;
}
在消息映射表中,指定了特定的消息类型,LButtonDown最终会被此处执行。
case AfxSig_v_u_p:
{
CPoint point(lParam);
(this->*mmf.pfn_v_u_p)(static_cast<UINT>(wParam), point);
}
break;
最终执行到了重写的OnLButtonDown这个函数。union MessageMapFunctions是一个联合体,大小为一个指针大小,存放着一个函数指针,也就是对应的消息响应函数(这里很特别,联合体的定义是所有的消息响应函数,直接根据sig可以获得具体的参数及返回值后无需强转就执行到了那个函数)。假如直接用WM_消息的话,还得每次都强转为相应的指针,而这里只需要依据特定的sig就能执行。
(转) MFC的入口点与消息循环,消息映射的更多相关文章
- Chromium on Android: Android在系统Chromium为了实现主消息循环分析
总结:刚开始接触一个Chromium on Android时间.很好奇Chromium主消息循环是如何整合Android应用. 为Android计划,一旦启动,主线程将具有Java消息层循环处理系统事 ...
- Android之MessageQueue、Looper、Handler与消息循环
在android的activity中有各种各样的事件,而这些事件最终是转换为消息来处理的.android中的消息系统涉及到: * 消息发送 * 消息队列 * 消息循环 * 消息分发 * 消息 ...
- 深入探讨MFC消息循环和消息泵
首先,应该清楚MFC的消息循环(::GetMessage,::PeekMessage),消息泵(CWinThread::PumpMessage)和MFC的消息在窗口之间的路由是两件不同的事情.在MFC ...
- MFC模态对话框的消息循环
MFC模态对话框的消息循环 单线程程序, 当主窗口响应函数中弹出模态对话框时,为什么主窗口响应函数可能照常工作? 当弹出模态对话框时,线程的消息循环无法返回,父窗口的事件本应没人处理,应该处于卡死状态 ...
- C# 一、语法结构、注释、命名空间、Main入口点、变量与常量、运算符、流程控制(循环)
一.代码结构 1)一个 C# 程序主要包括以下部分: 命名空间声明(Namespace declaration) 一个 class Class 方法 Class 属性 一个 Main 方法 语句(St ...
- MFC消息循环
MFC消息循环 MFC应用程序中处理消息的顺序 1.AfxWndProc() 该函数负责接收消息,找到消息所属的CWnd对象,然后调用AfxCallWndProc. 2.AfxCallWndProc ...
- MFC中PeekMessage的使用,非阻塞消息循环
在程序设计的时候经常要进行一个数据循环,比如播放音乐需要循环的向缓冲区里面写入数据,在这个时候比较通用的方法是建立一个线程做事情,但是有时候不想创建多线程就可以使用微软提供的PeekMessage方法 ...
- MFC项目中:报错:“fatal error LNK1561: 必须定义入口点”解决方法
编译的时候,报错:“fatal error LNK1561: 必须定义入口点” 解决方案1: 右键->属性->链接器->高级->入口点,设置成:WinMainCRTStartu ...
- Android的消息循环机制 Looper Handler类分析
Android的消息循环机制 Looper Handler类分析 Looper类说明 Looper 类用来为一个线程跑一个消息循环. 线程在默认情况下是没有消息循环与之关联的,Thread类在ru ...
随机推荐
- 查看Aix系统配置命令
prtconf#topas http://baike.baidu.com/link?url=QruEnlfCqyoqQ565LicyKxIGMQYSkVesj6j9GzHWwzpDOagXtuprhT ...
- Nginx日志按天分割
核心思想:使用crontab在每日23:59执行日志分割. 1.配置nginx日志信息,vim /etc/logrotate.d/nginx /var/log/nginx/*.log { nocomp ...
- hibernate导入大量数据时,为了避免内存中产生大量对象,在编码时注意什么,如何去除?
Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); for ( i ...
- C#编写记事本(高仿)
近一周写的关于记事本的代码,高仿记事本.本人C#入门不久,其中存在代码冗余,但懒得修改了. 经测试运行正常. 一.主窗体设计及代码 namespace BestEditor { public part ...
- asp.net runat="server" && hiddenfield
runat="server", c#可以直接获得client控件,并且赋值 hiddenfield 可以作为传值,或者界面存值,后台每次读取,并且再赋值到前台,这样前台就可以把上一 ...
- ajaxFileUpload - Post file and data together
jQuery.extend({ createUploadIframe: function(id, uri) { //create frame var frameId = 'jUploadFrame' ...
- [译]深入理解JVM
深入理解JVM 原文链接:http://www.cubrid.org/blog/dev-platform/understanding-jvm-internals 每个使用Java的开发者都知道Java ...
- VB.Net 文件处理类
1.编写日志 2.本地文件的读取和写入 3.Base64与图片的互相转换 Imports System.IO Imports System.Text Public Class Cls_File #Re ...
- AJAX 基础知识
1.AJAX 是一种用于创建快速动态网页的技术.而是一种用于创建更好更快以及交互性更强的 Web 应用程序的技术.通过在后台与服务器进行少量数据交换,AJAX 可以使网页实现异步更新.这意味着可以在不 ...
- asp.net 运行时, 报控件不存在
Asp.net 运行时,报控件不存在,但系统中确实加入了控件z, 但是生成网站的时候,报控件不存在,输入代码的时候,this.edtxx.Text 确实可以输入 原因: 系统修改的时候,作了一个备份, ...