最简 MFC 窗口程序
通过 MSVC 的 MFC 模板向导生成的 MFC 工程让人看得眼花缭乱,给你生成了一堆“可能用得到”(很可能用不到)的垃圾代码,让人觉得不太好上手。
但其实 MFC 不需要那么复杂,就像纯 C 写 Win32 App 一样,一个单文件即可搞定。
需要注意的是 MFC 的代码风格是古早时期的“VC++”的风格,可以说是罪孽深重:
- 不使用命名空间
- 不使用智能指针(但是却半吊子使用 RAII 就很让人抓耳挠腮)
- 使用 114514 个宏,其中包括
TRUE 、 FALSE 、NO_ERROR 、WAIT_FAILED (去和 khronos.org 打一架吧)
- MFC 相关的 C++ 头文件的后缀竟然是
.h 。
- 混合使用驼峰式、匈牙利式、咆哮蛇式(全大写 + 下划线分词)命名风格
- 混合使用异常处理模型:
- 调用函数,判断返回值是
TRUE 还是 FALSE 的模型
例子代码:if (!xxx()) goto FailExit;
- 调用函数,判断返回值是
SUCCESS (值为零) 还是 FAIL (值为各种各样的错误代码)的模型
例子代码:if ((err = xxx()) != 0) goto FailExit;
- 调用函数,这个函数设置一个全局的 last error,然后让你去判断 last error 的值的模型
例子代码:xxx(); if ((err = GetLastError()) != 0) goto FailExit;
- C++ 的 try throw catch 异常处理模型
非常不清真,五味杂陈,污染严重。各位千万注意自己写 C++ 的时候一定不要这样搞(因此也请珍爱生命、远离 MFC),请合理使用命名空间、智能指针、统一的代码风格,统一的异常处理模型(请合理使用 try throw catch 异常处理模型),避免定义宏常量,而是要使用 enum,并且对头文件需要合理使用文件后缀来区分 C 语言的头文件(.h )和 C++ 的头文件(.hpp )。
以下是最简 MFC 窗口程序(我开了一个 Win32 Console 工程,所以你会看到我写了 int main() 作为我的程序入口点)。
main.cpp
#include <afxwin.h>
class MinMFCDemo : public CWinApp
{
protected:
public:
BOOL InitInstance() override;
int ExitInstance() override;
};
class DemoMainWindow : public CFrameWnd
{
public:
virtual BOOL DestroyWindow() override;
};
BOOL MinMFCDemo::InitInstance()
{
if (!CWinApp::InitInstance()) return FALSE;
auto MainWindow = new DemoMainWindow();
MainWindow->Create(NULL, _T("MFC Demo"));
MainWindow->ShowWindow(SW_SHOW);
m_pMainWnd = MainWindow;
return TRUE;
}
int MinMFCDemo::ExitInstance()
{
return CWinApp::ExitInstance();
}
BOOL DemoMainWindow::DestroyWindow()
{
AfxPostQuitMessage(0);
return CFrameWnd::DestroyWindow();
}
MinMFCDemo g_MinMFCDemoApp;
extern int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow);
int main(int argc, char** argv)
{
return AfxWinMain(GetModuleHandle(NULL), NULL, GetCommandLine(), SW_SHOW);
}
开发思路(对比纯 C 写 Win32 窗口)
其实使用 MFC 的开发思路依然是 Win32 API 的思路,创建窗口,处理窗口消息,走消息循环,退出程序。但是因为 MFC 对 Win32 的 API 进行了一层面向对象的方式封装,我们按照 MFC 的这层封装来 重新理解 Win32 API:
- 所有 Win32 API 的“句柄”(比如
HWND 、HDC 等)都被包一层 class 变为类(比如 CWnd ,CDC ),使用这些类可以使原本看起来混乱的 Win32 API 函数在 MFC 的组织下被归类到每个类的成员方法里,配合自动提示,可以使 Win32 API 更容易使用。
- 可以取回原始句柄,比如
CWnd 有个成员变量 m_hWnd 以及方法 GetSafeHwnd() (这个方法内部就是判断一下 this 是不是 NULL ,如果是则返回 NULL ,否则返回 m_hWnd )。
- 类析构的时候会自动销毁句柄。
- 窗口的 MFC 类(
HWND 包一层 class 变为 CWnd ):我继承这个类以便于实现我自己的主窗口,控制主窗口的行为。
- 比如我想在窗口被销毁的时候(收到窗口消息
WM_DESTROY 的时候)调用 PostQuitMessage() 来结束消息循环。按照 MFC 的风格,你的窗口收到 WM_DESTROY 消息的时候,类的成员函数 DestroyWindow() 被 MFC 框架调用),我给我的窗口的类编写 DestroyWindow() 方法,它的内部调用 AfxPostQuitMessage() 来通知退出消息循环。
- 上述代码我继承的是
CFrameWnd 类,这是一种“典型单文档框架窗口”,它的父类是 CWnd 。
- 整个应用程序是个类(
CWinApp ),按照 MFC 的框架,你的应用程序启动的时候会需要加载你的资源、进行一个初始化的动作(此时 MFC 框架调用 InitInstance() ,你重载这个函数来进行你的初始化),运行的时候要跑消息循环(MFC 框架调用 Run() ),退出的时候要清理内存(MFC 框架调用 ExitInstance() )。
- 因此我继承这个类,编写我自己的
InitInstance() 方法,重载父类的 InitInstance() 。我在这个方法实现里初始化我的东西,比如:创建我的主窗口。
- MFC 的
CWinApp 有个成员变量 CWnd* m_pMainWnd 是用来存你的主窗口的,你自己先 new 出你的主窗口的类,然后再赋值过去给它以便于 MFC 框架管理你的主窗口。注意这个成员变量会在窗口被关闭后被 MFC 框架自动销毁。这里其实有个很令人不爽的一点是它是个 原始指针。如果是智能指针的话,它啥时候会被销毁掉这一点会变得很明确。但是因为 MFC 似乎诞生于智能指针被引入 C++ 之前的年代,当时是没有智能指针可以用的。所以这是个历史遗毒。
- 你的应用程序类需要被写成全局变量,如上述代码的
MinMFCDemo g_MinMFCDemoApp; 这一行。这个地方会产生函数调用:你的应用程序类的构造函数会在你的程序入口点进入前被调用,它会把自己注册到 MFC 的框架里,这样 MFC 的框架就知道哪个是你的应用程序类了。其实全局变量是坏文明,它其实很妨碍编译器优化,但 MFC 这个性质的东西是为了让你写 Win32 App 的时候可以爽,所以在这块,针对你的应用程序类的方法调用的优化并不是一个重要的考虑内容。
- MFC 框架有它自己的程序入口点:
AfxWinMain() ,调用约定和参数都和普通的 _tWinMain() 一样。这个函数会初始化 MFC 的框架,找到你的应用程序类,调用它的 InitApplication() ,然后调用 InitInstance() 并判断返回值,如果返回了 FALSE 那就拒绝启动,否则继续;调用 Run() 进入主消息循环,等到 Run() 返回了,则调用 ExitInstance() 来让你把你分配的内存释放一下,最后清理内存,退出程序。
- 你既可以通过调用 MFC 提供的入口点函数来使 MFC 框架运行,也可以你自己去初始化 MFC 的环境然后你自己调用你的
InitApplication() 、InitInstance() 、Run() 、ExitInstance() 来跑你的应用程序类。
示例工程下载
使用 VS2022 打开 sln 文件。
minmfc.zip
(3.88 KB, 下载次数: 0, 售价: 1 个宅币)
|