Windows 编程/Microsoft Foundation Classes
本质上,MFC 是一个 SDK 接口,一个由一系列类组成的库,这些类充当 Windows API 部分的包装器,以便 C++ 程序员可以使用面向对象编程 (OOP) 范式和 C++ 语言来编写 Windows 程序(Win32 API 基于 C,如本书的 C 和 Win32 API 部分 所示)。应该学习 Win32 API 或至少了解一些概念,因为 MFC 中缺少一些函数,这将有助于更好地理解 SDK。
一些工具,例如 Microsoft Visual Studio,能够自动生成大量用于项目中的 MFC 骨架代码。因此,大多数 MFC 教程或参考材料都将使用自动化的 Visual Studio 工具来教授该主题,并省略一些细枝末节。在这本书中,我们尽可能保持中立。
MFC 最初主要面向企业级编程项目,创建于大多数代码都使用 C 语言且面向对象编程仅限于 Smalltalk 领域的时代。
自 Visual Studio 6.0 和 MFC 6.0 发布以来,由于该公司更青睐 .NET Framework,因此人们对 MFC 的未来支持知之甚少。版本 7.0、7.1 和 8.0 主要是在扩展以支持新的操作系统并帮助开发人员迁移到新的框架。从那时起,有关 MFC 未来信息只能从 Steve Teixeira(微软公司,2005 年 6 月)的论文中提取 - MFC:Visual Studio 2005 及以后,在 Visual Studio 2008 Service Pack 1 发布时,微软似乎再次积极支持 MFC。
如今,许多用户发现,对于低复杂度的程序,30-80Mb 的内存占用量是可以接受的(这在 Java 或 .Net 应用程序中很常见),响应时间较慢或诸如互联网现在提供的“超出您的控制范围”的应用程序也是如此。因此,在小型应用程序中使用 MFC 的影响是否大于库提供的优势尚有争议。如今,大多数专门为 Windows 制作的软件都使用 MFC。
如果您不打算
- 使用复杂的 GUI、使用文档/视图架构或复杂的控件。
这将增加系统资源的使用(内存使用、exe 和安装大小)。 - 使用依赖于 MFC 的其他库。
- 为您的应用程序提供复杂的安装。
则应首选 Win32 API SDK 或其替代包装器。要分发基于 MFC 的项目,必须使用静态 MFC 库构建它或将项目与所需的 MFC dll 一起分发。不能保证您的客户拥有程序所需的 dll。旧的基于 MFC 的程序必须与新的 MFC 版本一起工作。它们应该可以,但并不总是这样。如果在安装您的项目后,客户的一些旧软件产品开始挂起,他们将不会感到高兴。
MFC 设计原则旨在简化。包装器类旨在简化某些任务并自动化其他任务。但是,由于这些事实,从原始 Win32 API 中丢失了一定程度的微调控制或存档了过度的自动化。MFC 被认为存在严重的架构缺陷和不一致性,并且没有得到积极维护。C++ 语言和最佳实践已经发展,如今这构成了使用该框架的障碍。
由于 MFC 早于 STL 标准化到 C++ 语言,因此它实现了 STL 容器的自身版本,这些版本并不完整甚至不一致,MFC 实现的这些简单解决方案往往更快,但是您应该尽可能使用 STL,它将使代码更符合 C++ 标准并允许在将代码转换为多平台时更容易移植。
- 多重继承
MFC 类库不使用多重继承,并且不是为完全支持多重继承而设计的。
由于大多数 MFC 类都派生自 CObject,因此使用多重继承会导致歧义问题(对 CObject 成员函数的任何引用都必须消除歧义)。静态成员函数,包括operator new和operator delete也必须消除歧义。
最佳选择是避免在 MFC 中使用多重继承,但请查看 在 MFC 中使用 C++ 多重继承(msdn.microsoft.com)以获取有关如何绕过这些限制所需的信息。
MFC 使用 匈牙利命名法。它使用前缀,例如“m_”表示成员变量或“p”表示指针,其余名称通常以驼峰命名法编写(每个单词的首字母大写)。
- CObject 作为大多数 MFC 类的根
MFC 中所有重要的类都派生自 CObject 类。CObject 没有任何成员数据,但具有一些默认功能。
MFC 需要与标准 <windows.h> 头文件分开的头文件。MFC 系统的核心需要包含 <afxwin.h>。其他一些有用的头文件是 <afxext.h>(用于 MFC 扩展)和 <afxcmn.h>(用于 MFC 通用对话框)。
不幸的是,仅仅更改头文件还不够。MFC DLL 库必须由项目链接,因为 DLL 文件包含将在每个程序中使用的类定义。要使用 MFC,必须链接到 MFC 库
stdafx.h 是 MFC 项目的标准包含文件——也就是说,如果您创建一个新的 MFC 项目,则会自动为您创建一个 stdafx.h。它将包含所有其他必要的 MFC 头文件。
extern CYourAppClass theApp;
在应用程序类的头文件中使用,然后在需要使用 theApp 的任何地方包含它。
您可以尝试使用AfxGetApp函数获取指向theApp的指针,这是一种访问应用程序成员的有效方法,即将指向theApp的指针作为需要它的类的成员变量——例如
class CMyDialog : public CDialog
{
// other class stuff here...
// Attributes
public:
CMdiApp* m_pApp;
};
并确保在构造函数中初始化m_pApp,否则将访问NULL指针。
CMyDialog::CMyDialog(CWnd* pParent /*=NULL*/): CDialog(CMyDialog::IDD, pParent)
{
//{{AFX_DATA_INIT(CMyDialog)
//}} AFX_DATA_INIT
// Outside the special-format comments above...
m_pApp = (CMdiApp*)AfxGetApp( );
}
瞧!现在任何时候您都需要访问您的应用程序,您都可以获得它!
m_pApp->m_nMemberVar;
m_pApp->MemberFunction(nParam1, strParam2);
首先必须提到,MFC 不是那种“看起来像 C”的 C++ 编程风格。MFC 大量使用了 C++ 的面向对象特性,对于新的 C++ 程序员来说,这可能看起来“密集”甚至难以阅读。强烈建议读者现在熟悉 C++ 概念,例如类和层次结构,如果他们还不熟悉这些概念。
MFC 的根类是CObject类。CObject 本身不支持多重继承,但派生类支持。每个应用程序都从CWinApp派生的类开始。每个程序都必须有一个 CWinApp 类,并且每个应用程序只能有一个。CWinApp 包含许多用于初始化应用程序和控制实例句柄(类似于 WinMain 函数的 HINSTANCE 成员)的函数。想要显示窗口的程序必须使用CWnd类的派生类。
我们将在此处概述一个基本的 MFC 程序,该程序将创建一个简单的窗口,但不会处理任何用户输入。从这个基本概述开始,我们将能够解决更困难的问题。
#include <afxwin.h> //basic MFC include
//class derived from CFrameWnd, which is derived from CWnd
class Basic_Window:public CFrameWnd
{
public:
Basic_Window()
{
Create(NULL, "Basic MFC Window");
// In some cases you might want to use
// Create(NULL, _T(":Basic MFC Window"));
}
};
//class derived from CWinApp, which is the main instance of our application
class MyProgram:public CWinApp
{
//a pointer to our window class object
Basic_Window *bwnd;
public:
//this is essentially our "entry point"
BOOL InitInstance()
{
bwnd = new Basic_Window();
m_pMainWnd = bwnd;
m_pMainWnd->ShowWindow(1);
return 1;
}
};
//the program class instance pointer
MyProgram theApp;
正如我们在这里看到的,我们立即依赖于类定义和继承,因此对于不完全熟悉这些主题的读者来说,在继续之前最好先复习一下。
MFC 提供了许多全局变量,这些变量被实例化,然后在底层的 MFC 框架中使用以编译您的程序。当我们使用变量m_pMainWnd时,可以在我们的基本示例代码中看到这一点。
通用解决方案是PostQuitMessage([退出代码]);,但请注意清理任何残留的资源(关闭文档、释放内存和资源、销毁创建的任何其他窗口等),另一方面,使用AfxGetMainWnd()->PostMessage( WM_CLOSE );在某些情况下可能是一种更好的方法,因为它会触发正确的关闭顺序。这在 MDI/SDI 应用程序中尤其重要,因为它让文档有机会在退出前提示保存或让用户取消退出。
为了显示繁忙/等待鼠标光标,MFC 添加了一个简单的辅助类。在函数内部实例化一个 CWaitCursor 类,然后在该函数的持续时间内显示等待光标,类的自动销毁将恢复光标状态。
CWaitCursor aWaitCursor;
正如预期的那样,MFC 也将其自身的特殊类和函数包装在 Win32 线程原语中。有关线程和 C++ 的一般信息,请参见C++ 编程 Wikibook 关于多任务处理的部分。MFC 将线程设计为工作线程和 GUI 线程。
工作线程特别适用于执行后台任务或任何不需要用户干预的异步工作,例如打印作业、计算、等待事件等。要创建工作线程,最简单的方法是实现一个执行所需工作的函数,然后使用AfxBeginThread()
创建线程,该线程将使用该特定函数。
除非您别无选择以保证线程终止,否则永远不要使用TerminateThread()
。这是不好的做法并且很危险。
可以通过从线程中正常返回(完成)或发出信号使其过早返回来实现正确的线程退出。由于我们在 Windows 上并且在 32 位绑定 CPU 上(使 32 位访问成为原子操作),因此使用共享bool
变量来指示线程退出被认为是安全的(但不可移植),如果可用,可以使用任何其他同步方法。
由于关于线程终止的唯一问题是在中止作业或退出程序时没有运行的线程,因此在处理工作线程时,在创建线程的类的析构函数中,您应该向线程发出中止信号,然后使用WaitForSingleObject()
等待线程终止。