找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 479|回复: 2

【MFC】最精简 MFC 窗口程序

[复制链接]

1110

主题

1651

回帖

7万

积分

用户组: 管理员

一只技术宅

UID
1
精华
244
威望
743 点
宅币
24217 个
贡献
46222 次
宅之契约
0 份
在线时间
2296 小时
注册时间
2014-1-26
发表于 2023-4-21 17:06:42 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有账号?立即注册→加入我们

×

最简 MFC 窗口程序

通过 MSVC 的 MFC 模板向导生成的 MFC 工程让人看得眼花缭乱,给你生成了一堆“可能用得到”(很可能用不到)的垃圾代码,让人觉得不太好上手。

但其实 MFC 不需要那么复杂,就像纯 C 写 Win32 App 一样,一个单文件即可搞定。

需要注意的是 MFC 的代码风格是古早时期的“VC++”的风格,可以说是罪孽深重:

  • 不使用命名空间
  • 不使用智能指针(但是却半吊子使用 RAII 就很让人抓耳挠腮)
  • 使用 114514 个宏,其中包括 TRUEFALSENO_ERRORWAIT_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 的“句柄”(比如 HWNDHDC 等)都被包一层 class 变为类(比如 CWndCDC),使用这些类可以使原本看起来混乱的 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 个宅币)
回复

使用道具 举报

12

主题

32

回帖

842

积分

用户组: 大·技术宅

UID
5148
精华
2
威望
7 点
宅币
744 个
贡献
30 次
宅之契约
0 份
在线时间
75 小时
注册时间
2019-7-17
发表于 2023-4-26 23:06:46 | 显示全部楼层
想知道如果不手动释放内存,不按标准流程走,直接退出程序的话...
Windows 也还是会完美地回收干净这个进程使用的内存吧?
回复 赞! 靠!

使用道具 举报

1110

主题

1651

回帖

7万

积分

用户组: 管理员

一只技术宅

UID
1
精华
244
威望
743 点
宅币
24217 个
贡献
46222 次
宅之契约
0 份
在线时间
2296 小时
注册时间
2014-1-26
 楼主| 发表于 2023-4-28 15:10:52 | 显示全部楼层
戈登走過去 发表于 2023-4-26 23:06
想知道如果不手动释放内存,不按标准流程走,直接退出程序的话...
Windows 也还是会完美地回收干净这个进 ...

如果只说“内存”的话,是这样。

但是按照 RAII 的思想,对象的资源并不仅仅是内存,它还可以是别的资源,比如 TCP 连接、比如临时文件。类可以在构造对象的时候产生临时文件,析构对象的时候销毁临时文件。

正常走流程退出程序的时候,所有的析构都会被自动调用,自动处理所有的资源回收,而如果不正常走流程退出,直接以结束进程的方式退出,不进行类实例析构,虽然内存可以被 Windows 回收,但是其它的资源比如临时文件则不一定会被清理。

回复 赞! 靠!

使用道具 举报

QQ|Archiver|小黑屋|技术宅的结界 ( 滇ICP备16008837号 )|网站地图

GMT+8, 2024-4-16 21:37 , Processed in 0.046338 second(s), 36 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表