VC++深入详解(修订版)(含DVD光盘1张)(txt+pdf+epub+mobi电子书下载)


发布时间:2020-06-27 09:06:11

点击下载

作者:孙鑫

出版社:电子工业出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

VC++深入详解(修订版)(含DVD光盘1张)

VC++深入详解(修订版)(含DVD光盘1张)试读:

作者简介

孙鑫

国内著名的IT技术和教育专家,2009中国教育杰出人物,程序员之家网站站长(www.phome.asia/www.sunxin org)。具有十多年的软件开发和教育培训经验,精通多种程序语言和技术架构,曾主讲过的课程包括C/C++、VC++、COM/DCOM/COM+、SQL Server、Oracle、Java、J2EE、Struts/Struts2、Hibernate、iBatis、Spring、数据库建模等。2004年曾推出《Java无难事》和《VC++深入编程》教学视频,获得了强烈反响,在网络上掀起了一股视频教学的风潮,数十万的学员通过这两套视频走上了软件开发的道路。从2006年开始,相继出版了畅销技术专著:《JraWeb开发详解》、《VC++深入详解》、《Struts2深入详解》、《Servlet/JSP深入详解》、《XML、XML Scherna、XSLT2.0和×Ouery开发详解》、《HTML5、CSS和JavaScript开发》。目前专注于Android和iPhone开发。VC++深入详解(修订版)孙鑫 编著电子工业出版社Publishing House of Electronics Industry北京·BEIJING内容简介

从实际应用入手,由浅入深、循序渐进地讲述Windows程序内部运行机制、MFC框架、文本、菜单、对话框、文件操作、网络编程、进程间通信、ActiveX控件、动态链接库、HOOK编程等多个主题。全书还贯穿作者多年来学习编程的一些经验,以及一些学习方法的建议,为读者进一步的学习提供指导。

本书不同于一般的讲述VC++使用的书籍,本书主要从程序内部运行的机制和MFC程序的组织脉络入手,使读者在学习VC++编程知识时,既能够知其然,又能知其所以然,从而帮助读者从根本上理解和掌握Windows的程序设计。

本书内容丰富、实用性强,许多代码可以直接应用到工程项目中。

本书的配套光盘还免费提供近45小时的VC++教学视频,读者在学习的过程中可以将视频和书互为参考,配合学习,这样可以更快、更好地掌握VC++编程。

本书适合VC++的初学者和使用VC++从事开发的程序员,对于具有一定VC++编程经验的读者,也具有很好的参考价值。

未经许可,不得以任何方式复制或抄袭本书之部分或全部内容。

版权所有,侵权必究。

图书在版编目(CIP)数据

VC++深入详解/孙鑫编著.—修订本.—北京:电子工业出版社,2012.7

ISBN 978-7-121-17360-8

Ⅰ.①V… Ⅱ.①孙… Ⅲ.①C语言—程序设计 Ⅳ.①TP312

中国版本图书馆CIP数据核字(2012)第125177号

责任编辑:李冰

印刷:北京东光印刷厂

装订:三河市皇庄路通装订厂

出版发行:电子工业出版社 北京市海淀区万寿路173信箱

邮编 100036

开本:787×1092 1/16

印张:49

字数:1257千字

印次:2012年7月第1次印刷

印数:4000册

定价:99.00元(含DVD光盘1张)

凡购买电子工业出版社的图书,如有缺损问题,请向购买书店调换。若书店售缺,请与本社发行部联系。联系电话:(010)68279077。质量投诉请发邮件至zlts@phei.com.cn,盗版侵权举报请发邮件至dbqq@phei.com.cn。

修订版前言

从本书第1版面市,一晃6年过去了,作为一本技术图书,《VC++深入详解》的生命力让我惊叹,时至今日,该书仍畅销不衰。想当初在出版本书时,笔者还担心 VC++图书看的人会不会很少啊 ☺。6年时光,笔者见证了很多程序爱好者通过本书进入软件开发岗位,在给企业做培训时,经常有企业同行告诉笔者他的业务能力是看着笔者的图书和视频成长的,这让笔者感到莫大的欣慰。

本书成书说来也是偶然,当初录制了一套《VC++深入详解》视频,在网络上流传很广,很多 VC++程序员都是通过学习该套视频而走上软件开发岗位的,然而由于视频不利于知识的复习和查找,因此很多读者来信希望能出一套纸质的教材,后来在博文视点公司郭立总编和余安萍的帮助下,最终本书得以面市。

本书面市后,很多读者给予了本书极高的评价,将之和《深入浅出MFC》相提并论,甚至将笔者和侯捷老师等同,这让笔者诚惶诚恐。从技术角度上来说,笔者和侯捷老师相差甚远,从图书角度上来,本书对 MFC 的阐述部分仅仅是让读者快速入门,能快速应用于开发,深入地理解 MFC 框架的各种原理和设计思想,还需要进一步参看《深入浅出MFC》。读者一定要正确地对待本书,切不可因本书而产生 C++编程不过如此的念头,那样实非此书之福,也非读者之福。

C++编程领域浩瀚博大,本书只是把读者领入了C++开发的道路,前方的路还很远很长,衷心希望读者能够继续学习,继续成长,终有一天成长为C++领域的编程高手。

本书读者对象

本书读者群包括:

■ 掌握了C语言,想进一步学习Windows编程的读者。

■ 学习VC++多年,但始终没有真正入门的读者。

■ 正在从事VC++开发的初级程序员。

■ 有一定VC++开发经验,想要系统地学习VC++的读者。

在本书的配套光盘中,提供了一套完整的 VC++教学视频,以帮助读者更快、更好地掌握VC++编程。关于本书配套光盘的内容,请参见“

本书的配套光盘说明

”。

本书的内容组织

本书在内容的组织上按循序渐进、由浅入深进行;在知识的介绍上,以从内到外、从原理到实践的方式编排。

第1章帮助读者掌握Windows平台下程序运行的内部机制。第2章帮助读者复习C++中的重要知识,为后续知识的学习打下良好的基础。第3章重点剖析MFC框架程序的运行脉络,并与第1章的知识做对照,为读者彻底扫清学习MFC的迷雾。相信通过这章的学习,很多以前学过MFC的读者都会有一种恍然大悟的感觉。前三章可以归为基础部分。从第4章开始就是实际应用开发的讲解了,包括绘图、文本、菜单、对话框、定制程序外观、图形保存和重绘、文件和注册表操作、网络编程、多线程、进程间通信、ActiveX 控件、动态链接库、HOOK编程等多个主题,并且每一章都有一个完整的例子。

本书的讲解理论结合实际,选用的例子和代码具有相当的代表性和实用价值,我和我的学员曾经在实际开发项目的过程中就直接使用过很多书中的代码。

本书的实例程序

笔者在编写本书时,使用的操作系统是 Windows 2000 Service Pack 4,开发工具是VC++ 6.0 SP5,MSDN是2001年1月版的(提示:与VC++6.0匹配的MSDN截止到2001年10月版,之后的版本都与.NET匹配,可能与本书使用的版本有所不同,如果需要匹配VC++6.0的MSDN,可以通过百度(www.baidu.com)或者Google(www.google.com)搜索关键字“MSDN 2001”,进行下载)。

本书所有的实例程序都在上述环境中运行正常。

提示:由于QQ运行时占用了多个端口,可能与本书例子中的网络程序使用的端口冲突,在运行本书例子中的网络程序时,如果出错,请更换程序中的端口号,或者关闭 QQ后再运行书中的程序。

学习建议

配套光盘提供的视频课数与本书的章数是一一对应的,建议读者先看视频,有一个初步印象后再看相应的章节,这样能够更快、更好地掌握VC++编程。

在学习本书时,建议读者多动脑(想想为什么),多动手(将知识转换为自己的)。在理解的前提下,独立地编写出书中每章的例子程序,可以作为是否掌握本章内容的一个考核。

由于笔者的水平有限,错误和疏漏之处在所难免,欢迎广大技术专家和读者指正。笔者的联系方式是 csunxin@sina.com,读者也可以上作者的网站发表意见,网址是www.phome.asia和www.sunxin.org。

致谢

本书在编著过程中,赵岚岚、许天玲、高莹、张素芳、瞿瑞强、张欣、张玲、窦建涛、涂君君、邵佳赜、马文勇参与编著,这里一并感谢。

封面的漫画头像由王裕民绘制,在此表示感谢。编者2012年5月本书的配套光盘说明

本书的配套光盘提供了两部分内容:本书所有实例的代码;VC++教学视频(Flash格式)。

实例代码

配套光盘中的实例代码是按照章节的顺序提供的,例如,第7章的例子程序位于光盘的Chapter7目录下。

VC++教学视频

本套教学视频由笔者本人录制,为了帮助读者更好地掌握 VC++开发,笔者花费了大量的时间和精力将AVI格式的视频转换为Flash格式,免费提供给读者使用,希望读者能够从中受益。这套视频的内容简介如下。

Lesson1:Windows 程序运行原理及程序编写流程,窗口产生过程,句柄原理,消息队列,回调函数,窗口关闭与应用程序退出的工作关系,使用VC++的若干小技巧,stdcall与cdecl调用规范的比较,初学者常犯错误及注意事项。

Lesson2:C++经典语法与应用,类的编写与应用,构造与析构函数,函数的重载,类的继承,函数覆盖,基类与派生类的构造函数、析构函数调用顺序,如何在派生类构造函数中向基类的构造函数传递参数,this 成员变量,类型转换的内幕,虚拟函数与多态性,引用和指针变量的区别与共同处,VC 工程的编译原理与过程,将工程中不同的类拆分到不同的文件中,每一个类由一个.h和.cpp文件共同完成,头文件重复定义问题的解决,培养了学员良好的编程习惯,也为以后分析MFC AppWizard生成的工程奠定良好基础。

Lesson3:讲述MFC AppWizard的原理与MFC程序框架的剖析。AppWizard是一个源代码生成工具,是计算机辅助程序设计工具,WinMain在MFC程序中是如何从源程序中被隐藏的,theApp 全局变量是如何被分配的,MFC 框架中的几个类的作用与相互关系,MFC框架窗口是如何产生和销毁的,对窗口类的PreCreateWidow和OnCreate两个函数的着重分析,Windows窗口与C++中的CWnd类的关系。

Lesson4:MFC消息映射机制的剖析,讲述如何运用ClassWizard,理解发送给窗口的消息是如何被MFC框架通过窗口句柄映射表和消息映射表来用窗口类的函数进行响应的。掌握设备描述表及其封装类CDC的使用,CDC是如何与具体的设备发生关联的,融合具体的画图程序进行分析。如何设置封闭图形的填充刷子(位图画刷与透明画刷的使用)。

Lesson5:掌握CDC的文字处理程序的编写,如何产生自定义字体和自定义插入符,熟悉对CString类的使用。通过对卡拉OK程序的编写,讲解定时器的使用和DrawText函数的巧妙运用,讲解如何使用CDC的裁剪功能。

Lesson6:菜单的工作原理及编写应用,菜单命令消息在MFC框架程序的几个类中的传递顺序和处理过程。标记菜单、默认菜单的实现原理、图形菜单的实现及常犯错误的分析,GetSystemMetrics 的应用,弹出快捷菜单的实现方式及其命令响应函数有效范围(与弹出菜单时所指定的父窗口有密切的关系,最底层的子窗口具有最优先的处理机会)。动态菜单的编写,如何让程序在运行时产生新的菜单项及如何手工为这些新产生的菜单命令安排处理函数,如何在顶层窗口中截获对菜单命令的处理,更进一步掌握CString类的应用。

Lesson7:对话框用户界面程序的编写,如何向对话框控件关联数据成员及其实现机理,如何利用对话框类的成员函数向控件发送消息和获取对话框控件的类指针,如何直接利用对话框控件类操纵对话框控件(发送消息和直接调用成员函数),如何在程序运行时产生和销毁控件。对话框控件的几种操作方式的优劣比较分析。如何实现对话框的部分收缩和展开。如何让对话框上的文本框在程序启动后立即获得焦点,如何利用SetWindowLong改变窗口的回调函数,通过改变文本框的默认回车处理方式进行演示。实现多个输入文本框间通过回车逐一向下传递焦点的另一种巧妙方法(用默认按钮来处理)。

Lesson8:逃跑按钮的巧妙实现。如何制作属性页对话框和向导对话框,融合讲解组合框(如何调整组合框的大小)、列表框、单选按钮、复选按钮等常用对话框控件的多种使用方法。如何限制用户在不满足设定的条件时切换到其他属性页和向导页。

Lesson9:如何修改MFC AppWizard向导生成的框架程序的外观和大小,修改图标、光标、背景的三种方法。如何增加和删除工具栏按钮,如何给应用程序增加工具栏,如何显示和隐藏工具栏。定制状态栏,在状态栏中添加时钟显示,CTime类及其用法。在状态栏中添加进度条(主窗口产生后立即产生进度条的巧妙思想,不能在OnCreate函数中直接处理,要用到自定义消息的方法)。鼠标坐标显示,在CView中获取状态栏对象的几种方式。如何为应用程序添加启动画面。

Lesson10:图形的绘制,如何使用自定义画笔(颜色,线宽,线型)。如何为程序中添加选项菜单和选项设置对话框,如何使用标准颜色对话框,如何使用字体对话框,在选项对话框中实现预览功能。实现选项对话框和窗口类中的数据交换。如何改变对话框和控件的背景色,如何改变控件的文本颜色,对按钮控件的特殊处理。如何在窗口中显示一幅位图。

Lesson11:如何让CDC上输出的文字、图形具有保持功能,集合类CPtrArray的使用, CPaintDC与CClientDC的区别与应用,OnPaint与OnDraw在CView中的关系及实现内幕,滚动窗口的实现,坐标空间、映射方式、设备坐标与逻辑坐标的转换。元文件设备描述表的使用,如何利用兼容DC实现图形的保存和再现。

Lesson12:const char *与char * const的区别。C语言对文件读/写的支持,FILE指针;文本文件和二进制文件的区别。用文本方式读/写文件和以二进制方式读/写文件的注意事项。C++对文件读/写的支持,ofstream和ifstream的用法。Win32 SDK对文件读/写的支持, CreateFile函数、WriteFile函数、ReadFile函数的使用;MFC对文件读/写的支持,CFile类和CFileDialog的使用,文件过滤器的设置。Win.ini文件和注册表的读/写方式及相关知识点。

Lesson13:使用CArchive类对文件进行操作。MFC框架程序提供的文件新建与打开功能内部的实现机制。如何利用CDocument类的串行化存储功能保存与加载数据。如何实现类对串行化的支持,CObArray 的串行化实现内幕。删除文档数据时常犯的错误。MFC框架程序的文档类和视类的关系,以及如何获得相互的指针引用。

Lesson14:网络的相关知识,网络程序的编写,Socket是连接应用程序与网络驱动程序的桥梁,Socket在应用程序中创建,通过bind与驱动程序建立关系。此后,应用程序送给 Socket 的数据,由 Socket 交给驱动程序向网络上发送出去。计算机从网络上收到与该Socket绑定的IP+Port相关的数据后,由驱动程序交给Socket,应用程序便可从该Socket中提取接收到的数据。网络应用程序就是这样通过Socket进行数据的发送与接收的。TCP与UDP的工作原理与编写过程,如何在程序中链接库文件,一个字符界面的聊天程序。

Lesson15:多线程程序的编写,多线程应用中容易出现的问题。互斥对象的讲解,如何使用互斥对象来实现多线程的同步。如何利用命名互斥对象保证应用程序只有一个实例运行。应用多线程编写网络聊天室程序。

Lesson16:事件内核对象、关键代码段(临界区)的讲解,以及在多线程同步中的应用。在Windows下编写基于消息的网络应用程序,掌握阻塞与非阻塞网络程序的编写,理解在Windows平台下,采用异步选择机制可以提高网络应用程序的性能。

Lesson17:详细讲解进程间通信的四种方式:剪贴板、匿名管道、命名管道和邮槽,并比较分析这几种进程间通信的优点和缺点。

Lesson18:ActiveX控件的应用与工作原理。ActiveX控件的编写,如何为控件安排属性、方法、事件、属性页、持久性存储,控件如何通知容器自身属性的改变。如何注册控件与取消控件注册,在VB和VC中访问ActiveX控件。

Lesson19:动态链接库程序的编写。静态库与动态库的区别,以及调用程序在链接静态库和动态库时的区别。如何利用工具查看动态链接库输出的函数,Depends工具的使用, C++编译器名字改编技术对动态链接库输出函数的影响,extern “C”的用法,利用模块定义文件来解决 C++名字改编的问题。用 typedef 定义指向函数的指针类型,如何获得动态链接库里的函数的指针。

Lesson20:Hook 编程。如何安装钩子过程,如何编写全局钩子。动态链接库里的全局变量数据共享问题分析。ADO数据库编程。在VB中利用ADO控件和ADO对象访问数据库,在VC中利用ADO技术访问数据库。

第1章 Windows程序内部运行机制

要想熟练掌握Windows应用程序的开发,首先需要理解Windows平台下程序运行的内部机制。市面上很多介绍Visual C++开发的书籍,一上来就讲解MFC,并且只讲操作不讲原理,结果使得很多初学者看完书后感觉云山雾绕。本章将深入剖析Windows程序的内部运行机制,为读者扫清VC++学习路途中的第一个障碍,为进一步学习MFC程序打下基础。

1.1 API与SDK

我们在编写标准C程序的时候,经常会调用各种库函数来辅助完成某些功能;初学者使用得最多的C库函数就是printf了,这些库函数是由你所使用的编译器厂商提供的。在Windows平台下,也有类似的函数可供调用;不同的是,这些函数是由Windows操作系统本身提供的。

Windows操作系统提供了各种各样的函数,以方便我们开发Windows应用程序。这些函数是Windows操作系统提供给应用程序编程的接口(Application Programming Interface),简称为API函数。我们在编写Windows程序时所说的API函数,就是指系统提供的函数,所有主要的Windows函数都在Windows.h头文件中进行了声明。

Windows操作系统提供了1000多种API函数,作为开发人员,要全部记住这些函数调用的语法几乎是不可能的。那么我们如何才能更好地去使用和掌握这些函数呢?微软提供的API函数大多是有意义的单词的组合,每个单词的首字母大写,例如CreateWindow,读者从函数的名字上就可以猜到,这个函数是用来为程序创建一个窗口的。其他的,例如, ShowWindow(用于显示窗口),LoadIcon(用于加载图标),SendMessage(用于发送消息)等,这些函数的准确拼写与调用语法都可以在MSDN中查找到。

你可以把 MSDN 理解为微软向开发人员提供的一套帮助系统,其中包含大量的开发文档、技术文章和示例代码。MSDN包含的信息非常全面,程序员不但可以利用MSDN来辅助开发,还可以利用 MSDN 来进行学习,从而提高自己。对于初学者来说,学会使用MSDN并从中汲取知识,是必须要掌握的技能。

我们在程序开发过程中,没有必要去死记硬背函数的调用语法和参数信息,只要能快速地从 MSDN 中找到所需的信息就可以了,等使用的次数多了,这些函数自然也就记住了。

我们经常听人说 Win32 SDK 开发,那么什么是 SDK 呢。SDK 的全称是 Software Development Kit,中文译为软件开发包。假如现在我们要开发呼叫中心,在购买语音卡的同时,厂商就会提供语音卡的 SDK 开发包,以方便我们对语音卡的编程操作。这个开发包通常都会包含语音卡的API函数库、帮助文档、使用手册、辅助工具等资源。也就是说, SDK实际上就是开发所需资源的一个集合。现在读者应该明白Win32 SDK的含义了吧,即Windows 32位平台下的软件开发包,包括了API函数、帮助文档、微软提供的一些辅助开发工具。

提示:API和SDK是一种广泛使用的专业术语,并没有专指某一种特定的API和SDK,例如,语音卡API、语音卡SDK、Java API、Java SDK等。

1.2 窗口与句柄

窗口是Windows应用程序中一个非常重要的元素,一个Windows应用程序至少要有一个窗口,称为主窗口。窗口是屏幕上的一块矩形区域,是Windows应用程序与用户进行交互的接口。利用窗口,可以接收用户的输入,以及显示输出。

一个应用程序窗口通常都包含标题栏、菜单栏、系统菜单、最小化框、最大化框、可调边框,有的还有滚动条。本章应用程序创建的窗口如图1.1所示。图1.1 WS_OVERLAPPEDWINDOW类型的窗口

窗口可以分为客户区和非客户区,如图1.1所示。客户区是窗口的一部分,应用程序通常在客户区中显示文字或者绘制图形。标题栏、菜单栏、系统菜单、最小化框和最大化框、可调边框统称为窗口的非客户区,它们由Windows系统来管理,而应用程序则主要管理客户区的外观及操作。

窗口可以有一个父窗口,有父窗口的窗口称为子窗口。除了如图1.1所示类型的窗口外,对话框和消息框也是一种窗口。在对话框上通常还包含许多子窗口,这些子窗口的形式有按钮、单选按钮、复选框、组框、文本编辑框等。

提示:我们在启动Windows系统后,看到的桌面也是一个窗口,称为桌面窗口,它由Windows系统创建和管理。

在 Windows 应用程序中,窗口是通过窗口句柄(HWND)来标识的。我们要对某个窗口进行操作,首先就要得到这个窗口的句柄。句柄(HANDLE)是 Windows 程序中一个重要的概念,使用也非常频繁。在Windows程序中,有各种各样的资源(窗口、图标、光标等),系统在创建这些资源时会为它们分配内存,并返回标识这些资源的标识号,即句柄。在后面的内容中我们还会看到图标句柄(HICON)、光标句柄(HCURSOR)和画刷句柄(HBRUSH)。

1.3 消息与消息队列

在传统的C程序中,我们调用fopen函数打开文件,这个库函数最终调用操作系统(提供的函数)来打开文件。而在 Windows 中,不仅用户程序可以调用系统的 API 函数,反回来,系统也会调用用户程序,这个调用是通过消息来进行的。

Windows 程序设计是一种完全不同于传统的 DOS 方式的程序设计方法。它是一种事件驱动方式的程序设计模式,主要是基于消息的。例如,当用户在窗口中画图的时候,按下鼠标左键,此时,操作系统会感知到这一事件,于是将这个事件包装成一个消息,投递到应用程序的消息队列中,然后应用程序从消息队列中取出消息并进行响应。在这个处理过程中,操作系统也会给应用程序“发送消息”。所谓“发送消息”,实际上是操作系统调用程序中一个专门负责处理消息的函数,这个函数称为窗口过程。

1.消息

在Windows程序中,消息是由MSG结构体来表示的。MSG结构体的定义如下(参见MSDN):

该结构体中各成员变量的含义如下:

第一个成员变量hwnd表示消息所属的窗口。我们通常开发的程序都是窗口应用程序,一个消息一般都是与某个窗口相关联的。例如,在某个活动窗口中按下鼠标左键,产生的按键消息就是发给该窗口的。在Windows程序中,用HWND类型的变量来标识窗口。

第二个成员变量message指定了消息的标识符。在Windows中,消息是由一个数值来表示的,不同的消息对应不同的数值。但是由于数值不便于记忆,所以Windows将消息对应的数值定义为WM_XXX宏(WM是Window Message的缩写)的形式,XXX对应某种消息的英文拼写的大写形式。例如,鼠标左键按下消息是WM_LBUTTONDOWN,键盘按下消息是 WM_KEYDOWN,字符消息是 WM_CHAR,等等。在程序中我们通常都是以WM_XXX宏的形式来使用消息的。

提示:如果想知道WM_XXX消息对应的具体数值,可以在Visual C++开发环境中选中 WM_XXX,然后单击鼠标右键,在弹出菜单中选择 goto definition,即可看到该宏的具体定义。跟踪或查看某个变量的定义,都可以使用这个方法。

第三、第四个成员变量wParam和lParam,用于指定消息的附加信息。例如,当我们收到一个字符消息的时候,message成员变量的值就是WM_CHAR,但用户到底输入的是什么字符,那么就由wParam和lParam来说明。wParam、lParam表示的信息随消息的不同而不同。如果想知道这两个成员变量具体表示的信息,可以在 MSDN 中关于某个具体消息的说明文档查看到。读者可以在 VC++的开发环境中通过 goto definition 查看一下WPARAM和LPARAM这两种类型的定义,可以发现这两种类型实际上就是unsigned int和long。

最后两个变量分别表示消息投递到消息队列中的时间和鼠标的当前位置。

2.消息队列

每一个Windows应用程序开始执行后,系统都会为该程序创建一个消息队列,这个消息队列用来存放该程序创建的窗口的消息。例如,当我们按下鼠标左键的时候,将会产生WM_LBUTTONDOWN消息,系统会将这个消息放到窗口所属的应用程序的消息队列中,等待应用程序的处理。Windows将产生的消息依次放到消息队列中,而应用程序则通过一个消息循环不断地从消息队列中取出消息,并进行响应。这种消息机制,就是 Windows程序运行的机制。关于消息队列和消息响应,在后面我们还会详细讲述。

3.进队消息和不进队消息

Windows 程序中的消息可以分为“进队消息”和“不进队消息”。进队的消息将由系统放入到应用程序的消息队列中,然后由应用程序取出并发送。不进队的消息在系统调用窗口过程时直接发送给窗口。不管是进队消息还是不进队消息,最终都由系统调用窗口过程函数对消息进行处理。

1.4 WinMain函数

接触过Windows编程方法的读者都知道,在应用程序中有一个重要的函数WinMain,这个函数是应用程序的基础。当Windows操作系统启动一个程序时,它调用的就是该程序的WinMain函数(实际是由插入到可执行文件中的启动代码调用的)。WinMain是Windows程序的入口点函数,与DOS程序的入口点函数main的作用相同,当WinMain函数结束或返回时,Windows应用程序结束。

下面,让我们来看一个完整的Win32程序,该程序实现的功能是创建一个窗口,并在该窗口中响应键盘及鼠标消息,程序实现的步骤为:WinMain函数的定义;创建一个窗口;进行消息循环;编写窗口过程函数。1.4.1 WinMain函数的定义

WinMain函数的原型声明如下:

WinMain函数接收4个参数,这些参数都是在系统调用WinMain函数时,传递给应用程序的。

第一个参数 hInstance 表示该程序当前运行的实例的句柄,这是一个数值。当程序在Windows下运行时,它唯一标识运行中的实例(注意,只有运行中的程序实例,才有实例句柄)。一个应用程序可以运行多个实例,每运行一个实例,系统都会给该实例分配一个句柄值,并通过hInstance参数传递给WinMain函数。

第二个参数hPrevInstance表示当前实例的前一个实例的句柄。通过查看MSDN我们可以知道,在Win32环境下,这个参数总是NULL,即在Win32环境下,这个参数不再起作用。

第三个参数lpCmdLine是一个以空终止的字符串,指定传递给应用程序的命令行参数。例如:在D盘下有一个sunxin.txt文件,当我们用鼠标双击这个文件时将启动记事本程序(notepad.exe),此时系统会将D:\sunxin.txt作为命令行参数传递给记事本程序的WinMain函数,记事本程序在得到这个文件的全路径名后,就在窗口中显示该文件的内容。要在VC++开发环境中向应用程序传递参数,可以单击菜单【Project】→【Settings】,选择“Debug”选项卡,在“Program arguments”编辑框中输入你想传递给应用程序的参数。

第四个参数nCmdShow指定程序的窗口应该如何显示,例如最大化、最小化、隐藏等。这个参数的值由该程序的调用者所指定,应用程序通常不需要去理会这个参数的值。

关于WinMain函数前的修饰符WINAPI,请参看下面关于__stdcall的介绍。读者可以利用goto definition功能查看WINAPI的定义,可以看到WINAPI其实就是__stdcall。1.4.2 窗口的创建

创建一个完整的窗口,需要经过下面几个操作步骤:

设计一个窗口类;注册窗口类;创建窗口;

显示及更新窗口。

下面的四个小分节将分别介绍创建窗口的过程。完整的例程请参见光盘中的例子代码Chapter1目录下WinMain。

1.设计一个窗口类

一个完整的窗口具有许多特征,包括光标(鼠标进入该窗口时的形状)、图标、背景色等。窗口的创建过程类似于汽车的制造过程。我们在生产一个型号的汽车之前,首先要对该型号的汽车进行设计,在图纸上画出汽车的结构图,设计各个零部件,同时还要给该型号的汽车取一个响亮的名字,例如“奥迪A6”。在完成设计后,就可以按照“奥迪A6”这个型号生产汽车了。

类似地,在创建一个窗口前,也必须对该类型的窗口进行设计,指定窗口的特征。当然,在我们设计一个窗口时,不像汽车的设计这么复杂,因为Windows已经为我们定义好了一个窗口所应具有的基本属性,我们只需要像考试时做填空题一样,将需要我们填充的部分填写完整,一种窗口就设计好了。在Windows中,要达到作填空题的效果,只能通过结构体来完成,窗口的特征就是由WNDCLASS结构体来定义的。WNDCLASS结构体的定义如下(请读者自行参看MSDN):

下面对该结构体的成员变量做一个说明。

第一个成员变量style指定这一类型窗口的样式,常用的样式如下:

■ CS_HREDRAW

当窗口水平方向上的宽度发生变化时,将重新绘制整个窗口。当窗口发生重绘时,窗口中的文字和图形将被擦除。如果没有指定这一样式,那么在水平方向上调整窗口宽度时,将不会重绘窗口。

■ CS_VREDRAW

当窗口垂直方向上的高度发生变化时,将重新绘制整个窗口。如果没有指定这一样式,那么在垂直方向上调整窗口高度时,将不会重绘窗口。

■ CS_NOCLOSE

禁用系统菜单的Close命令,这将导致窗口没有关闭按钮。

■ CS_DBLCLKS

当用户在窗口中双击鼠标时,向窗口过程发送鼠标双击消息。

style成员的其他取值请参阅MSDN。

知识点 在Windows.h中,以CS_开头的类样式(Class Style)标识符被定义为16位的常量,这些常量都只有某1位为1。在VC++开发环境中,利用goto definition功能,可以看到CS_VREDRAW=0x0001,CS_HREDRAW=0x0002,CS_DBLCLKS =0x0008, CS_NOCLOSE=0x0200,读者可以将这些十六进制数转换为二进制数,就可以发现它们都只有1位为1,并且为1的位各不相同。用这种方式定义的标识符称为“位标志”,我们可以使用位运算操作符来组合使用这些样式。例如,要让窗口在水平和垂直尺寸发生变化时发生重绘,我们可以使用位或(|)操作符将CS_HREDRAW和CS_VREDRAW组合起来,如style=CS_HREDRAW | CS_VREDRAW。假如有一个变量具有多个样式,而我们并不清楚该变量都有哪些样式,现在我们想要去掉该变量具有的某个样式,那么可以先对该样式标识符进行取反(~)操作,然后再和这个变量进行与(&)操作即可实现。例如,要去掉先前的 style 变量所具有的 CS_VREDRAW 样式,可以编写代码:style=style &~CS_VREDRAW。

在Windows程序中,经常会用到这种位标志标识符,后面我们在创建窗口时用到的窗口样式,也是属于位标志标识符。

第二个成员变量lpfnWndProc是一个函数指针,指向窗口过程函数,窗口过程函数是一个回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外一方调用的,用于对该事件或条件进行响应。回调函数实现的机制是:(1)定义一个回调函数。(2)提供函数实现的一方在初始化的时候,将回调函数的函数指针注册给调用者。(3)当特定的事件或条件发生的时候,调用者使用函数指针调用回调函数对事件进行处理。

针对Windows的消息处理机制,窗口过程函数被调用的过程如下:(1)在设计窗口类的时候,将窗口过程函数的地址赋值给lpfnWndProc成员变量。(2)调用RegisterClass(&wndclass)注册窗口类,那么系统就有了我们所编写的窗口过程函数的地址。(3)当应用程序接收到某一窗口的消息时,调用 DispatchMessage(&msg)将消息回传给系统。系统则利用先前注册窗口类时得到的函数指针,调用窗口过程函数对消息进行处理。

一个Windows程序可以包含多个窗口过程函数,一个窗口过程总是与某一个特定的窗口类相关联(通过 WNDCLASS 结构体中的 lpfnWndProc 成员变量指定),基于该窗口类创建的窗口使用同一个窗口过程。

lpfnWndProc 成员变量的类型是 WNDPROC,我们在 VC++开发环境中使用 goto definition功能,可以看到WNDPROC的定义:

在这里又出现了两个新的数据类型 LRESULT 和 CALLBACK,再次使用 goto definition,可以看到它们实际上是long和__stdcall。

从WNDPROC的定义可以知道,WNDPROC实际上是函数指针类型。

注意:WNDPROC被定义为指向窗口过程函数的指针类型,窗口过程函数的格式必须与WNDPROC相同。

知识点 在函数调用过程中,会使用栈。__stdcall与__cdecl是两种不同的函数调用约定,定义了函数参数入栈的顺序,由调用函数还是被调用函数将参数弹出栈,以及产生函数修饰名的方法。关于这两个调用约定的详细信息,读者可参看MSDN。对于参数个数可变的函数,例如printf,使用的是__cdecl调用约定,Win32的API函数都遵循__stdcall 调用约定。在 VC++开发环境中,默认的编译选项是__cdecl,对于那些需要__stdcall调用约定的函数,在声明时必须显式地加上__stdcall。在Windows程序中,回调函数必须遵循__stdcall 调用约定,所以我们在声明回调函数时要使用 CALLBACK。使用CALLBACK而不是__stdcall的原因是为了告诉我们这是一个回调函数。注意,在Windows 98和Windows 2000下,声明窗口过程函数时,即使不使用CALLBACK也不会出错,但在Windows NT4.0下,则会出错。

WNDCLASS 结构体第三个成员变量 cbClsExtra:Windows 为系统中的每一个窗口类管理一个 WNDCLASS 结构。在应用程序注册一个窗口类时,它可以让 Windows 系统为WNDCLASS 结构分配和追加一定字节数的附加内存空间,这部分内存空间称为类附加内存,由属于这种窗口类的所有窗口所共享,类附加内存空间用于存储类的附加信息。Windows系统把这部分内存初始化为0。一般我们将这个参数设置为0。

第四个成员变量cbWndExtra:Windows系统为每一个窗口管理一个内部数据结构,在注册一个窗口类时,应用程序能够指定一定字节数的附加内存空间,称为窗口附加内存。在创建这类窗口时,Windows系统就为窗口的结构分配和追加指定数目的窗口附加内存空间,应用程序可用这部分内存存储窗口特有的数据。Windows系统把这部分内存初始化为0。如果应用程序用WNDCLASS结构注册对话框(用资源文件中的CLASS伪指令创建),必须给DLGWINDOWEXTRA设置这个成员。一般我们将这个参数设置为0。

第五个成员变量hInstance指定包含窗口过程的程序的实例句柄。

第六个成员变量hIcon指定窗口类的图标句柄。这个成员变量必须是一个图标资源的句柄,如果这个成员为NULL,那么系统将提供一个默认的图标。

在为hIcon变量赋值时,可以调用LoadIcon函数来加载一个图标资源,返回系统分配给该图标的句柄。该函数的原型声明如下所示:

LoadIcon函数不仅可以加载Windows系统提供的标准图标到内存中,还可以加载由用户自己制作的图标资源到内存中,并返回系统分配给该图标的句柄,请参看MSDN关于LoadIcon的解释。但要注意的是,如果加载的是系统的标准图标,那么第一个参数必须为NULL。

LoadIcon的第二个参数是LPCTSTR类型,利用goto definition命令将会发现它实际被定义成CONST CHAR*,即指向字符常量的指针,而图标的ID是一个整数。对于这种情况我们需要用MAKEINTRESOURCE宏把资源ID标识符转换为需要的LPCTSTR类型。

知识点 在VC++中,对于自定义的菜单、图标、光标、对话框等资源,都保存在资源脚本(通常扩展名为.rc)文件中。在VC++开发环境中,要访问资源文件,可以单击左边项目视图窗口底部的ResourceView选项卡,你将看到以树状列表形式显示的资源项目。在任何一种资源上双击鼠标左键,将打开资源编辑器。在资源编辑器中,你可以以“所见即所得”的方式对资源进行编辑。资源文件本身是文本文件格式,如果你了解资源文件的编写格式,也可以直接使用文本编辑器对资源进行编辑。

在VC++中,资源是通过标识符(ID)来标识的,同一个ID可以标识多个不同的资源。资源的ID实质上是一个整数,在“resource.h”中定义为一个宏。我们在为资源指定 ID 的时候,应该养成一个良好的习惯,即在“ID”后附加特定资源英文名称的首字母,例如,菜单资源为IDM_XXX(M表示Menu),图标资源为IDI_ XXX(I表示Icon),按钮资源为IDB_ XXX(B表示Button)。采用这种命名方式,我们在程序中使用资源ID时,可以一目了然。

WNDCLASS 结构体第七个成员变量 hCursor 指定窗口类的光标句柄。这个成员变量必须是一个光标资源的句柄,如果这个成员为NULL,那么无论何时鼠标进入到应用程序窗口中,应用程序都必须明确地设置光标的形状。

在为hCursor变量赋值时,可以调用LoadCursor函数来加载一个光标资源,返回系统分配给该光标的句柄。该函数的原型声明如下所示:

LoadCursor函数除了加载的是光标外,其使用方法与LoadIcon函数一样。

第八个成员变量hbrBackground指定窗口类的背景画刷句柄。当窗口发生重绘时,系统使用这里指定的画刷来擦除窗口的背景。我们既可以为hbrBackground成员指定一个画刷的句柄,也可以为其指定一个标准的系统颜色值。关于hbrBackground成员的详细说明,请参看MSDN。

我们可以调用 GetStockObject 函数来得到系统的标准画刷。GetStockObject 函数的原型声明如下所示:

参数 fnObject 指定要获取的对象的类型,关于该参数的取值,请参看 MSDN。GetStockObject 函数不仅可以用于获取画刷的句柄,还可以用于获取画笔、字体和调色板的句柄。由于GetStockObject函数可以返回多种资源对象的句柄,在实际调用该函数前无法确定它返回哪一种资源对象的句柄,因此它的返回值的类型定义为 HGDIOBJ,在实际使用时,需要进行类型转换。例如,我们要为hbrBackground成员指定一个黑色画刷的句柄,可以调用如下:

当窗口发生重绘时,系统会使用这里指定的黑色画刷擦除窗口的背景。

第九个成员变量lpszMenuName是一个以空终止的字符串,指定菜单资源的名字。如果你使用菜单资源的 ID 号,那么需要用 MAKEINTRESOURCE 宏来进行转换。如果将lpszMenuName 成员设置为 NULL,那么基于这个窗口类创建的窗口将没有默认的菜单。要注意,菜单并不是一个窗口,很多初学者都误以为菜单是一个窗口。

第十个成员变量 lpszClassName 是一个以空终止的字符串,指定窗口类的名字。这和汽车的设计类似,设计一款新型号的汽车,需要给该型号的汽车取一个名字。同样地,设计了一种新类型的窗口,也要为该类型的窗口取个名字,这里我们将这种类型窗口的命名为“sunxin2006”,后面将看到如何使用这个名称。

2.注册窗口类

在设计完汽车后,需要报经国家有关部门审批,批准后才能生产这种类型的汽车。同样地,设计完窗口类(WNDCLASS)后,需要调用 RegisterClass 函数对其进行注册,注册成功后,才可以创建该类型的窗口。注册函数的原型声明如下:

该函数只有一个参数,即上一步骤中所设计的窗口类对象的指针。

3.创建窗口——步骤3

设计好窗口类并且将其成功注册之后,就可以用 CreateWindow 函数产生这种类型的窗口了。CreateWindow函数的原型声明如下:

参数lpClassName指定窗口类的名称,即我们在步骤1设计一个窗口类中为WNDCLASS的 lpszClassName 成员指定的名称,在这里应该设置为“sunxin2006”,表示要产生“sunxin2006”这一类型的窗口。产生窗口的过程是由操作系统完成的,如果在调用CreateWindow函数之前,没有用RegisterClass函数注册过名称为“sunxin2006”的窗口类型,操作系统将无法得知这一类型窗口的相关信息,从而导致创建窗口失败。

参数lpWindowName指定窗口的名字。如果窗口样式指定了标题栏,那么这里指定的窗口名字将显示在标题栏上。

参数 dwStyle 指定创建的窗口的样式。就好像同一型号的汽车可以有不同的颜色一样,同一型号的窗口也可以有不同的外观样式。要注意区分 WNDCLASS 中的 style 成员与CreateWindow函数的dwStyle参数,前者是指定窗口类的样式,基于该窗口类创建的窗口都具有这些样式,后者是指定某个具体的窗口的样式。

在这里,我们可以给创建的窗口指定 WS_OVERLAPPEDWINDOW 这一类型,该类型的定义为:

可以看到,WS_OVERLAPPEDWINDOW是多种窗口类型的组合,其原理和前面知识点所讲的内容是一致的。下面是这几种常用窗口类型的说明。

■ WS_OVERLAPPED:产生一个层叠的窗口,一个层叠的窗口有一个标题栏和一个边框。

■ WS_CAPTION:创建一个有标题栏的窗口。

■ WS_SYSMENU:创建一个在标题栏上带有系统菜单的窗口,要和 WS_CAPTION类型一起使用。

■ WS_THICKFRAME:创建一个具有可调边框的窗口。

■ WS_MINIMIZEBOX:创建一个具有最小化按钮的窗口,必须同时设定 WS_SYSMENU类型。

■ WS_MAXIMIZEBOX:创建一个具有最大化按钮的窗口,必须同时设定 WS_SYSMENU类型。

使用WS_OVERLAPPEDWINDOW类型的窗口如图1.1所示。

CreateWindow函数的参数x,y,nWidth,nHeight分别指定窗口左上角的x,y坐标,窗口的宽度,高度。如果参数x 被设为CW_USEDEFAULT,那么系统为窗口选择默认的左上角坐标并忽略y参数。如果参数nWidth被设为CW_USEDEFAULT,那么系统为窗口选择默认的宽度和高度,参数nHeight被忽略。

参数hWndParent指定被创建窗口的父窗口句柄。在1.2节中已经介绍了,窗口之间可以有父子关系,子窗口必须具有 WS_CHILD 样式。对父窗口的操作同时也会影响到子窗口,表1.1列出了对父窗口的操作如何影响子窗口。表1.1 对父窗口的操作对子窗口的影响

参数hMenu指定窗口菜单的句柄。

参数hInstance指定窗口所属的应用程序实例的句柄。

参数lpParam:作为WM_CREATE消息的附加参数lParam传入的数据指针。在创建多文档界面的客户窗口时,lpParam必须指向CLIENTCREATESTRUCT结构体。多数窗口将这个参数设置为NULL。

如果窗口创建成功,CreateWindow函数将返回系统为该窗口分配的句柄,否则,返回NULL。注意,在创建窗口之前应先定义一个窗口句柄变量来接收创建窗口之后返回的句柄值。

4.显示及更新窗口(1)显示窗口

窗口创建之后,我们要让它显示出来,这就跟汽车生产出来后要推向市场一样。调用函数ShowWindow来显示窗口,该函数的原型声明如下所示:

ShowWindow函数有两个参数,第一个参数hWnd就是在上一步骤中成功创建窗口后返回的那个窗口句柄;第二个参数nCmdShow指定了窗口显示的状态,常用的有以下几种。

■ SW_HIDE:隐藏窗口并激活其他窗口。

■ SW_SHOW:在窗口原来的位置以原来的尺寸激活和显示窗口。

■ SW_SHOWMAXIMIZED:激活窗口并将其最大化显示。

■ SW_SHOWMINIMIZED:激活窗口并将其最小化显示。

■ SW_SHOWNORMAL:激活并显示窗口。如果窗口是最小化或最大化的状态,系统将其恢复到原来的尺寸和大小。应用程序在第一次显示窗口的时候应该指定此标志。

关于nCmdShow参数的详细内容请参见MSDN。(2)更新窗口

在调用ShowWindow函数之后,我们紧接着调用UpdateWindow来刷新窗口,就好像我们买了新房子,需要装修一下。UpdateWindow函数的原型声明如下:

其参数 hWnd 指的是创建成功后的窗口的句柄。UpdateWindow 函数通过发送一个WM_PAINT消息来刷新窗口,UpdateWindow将WM_PAINT消息直接发送给了窗口过程函数进行处理,而没有放到我们前面所说的消息队列里,请读者注意这一点。关于WM_PAINT消息的作用和窗口过程函数,后面我们将会详细讲解。

到此,一个窗口就算创建完成了。1.4.3 消息循环

在创建窗口、显示窗口、更新窗口后,我们需要编写一个消息循环,不断地从消息队列中取出消息,并进行响应。要从消息队列中取出消息,我们需要调用GetMessage()函数,该函数的原型声明如下:

参数 lpMsg 指向一个消息(MSG)结构体,GetMessage 从线程的消息队列中取出的消息信息将保存在该结构体对象中。

参数hWnd指定接收属于哪一个窗口的消息。通常我们将其设置为NULL,用于接收属于调用线程的所有窗口的窗口消息。

参数wMsgFilterMin指定要获取的消息的最小值,通常设置为0。

参数wMsgFilterMax指定要获取的消息的最大值。如果wMsgFilterMin和wMsgFilter Max都设置为0,则接收所有消息。

GetMessage函数接收到除WM_QUIT外的消息均返回非零值。对于WM_QUIT消息该函数返回零。如果出现了错误,该函数返回−1,例如,当参数hWnd是无效的窗口句柄或lpMsg是无效的指针时。

通常我们编写的消息循环代码如下:

前面已经介绍了,GetMessage函数只有在接收到WM_QUIT消息时,才返回0。此时while语句判断的条件为假,循环退出,程序才有可能结束运行。在没有接收到WM_QUIT消息时,Windows应用程序就通过这个while循环来保证程序始终处于运行状态。

TranslateMessage 函数用于将虚拟键消息转换为字符消息。字符消息被投递到调用线程的消息队列中,当下一次调用 GetMessage 函数时被取出。当我们敲击键盘上的某个字符键时,系统将产生 WM_KEYDOWN 和 WM_KEYUP 消息。这两个消息的附加参数(wParam和lParam)包含的是虚拟键代码和扫描码等信息,而我们在程序中往往需要得到某个字符的 ASCⅡ 码,TranslateMessage 这个函数就可以将 WM_KEYDOWN 和 WM_KEYUP消息的组合转换为一条WM_CHAR消息(该消息的wParam附加参数包含了字符的ASCⅡ码),并将转换后的新消息投递到调用线程的消息队列中。注意,TranslateMessage函数并不会修改原有的消息,它只是产生新的消息并投递到消息队列中。

DispatchMessage 函数分派一个消息到窗口过程,由窗口过程函数对消息进行处理。DispachMessage实际上是将消息回传给操作系统,由操作系统调用窗口过程函数对消息进行处理(响应)。

Windows应用程序的消息处理机制如图1.2所示。图1.2 Windows应用程序的消息处理机制(1)操作系统接收到应用程序的窗口消息,将消息投递到该应用程序的消息队列中。(2)应用程序在消息循环中调用GetMessage函数从消息队列中取出一条一条的消息。取出消息后,应用程序可以对消息进行一些预

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载