第1章CHAPTERWindows编程基础基于Windows的编程方式有两种。一种是使用Windows的API(ApplicationProgrammingInterface,应用程序编程接口)函数,通常用C/C++语言按相应的程序框架进行编程。这些程序框架往往就程序应用提供相应的文档、范例和软件开发工具包(SoftwareDevelopmentKit,SDK),所以这种编程方式有时又称为SDK方式。另一种是使用“封装”方式,例如VisualC++的MFC方式,它是将SDK中的绝大多数函数、数据等按C++“类”的形式进行封装,并提供相应的应用程序框架和编程操作。事实上,无论是哪种编程方式,人们最关心的内容有三个:一是程序入口,二是窗口、资源等的创建和使用,三是键盘、鼠标等所产生的事件或消息的接收和处理。本章就来讨论这些内容。1.1从main到WinMain学习编程往往从简单的例子入手,例如一个C程序常有下列简单的框架代码:#includestdio.hintmain(){printf(HelloWorld!\n);/*输出*/return0;/*指定返回值*/}事实上,该程序已包括C程序中最常用的#include指令、必须的程序入口main函数、库函数printf调用和return语句。由于此程序是在早期的DOS(DiskOperatingSystem,磁盘操作系统)环境的字符模型下运行的,因而printf函数所输出的都是字符流,也就是说,它在屏幕上输出一行文本“HelloWorld!”。在Windows环境下,这里的屏幕就由控制台窗口来兼作,而基于Windows的上述C程序代码框架肯定是有所不同的。特别地,由于目前所在的Windows环境基本上都是32位,所以这里的Windows程序平台就是Win32,Windows编程可直接理解为是Win32编程。VisualC++教程(第2版)21.1.1Windows等价程序等价的Windows程序可以写成:HelloMsg.c#includewindows.hintWINAPIWinMain(HINSTANCEhInstance,HINSTANCEhPrevInstance,PSTRszCmdLine,intnCmdShow){MessageBox(NULL,TEXT(Hello,World!),TEXT(Hello),0);return0;}在深入剖析上述程序之前,先来看一看在VisualC++6.0中的编辑、连接和运行的过程:①选择“开始”→“程序”→MicrosoftVisualStudio6.0→MicrosoftVisualC++6.0,运行VisualC++6.0。第一次运行时,将显示如图1.1所示的“每日提示”对话框。单击“下一条”按钮,可看到有关各种操作的提示。如果在“启动时显示提示”复选框中单击鼠标,去除复选框的选中标记“”,那么下一次运行VisualC++6.0,将不再出现此对话框。单击“关闭”按钮关闭此对话框,进入VisualC++6.0开发环境。②选择“文件”→“新建”菜单命令,打开应用程序向导,显示出“新建”对话框,如图1.2所示。选择“工程”选项卡,从列表框中选中Win32Application(Win32应用程序)项(图1.2中的标记1)。③单击“位置”编辑框右侧的“浏览”按钮(图1.2中的标记2),从弹出的“选择目录”对话框指定项目所在的文件夹,如图1.3所示(图中的数字标记表示最经常的操作次序,下同)。单击“确定”按钮,退出“选择目录”对话框,回到“新建”对话框中。需要说明的是,为了便于程序的管理和查找,本书所涉及的程序均放入VisualC++6.0的工作文件夹“VisualC++程序”中,第1章程序放入子文件夹“第1章”中,第2章程序放入子文件夹“第2章”,依此类推。④在“新建”对话框的“工程名称”编辑框(图1.2中的标记3)中,输入项目名称Ex_HelloMsg,保留“平台”下Win32复选框的默认“选中”状态,单击“确定”按钮进入下一步。⑤出现Win32Application向导的“步骤1共1步”对话框,从中可选择要创建的应用程序类型:“一个空工程”、“一个简单的Win32程序”和“一个典型的HelloWorld!程图1.1“每日提示”对话框第1章Windows编程基础3序”,如图1.4所示。它们的区别在于:“一个空工程”仅创建Win32应用程序文件框架,不含任何代码;“一个简单的Win32程序”是在“一个空工程”基础上添加了程序框架(有入口函数、#include指令等);“一个典型的‘HelloWorld!’程序”在“一个简单的Win32程序”基础上增加了MessageBox函数调用,用来输出“HelloWorld!”。图1.2“新建”对话框“工程”选项卡图1.3“选择目录”对话框⑥选中“一个空工程”,单击“完成”按钮,弹出“新建工程信息”对话框,如图1.5所示。单击“确定”按钮,系统将按前面的选择自动创建此应用程序。图1.4应用程序的向导对话框图1.5“新建工程信息”对话框⑦再次选择“文件”→“新建”菜单命令,VisualC++将打开“新建”对话框并自动切换到“文件”选项卡,如图1.6所示。在左侧的文件类型列表中选中C++SourceFile(C++源文件),在右侧的“文件名”编辑框中输入“HelloMsg.c”或输入“HelloMsg.cpp”(文件扩展名也可不输入,系统会自动添加cpp扩展名,cpp是CPlusPlus的缩写,是C++的意思)。⑧单击“确定”按钮,系统将在创建的Win32项目工程Ex_HelloMsg中创建并添加VisualC++教程(第2版)4一个新的文件HelloMsg.c,同时打开该文件窗口。现在可以在HelloMsg.c中输入前面例HelloMsg.c中的代码了。输完后,单击编译工具条上的“生成工具”按钮或直接按F7键,系统开始对Ex_HelloMsg项目工程中的文件进行编译、连接,同时在输出窗口中观察出现的内容,当出现Ex_HelloMsg.exe-0error(s),0warning(s)表示Ex_HelloMsg.exe可执行文件已经正确无误地生成了。同时也可看到在文档窗口中所有代码的颜色都发生改变,这是VisualC++6.0的文本编辑器所具有的语法颜色功能(绿色表示注释,蓝色表示关键字等)。⑨单击编译工具条上的“运行工具”按钮或直接按Ctrl+F5键,就可以运行刚刚生成的Ex_HelloMsg.exe,结果如图1.7所示。单击“确定”按钮,Hello对话框退出。图1.6创建并添加程序文件图1.7开发环境和运行结果1.1.2头文件HelloMsg.c是一个#include预处理指令开始,实际上在用C/C++编写的Windows应用程序的头部都可以看到这样的指令:#includewindows.h头文件Windows.h是最主要的包含头文件,它还包含了其他一些Windows头文件。例如:windef.h:基本类型定义winbase.h:内核函数wingdi.h:用户接口函数winuser.h:图形设备接口函数这些头文件定义了Windows的所有数据类型、函数调用、数据结构和符号常量,它们第1章Windows编程基础5是Windows应用程序文档中的一个重要部分。1.1.3程序入口函数在C/C++程序中,其入口函数都是main。但在Windows程序中,这个入口函数由WinMain来代替。该函数是在winbase.h中声明的,其原型如下:intWINAPIWinMain(HINSTANCEhInstance,HINSTANCEhPrevInstance,LPSTRlpCmdLine,intnShowCmd);可以看出,这个WinMain函数除了形参名、个数与main函数不同外,类型名也有了新的变化。下面就来分析:①WinMain函数被声明成为返回一个int值,同时WinMain函数名前还有WINAPI标识符的修饰。WINAPI是一种“调用约定”宏,它在windef.h中有如下定义:#defineWINAPI__stdcall所谓“调用约定”,就是指程序生成机器码后,函数调用的多个参数是按怎样的次序来传递,同时函数调用结束后堆栈由谁来恢复,以及编译器对函数名的修饰约定等的协议。函数调用约定“协议”有许多,其中由WINAPI宏指定的__stdcall是一个常见的协议,内容包括:参数从右向左压入堆栈;函数自身修改堆栈;机器码中的函数名前面自动加下划线,而函数后面接@符号和参数的字节数。特别地,VisualC++的MFC方式却采用了__cdecl调用约定:参数从右向左压入堆栈;传递参数的内存栈由调用者来维护(正因为如此可实现变参函数);机器码中的函数名只在前面自动加下划线。②WinMain函数的第一个和第二个参数都是HINSTANCE(实例句柄)类型。HINSTANCE中,H表示Handle,是“句柄”的意思。在Windows编程中,句柄是一个应用程序用来识别某些资源、状态、模块等的数字。由于句柄唯一标识着对应的资源、状态、模块等,因而使用句柄就是使(调)用相应的资源、状态、模块。当应用程序运行多次时,每一次都是应用程序的“实例”。由于同一个应用程序的所有实例都共享着应用程序的资源,因而程序通过检查hPrevInstance参数就可确定自身的其他实例是否正在运行。③WinMain函数的第三个参数lpCmdLine用来指定程序的命令行,其参数类型为LPSTR。但在HelloMsg.c中,却将其改为PSTR。这两种数据类型都是合法的,也都是指向字符串的指针类型。其中的STR是“STRING,字符串”的含义,是指以\0结尾的字符串,LP前缀表示“长指针”,在Win32中它与“P”前缀表示的“指针”含义相同。④WinMain函数的第四个参数nShowCmd用来指定程序最初显示的方式,它可以是正常、最大化或最小化来显示程序窗口。VisualC++教程(第2版)6纵观上述参数和类型名可以发现它们的命名规则:C/C++的类型名仍保留其小写,但新的类型都是用大写字母来命名。参数名(变量名)都是采用“匈牙利表示法”的命名规则来定义的。它的主要方法是将变量名前后加上表示“类型”和“作用”的“前缀(小写)”,而变量名本身由“状态”、“属性”和“含义”等几个部分组成,每一个部分的名称可以是全称,也可以是缩写,但通常只有第一个字母是大写。例如,hPrevInstance则是由前缀h(表示“句柄”类型)+状态Prev(表示“以前的”)+属性Instance(表示“实例”)组成的。1.1.4MessageBox函数MessageBox是一个Win32API函数,用来弹出一个对话框窗口,显示短信息。该函数具有下列原型:intMessageBox(HWNDhWnd,LPCTSTRlpText,LPCTSTRlpCaption,UINTuType);其中,第一个参数hWnd用来指定父窗口句柄,即对话框所在的窗口句柄。第二、三个参数分别用来指定显示的消息内容(lpText)和对话框窗口的标题(lpCaption),最后一个参数用来指定在对话框中显示的预定义的按钮和图标标识,它们是在winuser.h定义的一组以MB_开始的常数组合。例如,下面是在HelloMsg.c中改变MessageBox的第四个参数。#includewindows.hintWINAPIWinMain(HINSTANCEhInstance,HINSTANCEhPrevInstance,PSTRszCmdLine,intnCmdShow){MessageBox(NULL,TEXT(Hello,World!),TEXT(Hello),MB_ICONQUESTION|MB_ABORTRETRYIGNORE);return0;}程序运行后,结果如图1.8所示。可见,MB_ICONQUESTION用来指定在对话框中显示图标,而MB_ABORTRETRYIGN