20240604 MFC急速项目通关
明昧 Lv7

感悟

只要赶得好,天天期末考

MFC实现基本机制

基本介绍

微软基础类库(英语:Microsoft Foundation Classes,简称MFC)是一个微软公司提供的类库(class libraries),以C++类的形式封装了Windows API,并且包含一个应用程序框架,以减少应用程序开发人员的工作量。其中包含的类包含大量Windows句柄封装类和很多Windows的内建控件和组件的封装类。

MFC除了是一个类库以外,还是一个框架,在vc++里新建一个MFC的工程,开发环境会自动帮你产生许多文件,同时它使用了mfcxx.dll。

xx是版本,它封装了mfc内核,所以你在你的代码看不到原本的SDK编程中的消息循环等等东西,因为MFC框架帮你封装好了,这样你就可以专心的考虑你程序的逻辑,而不是这些每次编程都要重复的东西,但是由于是通用框架,没有较好的针对性,当然也就丧失了一些灵活性和效率。但是MFC的封装很浅,所以效率上损失不大。

基本过程

在MFC软件开发中,界面操作或者线程之间通信都会经常用到消息,通过对消息的处理实现相应的操作。

比较典型的过程是,用户操作窗口,然后有消息产生,送给窗口的消息处理函数处理,对用户的操作做出响应。

框架基本理解/我的C++结合

1、MFC为微软基础类库。以程序的形式封装微软的API,即操作系统留给应用程序的调用接口。MFC框架包含应用程序框架,以减少应用程序开发人员的工作量。

2、**MFC框架为窗口对象。**MFC是程序直接创建,然后在程序执行中随着窗口类构造函数的调用而生成,随着析构函数的调用而消失。

3、MFC框架可以创建窗口对象**。MFC框架下创建窗口对象分两步,首先创建MFC窗口对象,然后创建对应的微软窗口。最后在内存使用上,MFC窗口对象可以自动创建。**

MFC基本结构分析

主要有4个部分组成:

  1. 资源文件Resource.h:主要是定义资源的ID。

  2. 预编译文件:可以用来解决头文件包含冲突的问题,定义一些需要全局性包含的文件。

  3. 应用程序类对应文件:项目名称是TestOne,对应类名为CTestOneApp。

  4. 对话框类:项目名称是TestOne,对应类名为CTestOneDlg。

应用程序类

MFC定义了一个应用程序基类CWinApp,所有基于MFC的应用程序都会继承这个类。

TestOne项目也不例外,此时的应用程序类是CTestOneApp,定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class CTestOneApp : public CWinApp
{
public:
CTestOneApp();
public:
// 重载虚函数
virtual BOOL InitInstance();
// 实现
DECLARE_MESSAGE_MAP()
};


BOOL CTestOneApp::InitInstance()
{
//初始化应用程序环境包控件等操作,省略部分代码.
CWinApp::InitInstance();..
//省略部分代码...


//定义对话框对象
CTestOneDlg dlg;
//保存对话框到全局变量
m_pMainWnd = &dlg;


//显示对话框
INT_PTR nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
//“确定”来关闭对话框的代码
}
else if (nResponse == IDCANCEL)
{
//“取消”来关闭对话框的代码
}
// 由于对话框已关闭,所以将返回 FALSE 以便退出应用程序, 而不是启动应用程序的消息泵。
return FALSE;
}

InitInstance()这个函数可以看作MFC程序的入口函数,main函数隐藏在这个函数中。在实际的开发中,一般不需要对这个类进行操作,但如果需要在建立主对话框之前处理一些数据或者准备工作,那么就可以把代码添加到这个函数中,主对话框显示之前。

对话框类

MFC主对话框类(主对话框是指与项目名称一致的那个类对应的对话框,下同)CTestOneDlg继承CDialogEx类,CDialogEx又继承CDialog类,这个可以通过类转到定义查看,对话框类负责与用户交互,处理用户消息,接受用户输入。类的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// CTestOneDlg 对话框
class CTestOneDlg : public CDialogEx
{
public:
// 标准构造函数
CTestOneDlg(CWnd* pParent = NULL);
// 对话框数据
enum { IDD = IDD_TESTONE_DIALOG };
protected:
// 动态数据交换,负责控件与变量之间的关联
virtual void DoDataExchange(CDataExchange* pDX);
protected:
//应用程序句柄
HICON m_hIcon;
//重载初始化对话框
virtual BOOL OnInitDialog();
//定义消息WM_SYSCOMMAND处理函数
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
//定义消息WM_PAINT处理函数
afx_msg void OnPaint();
//定义消息ON_WM_QUERYDRAGICON处理函数
afx_msg HCURSOR OnQueryDragIcon();
//消息映射
DECLARE_MESSAGE_MAP()
};

基本概念的理解

句柄

句柄只是从英文handle翻译过来的,只问句是什么意思难以解释,这个是我从别人的空间收集的信息,

功能上的理解:

什么是"句柄"(handle),handle的本意是把柄,把手的意思。是你与操作系统打交道的东东。举个通俗的例子,比如你考上了大学,入学后,学校(操作系统)会给你一个学生证号。注意,这个号码是学校指定的,你无法自选。有了这个号码(学生证,假设一证多用)享受学校提供的服务:

如你就可以去图书馆借书,去食堂吃饭,去教室上课等等。但你不能到食堂里买啤酒,因为学校不允许这种服务。而在计算机中系统提供的服务就是API调用,你有了HANDLE,就可以理直气壮地向系统提出调用API的服务。而指针的权力就大多了,有了指针你可以到处去喝酒,打架,学校(操作系统)管不着,所以句柄和指针的区别在于句柄指针调用系统提供的服务。而句柄虽然是一个能相互区别的号码,但与我们普通的ID号又有区别,普通的ID号是可以由程序员自己定义的,而句柄不行,它是对象生成是系统指定的,是为了区别系统中存在的各个对象,这个句柄不是由程序员符给的。

概念上的理解:

   1. 句柄,是整个windows编程的基础,一个句柄是指使用的一个唯一的整数值,是指一个四字节长的数值,用于标志应用程序中的不同对象和同类对象中的不同的实例,诸如,一个窗口,按钮,图标,滚动条,输出设备,控件或者文件等。应用程序能够通过句柄访问相应的对象的信息。

  2. 句柄不是一个指针,程序不能利用它句柄来直接阅读文件中的信息。如果句柄不用在I/O文件中,它是毫无用处的。

  3. 句柄是windows用来标志应用程序中建立的或是使用的唯一整数,windows使用了大量的句柄来来标志很多对象。

机制上的理解:

前面的分析很经典,但我认为有一点必须指出的。如果不对,请各位指证。句柄是指针,一点不假,但是这个指针又与C中的指针有不同之处。

因为Windows是一个多任务的系统,其内存是可以移动的,这样的话如果某一时刻有一个指针指向一块内存,之后的某个时刻却被系统移走了,如果你再用这个指针的话就会出错。为了解决这一问题,windows在系统专区开一块内存用于存放句柄,这个句柄的值就是一个地址,当这一块内存被移走后,windows就修改这个句柄的值,再访问这块内存时,句柄的值总是有效的。正因为这样当你使用GlobalAlloc分配的内存时,如果你指定这块内存的属性是固定的,那么它的返回值可以直接给一个指针,如果是可以移动的,返回值就必须给一个句柄,你就必须先GlobalLock后才能使用。这是我对句柄理解,不知道对不对?

我的理解:

其实,句柄是一个指向指针的指针。即:在windows程序设计中,句柄仅是一个应用程序用来识别某些事情的数字;如果想更透彻一点地认识句柄,我可以告诉大家,句柄是一种指向指针的指针。我们知 道,所谓指针是一种内存地址。应用程序启动后,组成这个程序的各对象是住留在内的 。

如果简单地理解,似乎我们只要获知这个内存的首地址,那么就可以随时用这个地址访问对象。但是,如果您真的这样认为,那么您就大错特错了。

我们知道,Windows是一个以虚拟内存为基础的操作系统。在这种系统环境下,Windows内存管理器经常在内存中来回移动对象,依此来满足各种应用程序的内存需要。对象被移动意味着它的地址变化 了。如果地址总是如此变化,我们该到哪里去找该对象呢?为了解决这个问题,Windows操作系统为各应用程序腾出一些内存储地址,用来专门 登记各应用对象在内存中的地址变化,而这个地址(存储单元的位置)本身是不变的。Wi ndows内存管理器在移动对象在内存中的位置后,把对象新的地址告知这个句柄地址来保存。这样我们只需记住这个句柄地址就可以间接地知道对象具体在内存中的哪个位置。这个地址是在对象装载(Load)时由系统分配给的,当系统卸载时(Unload)又释放给系统 。

句柄地址(稳定)→记载着对象在内存中的地址→对象在内存中的地址(不稳定) →实际对象

   但是,必须注意的是程序每次从新启动,系统不能保证分配给这个程序的句柄还是原来的那个句柄,而且绝大多数情况的确不一样的。假如我们把进入电影院看电影看成是一个应用程序的启动运行,那么系统给应用程序分配的句柄总是不一样,这和每次电

影院售给我们的门票总是不同的一个座位是一样的道理。
————————————————

                        版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/xiaomingZeng/article/details/72765497

句柄、指针、ID

win32直接操作的是句柄HANDLE。每一个句柄就相应windows窗体,而vc对HANDLE进行类封装,间接操作的都是HANDLE,如今句柄仅仅是类的一个成员变量。

从句柄到指针
CWnd* pWnd=CWnd::FromHandle(hWnd); //a temporary CWnd object is created //and attached.
pWnd->Attach(hWnd); //Attaches a Windows window to a CWnd object

** 消息 **

消息产生**,送给窗口的**消息处理函数处理(想想这里得代码实现

窗口消息一般由三个部分组成:

1、一个无符号整数,是消息值;

2、消息附带的WPARAM类型的参数;

3、消 息附带的LPARAM类型的参数。

无符号证书 WPARAM LPARAM


其实,我们一般所说的消息是侠义上的消息值,也就是一个无符号整数,经常被定义为宏。

二、MFC处理消息机制

MFC使用一种消息映射机制来处理消息,
在应用程序框架中的表现就是一个消息与消息处理函数一一对应的消息映射表,以及消息处理函数的声明和实现等代码。当窗口接收到消息时,会到消息映射表中查找该消息对应的消息处理函数, 然后由消息处理函数进行相应的处理。

一个对应的消息映射表

当窗口接收到消息时,会到消息映射表中查找该消息对应的消息处理函数, 然后由消息处理函数进行相应的处理。

与QT信号与槽的机制比较

MFC消息映射机制

优点

  1. 直接与Windows消息系统集成

    • MFC直接处理Windows消息,这意味着开发者可以更细粒度地控制消息传递和处理。这在需要高度优化和直接与操作系统交互的应用中非常有用。
  2. 简单明了的消息映射

    • MFC使用消息映射表(Message Map)将消息与处理函数关联起来,代码结构清晰。例如:

      1
      cpp
    • BEGIN_MESSAGE_MAP(CMyDialog, CDialogEx)
          ON_WM_PAINT()
          ON_BN_CLICKED(IDC_BUTTON1, &CMyDialog::OnBnClickedButton1)
      END_MESSAGE_MAP()
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24

      2. **高性能**:

      - 由于MFC的消息处理是基于Windows消息机制,通常在性能上更优,尤其是在处理大量窗口消息或需要精确控制消息传递的情况下。

      3. **细粒度控制**:

      - MFC允许开发者直接操作窗口句柄和Windows API,这在需要进行复杂的自定义绘制和操作时非常有用。

      #### 缺点

      1. **学习曲线陡峭**:
      - MFC的学习曲线相对陡峭,开发者需要深入理解Windows消息机制和MFC的内部工作原理。
      2. **代码冗长**:
      - MFC代码相对冗长,消息映射表和处理函数的定义可能使代码变得复杂。

      ### Qt信号与槽机制

      #### 优点

      1. **更高的抽象层次**:

      - Qt的信号与槽机制提供了更高的抽象层次,使得对象之间的通信更加直观和灵活。例如:

      cpp
      1
      2
      3

      1. - ```
      connect(button, SIGNAL(clicked()), this, SLOT(handleButtonClick()));
  3. 松耦合

    • 信号与槽机制实现了对象之间的松耦合,信号发出者并不需要知道槽的存在。这使得代码更易于维护和扩展。
  4. 跨平台支持

    • Qt是一个跨平台框架,信号与槽机制在不同操作系统上保持一致,而MFC则是特定于Windows平台的。
  5. 更简洁的代码

    • Qt的信号与槽机制可以减少代码的复杂度,使得事件处理更加简洁明了。

缺点

  1. 性能开销
    • 信号与槽机制引入了一些额外的性能开销,尤其是在大量信号与槽连接或频繁触发信号的情况下。
  2. 间接性
    • 由于信号与槽机制的高抽象层次,某些情况下可能会导致间接性过高,降低了对低级消息处理的控制。

综合比较

  1. 控制与性能
    • 如果需要直接控制Windows消息,并且对性能有高要求,MFC的消息映射机制可能更适合。
  2. 开发效率与跨平台
    • 如果追求开发效率和代码简洁性,特别是需要跨平台支持,Qt的信号与槽机制会更有优势。
  3. 复杂性与维护
    • MFC的代码可能更加复杂和冗长,维护难度较大。Qt通过信号与槽机制实现松耦合,使得代码维护和扩展更容易。

结论

MFC的消息映射机制和Qt的信号与槽机制各有优缺点。选择使用哪个机制,主要取决于具体的应用场景、开发者的需求和对特定特性的偏好。如果你开发的是Windows平台下的高性能应用程序,并且需要细粒度控制,那么MFC可能更适合。如果你需要跨平台支持,并且希望代码更简洁和易于维护,那么Qt的信号与槽机制会更好。

三、Windows消息分类

Windows消息分为系统消息和用户自定义消息。

Windows系统消息有三种:

1、标准Windows消息。除WM_COMMAND以外WM_开头的消息是标准消息。

例如,WM_CREATE、WM_CLOSE。

2、命令消息。

消息名为WM_COMMAND,消息中附带了标识符ID来区分是来自哪个菜单、工具栏按钮或加速键的消息。

3、通知消息。

通知消息一般由列表框等子窗口发送给父窗口,消息名称也是WM_COMMAND,其中附带了控件通知码 来区分控件。

CWnd的派生类都可以接收到标准Windows消息、通知消息和命令消息。命令消息还可以由文档类等接收。

用户自定义消息实际上就是用户定义一个宏作为消息,此宏的值应该大于等于WM_USER,然后此宏就可以跟系统消息 一样使用,窗口类中可以定义它的处理函数。

四、消息映射表

除了一些没有基类的类或CObject的直接派生类外,其他的类都可以自动生成消息映射表

下面的讲解都以CMainFrame 为例。

消息映射表如下:

1717502513425

在BEGIN_MESSAGE_MAG和END_MESSAGE_MAP之间的内容成为消息映射入口项。

消息映射除了在CMainFrame的实线文件中 添加消息映射表外,

在类的定义文件MainFrame.h中还会添加一个宏调用:

DECLARE_MESSAGE_MAP() 一般这个宏调用写在类定义的结尾处。

五、添加消息处理函数

要注意MFC的定义形式

不管是自动添加还是手动添加都有三个步骤:

加入 afx_msg 开头函数 afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);

添加映射入口项 ON_WM_CREATE()

添加消息处理函数的函数实现

1、在类定义中加入消息处理函数的函数声明,注意要以afx_msg打头。例如MainFrame.h中WM_CREATE的消息处理函数 声明:afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);

2、在类的消息映射表中添加该消息的消息映射入口项。例如WM_CREATE的消息映射入口项:ON_WM_CREATE()。

3、在类的实现中添加消息处理函数的函数实现。

例如,MainFrm.cpp中WM_CREATE的消息处理函数的实现:

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

}

通过以上三个步骤以后,WM_CREATE等消息就可以在窗口类中被消息处理函数处理了。

六、各种Windows消息的消息处理函数 *

标准Windows消息的消息处理函数都与WM_CREATE消息类似。

1.命令消息的消息映射入口项形式如:

ON_COMMAND( ID_VIEW_CUSTOMIZE , &CMainFrame::OnViewCustomize),

消息为 ID_VIEW_CUSTOMIZE,消息处理函数为OnViewCustomize。

2.如果想要使用某个处理函数批量处理某些命令消息,则可以像CMainFrame消息映射表中的

ON_COMMAND_RANGE(ID_VIEW_APPLOOK_WIN_2000, ID_VIEW_APPLOOK_WINDWOS_7, &CMainFrame::OnApplicationLook)一样添加消息映射入口项

这样值在ID_VIEW_APPLOOK_WIN_2000到ID_VIEW_ APPLOOK_WINDOWS_7之间的菜单项等的命令消息都由CMainFrame的OnApplicationLook函数处理。

函数原型为afx_msg void OnApplicationLook (UINT id);参数id为用户操作的菜单项等的ID。

  1. 在操作列表框等控件时往往会给父窗口发送WM_NOTIFY通知消息。WM_NOTIFY消息的wParam参数为发送通知消息的控件的ID,lParam参数指向
    一个结构体,可能是NMHDR结构体,也可能是第一个元素为NMHDR结构体变量的其他结构体。NMHDR结构体的定义如下(仅作了解):
      Typedef struct tagNMHDR
      {
        HWND hwndFrom;
        UINT idFrom;
        UINT code;
      } NMHDR;
    hwndFrom为发送通知消息控件的句柄,idFrom为控件ID,code为要处理的通知消息的通知码,例如NM_CLICk。

  2. 通知消息的消息映射入口项形式如:
    ON_NOTIFY(wNotifyCode, id, memberFxn)
    wNotifyCode为要处理的消息通知码,例如:NM_CLICK。id为控件标识ID。memberFxn为此消息的处理函数。
    通知消息的处理函数的原型为:
      afx_msg void memberFxn(NMHDR * pNotifyStruct, LRESULT *result);

  3. 如果需要使用用户自定义消息,首先要定义消息宏,如:#define WM_UPDATE_WND(WM_USER+1),再到消息映射表中添加消息映射入口项:
    ON_MESSAGE(WM_UPDATE_WND, &CMainFrame::OnUpdateWnd),然后在MainFrm,h中添加消息处理函数的函数声明:afx_msg LRESULT OnUpdateWnd(
    WPARAM wParam, LPARAM lParam);最后在MainFrm.cpp中实现此函数。

 Comments
Comment plugin failed to load
Loading comment plugin
Powered by Hexo & Theme Keep
Unique Visitor Page View