Windows运行原理以及MFC框架分析_第1页
Windows运行原理以及MFC框架分析_第2页
Windows运行原理以及MFC框架分析_第3页
Windows运行原理以及MFC框架分析_第4页
Windows运行原理以及MFC框架分析_第5页
已阅读5页,还剩27页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1 1 Windows 程序运行原理程序运行原理 我们这趟课主要是讲一下 Windows 程序的内部运行原理 大家都知道 VC 是 Windows 开发语言 要理解 visoC 的开发过程 先要理解 Windows 程序内部运行机制 我们要明白在 Windows 环境下和在其它操作系统环境下编程的根本性区别 全面地了解 Windows 的内部运行机制将需要一本书的容量 我没有必要深入了解每一个技术细节 但 是对于 Windows 程序基本的运行机制 是一个 VC 程序员必需要掌握的知识 我们看一下这幅图 这幅图是应用程序和操作系统和硬件输入输出设备之间交互的一 个图 Windows 的程序是一个完全不同于传统的 Dos 程序的设计方法 它是一种事件驱动 方式的程序设计模式 主要是基于消息的 当用户需要完成某种功能时 需要调用操作系 统的某种支持 然后操作系统将用户需要操作包装成消息 并投递到消息队列中 最后应 用程序从消息队列中取走消息 并进行响应 向下的箭头 1 表示操作系统能操作输出设备 以执行特定的功能 例如 让声卡发出声音 让显卡画出图形 向上的箭头 2 表示操作系 统能感知输入设备状态的变化 如鼠标移动和键盘按下等 并且能够知道鼠标移动的具体 位置和键盘按下的具体键位 这就是操作系统和计算机硬件的交到关系 应用程序开发者 通常不需要知道其具体的实现细节 我们所关心的仅仅是应用程序和操作系统之间的交互 关系 应用程序 操作系统 输入输出设备 消息队列 2 1 1 API 的定义的定义 那么 应用程序是如何通知操作系统执行某个功能的呢 有过编程经验的读者都应该 知道 在应用程序中要完成某个功能 都是以函数调用的形式实现的 同样 应用程序也 是以函数调用的方式来通知操作系统执行相应的功能的 操作系统所能够完成的每个特殊 功能通常都有一个函数与其对应 也就是说 操作系统把它所能够完成的功能以函数的形 式提供给应用程序使用 应用程序对这些函数的调用就叫做系统调用 这些函数的集合就 是 Windows 操作系统提供给应用程序编程的接口 Application Programming Interface 简称 Windows API 如 CreateWindow 就是一个 API 函数 应用程序中调用这个函数 操作 系统就会按照该函数提供的参数信息产生一个相应的窗口 那么我们不要把这里的 API 和 java API 或者说其它的系统的 API 混淆 API 正如其语义一样 它已经被称为一种被广 乏使用的俗语 如果说某个系统 某个设备提供给应用程序进行操作函数 类 主键等的 集合 那么就称作该系统的 API 我们通常在做呼叫中心的时候 我们知道 我们可以买 一个语音卡 通常一个语音卡会提供一个开发包 那么它所提供的开发包的一些函数的集 合 我们就可以把它叫做这种语音卡的 API 1 2 消息及其消息队列消息及其消息队列 向上的箭头 表示操作系统能够将输入设备的变化上传给应用程序 如用户在某个程 序活动时按了一下键盘 操作系统马上能够感知到这一事件 并且能够知道用户按下的是 哪一个键 操作系统并不决定对这一事件如何作出反应 而是将这一事件转交给应用程序 由应用程序决定如何对这一事件作出反应 好比有个蚊子叮了我们一口 我们的神经末梢 相当于操作系统 马上感知到这一事件 并传递给了我们的大脑 相当于应用程序 我 们的大脑最终决定如何对这一事件作出反应 如将蚊子赶走 或是将蚊子拍死 对事件作 出反应的过程就是消息响应 那么操作系统是怎样将感知到的事件传递给应用程序的呢 这是通过消息机制 Message 来实现的 操作系统将每个事件都包装成一个称为消息的结构体 MSG 来传递给应 用程序 我们可以参看一下 MSDN MSDN 是微软提供给我们的一个在线帮助系统 这个在线 帮助系统提供了所有微软提供的开发语言 开发工具等的帮助文件 它的信息量相当全 而且相当专业 这里介绍一下 MSDN 再说明一下如何使用 MSDN MSG 结构定义如下 typedef struct tagMSG 3 HWND hwnd UINT message WPARAM wParam LPARAM lParam DWORD time POINT pt MSG 第一个成员变量 hwnd 即代表消息所属的窗口 一个消息一般都是与某个窗口相联系 的 如在某个活动窗口中按下键盘 该键盘消息就是发给该窗口的 在 VC 中 用 HWND 变量类型来标识窗口 有关窗口的知识 在稍后有详细解释 第二个成员变量 message 代表消息代号 无论是键盘按下 还是鼠标移动 都是用一 个数字来表示的 不同的数值对应不同的消息 由于数值不便于记忆 在 VC 中将消息对 应的数值定义为 WM xxx 宏的形式 xxx 对应某种消息的英文拼写的大写 如鼠标移动消 息为 WM MOUSEMOVE 键盘按下消息为 WM KEYDOWN 输入一个字符消息为 WM CHAR 等 等 我们在程序中一般以 WM xxx 宏的形式来使用消息 提示 如果想知道 WM xxx 消息对应的具体数值 请在程序中选中 WM xxx 单击右 键 在弹出菜单中选择 goto definition 即可看到该宏的具体定义 跟踪 查看某个变量的定 义 使用此方法非常有效 第三个和四个成员变量分别为 wParam lParam 用于对消息进行补充说明 如 message 成员表示字符消息 但没有说明输入的是哪个字符 这就需要用其他变量对其进行补充说 明 wParam lParam 代表的意义 随消息的不同而异 读者可用 goto definition 功能查看 WPARAM LPARAM 的定义 发现它们分别为 unsigned int 和 long 并不是什么神秘莫测的变 量类型 VC 中之所以要这样做 是希望从变量定义的类型上 就能区分出变量的用途 对于同一种变量类型 可按其用途细分定义成多种其他的形式 这种概念在 VC 中被广泛 使用 也是导致初学者困惑的一个因素 最后两个变量分别代表发出消息的时间和鼠标的当前位置 这里没有什么需要特殊解 释的 明白了消息 我们再来看看消息队列 如上面的图例所示 每个 Windows 程序都有一 个消息队列 队列是一个先进先出的缓冲区 通常是一个某种变量类型的数组 消息队列 里的每一个元素即一条消息 操作系统将生成的每个消息按先后顺序放进消息队列 第一 条消息放入第一格 第二条消息放入第二格 依次类推 应用程序总是取走队列里的第 一条消息 消息取走后 第二条消息成为第一条 剩余的消息依次前移 应用程序取得消 4 息后 便能够知道用户的操作和程序状态的变化 例如 应用程序从队列里取到了一条 WM CHAR 消息 那一定是用户输入了一个字符 并且能够知道输入的是哪个字符 应用 程序得到消息后 就要对消息进行处理 这即我们通常说的消息响应 消息响应是我们通 过编码实现的 这也是 Windows 程序的主要代码区 在消息响应代码中 我们很可能又要 调用操作系统提供的 API 函数 以便完成特定的功能 如果我们收到窗口的 WM CLOSE 消 息 我们可以调用 DestroyWindow 这个 API 函数来关闭该窗口 或是用 MessageBox 这个 API 函数来提示用户是否真的要关闭窗口 通过上面的分析 我们可以想象到 要用 VC 编写 Windows 程序 除了要具备良好 的 C 语言功底外 还要求掌握掌握两点知识 1 不同的消息所代表的用户操作和程序状态 2 要让操作系统执行某个功能所对应的 API 函数 1 3 句柄句柄 在 Windows 编程中我们时刻接触到一个称为句柄 HANDLE 的东西 可以这样去理解句 柄 Windows 程序中产生的任何资源 要占用某一块或大或小的内存 如图标 光标 窗口 应用程序的实例 已加载到内存运行中的程序 操作系统每产生一个这样的资源时 都要将它们放入相应的内存 并为这些内存指定一个唯一的标识号 这个标识号即该资源 的句柄 操作系统要管理和操作这些资源 都是通过句柄来找到对应的资源的 按资源的 类型 又可将句柄细分成图标句柄 HICON 光标句柄 HCURSOR 窗口句柄 HWND 应用程序实例句柄 HINSTANCE 等等各种类型的句柄 操作系统给每一个窗口指定的一 个唯一的标识号即窗口句柄 1 4 WinMain 函数函数 WinMain 是 Windows 程序的入口点函数 同 dos 程序的入口点函数 main 的作用相同 当 WinMain 函数结束或返回时 Windows 应用程序结束 WinMain 函数的原型如下 int WINAPI WinMain HINSTANCE hInstance handle to current instance HINSTANCE hPrevInstance handle to previous instance LPSTR lpCmdLine pointer to command line int nCmdShow show state of window 5 该函数接受四个参数 这些参数都是系统调用 WinMain 函数时 传递给应用程序的 第一个参数 hInstance 表示该程序的当前运行的实例句柄 同一应用程序在同一计算机 上可运行多份实例 每启动一个这样的实例 操作系统都要给该实例分配一个标识号 即 实例句柄 随后系统调用程序中的 WinMain 函数 并将该实例句柄传递给参数 hInstance 第二个参数 hPrevInstance 表示当前实例的上一个正在运行的 由同一个应用程序所产 生的实例的句柄 即当前实例的 哥哥 的句柄 如果该值为 NULL 则表示当前实例是该程序 正在运行的第一份实例 是 长子 是 老大 如果该值不为 NULL 只能表示当前实例不是 该程序正在运行的第一份实例 不是 长子 不是 老大 但到底是 老几 就无从得知了 这个参数到底有什么作用呢 如果想让我们的程序只能有一份实例运行 不能同时有多份 实例运行 我们可以在 WinMain 函数的开始部分加上如下代码实现 if hPrevInstance return 0 大多数没有实际开发经验的读者都很难理解这段简单的代码 大家平时见到的 if 语句 通常是 if x 0 或 if x 0 其实 if 判断的是括号中的表达式的结果为 真 还是为 假 在 C 语言中 结果为 0 即 假 非 0 即 真 所以 我们也可以直接用 if 对某个变量的值进行 判断 if 变量 代表 如果变量不等于 0 if 变量 代表 如果变量等于 0 我们再来看看 if hPrevInstance return 0 的作用 如果 hPrevInstance 为 NULL 即 0 说明当前运行的实例是程序的第一个实例 WinMain 函数将不返回 程序正常向下运行 只要 hPrevInstance 不为 NULL 说明已经有同样程序的实例在运行 WinMain 函数将返回 当前实例启动后立马结束 这样就保证了只有程序的一个实例可以运行 这个过程好比 计 划生育 只能要一个孩子 如果第二个孩子已经出世 当他发现自己不是老大 属于计划 之外 便进行 自杀 在程序中对应的是 return 0 语句 顺便说一下 计划生育 的比喻只 是为了方便大家理解问题 并不是我们的现实生活中真的要如此做法 第三个参数 lpCmdLine 是一个字符串 里面包含有传递给应用程序的参数串 如 双 击 C 盘下的 1 txt 文件方式启动 notepad exe 程序 传递给 notepad exe 程序的参数串即 c 1 txt 不包含应用程序名本身 要在 VC 开发环境中给应用程序传递参数 请选择菜单 Project Settings 在弹出的 Project Settings 对话框中选择 Debug 标签 在该标签页的 Program arguments 编辑框中输入你想传递给应用程序的参数 我们在 WinMain 函数的入 口点设置一运行断点 以调试方式启动程序运行至该断点处 将鼠标移动到参数 lpCmdLine 上 在弹出的黄色小浮框中便能观察到该变量的值 在我们的程序调试中 经常要用到这 6 种方法查看变量的值和状态 第四个参数 nCmdShow 指定的程序的窗口应该如何显示 如最大化 最小化 隐藏等 WinMain 函数前的修饰符 WINAPI 的解释 请参看下面关于 stdcall 的讲解 我们使用 goto definition 功能 发现 WINAPI 其实就是 stdcall Winmain 函数的程序代码按功能划分主要有两部分 1 产生并显示程序的主窗口 窗口创建并显示后 用户便可以在窗口上进行各种操 作了 用户的操作及程序状态的变化都以消息的形式放到了应用程序的消息队列中 2 从消息队列循环取走消息 并将消息派发到窗口过程函数中去处理 当消息循环 取到一条 WM QUIT 消息时 将结束循环 WinMain 函数返回 结束整个程序的运行 如果 WinMain 在消息循环之前返回 程序没有正常运行 返回值为 0 如果在消息循 环之后返回 返回值为 WM QIUT 消息的 wParam 参数 1 5 窗口窗口 不妨简单地将窗口看做带有边界的矩形区域 除文字处理程序中的文档窗口或者弹出 提示有约会信息的对话框等这些最普通的窗口外 实际上还有许多其它类型的窗口 命令 按钮 文本框 选项按钮都是窗口 一个通常的 Windows 程序都有窗口 通过窗口 用户可以对应用程序进行各种操作 反之 应用程序可以通过窗口收集用户的操作信息 如在窗口上移动鼠标 按下键盘 可 以说 窗口是应用程序和用户之间交互的界面 沟通的桥梁 联系的纽带 所以窗口的编 写与管理在 Windows 程序中占有重要的地位 一个完整的窗口具有许多特征 包括光标 鼠标进入该窗口时的形状 图标 菜单 背景色等 产生窗口的过程类似汽车的生产过程 在生产汽车前 必须先在图纸设计好该 车型 选择搭配汽车的各个部件 并要为这种新设计好的车型起个名称 如 奔驰 200 以后 便可以生产 奔驰 200 这款汽车了 可以按照这个型号生产若干辆汽车 同一型号 的车 可以具有不同的颜色 创建一个完整的窗口需要经过下面四个操作步骤 1 设计一个窗口类 7 2 注册窗口类 3 创建窗口 4 显示及更新窗口 1 5 11 5 1 设计窗口类设计窗口类 产生一个窗口前 也必须设计好窗口 指定窗口的那些特征 窗口的特性是由一个 WNDCLASS 结构体进行定义的 参看 MSDN WNDCLASS 定义如下 typedef struct WNDCLASS UINT style WNDPROC lpfnWndProc int cbClsExtra int cbWndExtra HANDLE hInstance HICON hIcon HCURSOR hCursor HBRUSH hbrBackground LPCTSTR lpszMenuName LPCTSTR lpszClassName WNDCLASS 1 style 成员指定了这一类型窗口的样式 比较典型的取值有 CS NOCLOSE 这一类型的窗口没有关闭按钮 请实验体会 CS VREDRAW 当改变窗口的垂直方向上的高度时 将引发窗口重画 窗口的重画过 程好比汽车重新喷漆一样 汽车车身上原有的文字与图案 如 锐信培训中心 的字样将被 擦除 同样 当窗口重画时 窗口上原有的文字和图形将被擦除 如果没有指定该值 当 垂直方向上拉动窗口时 窗口不会重画 窗口上原有的文字和图形将被保留 CS HREDRAW 当改变窗口的水平方向上的宽度时 将引发窗口重画 CS DBLCLKS 设置该 可以接受到用户双击的消息 8 其他的设置值请参阅 MSDN 在一些特殊的场合可能要用到这些置 提示 在我们的程序中经常要用到一类变量 这个变量里的每一位 bit 都对应某一 种特性 当该变量的某位为 1 时 表示有该位对应的那种特性 当该位为 0 时 即没有那 位所对应的特性 当变量中的某几位同时为 1 时 就表示同时具有那几种特性的组合 一 个变量中的哪一位代表哪种意义 不容易记忆 所以我们经常根据特征的英文拼写的大写 去定义一些宏 该宏所对应的数值中仅有与该特征相对应的那一位 bit 为 1 其余的 bit 都为 0 我们再次使用 goto definition 就能发现 CS VREDRAW 0 x0001 CS HREDRAW 0 x0002 CS DBLCLKS 0 x0008 CS NOCLOSE 0 x0200 他们 的共同点就是只有一位为 1 其余位都为 0 如果我们希望某一变量的数值即有 CS VREDRAW 特性 又有 CS HREDRAW 特性 我们只需使用 操作符将他们相组合 如 style CS VREDRAW CS HREDRAW CS NOCLOSE 如果我们希望在某一变量原有的几个特 征上去掉其中一个特征 用 读者不要被新 的数据类型 LRESULT CAllBACK 所吓倒 只要再次使用 goto definition 就知道他们的庐山真面 目分别为 long 和 stdcall 顺便帮助大家复习一下 C 语言的知识 首先是关于用 typedef 定 义指向函数的指针类型的问题 其次是 stdcall 修饰符的问题 typedef int PFUN int x int y 这样就定义了一个函数指针类型 PFUN 以后便可以用 PFUN 定义变量 应用如 9 下 int add int x int y PFUN pfun add int sum pfun 3 5 能够赋值给 pfun 的函数原型必须严 格与 PFUN 的定义相同 WNDPROC 定义了指向窗口回调函数的指针类型 回调函数的格式 必须与 WNDPROC 相同 stdcall 与 cdecl 是两种不同的函数调用习惯 定义了参数的传递顺序 堆栈清除等 关于它们的详细信息请参看 msdn 由于除了那些可变参数的 API 函数外 其余的 API 函数 都是 stdcall 习惯 由于 VC 程序默认的编译选项是 cdecl 所以在 VC 中调用这些 stdcall 习惯的 API 函数 必须在声明这些函数的原型时加上 stdcall 修饰符 以便对该 函数的调用使用 stdcall 习惯 我们曾有这样的经验 在 Delphi 默认的编译选项是 stdcall 中编写的 dll 中的函数 在 VC 中被调用时 总是造成程序崩溃 在函数的原型 声明中加上 stdcall 修饰符 便解决了这个问题 回调函数也必须是 stdcall 调用习惯 在这里是用 CALLBACK 来标识的 否则 在 NT4 0 环境 程序将崩溃 但 win98 和 win2000 却没有这种现象 如下代码是设计一个窗口类以及回调函数的代码 WNDCLASSEX wcex wcex cbSize sizeof WNDCLASSEX wcex style CS HREDRAW CS VREDRAW 定义为水平和垂直重画 wcex lpfnWndProc WndProc 消息响应函数 wcex cbClsExtra 0 类的额外内存 wcex cbWndExtra 0 窗口的额外内存 wcex hInstance hInstance 实例句柄赋值为程序启动系统分配的句柄值 wcex hIcon LoadIcon hInstance MAKEINTRESOURCE IDI LESSION1 wcex hCursor LoadCursor NULL IDC ARROW 加载游标 如果是加载标准游标 则第一个 实例标识设置为空 wcex hbrBackground HBRUSH GetStockObject NULL BRUSH 创建一个空画刷填充背景 wcex lpszMenuName MAKEINTRESOURCE IDC LESSION1 默认为 NULL 没有标题栏 wcex lpszClassName szWindowClass 窗口类的名子 在注册时会使用到 wcex hIconSm LoadIcon wcex hInstance MAKEINTRESOURCE IDI SMALL 指定一个 和类相关的小的图标资源句柄 如果是空 系统会根据 hIcon 的图标来生成一个合适大小的图标来作为和 类相关的小的图标资源句柄 LRESULT CALLBACK WndProc HWND hWnd UINT message WPARAM wParam LPARAM lParam int wmId wmEvent PAINTSTRUCT ps HDC hdc switch message 10 case WM COMMAND wmId LOWORD wParam wmEvent HIWORD wParam Parse the menu selections switch wmId case IDM ABOUT DialogBox hInst MAKEINTRESOURCE IDD ABOUTBOX hWnd About break case IDM EXIT DestroyWindow hWnd break default return DefWindowProc hWnd message wParam lParam break case WM PAINT hdc BeginPaint hWnd TODO Add any drawing code here EndPaint hWnd break case WM DESTROY PostQuitMessage 0 break default return DefWindowProc hWnd message wParam lParam return 0 1 5 21 5 2 注册窗口类注册窗口类 调用 RegisterClass 保存创建窗口后的生成窗口句柄用于显示 如果是多文档程序 则最后一个参数 lParam 必须指向一个 CLIENTCREATESTRUCT 结构体 11 hwnd CreateWindow gaojun WIN32 应用程序 WS OVERLAPPEDWINDOW CW USEDEFAULT CW USEDEFAULT 800 600 NULL NULL hInstance NULL 1 5 41 5 4 显示和更新窗口显示和更新窗口 显示窗口 ShowWindow hwnd SW SHOWDEFAULT 更新窗口 UpdateWindow hwnd 1 5 51 5 5 消息循环消息循环 while GetMessage DispatchMessage 这一课对第一次接触 Windows 编程的读者来说 新的东西太多 但只要你把这些知识 基本理解和掌握 学好 VC 的日子离你就不太遥远了 惟有如此 你才可能精通 VC 编 程 12 2 C 基础基础 C 基础在这里我们就不多讲了 13 3 MFC 的框架分析的框架分析 3 1 MFC 简介简介 MFC Microsoft Foundation Class 微软基础类库 是微软为了简化程序员的开发工作所开 发的一套 C 类的集合 是一套面向对象的函数库 以类的方式提供给用户使用 利用这些类 可以有效地帮助程序员完成 Windows 应用程序的开发 MFC 的类组成了 应用程序框架 应用程序框架 定义应用程序并提供基本的通用功能 程序开发者只须另外添加自己需 要的功能即可 MFC AppWizard 是一个辅助生成源代码的向导工具 它可以帮助我们自动生成基于 MFC 框架的源代码 在向导的每一个步骤中 我们可以根据需要来选择各种特性 从而实现定制应 用程序 通过 APPWIZARD 可以创建 MFC 的应用程序 不需要写一行代码就产生的一个应 用程序框架 然后我们要做的就是在程序中添加我们自己的代码 这样 MFC 简化了我们 对于界面的设计 3 2 文档程序文档程序 SDI 结构框架结构框架 下面我们就利用 MFC AppWizard 来创建一个基于 MFC 的单文档界面 SDI 应用程序 Test 下面我们可以看一下 MFC AppWizard 帮助我们生成的这些代码 单击左边工作区窗 格中的 ClassView 类视图 标签页 可以看到如下图所示的五个类 14 其中 CFrameWnd 是由 CWnd 派生的 CTestView 类是由 CView 类派生的 另外 CView 也是由 CWnd 派生的 这就说明这个程序中的 CMainFram 类和 CTestView 类追本溯源有一 个共同的基类 CWnd 类 CWnd 是 MFC 中一个非常重要的类 它封装了与窗口相关的操 作 具体结构如下图 3 3 程序运行步骤程序运行步骤 我们已经知道 WinMain 函数是所有 Windows 程序的入口函数 就像 DOS 下的 main 函 15 数一样 我们创建的这个 MFC 程序也不例外 它也有一个 WinMain 函数 但这个 WinMain 函数是在程序编译链接时 由链接器将该函数链接到 Test 程序中的 而不用程序 员自已显示地去写的 在安装完 Microsoft Visual Studio 以后 在安装目录下 例如 Microsoft Visual Studio 安 装在 D Program Files 下 微软提供了部分 MFC 的源代码 我们可以跟踪这些源代码来找 出程序运行的脉络 例如我的机器是 Microsoft Visual Studio 2008 MFC 的源代码的具体路 径为 E Program Files x86 Microsoft Visual Studio 9 0 VC atlmfc src mfc 不同的 VS 版本 路径不太一样 读者可以根据这个目录结构在自己的机器上查找相应的目录 在该目录中 查找 WinMain 函数 可以发现 WinMain 函数在 appmodul cpp 这个文件中 3 3 13 3 1 全局对像全局对像 theApptheApp 的创建的创建 找到 WinMain 函数 那么它是如何与 MFC 程序中的各个类组织在一起的呢 也就是 说 MFC 程序中的类是如何与 WinMain 函数关联起来的呢 WinMain 函数是程序第一个调 用的函数吗 在我们程序中 CTestApp 类有一个全局对象 theApp 定义在 TestApp cpp 文件中 在 调用 WinMain 函数之前 会对全局变量进行初始化 所以在这里会先调用 CTestApp 类的 构造函数 读者可以自行在 CTestApp 类的构造函数进行断点跟踪 注意 无论全局变量 还是全局对象 程序在运行时 在加载 WinMain 函数之前 就已经为全局变量或全局对象分配了内存空间 对于一个全局对象来说 此时就会调用该 对象的构造函数 构造该对象 并进行初始化操作 那么为什么要生成 theApp 全局对象呢 对于 MFC 程序来说 通过产生一个应用程序 类的对象来惟一标识应用程序的实例 每一个 MFC 程序有且仅有一个从应用程序类 CWinApp 派生的类 每一个 MFC 程序实例有且仅有一个该派生类的实例化对象 也就 是 theApp 全局对象 该对象就表示了应用程序本身 通过构造应用程序对象过程中调用基类 CWinApp 的构造函数 在 CWinApp 的构造函 数中完成对程序包括运行时的一些初始化工作 CWinApp 构造函数 路径 MFC SRC APPCORE CPP 3 3 23 3 2 AfxWinMainAfxWinMain 函数的调用函数的调用 当程序调用了 CWinApp 类的构造函数 并执行了 CTestApp 类的构造函数 且产生了 16 theApp 对象之后 接下来就进入 WinMain 函数 WinMain 代码段如下 tWinMain HINSTANCE hInstance HINSTANCE hPrevInstance In LPTSTR lpCmdLine int nCmdShow call shared exported WinMain return AfxWinMain hInstance hPrevInstance lpCmdLine nCmdShow 注意 define tWinMain WinMain tWinMain 函数实际调用的是 winmain cpp 中的 AfxWinMain 函数 winmain cpp 文件 的路径和 AppModul cpp 在同一个目录中 tWinMain 函数中通过调用 AfxWinMain 函数来完成它要完成的功能 说明 Afx 前缀代表这是应用程序框架 Application Framwork 函数 应用程序框架 实际上是一套辅助我们生成应用程序的框架模型 该模型把多个类进行了一个有机的集成 可以根据该模型提供的方案来设设计我们自己的应用程序 在 MFC 中 以 Afx 为前缀的函 数都是全局函数 可以在程序的任何地方调用它 在 AfxWinMain 函数中 int AFXAPI AfxWinMain HINSTANCE hInstance HINSTANCE hPrevInstance In LPTSTR lpCmdLine int nCmdShow CWinThread pThread AfxGetThread CWinApp pApp AfxGetApp AFX internal initialization if AfxWinInit hInstance hPrevInstance lpCmdLine nCmdShow goto InitFailure App global initializations rare if pApp NULL Perform specific initializations if pThread InitInstance if pThread m pMainWnd NULL 17 TRACE traceAppMsg 0 Warning Destroying non NULL m pMainWnd n pThread m pMainWnd DestroyWindow nReturnCode pThread ExitInstance goto InitFailure nReturnCode pThread Run AfxGetThread 返回的是当前界面线程对象的指针 AfxGetApp 返回的是应用程序对象 的指针 如果该应用程序 或进程 只有一个界面线程在运行 那么这两者返回的都是一 个全局的应用程序对象指针 这个全局的应用程序对象就是 MFC 应用框架所默认的 theApp 对象 AfxGetApp 返回的就是这个 theApp 的地址 在这里 pApp 存储的是指向 WinApp 派生类对象 theApp 的指针 pThread 也指向 theApp 我们看 AfxWinMain 函数 可以看到如下三行代码 pApp InitApplication pThread InitInstance pThread Run 这三个函数就完成了 Win32 程序所需要的几个步骤 设计窗口类 注册窗口类 创建 窗口 显示窗口 更新窗口 消息循环 以及窗口过程函数 pApp 首先调用 InitApplication 函数 该函数完成 MFC 内部管理方面的工作 InitInstance 函数完成窗口的创建过程 由于基类中 virtual BOOL InitInstance 定义为虚 函数 所以调用 pThread InitInstance 时候 调用的是派生类的 InitInstance 函数 例如我 们的工程名叫 Text 那么调用的是 CTestApp 类中的 InitInstance 函数 在 CTestApp 类中的 InitInstance 函数完成了单文档 SDI 或者多文档 MDI 相关资源的初始化过程 如应用程 序主窗口 CMainFrame 视图窗口 CTestView 文档类 CTestDoc 的对象的创建 以及窗口的 注册 创建 显示过程 这个过程在后面进行细分 pThread Run 完成了消息循环 3 3 33 3 3 注册窗口类注册窗口类 18 有了 WinMain 函数 根据创建 Win32 应用程序的步骤 接下来应该是设计窗口类和注 册窗口类了 窗口的注册和创建过程是在 CTextApp 类中的 InitInstance 函数中调用 ProcessShellCommand cmdInfo 完成的 ProcessShellCommand cmdInfo 函数的具体细节这里 不再做分析 大家可以自行跟踪一下 我们主要分析一下注册和创建窗口的过程 注册窗口类是由函数 AfxEndDeferRegisterClass 完成的 AfxEndDeferRegisterClass 函数 的定义位于 APPCORE CPP 文件中 其定义代码较长 这里仅列出部分代码 BOOL AFXAPI AfxEndDeferRegisterClass LONG fToRegister mask off all classes that are already registered AFX MODULE STATE pModuleState AfxGetModuleState fToRegister if fToRegister 0 return TRUE LONG fRegisteredClasses 0 common initialization WNDCLASS wndcls memset start with NULL defaults wndcls lpfnWndProc DefWindowProc 窗口过程函数 wndcls hInstance AfxGetInstanceHandle wndcls hCursor afxData hcurArrow 在 MFC 中事先设计好了几种缺省的窗口类 根据不同的应用程序的选择 调用 AfxEndDeferRegisterClass 函数注册所选择的窗口类 如 work to register classes as specified by fToRegister populate fRegisteredClasses as we go if fToRegister wndcls lpszClassName afxWnd if AfxRegisterClass if fToRegister wndcls lpszClassName afxWndOleControl if AfxRegisterClass if fToRegister control bars don t handle double click wndcls lpszClassName afxWndControlBar wndcls hbrBackground HBRUSH COLOR BTNFACE 1 if AfxRegisterClass 这里 AfxRegisterClass 是一个全局函数 和 windows API 有相似之处 不再做细分了 而在框架类窗口和视图类窗口创建之前 会调用各自的 PreCreateWindow 函数 PreCreateWindow 主要是对相应窗口的注册过程 其实 PreCreateWindow 内部的实现也是 调用的 AfxEndDeferRegisterClass 函数 CFrameWnd PreCreateWindow 函数的定义位于 WINFRM CPP BOOL CFrameWnd PreCreateWindow CREATESTRUCT 判断 AFX WNDFRAMEORVIEW REG 型号窗口类是否注册 如果没有注册则注册 cs lpszClass afxWndFrameOrView COLOR WINDOW background 把注册后的窗口类名赋给 cs lpszClass if cs style if afxData bWin4 cs dwExStyle WS EX CLIENTEDGE return TRUE 其中 PreCreateWindow 是个虚函数 如果子类有则调用子类的 20 3 3 43 3 4 创建窗口创建窗口 按照 Win32 程序编写步骤 设计窗口类并注册窗口类之后 应该是创建窗口了 在 MFC 程序中 窗口的创建功能是由 CWnd 类的 CreateEx 函数实现的 该函数的声明位于 AFXWin h 文件中 实现代码位于 wincore cpp 文件中 具体代码如下所示 virtual BOOL CreateEx DWORD dwExStyle LPCTSTR lpszClassName LPCTSTR lpszWindowName DWORD dwStyle int x int y int nWidth int nHeight HWND hWndParent HMENU nIDorHMenu LPVOID lpParam NULL allow modification of several common create parameters CREATESTRUCT cs cs dwExStyle dwExStyle cs lpszClass lpszClassName cs lpszName lpszWindowName cs style dwStyle cs x x cs y y cs cx nWidth cs cy nHeight cs hwndParent hWndParent cs hMenu nIDorHMenu cs hInstance AfxGetInstanceHandle cs lpCreateParams lpParam if PreCreateWindow cs 虚函数 如果子类有调用子类的 PostNcDestroy return FALSE AfxHookWindowCreate this HWND hWnd AfxCtxCreateWindowEx cs dwExStyle cs lpszClass cs lpszName cs style cs x cs y cs cx cs cy cs hwndParent cs hMenu cs hInstance cs lpCreateParams AfxCtxCreateWindowEx 是 CreateWindowEx 的一个宏定义 ifdef DEBUG if hWnd NULL 21 TRACE traceAppMsg 0 Warning Window creation failed GetLastError returns 0 x 8 8X n GetLastError endif if AfxUnhookWindowCreate PostNcDestroy cleanup if CreateWindowEx fails too soon 在 MFC 底层代码中 CFrameWnd 类的 Create 函数内部也是调用了上述 CreateEx 函数 CFrameWnd 类的 Create 函数的声明也位于 AFWin h 文件中 具体代码如下 BOOL CFrameWnd Create LPCTSTR lpszClassName LPCTSTR lpszWindowName DWORD dwStyle const RECT if hMenu NULL DestroyMenu hMenu return FALSE 说明 我们在创建窗口之前通过可 PreCreateWindow CREATESTRUCT typedef struct tagCREATESTRUCTW LPVOID lpCreateParams HINSTANCE hInstance HMENU hMenu HWND hwndParent int cy int cx int y int x LONG style LPCWSTR lpszName LPCWSTR lpszClass DWORD dwExStyle CREATESTRUCTW LPCREATESTRUCTW 3 3 53 3 5 显示和更新窗口显示和更新窗口 在 Test 程序的应用程序类 CTestApp 中有一个名为 m pMainWnd 的成员变量 该变 量是一个 CWnd 类型的指针 它保存了应用程序框架窗口对象的指针 也就是说 是指向 CMainFrame 对象的指针 在 CTestApp 类的 InitInstance 函数实现内部有如下两句代码 The one and only window has been initialized so show and update it m pMainWnd ShowWindow SW SHOW 显示窗口 m pMainWnd 指向框架窗口 m pMainWnd UpdateWindow 更新窗口 3 3 63 3 6 消息循环消息循环 至此 注册窗口类 创建窗口 显示和更新窗口的工作都已经完成 就该进入消息循 环了 CWinThread 类的 Run 函数就是完成消息循环这一任务的 该函数是在 AfxWinMain 23 函数中调用的 调用形式如下 int AFXAPI AfxWinMain Perform specific initializations if pThread InitInstance 完成窗口初始化工作 完成窗口的注册 完成窗口的创建 显示和更新 nReturnCode pThread Run 继承基类 Run 方法 调用 CWinThread Run 来完成消息循环 CWinThread 类的 Run 函数定义于 thrdcode cpp 文件中 实现代码如下 main running routine until thread exits int CWinThread Run ASSERT VALID this AFX THREAD STATE pState AfxGetThreadState for tracking the idle time state BOOL bIdle TRUE LONG lIdleCount 0 acquire and dispatch messages until a WM QUIT message is received for phase1 check to see if we can do idle work while bIdle assume no idle state phase2 pump messages while available do pump message but quit on WM QUIT if PumpMess

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论