3.2 Windows 编程基础

3.2.1 你需要掌握的技能

下面是笔者总结的在 Windows 下做开发需要掌握的一些基本技能,这些都是在实际工 作中经常会用到的,在后面的章节中,我们有重点的讲解相关内容,但这并不是全部 。

  • 了解 Windows 系统各个版本的特点,能够根据需求选用合适的版本;
  • 熟悉 Windows 的运行机理和编程模式
  • 能够熟练配置环境变量,了解注册表的功用,能够修改注册表项
  • 能够熟练配置网络连接,包括局域网和 Internet
  • 掌握常见的 DOS 命令,熟悉命令行的使用
  • 能够熟练使用 MSDN,获取帮助
  • 能够熟练使用一种代码编辑器,如记事本、 emacs 等,推荐掌握 Mingw 和 Visual Studio 的使用
  • 了解 Windows 系统下打包软件的常用方法,掌握至少一种打包软件的使用

3.2.2 Windows 运行机理

要想熟练掌握 Windows 应用程序的开发,我们首先需要理解 Windows 平台下程序运行 的内部机制。

1.API 与 SDK

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

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

我们经常听到 Win32 SDK 开发、Qt SDK 开发等等说法。那么什么是 SDK 呢?SDK 的全 称是 Software Development Kit,中文译为软件开开发包。比如我们现在要开发与短信猫 相关的通信程序,在购买短信猫的同时,厂商会提供短信猫的 SDK 开发包,以方便我们对 短信猫的编程操作。这个开发包通常都会包括短信猫的 API 函数库、帮助文档、使用手 册、辅助工具等资源。也就是说, SDK 实际上就是开发所需资源的一个集合。

需要明确的是,API 和 SDK 是一种广泛使用的专业术语,并没有专指某一种特定的 API 和 SDK。

Windows 操作系统提供了 1000 多种 API 函数,作为开发人员,要全部记住这些函数调 用的语法几乎是不可能的。那么我们如何才能更好的去使用和掌握这些函数呢?答案是熟练 使用 MSDN,我们将在后面详细为大家介绍。

2.窗口和句柄

(1) 窗口

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

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

图 3-1 一个典型的窗口

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

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

此外,我们在启动 Windows 系统后,看到的桌面也是一个窗口,称为桌面窗口,是位 于最上层的窗口,由 Windows 系统创建和管理。

(2) 句柄

下面再说说句柄(handle)。Windows 具有很强的面向对象特性。Windows 对象有很 多,譬如桌面、读取所使用的程序等等。那么,如何区分这些东西呢?答案是使用句柄。句 柄是引用不同 Windows 对象的方式。可以使用 Windows 的句柄、文件的句柄、分配内存的 句柄、图像的句柄等等。系统在创建这些资源时会为它们分配内存,并返回标识这些资源的 标识号,这就是句柄。实际上我们也可以将这些句柄看作指针。

在使用句柄之前,必须先创建它们,当不再使用时,应当及时销毁它们。如果不销毁 它们,最终将导致资源泄露(resource leak),资源泄露有可能导致系统崩溃,所以,务 必确保在适当的时候销毁不再使用的句柄。

在 windows 应用程序中,窗口是通过窗口句柄( HWND)来标识的。我们要对某个窗口 进行操作,首先就要得到这个窗口的句柄,这就是窗口和句柄的联系。

3.消息与消息队列

Windows 程序设计是一种基于消息的事件驱动方式的设计模式,完全不同于传统的 DOS

方式的程序设计方法。在 Windows 中,编程的骨架都是响应和发送消息。例如,当用户在

窗口中画图的时候,按下鼠标左键,此时操作系统会感知这一事件,于是将这个事件包装成 一个消息,投递到应用程序的消息队列中,然后应用程序从消息队列中取出消息并响应。在

这个处理过程中,操作系统也会给应用程序 “发送消息”。所谓“发送消息”,实际上是 操作系统调用程序中一个专门处理消息的函数,称为 “窗口过程”。

(1) 消息

在 windows 程序中,消息是由 MSG 结构体来表示的。MSG 结构体的定义如下:

typedef struct tagMSG
{
    HWND hwnd;
    UINT message;
    WPARAM wParam;
    LPARAM lParam;
    DWORD time;
    POINT pt;
}MSG;

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

hwnd 表示消息所属的窗口。我们开发的程序都是窗口应用程序,消息一般都是与某个 窗口相关联的。在 Windows 程序中,用 HWND 类型的变量来标识窗口。

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

此外,我们可以定义自己的消息,并给窗口发送这些消息,您完全不用担心如何使这 些消息与代码联系起来,因为这是应用程序框架的事情。但是另一方面,这也在一定程度上 固定了程序设计上的一些结构。

wParam 和 lParam 用于指定消息的其他附加信息。比如,当我们收到一个字符消息的时 候,message 成员变量的值就是 WM_CHAR,但用户输入的是那些字符,就由 wParam 和 lParam 来说明。wParam、lParam 表示的信息随消息的不同而有变化。

time 和 pt 分别表示消息投递到消息队列的时间和鼠标的当前位置。

(2) 消息队列

每一个 Windows 应用程序开始执行后,系统都会为该程序创建一个消息队列,这个消 息队列用来存放该程序创建的窗口的消息。 Windows 将产生的消息依次放入消息队列中,而 应用程序则通过消息循环不断从队列中取出消息,进行响应。这种消息机制,就是 Windows

程序运行的基本机制。

4.窗口句柄和消息 现在我们将消息与句柄联系起来。假如有一个窗口,且拥有该窗口的一个句柄(称作一个 HWND),我们命名该句柄为 your_HWND。假设因为其他的窗口刚刚从该窗口上移走,那么操作系统希望重绘这个窗口。Windows 将传递如下所示消息:

PostMessage(your_HWND,WM_PAINT,0,0);

这个函数通过句柄 your_HWND 给窗口发送了一条绘制消息。最后两个参数用作消息的额外信息,暂时可以不必深究它们的具体细节。

现在,应用程序中有一个函数用一个庞大的 case 语句来处理所有信息。例如:

void HandleTheMessage()
{
    switch(Message)
    {
        case WM_PAINT:
            DrawWindow();
            break;
        case WM_KEYDOWN:
            break;
        ......
    }
}

以上就是 Windows 中的消息和句柄的大致工作过程。了解这些后原理后,下面就可以

学习一下有关主程序以及窗口创建的知识。

5.主程序-WinMain 函数

在 windows 操作系统下,用 C 或者 C++来编写 MS-DOS 应用程序时,最起码要有一个 main 函数。当用户运行该应用程序时,操作系统会自动调用 main。但当编写 Windows 应用 程序时,就一定要有 WinMain 函数,因为当用户运行该程序时,操作系统首先调用程序中 的 WinMain 函数。该函数一般用来完成某些特殊的任务,其中最重要的任务就是要创建该 应用程序的“主窗口”。许多 Windows 集成开发环境,包括使用 Microsoft MFC 类库的 Visual C++,都通过隐藏 WinMain 函数及构造消息-控制机制来简化编程。虽然使用 MFC 编程不再需要过多关注 WinMain 函数,但是弄清楚操作系统与程序之间的这种关系是最基 本的要求。

注意,有关于 Win32 API 编程基础的内容,大家可以参看候俊杰著的《深入浅出 MFC(第二版)》一书。

6.创建窗口

Windows 窗口在创建之前,其属性必须设定好,所谓属性包括类的名字、图标、光标及窗口过程处理函数等属性。为了设定这些属性, Windows 要求注册窗口类,一经注册,就可 以创建更多的同类窗口,无需再次注册。窗口类仅仅定义了窗口的特征,所有创建窗口的对象都用窗口类来创建窗口。程序必须在产生窗口前先利用 API 函数 RegisterClass 设定属 性,这一个过程就是注册窗口类。

窗口注册完之后,就可以创建相应的窗口。 注册窗口时,必须给函数传递一个指针,这个指针指向一个包含窗口属性的结构。该结构有 2 个版本,WNDCLASS 和 WNDCLASSEX,前者本来用于 Windows 早期版本,但现在仍可沿用;后者用于 32 位 Windows,该结构包含 1 个 cbSize 成员和 1 个指向小图标的句柄,其 它两者相同。

WNDCLASSEX 定义如下:

typedef struct_WNDCLASSEX
{
    UINT cbSize;
    UINT style;
    WNDPROC lpfn WndProc;
    int cbClsExtra;
    int cbWndExtra;
    HANDLE hInstance;
    HICON hIcon;
    HCURSOR hCursor;
    HBRUSH hbrBackground;
    LPCTSTR lpszMenuName;
    LPCTSTR lpszClassName;
    HICON hIconSm;
}WNDCLASSEX;

ATOM RegisterClassEx
(
    CONST WNDCLASSEX *lpwcx
);

调用过程如下:

WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW; //窗口风格
wcex.lpfnWndproc = (WNDPROC)WndProc; //窗口过程,处理消息响应
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance; //程序实例
wcex.hIcon = 0; //图标
wcex.hCursor = LoadCursor(NULL,IDC_ARROW); //光标
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); //背景色
wcex.lpszMenuName = NULL; //菜单资源
wcex.lpszClassName = “Your Class Name”; //窗口类名
wcex.hIconSm = NULL; //小图标
RegisterClassEx(&wcex); //注册窗口类

在此之后,就可以使用函数 CreateWindow 创建窗口了。不过,这个函数还有 11 个参数,第 1 个 参数就是:

wcex.lpszClassName = “Your Class Name”;

只有用注册过的窗口类名才可以创建窗体。不过,用户一般情况下不用担心这些事情,因为,MFC 已经做了其中的大部分事情。如:

(1) 在 3 个应用程序框架里,主要的窗口都已经被创建了,可以直接使用 C++对象;

(2) 在资源编辑器里,控件窗口也被设计好了,您可以用 Class Wizard 为控件窗口连 接 C++对象。

(3) 如果是动态创建控件,您只需要用 Create 等函数来创建窗口,这一过程中,MFC 提 供了方法来判断窗口类是否注册。若未注册,则先注册,再创建窗口(您完全可以不了解这 些内容)。

3.2.3 Windows 编程基础

1.环境变量

和类 Unix 系统一样,Windows 系统也有环境变量,分为系统环境变量和用户环境变量 两种。

一般来说,系统环境变量影响所有的用户和程序,但更改系统环境变量后,对已经启 动的服务程序并不起作用,因为此服务程序并不知道环境变量已发生改变,需要重新启动操 作系统。

在命令提示符下,运行 set 可以看到当前所有的环境变量。同时, set 也可以更改环境 变量的值,但仅对当前的命令提示符下启动的程序起作用。

Windows 也提供 GUI 工具来修改环境变量,做法是单击【开始】菜单, 或者在【我的电 脑】图标上单击鼠标右键,在弹出的菜单中选择【属性】命令,弹出【系统属性】对话框。 切换到【高级】选项页,单击下方的【环境变量】按钮,弹出【环境变量】对话框,可以查 看或修改增加环境变量,此时的界面如图 3-2 所示。

图 3-2 环境变量的查看设置对话框

环境变量 PATH 的功能和 Unix 下的 PATH 加上 LD_LIBRARY_PATH 是一样的,同时影响可 执行文件和动态链接库。执行命令时, Windows 会根据是否含路径,是否是 cmd 内部命令, 当前路径下是否有可执行扩展名的文件,然后再在 PATH 环境变量(先系统的再到用户的) 路径里查找有无可执行扩展名的文件。如果匹配,则执行。对于动态链接库,则是查找是否 包含路径,系统的 system32(32 位 OS 情况下)路径,然后是 PATH 环境变量(先系统的再 到用户的)路径里查找动态链接库的文件。

其他需要传递的信息对 C/C++程序员影响比较小,因为传递信息的方式已经被存取注册 表这种方式取代。

2.注册表

注册表可以看作是一个系统的数据库,大量系统及用户的数据在此存放,尤其是配置数据及运行状态数据。这些数据有字符、数字及二进制数组等类型。通过以 Reg 开头的一系列函数,可以存取注册表数据。

需要注意的是,注册表中的数据是透明的,其他程序及 regedit、regedt32 等系统工 具,也可以存取这些数据,这些数据 并不是被某个用户所独占的。

3.开机自动运行程序

通常我们开发的许多处理程序,往往是无人职守的开机自动运行的。

有许多种方式,可以让程序开机自动运行。通常可以在启动组里面,建立要运行程序的快捷方式。另外,可以放到全局或用户的启动注册表里面。全局的注册表项位于 “我的 电脑\HEKY_LOCAL_MACHINE\software\microsoft\Windows\currentversion\run”里面,用户注册表项在“我的电脑\HKEY_CURRENT_USER\software\microsoft\Windows\currentversion\run”里面。图 3-3 是用 regedit.exe 程序打开的注册表里面的全局启动程序数据。可以用工具或注册表的 API函数修改数据。

图 3-3 注册表中的全局自动运行程序数据

上述方法,有一个前提,就是用户必须能够自动登录系统。这可以通过修改注册表来 解决,方法是在注册表项 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsNT\CurrentVersion\Winlogon 下面,增 加 DefaultUserName、DefaultPassword、AutoAdminLogon 三项数据,分别填入用户名和 密码,及数字 1,其中,DefaultUserName, DefaultPassword 的类型是字符串, AutoAdminLogon 的类型是数字。这样做虽然可以实现目标,但是存在安全隐患,就是你的 用户密码以明文保存在注册表里面,这通常是非常危险的。

还可以采用其他方法来规避安全隐患。采用任务计划是一种方法,把计划的属性设为 在系统启动时启动,把计划的命令设置为要自动启动的程序。

其实最理想的方法是,把程序写成服务程序,设定此程序为服务,启动方式设为自动 启动。当然也可以使用 sc 命令,把普通的程序设为服务并设置为自动启动。

需要注意的是,如果自动运行的程序导致了系统错误,则很难进行跟踪调试,定位故障。

4.服务程序

服务程序是一种特殊机制的程序,用户不能直接运行或停止服务程序的可执行文件, 而是通过控制服务的启动停止来启动和退出。这些程序启动可以不需要用户登录就自动运 行,可以不需要 GUI 界面,非常适合业务处理类型程序的运行方式。

在控制面板里的服务管理面板中可以查看、启动和停止服务程序。

在 DOS 命令提示符环境下,可以用 net start 命令查看启动的服务列表;用“net start 服务名”命令启动服务;用“net stop 服务名”停止服务。

命令 sc 可以用来增加、删除、设置、启动或停止服务。

5.使用 Visual C++

Windows 编程是很复杂的事情。我们使用微软提供的 Develop Studio 中的 Visual C++ IDE,它能够处理编译和连接,提供帮助参考,自动生成程序框架代码,并且提供一种可视 化的设计环境。

在开始使用 Visual Studio 之前,需要掌握一些相应的知识和技能。最为重要的是要 熟练掌握在线帮助-MSDN 的使用方法。

然后是最重要的组合键 Ctrl+W。这个组合键能够调出 Class Wizard。Class Wizard 能 够为程序员在项目中加入代码,能够处理连接函数与 Windows 发送的消息的代码。

我们也会经常用到 Resource 视图面板。它可以用于设计 GUI。可以在对话框或其他框 架上放置窗口部件。通常情况下,通过若干次鼠标的点击,就可以完成界面的布局。然后再 使用 Class Wizard,完成类和对象的添加。剩下的工作主要是完成 Class Wizard 生成的函 数,并处理消息等。

这里不准备详细讨论 Visual C++的用法,并且从 VC6.0 到现在的 VC2008,每个版本都 拥有大量的使用者,而它们的操作方式也 不尽相同,请读者朋友在使用时阅读相关的书籍并 用心体会。

6.使用 MSDN

笔者最初学用 Visual C++时,经常会因为不知道某些类和方法如何使用而去问别人。 有一次,因为要问一个问题,把一位老鸟请到我的机器上,那位同事顺手就在我的计算机上 找 MSDN,找了半天没有找到,经询问我得知并没有安装时,他非常惊讶的说:没有装 MSDN 怎么搞开发啊?

这是真实的故事,从中我们可以看出 MSDN 在 Windows 平台的软件开发的重要地位,但 需要澄清的一个错误观念是 MSDN 并不是专门与 Visual C++配套使用的,它的内容涵盖了在 Windows 上开发所需的方方面面的内容。所以可以这样说,只要是在 Windows 上进行应用开 发,就需要使用 MSDN。下面我们就来了解和学用一下 MSDN。

(1) MSDN 简介

MSDN 是 Microsoft Software Developer Network 的简称,是微软针对开发者的帮助网络,可以在 http://msdn.microsoft.com 看到有关的详细介绍。MSDN 可以单独购买订阅,也可以在购买 Visual Studio 套件时得到。

(2) MSDN 的使用。MSDN 的安装比较简单,只需要按照向导提示安装即可完成。安装完 毕后,可以在 Windows 的开始菜单里找到【Microsoft Developer Network】→【MSDN Library for Visual Studio 2005】这一项(因为笔者安装的是这个版本,你的可能会有所 不同),通过这个菜单就可以打开 MSDN 了,也可以在 Visual Studio 中按下 F1 键打开 MSDN。打开后的 MSDN 主界面如图 3-4 所示。

图 3-4 MSDN 主界面

MSDN 菜单操作方式和一般的 Windows 帮助文件的样式并无不同。