欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 健康 > 美食 > Windows程序设计的基础知识以及MFC初探

Windows程序设计的基础知识以及MFC初探

2025/6/22 12:43:38 来源:https://blog.csdn.net/Oorchi/article/details/148714658  浏览:    关键词:Windows程序设计的基础知识以及MFC初探

写在前面

本文是我在复习Windows程序设计以及MFC的时候整理的笔记,修改了一下发布出来,希望对大家有所帮助。

win32程序开发流程

我们先给出一张图,图中展示了一个win32程序完整的开发流程。对于初学者,我先快速讲解一下图中的内容。
在这里插入图片描述

在传统的console应用程序开发中,我们一般只会用到源文件,但在win32应用程序开发中,我们还需要.rc文件,也就是资源文件,资源文件中包括了字体,对话框,图像等资源。通过使用.rc文件,我们可以很方便地使用资源。

在win32开发中,.c和.h文件是由c编译器编译的,而资源文件是交由资源编译器编译的。两个编译器生成的目标文件最终由链接器生成.exe文件。

函数库

在开发win32程序的过程中,我们经常需要函数库中的函数,并不是延伸文件名为 .dll 者才是动态链接函数库(DLL,Dynamic LinkLibrary),事实上 .exe、.dll、.fon、.mod、.drv、.ocx 都是所谓的动态链接函数库。

Windows应用程序调用的函数分为C Runtimes以及Windows API两大部分。其中,C Runtime库又分为两个版本:

  • LIBC.LIB,c运行时库的静态链接版本
  • MSVCRT.LIB,C Runtime的动态链接版本

对于动态链接来说,在链接时期,链接器会为调用者做一些准备,这样在运行时才可以正确地进行链接。

头文件

在win32程序的开发中,如果需要用到Windows API,通常是需要包含Windows.h头文件的。

消息和事件

Windows是一个由消息驱动的系统,不同的消息有不同的处理方式(函数)。消息处理机制是Windows编程中十分重要的内容,在后续的文章中会着重介绍。

消息又可以分为三种:

  • 标准消息
  • 命令消息
  • 通告消息

如果没有MFC应用程序开发的基础,遇到这三种消息是一脸懵逼的。我会在后续的MFC应用程序开发教程中更加详细地解释这三种消息,现在先知道有这么一回事就好了。

消息是源源不断地产生的,为了方便处理消息,Windows系统维护了两个队列:

  • 系统消息队列
  • 应用程序消息队列

先记住有这么一回事即可。我们还需要注意,硬件中断放在系统队列;Windows系统产生的消息和应用程序产生的消息放在程序队列。

消息产生后,是由user.dll模块帮忙进行消息路由,从而找到正确的消息处理函数。

简单的win32程序示例

在这里插入图片描述

这张图十分重要,它为我们展示一个win32程序的基本架构,接下我们对该图进行解释。

WinMain

WinMain函数是程序入口点。加载器加载程序,然后调用C Startup code,后者调用WinMain。WinMain的参数由作业系统传递进来。

窗口类注册与创建

在创建窗口前,必须设定好窗口的样式和属性,随后使用RegisterClass注册窗口类。随后调用CreateWindow创建窗口。
在这里插入图片描述

CreateWindow只是产生窗口,并不显式窗口,我们调用ShowWindow将窗口显式在屏幕上。随后调用UpdateWindow传递WM_PAINT消息驱动窗口绘制。

在程序源代码中,我们把注册窗口的过程封装到了InitApplication中,把创建并显示窗口的过程封装到了InitInstance中。这是十分普遍的操作,在后续的MFC应用程序设计中,我们还会再提及这两个函数。

窗口类只需注册一次,即可供同一程序的后续每一个执行个体(instance)使用(之所以能够如此,是因为所有进程共在一个地址空间中),所以我们把RegisterClass这个动作安排在“只有第一个执行个体才会进入” 的InitApplication数中。

产生窗口,是每一个执行个体(instance)都得做的动作,所以我们把CreateWindow这个动作安排在「任何执行个体都会进入」的InitInstance 函数中。InitApplication和InitInstance只不过是两个自定函数,但是,在MFC中,,两个函数包被装成了CWinApp类的两个虚成员函数。

消息循环

消息发生之时,操作系统已根据当时状态,为它标明了所属窗口,而窗口所属之窗口类又已经明白标示了窗口函数(也就是wc.lpfnWndProc 所指定的函数),以DispatchMessage(派发消息的函数)可找到正确的窗口过程。

窗口函数

发送给窗口函数的消息,经USER模块协助发送到指定的窗口函数中。窗口函数是CALLBAKC修饰的,这代表该函数应该永远被Windows系统所调用。

由于它是被Windows系统所调用的(我们并没有在应用程序任何地方调用此函数),所以这是一种callback函数,意思是指「在你的程序中,被 Windows 系统调用」的函数。

注意,不论什么消息,都必须被处理,所以switch/case 指令中的default: 处必须调用DefWindowProc,这是Windows内部预设的消息处理函数。

MFC消息映射初探

有了上面的基础之后,我们可以稍微介绍一下MFC的内容。我们把消息及其对应的处理函数封装进一个结构体中,并使用一个函数遍历结构体,找到对应消息的处理函数

struct MSGMAP_ENTRY {UINT nMessage;LONG (*pfn)(HWND, UINT, WPARAM, LPARAM);
};
#define dim(x) (sizeof(x) / sizeof(x[0]))// 消息与处理例程之对照表
struct MSGMAP_ENTRY _messageEntries[] =
{WM_CREATE,         OnCreate,WM_PAINT,          OnPaint,WM_SIZE,           OnSize,WM_COMMAND,        OnCommand,WM_SETFOCUS,       OnSetFocus,WM_CLOSE,          OnClose,WM_DESTROY,        OnDestroy,
};//这是消息       这是消息处理例程
// Command-ID 与处理例程之对照表格
struct MSGMAP_ENTRY _commandEntries =
{IDM_ABOUT,               OnAbout,IDM_FILEOPEN,            OnFileOpen,IDM_SAVEAS,              OnSaveAs,
};//这是WM_COMMAND命令项   这是命令处理例程// 窗口函数
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{int i;for(i=0; i < dim(_messageEntries); i++) {  // 消息对照表if (message == _messageEntries[i].nMessage)return((*_messageEntries[i].pfn)(hWnd,message,wParam,lParam))}return(DefWindowProc(hWnd, message, wParam, lParam));
}
// OnCommand——专门处理WM_COMMAND
LONG OnCommand(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{int i;for(i=0; i < dim(_commandEntries); i++) {  // 命令项目对照表if (LOWORD(wParam) == _commandEntries[i].nMessage)return((*_commandEntries[i].pfn)(hWnd, message, wParam, lParam));}return(DefWindowProc(hWnd, message, wParam, lParam));
}
//----------------------------------------------------------------------
LONG OnCreate(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam)
{//...
}
//----------------------------------------------------------------------
LONG OnAbout(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam)
{//...
}

这么一来,WndProc和OnCommand永远不必改变,每有新要处理的消息,只要在_messageEntries[]和_commandEntries[]两个数组中加上新元素,并针对新消息撰写新的处理例程即可。

这种观念以及作法就是MFC的Message Map的雏形。MFC把其中的动作包装得更好更精致(当然因此也就更复杂得多),成为一张庞大的消息地图;程序一旦获得消息,就可以按图上溯,直到被处理为止。

对话框的运作

对话框分为模态和非模态对话框。模态对话框是指在子对话框关闭之前,不可对父对话框进程操作的对话框,非模态对话框则可以。

对话框需要.rc文件中的对话框模板,还需要指定对话框函数。对话框处理过消息之后,应该返回TRUE,否则返回false。

模块定义文件

Windows 程序需要一个模块定义文件,将模块名称、程序节区和资料节区的内存特性、模块堆积(heap)大小、堆栈(stack)大小、所有callback 函数名称…等等登记下来。

.def文件。在集成开发环境中不在需要特别准备。

资源描述文档

RC文件是以文字描述资源的地方。经过RC编译器编译,产生可用的二进制代码。

Windows程序的生与死

在这里插入图片描述

  1. 程序初始化过程中调用CreateWindow,为程序建立了一个窗口,做为程序的萤
    幕舞台。CreateWindow 产生窗口之后会送出WM_CREATE 直接给窗口函数,
    后者于是可以在此时机做些初始化动作(例如配置内存、开文件、读初始资
    料…)。
  2. 程序活着的过程中,不断以GetMessage 从消息贮列中抓取消息。如果这个消
    息是WM_QUIT,GetMessage 会传回0 而结束while 循环,进而结束整个程序。
  3. DispatchMessage 透过Windows USER 模块的协助与监督,把消息分派至窗口
    函数。消息将在该处被判别并处理。
  4. 程序不断进行2. 和3. 的动作。
  5. 当使用者按下系统菜单中的Close 命令项,系统送出WM_CLOSE。通常程序
    的窗口函数不栏截此消息,于是DefWindowProc 处理它。
  6. DefWindowProc 收到WM_CLOSE 后, 调用DestroyWindow 把窗口清除。
    DestroyWindow 本身又会送出WM_DESTROY。
  7. 程序对WM_DESTROY 的标准反应是调用PostQuitMessage。
  8. PostQuitMessage 没什么其它动作,就只送出WM_QUIT 消息,准备让消息循
    环中的GetMessage 取得,如步骤2,结束消息循环。

空闲时间的处理:Onldle

所谓空闲时间(idle time),是指「系统中没有任何消息等待处理」的时间。

它们都是到消息队列中抓消息,如果抓不到,程序的主执行线程(primary thread,是一个UI 执行线程)会被操作系统悬住。当操作系统再次回来照顾此一执行线程,而发现消息队列中仍然是空的,这时候两个API函数的行为就有不同了:

  • GetMessage 会过门不入,于是操作系统再去照顾其它人。
  • PeekMessage 会取回控制权,使程序得以执行一段时间。于是上述消息循环进入OnIdle 函数中。

Console程序

指定子系统为console。

console程序设计

指定入口点为main

内核对象

内核对象是系统的一种资源,系统对象一旦产生,任何应用程序都可以开启并使用该对象,系统会对内核对象计数,便于其管理。内核对象有以下几种:

  • event
  • mutex
  • semaphore
  • file
  • file-mapping
  • process
  • thread

产生不同内核对象所使用的函数是不同的。但都产生一个handle作为内核对象的识别。每被使用一次,其对应的数值就加一。使用CloseHandle函数关闭内核对象。

内核对象也是Windows程序设计中的一个十分重要的内容,后续会出文章进行专门的讲解,有兴趣的读者可以阅读《Windows核心编程》一书。

关于进程的更多内容,会在后续的文章中讲解,有兴趣的读者可以先看下面的两篇文章:
的两篇文章:

  • 进程的基本概念
  • 创建进程

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词