Windows编程之MFC窗口程序浅析

 

除了传统的SDK方式编写Windows应用程序外,还可以使用微软的基础类库MFC。它采用C++程序设计语言对SDK函数进行包装,使Windows下的C语言程序设计,成为面向对象的MFC。以下为MFC窗口应用程序框架示例及解析。

 

1.建立Win32项目FirstMFC

打开Visual Studio 2005à文件à新建à项目àVisual C++àwin32àwin32项目àwin32应用程序à空项目(默认使用Unicode字符集)。然后在“项目属性à常规àMFC的使用”中选择“在共享 DLL 中使用MFC”。如果运行时提示找不到mfc80d.dllmsvcr80d.dll文件,在“项目属性à配置属性à清单工具à常规”中的“使用FAT32解决办法”处选择“是”,再重新生成解决方案。

 

2.编写源文件FirstMFC.cpp

 

 

 

 

3MFC程序框架浅析 

3.1 afxwin.h

afxwin.hMFC标准头文件,它包含了afx.hà afxver_.hàafxv_w32.hàwindows.h,等头文件。该头文件包含了常用的窗口类CWnd、应用程序类CWinApp、线程类CWinThread、文档类CDoc、视图类CView、菜单类CMenu、对话框类Cdialog、设备描述表类CDC及常见绘图工具类,因此每一个MFC应用程序都必须加载它。

3.2 框架程序的启动

上面的程序中,我们首先由CFrameWnd派生CMainFrame,并编写了其鼠标单击消息响应;CWinApp派生CMainApp,并重载CWinApp::InitInstance,在其中new一个CMainFrame对象赋给CWinApp::m_pMainWnd;最后声明了CMainApp一个全局对象实例theApp。鉴于MFC框架的封装性,初学者可能会云里雾里。MFC既然只是对Windows GUI程序的一个封装,其筋骨内核还是SDK那一套:创建窗口+消息循环。你肯定会问,MFC框架程序是怎么启动的?入口函数WinMain在哪里呢?关于Windows应用程序的运行机制,我们在《Windows编程之从控制台到SDK窗口》中已做过初步探讨。我们还是F10,断点将停留在appmodul.cpp中的_tWinMain处,其调用堆栈如下:

// CRTEXE.C

wmainCRTStartupà__tmainCRTStartupà(w)WinMain

_tWinMainWinMain的平台适应宏(_t)其定义如下:

// TCHAR.H

#ifdef  _UNICODE

#define _tWinMain   wWinMain

#else   /* ndef _UNICODE */

#define _tWinMain   WinMain

至此,我们寻找到了基于MFC框架的Windows GUI程序的启动点。然而,_tWinMain函数极其简单,其只是简单的调用AfxWinMain

// APPMODUL.CPP

extern "C" int WINAPI

_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

    __in LPTSTR lpCmdLine, int nCmdShow)

{

    // call shared/exported WinMain

    return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);

}

我们继续F11深入其其幕后。AfxWinMain的代码如下:

// WINMAIN.CPP

int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)

{

    ASSERT(hPrevInstance == NULL);

 

    int nReturnCode = -1;

    CWinThread* pThread = AfxGetThread(); // CMainApp theApp;

    CWinApp* pApp = AfxGetApp(); // CMainApp theApp;

 

    // AFX internal initialization

    if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))

       goto InitFailure;

 

    // App global initializations (rare)

    if (pApp != NULL && !pApp->InitApplication())

       goto InitFailure;

 

    // Perform specific initializations

    if (!pThread->InitInstance())

    {

       if (pThread->m_pMainWnd != NULL)

       {

           TRACE0("Warning: Destroying non-NULL m_pMainWnd/n");

           pThread->m_pMainWnd->DestroyWindow();

       }

       nReturnCode = pThread->ExitInstance();

       goto InitFailure;

    }

    nReturnCode = pThread->Run();

 

InitFailure:

#ifdef _DEBUG

    // Check for missing AfxLockTempMap calls

    if (AfxGetModuleThreadState()->m_nTempMapLock != 0)

    {

       TRACE1("Warning: Temp map lock count non-zero (%ld)./n",

           AfxGetModuleThreadState()->m_nTempMapLock);

    }

    AfxLockTempMaps();

    AfxUnlockTempMaps(-1);

#endif

 

    AfxWinTerm();

    return nReturnCode;

}

在这里,我们发现了MFC框架的心脏——CWinThread

3.3 CWinApp

应用程序实例类CMainApp:CWinApp:CWinThread,MFCCWinApp:CWinThread类实现了SDK应用程序的WinMain函数体。传统SDK应用程序中WinMain函数所完成的工作由CWinAppInitApplicationInitInstanceRun成员函数承担。CMainApp theApp;创建了应用程序唯一的全局对象,它代表了应用程序的主线程,使得程序得以执行。

应用程序主框架窗口为CMainFrameCMainFrame:CFrameWnd:CWndCWnd类代表了MFC中最基本的GUI对象,它提供包括注册窗口类、创建窗口及子窗口、获得窗口、管理窗口、访问窗口及控件、控制窗口光标、创建和使用句柄等功能。CWnd封装了SDK中的RegisterClassCreateWindow等操作CFrameWnd类的对象是一个框架窗口,它包含边框、标题栏、菜单、最大/小化按钮和一个激活的视图,m_pMainWnd = new CMainFrame();则创建了应用程序的主窗口。

入口函数AfxWinMain通过AfxGetThread()AfxGetApp())获取应用程序示例theApp,然后调用theApp.InitApplication()进行全局初始化,进而调用theApp.InitInstance()

CWinThread::InitInstance()函数是CWinApp派生类唯一需要重载的函数,它负责应用程序的初始化,如初始化数据、创建文档模板、处理命令行以及应用程序主窗口的创建和显示。一般在其中完成主窗口的创建(CreateWindowEx)、显示(ShowWindow)和刷新(UpdateWindow)

AfxWinMain紧接着调用theApp.Run()CWinThread::Run()是线程消息收发功能的控制函数,完成GetMessageDispatchMessage

MFC应用程序的启动过程如下图所示:

 

 

相关源码参考APPMODUL.CPPWINMAIN.CPPAPPCORE.CPPTHRDCORE.CPP

 

CWnd::Create函数调用CreateExàPreCreateWindow/AfxHookWindowCreate/::CreateWindowEx,参考WINCORE.CPP

1PreCreateWindow-窗口类的注册

PreCreateWindow函数调用AfxDeferRegisterClassàAfxEndDeferRegisterClass [à_AfxRegisterWithIcon]àAfxRegisterClassà::RegisterClass注册窗口类.lpfnWndProc = DefWindowProc)。用户可调用AfxRegisterWndClass/AfxRegisterClass注册自己的窗口类,然后将返回值(窗口类名称)作为Create的第一参数。

如果传递给Create函数的第一个参数lpszClassNameNULL则使用系统默认类(afxRegisteredClasses):

CWnd::PreCreateWindowßàAFX_WND_REGßà_afxWnd

CFrameWnd::PreCreateWindowßàAFX_WNDFRAMEORVIEW_REG ßà_afxWndFrameOrView

CView::PreCreateWindowßàAFX_WNDFRAMEORVIEW_REGßà_afxWndFrameOrView

AFXIMPL.H中定义了AFX_WND*_REG AFX_WNDCLASS等;WINCORE.CPP中定义了_afxWnd*等窗口类名称字符串(const TCHAR[])

用户可以重载虚函数PreCreateWindow(末尾需要调用基类的该函数),设置其CREATESTRUCT参数,以改变CreateWindowEx的部分参数。

2CreateWindow-消息钩子及窗口的创建

因为在CreateWindowEx返回窗口句柄之前,窗口函数就开始处理诸如WM_GETMINMAXINFOWM_NCCREATEWM_CREATE等消息,所以必须在CreateWindowEx之前安装WH_CBT类型的钩子,以便截获CreateWindowEx返回之前调用过程中发送的消息。 AfxHookWindowCreate函数调用::SetWindowsHookEx(WH_CBT,_AfxCbtFilterHook, NULL, ::GetCurrentThreadId());安装钩子。钩子函数_AfxCbtFilterHook中调用SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)afxWndProc);将创建hWnd时所使用的WNDCLASS窗口模板的过程函数由DefWindowProc更改为AfxWndProc

3)消息循环

CWinThread::Run()函数调用::PeekMessage(&m_msgCur, )PumpMessage()来查询和获取消息,如果有消息则将其记录到CWinThread::m_msgCur成员中。在PumpMessage()中调用::GetMessage(&m_msgCur, ),如果Get到的MessageWM_QUIT,则PumpMessage()返回FALSECWinThread::Run()函数调用CWinThread::ExitInstance()函数做线程退出清理。如果Get到的MessageWM_QUITWM_KICKIDLEdialog equivalent of OnIdle),则先调用PreTranslateMessage(&m_msgCur)对要处理的消息进行过滤,再调用::TranslateMessage(&m_msgCur);对消息进行“翻译”,再调用::DispatchMessage(&m_msgCur);将消息传送给指定窗口的窗口函数,即AfxWndProc

AfxWndProcàAfxCallWndProc

CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);

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

AfxCallWndProc将消息交由具体窗口对象的WindowProc处理:CWnd::WindowProcàCWnd::OnWndMsgOnWndMsg中,对消息进行分流处理:WM_COMMANDàOnCommand,WM_NOTIFYàOnNotify。对于常规窗口消息,OnWndMsg调用GetMessageMap()函数获得DECLARE_MESSAGE_MAP声明的消息映射表AFX_MSGMAP,根据nMessagenCode调用AfxFindMessageEntry函数从消息映射表中查找AFX_MSGMAP_ENTRY,根据函数签名nSigMessageMapFunctions中查询正确的函数指针,完成消息函数(AFX_PMSG pfn)的正确调用

CreateWindowEx函数完成窗口的创建。返回hWnd之前,其WM_CREATE消息将被_AfxCbtFilterHook截获(wParam参数即为hWnd)。这样WM_CREATE消息就会被_AfxCbtFilterHook设置的AfxWndProc窗口函数处理。这也是为什么在窗口创建之前就需要安装消息钩子的原因。

用户可以重载虚函数PreTranslateMessage(末尾需要调用基类的该函数),对感兴趣的消息进行过滤预处理。

4)消息映射

    现在再来看看MFC中的消息处理,传统的SDK Windows应用程序消息处理方式采用switch-case分支结构实现消息的分发。而在MFC中采用消息映射机制(Message Map)。这种消息映射机制包括一组消息映射宏,一条消息映射宏把一个Windows消息和其他消息处理函数联系起来。

//声明消息映射

BEGIN_MESSAGE_MAP(theClass, theBaseClass)

//{{AFX_MSG_MAP

ON_WM_LBUTTONDOWN() //单击鼠标左键的映射宏

ON_MESSAGE(messageID,memberFun)

……

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

在类定义文件中声明消息处理函数,例如Windows预定义的单击鼠标左键消息的处理函数为:

afx_msg void OnLButtonDown(UINT nFlags,CPoint point);

为了使用消息映射宏,还需在头文件中的类末尾声明使用消息映射宏DECLARE_MESSAGE_MAP(),该宏为类声明消息映射AFX_MSGMAP

在类实现文件中,BEGIN_MESSAGE_MAP宏为消息映射AFX_MSGMAP的定义开始({),END_MESSAGE_MAP()宏为消息映射的定义结束(})。这两个宏之间的内容为AFX_MSGMAP. lpEntries,即AFX_MSGMAP_ENTRY _messageEntries[]。用户向其中添加关注的消息映射,并编写其消息响应函数。

 

4MFC程序框架的研究之道

探究MFC内部机制,需参阅MFC源码,我们可以对一个简单的MFC框架程序进行F5,F10F11以管中窥豹其内部运行流程。

1MFC巧妙地使用了精致而又强大的宏,这些宏会给阅读源码会带来一定的困难。

2)框架的状态信息也是理解的难点,包括模块状态AFX_MODULE_STATE线程状态_AFX_THREAD_STATE和模块线程状态AFX_MODULE_THREAD_STATE其中涉及到线程局部存储(TLS)的知识。例如消息队列即是一种线程私有数据,GetMessageTranslateMessageDispatchMessage等操作都是线程相关的。源码参考AFXTLS_.H/AFXTLS.CPPAFXSTAT_.H/AFXSTATE.CPPAPPMODUL.CPP

3MFC类库体系建立在动态类型识别和动态创建的基础上,CObject为对象之根,由一个重要的结构体CRuntimeClass将对象帝国体系化。其中涉及到两组重要的宏:DECLARE_DYNAMIC/IMPLEMENT_DYNAMICDECLARE_DYNCREATE/IMPLEMENT_DYNCREATE源码参考AFX.HOBJCORE.CPP

4MFC框架中的窗口管理基于窗口句柄映射CHandleMap,其中CWnd::Attach函数Attaches a Windows handle to a CWnd objectCWnd::Detach函数Detaches a Windows handle from a CWnd objectstatic CWnd::FromHandlePermanent函数Returns a pointer to a CWnd object when given a handle to a windowCWnd::GetSafeHwnd函数Returns CWnd::m_hWnd源码参考AFXCOLL.HAFXWIN.HWINCORE.CPPVIEWCORE.CPPWINFRM.CPP

5MFC中的消息映射将消息和消息处理函数关联起来,例如ON_WM_CREATE() WM_CREATEßàOnCreate,这样大大简化了switch-case庞大的分支处理体系。其中牵涉到类成员函数指针、函数签名(AfxSig)及函数类型转换。涉及到的数据结构有AFX_MSGMAP_ENTRY AFX_MSGMAP涉及到的宏有DECLARE_MESSAGE_MAPBEGIN_MESSAGE_MAP/END_MESSAGE_MAP源码参考AFXMSG_.HAFXWIN.H

6)面向对象中基于虚函数的多态性在MFC框架窗口消息处理流程中扮演着极其重要的角色。通过基类对象指针调用虚函数将在运行期调用实际指向的派生类对象的相应函数,动态绑定以改变处理流向。窗口的虚函数接口使得我们可以根据实际需要编写自己的处理过程,以取代基类的默认处理。

 

除了以上手动编写MFC框架程序外,我们也可以利用MFC向导生成一个通用应用程序框架:打开Visual Studio 2005à文件à新建à项目àMFCàMFC应用程序à单文档项目;编译生成可执行文件即可运行。

至此,我们通过对比传统SDK应用程序和MFC应用程序格式,对各自的框架有了初步的认识了。后面我们将通过应用实例来一步一步深入MFC/windows应用程序编程。

 

参考:

《深入浅出MFC》候俊杰

MFC深入浅出》李进久

Windows程序设计》王艳平

《深入解析MFCGeorge Shepherd, Scot Wingo

MFC程序逆向-消息篇

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 外部程序嵌入MFC窗口是指将一个独立的外部程序嵌入到MFC(Microsoft Foundation Class)窗口中。这种技术通常被用于在应用程序中集成一些第三方的功能或工具。以下是一个大致的实现过程: 首先,需要创建一个MFC窗口,可以使用MFC的资源编辑器来创建一个对话框或其他窗口控件。 然后,在MFC窗口类中创建一个CWnd控件对象,这个对象将用于承载外部程序窗口。 接下来,使用WinAPI函数来创建外部程序窗口,并将其父窗口设置为CWnd对象的句柄。这样外部程序窗口就会嵌入到MFC窗口中,成为其子窗口。 在实现过程中,可能需要处理一些与外部程序窗口的交互,例如获取外部程序的句柄,处理消息传递等。可以使用一些函数如FindWindow来获取外部程序的句柄,并通过重载MFC窗口的消息处理函数来处理与外部程序窗口的交互。 最后,需要在适当的时候销毁外部程序窗口,并释放相关资源,以确保程序的正常运行和内存的管理。 总的来说,外部程序嵌入MFC窗口是一种将两个独立程序的界面进行整合的方法,通过合理地处理消息传递和交互逻辑,可以实现功能上的集成和用户体验的提升。 ### 回答2: 外部程序嵌入MFC窗口是指将一个独立的外部程序嵌入到MFC应用程序窗口中显示,并且能够与应用程序进行交互。这样的嵌入可以提供更多的功能和服务,增强应用程序的功能性和用户体验。 实现外部程序嵌入MFC窗口的关键是利用MFC的类和函数来创建和管理嵌入的进程。下面是实现的基本步骤: 1. 创建一个MFC应用程序,并在窗口中添加一个控件(如一个 CStatic 控件)来充当嵌入的容器。 2. 在应用程序中引入系统的 COM 组件,如 OLE ,以便能够与外部程序进行交互。 3. 在应用程序中创建并启动外部程序,可以使用 MFC 提供的 CWinApp 类的 CreateProcess 函数或者使用 Windows API 中的 CreateProcess 函数。 4. 获取外部程序窗口句柄,可以使用 Windows API 中的 FindWindow 函数或者使用外部程序的进程 ID。 5. 将外部程序窗口句柄嵌入到应用程序窗口的容器控件中,可以使用 MFC 提供的 CWnd 类的 Attach 函数。 6. 根据需要,可以设置嵌入的程序窗口位置和大小。 7. 在应用程序中处理嵌入程序的消息和事件,可以使用 MFC 提供的消息映射机制和事件处理函数。 8. 在应用程序关闭或嵌入程序退出时,释放相关的资源,可以使用 MFC 提供的相应函数(如 CWnd::Detach 、 TerminateProcess 等)。 总结:外部程序嵌入到MFC窗口是一种提供更多功能和增强用户体验的方法。通过使用MFC的类和函数,我们可以轻松地将外部程序嵌入到应用程序窗口中,并与之进行交互。这样的嵌入给用户带来了更多的便利和功能性。 ### 回答3: 外部程序嵌入MFC窗口是指将一个独立的外部程序嵌入到MFC(Microsoft Foundation Class)窗口中进行显示和操作。这种技术常用于在MFC应用程序中集成其他应用程序的功能,并且能够与MFC应用程序进行交互。 要实现外部程序嵌入MFC窗口,可以按以下步骤进行: 1. 获取目标外部程序的句柄:通过使用Win32 API函数,如FindWindow函数,获取到目标外部程序的句柄。句柄是一个唯一标识符,用于标识一个窗口。 2. 创建子窗口:在MFC程序窗口中,使用CreateEx函数创建一个子窗口来容纳外部程序。可以根据需要设置子窗口的样式和属性。 3. 设置窗口句柄:将外部程序的句柄设置为子窗口的句柄,使用SetParent函数实现。这样,外部程序窗口就能嵌入到MFC程序窗口中了。 4. 调整窗口大小和位置:根据需要,可以使用MoveWindow函数来调整外部程序窗口嵌入的位置和大小。 5. 处理交互事件:通过重写MFC程序的消息处理函数,处理与外部程序的交互事件。根据外部程序提供的消息、回调函数或API,来实现与外部程序的通信和交互。 6. 销毁子窗口:在不需要嵌入外部程序窗口时,使用DestroyWindow函数来销毁子窗口,释放资源。 外部程序嵌入MFC窗口能够使得MFC应用程序具备更加丰富和强大的功能,提升用户体验,实现不同程序之间的无缝衔接。但也要注意兼容性和安全性问题,确保嵌入的外部程序不会对系统和数据造成损害。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值