所有的32位版本的Windows都支持win16API以确保对旧的应用程序的兼容性。同时也支持Win32API来运行新式的程序。很有趣的是,在WindowsNT,Windows95和Windows98中这件工作是用不同的方式完成的。在WindowsNT中,Win16函数调用通过一个翻译层然后翻译为可被操作系统执行的Win32函数调用。在Windows95和Windows98中,这个过程是反过来的:Win32函数通过一个翻译层然后转化为操作系统处理的Win16函数。
曾经,还有另外两个WindowsAPIs集合(至少在名字上)。Win32s(“s”意为“子集(subset)”)是允许程序写Windows3.1平台的32位应用程序的API。这个API仅支持Win16已经支持的32位版本的函数调用。还有,Windows95API曾经被称为Win32c(“c”意为“兼容(compatibility)”),但这个术语被废弃了。
如今,WindowsNT和Windows98都被认为支持Win32API。然而,每个操作系统都支持一些对方不支持的特性。当然,由于交集的存在,写同时可以在两个平台上都运行的程序是可能的。同时,两个程序将在将来融合在一起这个观点被广泛的认同。
语言的其他选择
使用C和原始的APIs不是写Win98程序的唯一办法。然而,这条途径给你最好的性能,最强大的威力和利用Windows特性时最强大的多功能性。可执行文件相对的较小,而且不需要外部库(当然,除去Windows的Dlls自身)来运行。最重要的是,对API的熟悉使你对Windows内在有的更深了解。不管你最终如何写Windows应用程序。
虽然我认为对于任何Windows程序来说,学习经典的Windows编程都是重要的。但我没必要推荐每一个Windows应用程序都用C和API来写。许多程序员特别是那些作在家协作编程的程序员,或是那些在家作消遣性编程的程序员,享受像微软Visual Basic或是Borland Delphi(内含一个Pascal的面向对象版本)等开发环境带来的便利。这些环境使得程序员能够关注一个应用程序的界面和用户界面上物体之间连接的代码。要学习Visual Basic,你可以查阅一些微软出版社的书籍,诸如《现在学习Visual Basic(1996)》,由Michael Halvorson著。
在程序员,特别是用微软Visual C++的程序员中,Microsoft Foundation Class Libray(微软基础类库MFC)在近年来,成为其他的选择。MFC封装了Windows编程的一些乱七八糟的方面在一个C++类库中。Jeff Prosise的《用MFC进行Windows编程(微软出版社,1999)》提供了一个关于MFC的教材。
最近,Internet和World Wide Web的风行给Sun Microsystem的Java一个巨大的推动,一个由C++获得灵感的处理器无关的语言,包括一个将可以在好几个平台上运行的图形应用程序。一本关于Microsoft J++(微软的java开发环境)的微软出版社的好书, 《Visual J++6.0编程(1998)》,由Stephen R. Davis著。
明显的,几乎没有那个是写Windows程序的唯一正确方法。超出一切的是,程序本身的需求可能选择了它的开发工具。但学习Windows API使你有一个对Windows工作的深入查看,这很重要,管你最终用什么来实际的编码。Windows是一个复杂的系统。把一个编程层面置于API之上没有削弱它的复杂性,仅仅隐藏了他们。早晚,这种复杂性会跳出来在你的大腿上咬你。了解API给你更好的康复机会。
任何一个在原始的Windos API之上的软件层必要地把你限制在所有功能的一个子集中,Visual Basic对你的应用程序很理想除了它不允许你作一两件必不可少的重要事情。在这种情况下,你不得不使用原始的API调用。API定义了我么内在其中以Windows程序员身份而存在的宇宙。没有比直接调用API更有威力更多功能的方法了。
MFC是特别的有问题。当它简化了一些庞大的工作(例如OLE),但是为了使他如我所愿的工作,我时常发现我自己经常在其他特性上摔跤(例如Document/view architecture(文档视图对象结构))。MFC还没有成为许多人希望大的Windows编程的万能秘方。而且很少有人会把它描述为一个面向对象设计的好典范。MFC程序员从知道他们所用的类定义中发生了什么而获益许多,并且发现他们时常要查询MFC源代码。能够理解那些代码使学习Windows API的好处之一。
在这本书的编程环境部分,我将假定你正在使用将要成为标准的Visual C++6.0专业版或者企业版。更便宜的版本来做本书中的程序也是适宜的。Visual C++也是Visual Studio6.0的一部分。
Microsoft Visual C++包中有比如C编译器,和其他用于编译链接Windows程序必要的工具。它还包括Visual C++ Developer Studio,一个在其中你可以编辑你的源代码,交互式的创建诸如图标和对话框资源,编辑编译运行和调试你的程序。
如果你使用的是Visual C++ 5.0,你可能需要为Windows 98和Windows NT5.0更新头文件和引入库。这些在微软公司的网站上可以找到。访问http://www.mirosoft.com/msdn/并且选择Download(下载)然后是Platform SDk(平台软件开发套件“software development kit”)你可以下载和安装到你选择的更新目录中。为了引导Microsft Develop Studio 来查询这些目录,使用 Tool 菜单下的Options选项,然后选择Directories标签。
上面的微软Msdn,代表微软开发者网站(Microsoft Developer Network)。它向开发者提供频繁更新的CD-ROMs,其中包括许多你想要的关于Windows开发中的偏僻角落的知识。你可能要考虑订阅MSDN,避免频繁地从微软网站上下载东西。
API文档
本书不是官方正式Windows API文档的替代品。那个文档也不再以印刷的形式发布,它仅可以通过CD-ROM或者Internet获得。
当你安装了Visual C++6.0后,你将获得一个包括API文档的在线帮助系统。你可以通过订阅MSDN对它进行更新或是使用微软基于网站的在线帮助系统。访问http://www.microsoft.com/msdn/,然后选择MSDN在线图书馆。
在Visual C++6.0中,从Help菜单选Contents项启动MSDN窗口。API文档以一个树型结构组织。查找标为Platform SDK的部分。所有我在本书中将要参考的文档都是从那儿来的。我将使用由斜杠隔开表示PlatformSDK开始的网状层显示它们的位置。(我知道Platform SDK看上去就像MSDN整个知识财富的阴暗一隅。但我向你保证,它是Windows编程必不可少的核心)。例如,对于有关怎样在你的程序中使用鼠标的文档,你可以查阅
/Platform SDK/User Interface Services/User Input/Mouse Input。我在前面提到过,Windows的大部分被划分成Kernel, User和GDI三个子系统。Kernel接口在
/Platform SDK/Windows Base Services,用户界面函数在
/Platform SDK/User Interface Services以及GDI被列在
/Platform SDK/Graphics and Multimedia Services/GDI。
你的第一个Windows 程序
现在是做一些实际工作的时候了。让我从一个短小的Windows程序开始,为了比较,还有一个短小的字符模式程序。这些将帮助你在使用开发环境时获得方向和了解浏览,创建以及编译一个程序的大体过程。
一个字符模式的例子:
在程序中最受欢迎的书籍之一是The C Programming Language (Prentice Hall,1978和1988)由
Brain W. Kernighan和Dennis M. Ritchie著,妮称K&R。这本书的第一章由显示“Hello World”的程序开始。
这是那个出现在The C Programming Language第一版,第六页的程序:
main ()
{
printf ("hello, world\n") ;
}
是的。曾经C程序使用C运行时库函数,例如Printf,而不用事先声明。但现在是90年代,而且我们想要给我们的编译器查出我们代码中明显错误的机会。这里是K&R第二版中取出的修改后的代码:
#include <stdio.h>
main ()
{
printf ("hello, world\n") ;
}
这个程序仍然不像他们看上去的那样小。它当然可以顺利的编译和运行,但是许多那时的程序员更喜欢显式的指出main函数的返回值。这种情况下,ANSI C规定函数实际上返回了一个值:
#include <stdio.h>
int main ()
{
printf ("hello, world\n") ;
return 0 ;
}
我们可以通过包含给main的参数使这个程序更长,但不要去管它。只要有一个包含(include)语句,程序入口点,一个对运行时库的函数调用,和一个返回(return)语句。
Windows的类似版本
Windows和那个“Hello World”程序等价的版本几乎有和字符模式一摸一样的组成部分。它有一个包含语句,一个程序入口点,一个函数调用,和一个返回值语句。这里是那个程序:
/*--------------------------------------------------------------
HelloMsg.c -- Displays "Hello, Windows 98!" in a message box
(c) Charles Petzold, 1998
--------------------------------------------------------------*/
#include <windows.h>
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
MessageBox (NULL, TEXT ("Hello, Windows 98!"), TEXT ("HelloMsg"), 0) ;
return 0 ;
}
在我开始分析这个程序之前,让我们看一看在Visual C++ Developer Studio中创建一个程序的步骤。
开始,从“文件”菜单中选择“新建”。在新建对话框中,选工程(Project)标签。选Win32程序。在位置(Location)处选择一个子目录。在工程名(Project Name)处输入工程名。在本例中是HelloMsg。这将成为位置处填入目录的一个子目录。选中创建新的工作区按钮。平台部分应该显示的是Win32,选OK。一个标有“Win32应用程序-第一步”的对话框将会出现。选你想要创建一个空的工程,并按结束按钮。
再从文件菜单中选New,再新建对话框中选文件页。选C++源文件。加到工程项应该被选中。在文件名处输入HelloMsg.c。选Ok。
现在你可以把上面的内容输入HelloMsg.c文件中,或者你可以选择插入菜单和File As Text项把随书的CD-ROM中的HelloMsg.c文件插入。
结构上,HelloMsg.c和K&R“Hello World”程序是一样的。STDIO.H文件被Windows.H代替。入口点main被换成了WinMain,而且C运行时库函数printf被Windows API函数MessageBox代替。然而,程序还是有很多的新东西。包括好几个看上去怪怪的大写标识符。
让我们从头开始。
头文件
HelloMsg.c由一个你事实上会在每一个用C写的Windows程序的顶部发现的预处理器指令开头。
#include <windows.h>
Windows.h是一个总的包含文件,它包含了其他Windows头文件,它们中的一些还包括了别的头文件。这些头文件中最重要的,最基础的是:
WINDEF.H 基本的类型定义
WINNT.H 支持Unicode的类型定义
WINBASE.H 内核函数
WINUSER.H 用户界面函数
WINGDI.H 图形设备接口函数
这些头文件定义了所有的Windows数据类型,函数调用,数据结构和常量标识符。它们是Windows文档重要的一部分。你可能发现从Visual C++ Develper Studio的编辑菜单选择“在文件中查找”项来查找这些头文件很方便。你页可以在Develper Studio中打开这些头文件来直接检查它们。
程序入口点
就像对于C的程序的入口点是mian函数一样,对于每一个Windows程序的入口点是WinMain。它看起来像总是这样:
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
这个入口点的文档在in
/Platform SDK/User Interface Services/Windowing/Windows/Window Reference/Window Functions中。它在WINBASE.H中定义如下(所有的行被切断了):
int
WINAPI
WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nShowCmd
);
你可能注意到了我在HelloMsg.c中做了一点微小的改动。在WINBASE.H中,第三个参数被定义为LPSTR,而我把它变为PSTR。这两种数据类型都定义在WINNT.H中。是指向字符串的指针。LP前缀的意为:“长指针”,是16位Windows的遗留产物。
我还在WinMain声明中改变了两个参数的名称;许多Windows程序使用一种称为“匈牙利命名法”的方法来命名变量。这个方法包括给变量名一个短的前缀来标明变量的数据类型。我将在第三章,对此进行更多的讨论。对于现在而言,只需要记住前缀I代表int而sz代表“以0结尾的字符串”。
WinMain函数被声明为返回int的函数。WINAPI标识符在WINDEF.H中由一下语句定义:
#define WINAPI __stdcall
这个语句定义了一个调用习惯。他包含了用来在栈中存放函数调用的参数的机器码怎样生成。大多数Windows函数调用被声明为WINAPI。
给WinMain的第一个参数是一个称为“实例句柄”的玩艺。在Windows编程中,句柄仅仅是一个程序用来标识某物的数字。现在,这个句柄唯一的标识程序。它对于其他一些Windows函数调用是必须的。在Windows的早期版本中,当你同时运行一个程序多于一次。你创建了那个程序的多个实例。同一个应用程序的所有实例共享代码和只读的内存(通常是像菜单和对话框模板之类的资源)。一个程序通过检查hPreInstance参数,可以确定是否有自身的其他实例在运行。它可以跳过某些重要的过程并从先前的实例中移一些数据到自己的数据范围里来。
在32位版本的Windows中,这种概念被废弃了。给WinMain的第二个参数总是空的(定义为0)。
给WinMain的第三个参数是用来运行程序的命令行。一些Windows应用程序用这个在程序启动之时来把文件载入内存。给WinMain的第四个参数指示程序应该如何初始化显示。要么正常,要么最大化来填充窗口,或是最小化显示在任务栏上。我们将在第三章看到这些参数如何得到使用。
MessageBox函数
MessageBox函数被设立成用来显示简短的信息的东西。MessageBox显示的小窗口实际上被认为是对话框,虽然没有许多功能。
给MessageBox的第一个参数通常是一个窗口的句柄。我们将在第三章中看到这是什么意思。第二个参数是显示在MessageBox中的字符串。在HelloMsg.C中,所有这些字符串都被一个TEXT宏括住。你通常不需要用TEXT宏括住所有的字符串,但是如果你想要把你的程序转换为Unicode字符集,这是一个好的主意。我将在第二章,对此进行更深入的讨论。
给MessageBox的第四个函数是一些由前缀MB开头,定义在WINUSER.H中的常量组合。你可以从第一个集合中选出一个常量来显示那个你想要它出现在对话框中的按钮。
#define MB_OK 0x00000000L
#define MB_OKCANCEL 0x00000001L
#define MB_ABORTRETRYIGNORE 0x00000002L
#define MB_YESNOCANCEL 0x00000003L
#define MB_YESNO 0x00000004L
#define MB_RETRYCANCEL 0x00000005L
当你在HELLOMSG的第四个参数设为0时,仅仅有OK按钮出现。你可以使用C的OR(|)操作符把上面所示的每一个参数拿来指示哪个按钮和缺省的常量连接起来。
#define MB_DEFBUTTON1 0x00000000L
#define MB_DEFBUTTON2 0x00000100L
#define MB_DEFBUTTON3 0x00000200L
#define MB_DEFBUTTON4 0x00000300L
你也可以用一个参数来标明MessageBox的图标的外观。
#define MB_ICONHAND 0x00000010L
#define MB_ICONQUESTION 0x00000020L
#define MB_ICONEXCLAMATION 0x00000030L
#define MB_ICONASTERISK 0x00000040L
其中的一些有别名:
#define MB_ICONWARNING MB_ICONEXCLAMATION
#define MB_ICONERROR MB_ICONHAND
#define MB_ICONINFORMATION MB_ICONASTERISK
#define MB_ICONSTOP MB_ICONHAND
还有一点其他的MB_常量。你可以自己去查询头文件或是查找in
/Platform SDK/User Interface Services/Windowing/Dialog Boxes/Dialog Box Reference/Dialog Box Functions。
在这个程序中,MessageBox返回值是1。但是如果说它返回IDOK似乎更加恰当。IDOK定义在WINUSER.H中,值为1。根据位于MessageBox上有哪些按钮,MessageBox函数也可以返回IDYES,IDNO,IDCANCEL,IDABOUT,IDRETRY或是IDIGNORE。
这个短小的Windows程序真是K&R的“HelloWorld”程序的等价物?好,你可能认为MessageBox没有“Hello World”中printf函数重要的格式化的威力。但是我们将要在下一章中看到怎样写一个可以做到printf类似功能的MessageBox。
编译,链接和运行
当你准备好了要编译HELLOMSG,你可以从Build菜单中选Build HelloMsg.exe。或是按F1,要么从Build工具栏中选Build图标。(这个图标的样子在Build菜单中出现过。如果Build工具栏没有被显示,你可以从工具菜单中选Customize(定制),而且选ToolBars(工具栏)标页。选中Build或BuildMiniBar)。
或者,你可以从Build菜单选Execute HelloMsg.exe或按Ctrl+F5,或从Build工具栏按执行程序图标(看上去像一个红色的感叹号)。然后你可以看到一个问你是否要建立那个程序的对话框。
通常,在编译过程中。编译器从C源文件生成了一个.OBJ(对象)文件。在链接过程中,链接器合并.OBJ文件和.LIB(库)文件生成.exe(可执行)文件。你可以通过从Project页选Setting并选Link标页看到这些库文件的列表。特别的,你可能注意到KERNEL32.LIB, USER32.LIB和GDI32.LIB。这些是为三个主要Windows子系统服务的“重要的库”。它们包括动态链接库的名称和绑定到.exe文件中的参考信息。Windows使用这些信息,来解释程序对位于KERNEL32.DLL, USER32.DLL和GDI32.DLL动态链接库中的函数调用。
在Visual C++ Developer Studio中,你可以用不同的配置来编译和链接程序。缺省时,这些被称为Debug(调试)和Release(发布),可执行文件被储存在以此为名的子目录中。当为Debug配置时,帮助调试程序和跟踪程序源代码的信息被加入.exe文件中。
如果你更喜欢以命令行的方式工作,附带的CD-ROM中有所有例子程序的.MAK文件。(你可以通过TOOLS菜单下的Options选项选中Build标页。这里有同一个复选矿,选中它来告诉Develper Studio生成make文件)你需要运行位于Develper Studio的BIN目录中的VCVARS32.BAT文件来设置环境变量。要从命令行执行make文件,切换到HELLOMSG目录,执行:
NMAKE /f HelloMsg.mak CFG="HelloMsg _ Win32 Debug"
或者
NMAKE /f HelloMsg.mak CFG="HelloMsg _ Win32 Release"
你可以从命令行输入下面的内容运行.exe文件:
DEBUG /HELLOMSG
或者
RELEASE /HELLOMESG
我为本书配套光盘的工程文件的缺省Debug设置做了一个改动。在Project Setting对话框中,选择了C/C++页后,在预处理器定义处我定义了标识符UNICODE,我在下一章中对此有更多叙述。