Windows 程序设计 哈尔滨工业大学计算机系322教研室 陶海军
参考书 1.《Windows程序设计》,Charles Petzold著,北京大学出版社。上 下两册。 主要讲述Windows SDK程序设计方法。涉及到Windows程序设计的大部分内容,但是对于Windows内核涉及不多。多任务多线程 网络等内容略显单薄。适合中级Windows程序设计者。 2.《Windows高级编程指南》,Jeffrey Richter著,清华大学出版社,1999.6。 深入探讨Windows内核,是高级Windows程序员必备书。 3.《Microsoft® Win32TM 程序员参考大全》,Microsoft Corporation著,清华大 学出版社,1995.4,共5册。 为Windows SDK程序设计的基本指导书,也是32位Windows API的基本手册。由于Windows最初良好的规划,直到现在这套书依然具有相当的价值,尤其对于Windows程序设计的基本知识的讲解对于初学者来说尤为关键。
4.《Microsoft® Visual C++TM for Win32 ®大全》,Microsoft Corporation著,清华大学出版社,1996.3。共5册。 主要讲述Visual C++类库。现在看来,这本书关于MFC2.0的内容已经有些过时(MFC现已升级为4.22-From MSDN library-July 2000)。但是作为讲解Visual C++的类库结构以及类库用法的基本指南,还是很有意义的。 5.《Visual C++技术内幕》第四版,David J.Kruglinski著,清华大学出版社。 Visual C++程序设计入门的好书,但是由于对Windows内部结构以及工作原理基本没有讲解,因此想在此书基础上成为高级Windows程序设计者不太现实的。 另附COM与OLE技术相关书籍: 6.《COM技术内幕》,Dale Rogerson著,清华大学出版社,1999.3。 一个好的FTP站点(书籍,编程资料): FTP://SE.HIT.EDU.CN/ 关于Microsoft公司COM(组件对象模型)技术比较浅显易懂的(相对)书籍。概要性的介绍了COM技术的各个方面。 7.《Inside OLE》-MSDN中。 关于Microsoft公司OLE(对象链接与嵌入)技术的全面介绍。OLE技术是COM技术的前身,也是其基础。
Windows发展简史 1981年秋,随着IBM PC的推出,Microsoft设计了MS-DOS。DOS是一个小型操作系统,提供了命令行界面,并且给出一组函数调用(中断)进行文件的输入/输出。对于其他任务-尤其是将文本或图形写到视频显示器-应用程序可以直接访问PC硬件。 1985年11月,Microsoft推出了Windows 1.0。该图形系统的原型来自于Xerox Palo Alto Research Center (PARC)在70年代中期对图形用户界面所做的开拓性工作。 1987年11月,出现Windows 2.0,该系统最主要的特性是采用了层叠窗口,这样有效的增加了屏幕的利用率,而在Windows 1.0中采用的是平铺窗口(所有窗口并列排放在屏幕上)。而且2.0还增强了键盘和鼠标的接口,特别是加入了菜单和对话框。 在以上阶段中,Windows还只要求Intel 8086或者8088处理器,以“实模式”访问1MB内存。直到Intel 386出现,Microsoft推出了Windows/386,该系统使用386的“虚拟86”模式,实现直接硬件访问的多个MS-DOS程序的窗口化和多任务化。 1990年5月22日,Windows 3.0出现。该系统取得了巨大的成功,从这时开始,Windows开始正式进入家庭和办公室。其至关重要的一个改变是对Intel的286、386、486微处理器保护模式的支持。这使得Windows和Windows应用程序能够访问高达16MB的内存空间。Windows用于运行程序和维护文件的“外壳”程序得到了全面的改进。 1992年4月,Windows 3.1出现。这个系统已经接近于我们现在看到的Windows的样子。有许多现在仍然使用的技术就是在3.1中首先得到应用的,例如TrueType字体技术、多媒体技术、OLE技术和通用对话框。3.1仍然在保护模式下运行,并且要求至少1MB内存和286或386处理器。 这里讲解一些IBM与微软公司的纠纷。 1981年,正是IBM公司给微软公司以第一笔生意。 1987年两家公司合作推出OS/2 1.0。1988年10月,推出OS/2 1.1。 1990年9月,其冲突达到最高点,终止在OS/2上的合作。
1996年,推出Window NT 4.0。这是一个相当成熟的服务器操作系统,直到现在依然有很多用户。 1993年7月,Microsoft推出了Windows NT(New Technology)。这个系统的出现意味着Microsoft将要进入服务器操作系统市场。它采用了许多先进技术,为第一个支持386、486 Pentium微处理器32位模式的Windows。有32位平面地址空间,并且使用32位指令集。可以被移植到非Intel处理器上,并且能在几种RISC工作站上运行。 1995年8月,发布Windows 95。同Windows NT一样,Windows 95也支持Intel 386或更高级处理器的32位模式。虽然缺少Windows NT中的某些高级特性,但具有需要较少硬件资源的优点。Windows 95是一个非常受欢迎的32位操作系统。 1996年,推出Window NT 4.0。这是一个相当成熟的服务器操作系统,直到现在依然有很多用户。 1998年6月,发布Windows 98。在Windows 95上作了许多扩展。包括性能的提高、更好的硬件支持以及同Internet和WWW(World Wide Web)更紧密的结合。 2000年3月,发布Windows 2000。Windows 2000具有Windows NT的优秀性能以及Windows 98的易操作性,是Microsoft新一代操作系统的象征。 同年7月,推出Windows ME(千禧版)。这是在Windows 98上的升级版。 2001年10月,推出Windows XP,该操作系统为Windows2000的后续版本,其核心原理与Windows2000比较类似。 Windows 95,98,ME作为Microsoft家用操作系统系列,其内核为16位和32位混合模式。这是为了从Windows 3.1移植更为方便。 Windows NT自从出现就屏蔽了16位模式,是一个真正的32位,多任务的操作系统。 16位->32位,多线程、动态链接、协同式任务调度->抢占式任务调度
了解Windows API的必要性 Visual C++ Borland产品 Visual Basic 其它 MFC OWL VB控件集 。。。 API函数库 Windows
SDK(Software Development Kit)编程方法利用Windows系统提供的API(Application Programming Interface)函数直接与操作系统打交道,能够提供最佳的性能、最强大的功能和最大的灵活性。无论采取什么样的方式编写代码,了解Windows API都是必要的,它对于理解Windows原理非常重要。 当前市面上流行的开发工具通常都提供一个功能库,例如Visual C++的MFC,Borland C++的OWL以及Visual Basic的控件模式编程。这些功能库都是对API函数的封装,另外加上一些更为复杂的功能特性。然而,其基础是Windows API函数,其功能只是API的一个子集。而且出于易用性考虑,屏蔽了API的一些具体细节,因此在某些场合不能够满足编程者的需要。而且由于其对API一些特性的屏蔽,使得编程者对于所面对的Windows系统不能有很全面的认识,不利于向更高层次发展。当然,使用这些功能库有助于提高项目速度,掌握Windows API有助于更深入以及更好的掌握这些功能库,并且能有效的扩展其功能。 通常高级语言都提供了API的调用接口,只是其调用方式不太一样。例如Visual C++可以直接调用API,而Visual Basic则要通过API文本浏览器获得API函数的Visual Basic兼容定义以及对动态库的引用来使用API函数。高级语言的这个特性给了我们接触和使用底层Windows API,利用API强大功能的机会。
第一章 窗口 图1-1 一个Windows窗口 Microsoft Windows应用程序中的窗口是屏幕上的一个矩形区域,是应用程序用来显示输出或接受用户输入的。由于窗口是用户和应用程序交互的基本元素,所以应用程序首要的任务就是创建一个窗口。图1-1就是一个典型的Windows窗口。
桌面窗口: 应用程序窗口: 应用程序窗口的组成: 桌面窗口是系统定义的窗口,在Windows启动后,自动创建桌面窗口。这个窗口绘制了屏幕的背景,作为Windows应用程序显示窗口的基础(可以认为是所有应用程序窗口的父窗口)。 该窗口的背景图案在注册表中如下位置定义: HKEY_CURRENT_USER\Control Panel\Desktop\Wallpaper 可以通过SDK函数SetDeskWallpaper指定。 应用程序窗口: 每一个标准的Windows应用程序至少要创建窗口,称为主窗口。这个窗口是用户与应用程序间的主要接口。绝大部分应用程序还会直接或间接的创建许多其它的窗口,来完成与主窗口相关的任务,每一窗口都是用来显示输出或是从用户得到输入。 应用程序窗口的组成: 应用程序窗口一般包括标题栏、菜单栏、System菜单、最小化、最大化/还原、关闭按钮、改变大小的边框(Border)、客户区、水平滚动条和竖直滚动条。更为复杂的窗口还包括工具条、状态条等。图1-2为一个标准的应用程序窗口-精简的文件管理器。
图1-2 一个标准的Windows窗口
图1-3 一个扩展的Windows窗口
图1-4 文件打开对话框 其它类型的窗口:控制框、对话框和消息框 控制框是用来获得用户特定信息的窗口,通常与其他窗口连用,最典型的是与对话框合用。 对话框是含有一个或多个控制框的窗口。应用程序可以通过对话框提示用户提供完成某一个命令所需的输入。例如打开文件对话框。如图1-4 图1-4 文件打开对话框
图1-5 消息框例子 标题栏: 菜单栏: System菜单栏: 最大/最小化/关闭按钮: 图1-5为一个典型的Windows消息框。 用于显示应用程序定义的一行正文,通常是应用程序的名字或说明该窗口的用途,由应用程序在创建窗口时指定。标题栏使得用户可以通过鼠标或其它的定点设备来移动窗口。标题栏的正文可以通过API函数SetWindowText来改变。 菜单栏: 菜单栏列出了应用程序所支持的命令,菜单栏中的项是命令的主要分类。用户可以选择一个命令让应用程序完成该任务。 System菜单栏: System菜单是一个由Windows系统创建和管理的菜单,其中包含标准的菜单项设置,用户可以通过它改变窗口的大小或对窗口重新定位或关闭应用程序。 最大/最小化/关闭按钮: 最大/最小化按钮用来改变窗口的大小和位置,关闭按钮用来关闭当前窗口。
窗口边框: 窗口边框是围绕窗口四周的一个区域,通过它用户可以用鼠标或其他定点设备改变窗口的大小。 客户区: 客户区是窗口的一部分,应用程序用于显示输出,如正文或图形。应用程序必须提供一个称为窗口过程的函数,来处理窗口的输入并在客户区输出。 水平/竖直滚动条: 水平和竖直滚动条把鼠标或键盘的输入转换成一个数值,应用程序用来按水平或竖直方向移动客户区的内容。 创建窗口 应用程序可以通过函数CreateWindow或CreateWindowEx来创建窗口。CreateWindowEx比CreateWindow多一个参数dwExStyle(扩展风格)。 Windows系统还提供了另外一些函数-DialogBox、CreateDialog以及MessageBox来创建特殊用途的窗口,例如对话框和消息框。 CreateWindowEx的函数原型如下:
窗口扩展风格(dwExStyle): HWND CreateWindowEx( DWORD dwExStyle , // 扩展窗口风格 LPCTSTR lpClassName, // 注册的窗口类名 LPCTSTR lpWindowName, // 窗口名 DWORD dwStyle, // 窗口风格 int x , // 左上角x坐标位置 int y , // 左上角y坐标位置 int nWidth, // 窗口宽度 int nHeight, // 窗口高度 HWND hWndParent, // 父窗口句柄 HMENU hMenu, // 窗口菜单句柄 HINSTANCE hInstance, // 应用程序实例句柄 LPVOID lpParam, // 附加窗口创建数据 ); 窗口扩展风格(dwExStyle): 指定窗口的扩展风格。这个扩展风格可以参见MSDN中的Platform SDK:Windows User Interface部分。
父窗口或属主窗口(hWndParent): 窗口类(lpClassName): 每一个窗口都从属于某一窗口类,应用程序必须在创建某类窗口之前注册窗口类,窗口类定义了窗口的外观和特性。窗口类的主要部分是一个窗口过程,也就是接收和处理给窗口的输入和请求的函数,Windows系统以消息的形式给窗口提供输入或请求。Windows系统提供了一些预定义的窗口类,应用程序通常要注册自己的一些窗口类。 窗口名(lpWindowName): 窗口名是便于用户识别一个窗口的正文字符串。主窗口、对话框或消息框一般是在其标题栏中显示窗口名。对于控制,窗口名的外观取决于控制的类。应用程序可以通过SetWindowText来改变窗口名,通过GetWindowTextLength和GetWindowText来获得当前窗口名。 窗口风格(dwStyle): 窗口风格是一个命名的常量,由它定义窗口类没有指定的窗口外观及特性。 父窗口或属主窗口(hWndParent): 窗口可以有一个父窗口,有父窗口的窗口称之为子窗口,由父窗口提供的坐标系统对子窗口进行定位。父窗口会影响到子窗口的外观(例如裁剪)。如果一个窗口没有父窗口或者它的父窗口是桌面窗口,称之为顶层窗口。应用程序通过EnumWindows来获得每一个顶层窗口的句柄,再由EnumWindows把每一个顶层窗口的句柄传给应用程序定义的回调函数。 我们可以认为系统中所有窗口的祖先窗口就是桌面窗口。 回调函数:由操作系统调用的函数。 覆盖窗口堆:由于层叠窗口,按照窗口的堆放有一个Z轴次序。 实际上如果我们把桌面窗口认为是顶层窗口的父窗口的话,那么所有的子窗口都是根据父窗口客户区左上角的坐标决定的。 一个窗口可以拥有别的窗口,也可以被别的窗口所拥有。被拥有的窗口总是在其父窗口的前面,当它的父窗口最小化时,该窗口同时被隐藏,并随父窗口的销毁而销毁。 子窗口占据父窗口的一部分客户区,不能越出。
位置、尺寸和在Z轴中的次序(hWndParent): 每个窗口都有它自己的位置、尺寸和在Z轴中的次序。一般窗口位置由相对于屏幕左上角的坐标决定;如果是子窗口则相对于它的父窗口客户区左上角的坐标决定。窗口的尺寸是其高度和宽度的像素值;窗口在Z轴中的次序则是窗口在覆盖窗口堆中的位置。 子窗口标志或菜单句柄(hMenu): 对于覆盖窗口或者弹出窗口,这是一个标志窗口使用的菜单的句柄,如果该参数为NULL,那么使用缺省的窗口类的菜单。如果这是一个子窗口,那么hMenu就是子窗口标志(Identifier,简称ID)。这个标志是子窗口用来唯一的表明自己的整数。当子窗口向父窗口发送通知消息(Notify Message)时能够告诉父窗口是哪一个子窗口在发送消息。 实例句柄(hInstance): 每一个Windows程序都有一个与之相应的实例句柄,Windows系统在应用程序开始的时候就为它提供了实例句柄。由于每一个应用程序可以有多个拷贝,Windows系统就是利用实例句柄来区分应用程序的不同实例。该参数在Windows 95/98中可以是任意实例句柄(指定一个任意的实例作为窗口的属主,在Windows NT/2000中,出于安全性考虑,该参数被忽略(ignored)了-From MSDN。 创建数据(lpParam): 关于窗口大小和实例句柄可以通过实例演示。 我们在Visual C++中有时会看到这样一些消息:NM_****,这就是一个通知消息(Notify Message)。 那么它的消息映射是:ON_NOTIFY(NM_CLICK IDC_LIST1 OnClickList1),这里的IDC_LIST1就是子窗口标志,也就是创建这个窗口时提供的hMenu。 这里,LPVOID指针是一种没有类型的指针,实际上我们可以理解为该指针就是指向某一块内存的指针,这块内存中的数据格式需要我们 自己清楚,然后可以通过强制转换或者地址操作获得内存块中的内容。 每一个窗口都可以有与之相应的由应用程序定义的创建数据。在窗口第一次被创建时,Windows系统把数据的指针传给所创建窗口的窗口过程,窗口过程用这些数据初始化应用程序定义的变量。当Windows创建一个窗口后,会向窗口过程发送一个WM_CREATE消息,这个消息的LPARAM参数中保存着一个CREATESTRUCT结构,有关窗口的特性等内容,包括创建数据都在这个结构中,该结构中的LPVOID lpCreateParams就是lpParam指针。应用程序可以通过响应WM_CREATE消息来根据创建数据初始化窗口中的变量。
窗口风格: 1.覆盖窗口(overlapped):覆盖窗口是一个顶层窗口,具有标题栏、边框和客户区,用作应用程序的主窗口。也可以有一个System菜单、最小化和最大化按钮以及滚动条,通过给CreateWindowEx中的dwStyle参数加入WS_OVERLAPPED风格创建。 2.弹出窗口(popup):是一个特定类型的覆盖窗口,通常对应于对话框、消息框以及其他显示在应用程序主窗口之外的临时窗口中。标题栏可选,其他与覆盖窗口一样。通过给CreateWindowEx中的dwStyle参数加入WS_POPUP风格创建。 3.子窗口(child):子窗口具有WS_CHILD风格,被限制在其父窗口的客户区中,应用程序通常就是用子窗口把主窗口的客户区分成几个功能区域。子窗口必须有一个父窗口,父窗口可以是任意类型的窗口,甚至是child窗口。应用程序可以为子窗口提供除了菜单以外的任何东西。
一个真正的Windows程序:Hello.c 几个有用的关于窗口的函数: AdjustWindowRect AdjustWindowRectEx AnyPopup ArrangeIconicWindows BeginDeferWindowPos BringWindowToTop ChildWindowFromPoint CloseWindow CreateWindow CreateWindowEx DeferWindowPos DestroyWindow EndDeferWindowPos EnumChildProc EnumChildWindows EnumThreadWindows EnumThreadWndProc EnumWindows EnumWindowsProc FindWindow GetClientRect GetDesktopWindow GetForegroundWindow GetLastActivePopup GetNextWindow GetParent GetTopWindow GetWindow GetWindowPlacement GetWindowRect GetWindowText GetWindowTextLength GetWindowThreadProcessId IsChild IsIconic IsWindow IsWindowVisible IsZoomed MoveWindow OpenIcon SetDeskWallpaper SetForegroundWindow SetParent SetWindowPlacement SetWindowPos SetWindowText ShowOwnedPopups ShowWindow WindowFromPoint WinMain 这些函数的具体定义请到MSDN中去查找。 一个真正的Windows程序:Hello.c
/*------------------------------------------------------------ HELLOWIN.C -- Displays "Hello, Windows!" in client area (c) Charles Petzold, 1998 ------------------------------------------------------------*/ #include <windows.h> LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("HelloWin") ; HWND hwnd ; MSG msg ; WNDCLASS wndclass ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass)) {//注册窗口类 MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow (szAppName, // window class name TEXT ("The Hello Program"), // window caption WS_OVERLAPPEDWINDOW, // window style CW_USEDEFAULT, // initial x position CW_USEDEFAULT, // initial y position CW_USEDEFAULT, // initial x size CW_USEDEFAULT, // initial y size NULL, // parent window handle NULL, // window menu handle hInstance, // program instance handle NULL) ; // creation parameters ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0)) {//消息循环 TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {//窗口过程,回调函数 HDC hdc ; PAINTSTRUCT ps ; RECT rect ; CREATESTRUCT *cs = NULL; switch (message) { case WM_CREATE: PlaySound (TEXT (“hellowin.wav”), NULL, SND_FILENAME | SND_ASYNC) ;//播放声音 cs = (CREATESTRUCT *)lParam; return 0 ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; GetClientRect (hwnd, &rect) ;
DrawText (hdc, TEXT (“Hello, Windows DrawText (hdc, TEXT (“Hello, Windows!”), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;//绘制文本 EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: PostQuitMessage (0) ; } return DefWindowProc (hwnd, message, wParam, lParam) ;//传递给缺省的消息处理函数 //运行结果 窗口风格和扩展风格祥见文件Window Styles.doc 裁剪(Clip): Windows系统不会自动的裁剪父窗口客户区中的子窗口,这就意味着父窗口可以在子窗口的位置上进行绘制。但如果父窗口具有WS_CLIPCHILDREN风格,Windows系统就会自动裁剪父窗口客户区中的子窗口,这样父窗口就不能在上面进行绘制。 子窗口覆盖同一客户区中的其他窗口,一个或多个其它的子窗口共享一个父窗口叫做兄弟窗口。兄弟窗口也可以在相互间的客户区中绘画,除非其中某个子窗口具有WS_CLIPSIBLINGS风格。如果应用程序为子窗口指定了这个风格,那么画在这个子窗口中的子兄弟窗口的任何部分就被裁减。 Hellowin.c源码 每个子窗口都会消耗一定的系统资源,与此同时如果使用了WS_CLIPCHILDREN和WS_CLIPSIBLINGS风格,会进一步降低系统的性能。因此不能不加节制的使用子窗口。
被禁止的窗口(Disabled Window): 窗口是可以被禁止的,被禁止的窗口不再接收键盘或鼠标输入,但它能够接受来自其他窗口或者其他应用程序的以及Windows系统的消息。 应用程序可以通过EnableWindow来激活或者禁止一个窗口。使用IsWindowEnabled来确定一个窗口是否被禁止。 如果当前一个子窗口或其他的子孙窗口有输入焦点,那么当父窗口被禁止时,其子孙窗口也会失去输入焦点。 前台窗口和后台窗口(Foreground&Background Window): 每一个进程可以创建多个线程,每个线程都能创建窗口。创建正在使用的窗口的线程称作前台线程,这个窗口称作前台窗口。所有其它的线程为后台线程,由后台线程创建的窗口叫做后台窗口。 活动窗口(Active Window): Windows系统只有一个输入焦点,在某一时刻只属于某一个窗口。 活动窗口是应用程序的顶层窗口,也就是当前使用的窗口。我们可以通过ALT+TAB或者ALT+ESC来激活一个顶层窗口,另外有:SetActiveWindow, SetWindowPos, DeferWindowPos, SetWindowPlacement和DestroyWindow可以激活顶层窗口。
可见性: 窗口可以是可见的,也可以被隐藏。如果窗口被隐藏,这相当于被有效的禁止了。隐藏的窗口可以处理来自Windows系统或其他窗口的消息,但不能处理用户输入或者显示输出。 如果应用程序在使用CreateWindowEx时不指定WM_VISIBLE风格,那么它只创建一个隐藏窗口。应用程序可以通过IsWindowVisible函数来确定一个窗口是否可见。通过ShowWindow函数来显示或隐藏一个窗口。 如果一个窗口被最小化了,Windows系统会自动隐藏相应的从属窗口。同样当一个父窗口被恢复时,Windows系统也会自动地显示相应的从属窗口。在这两种情况下,Windows系统在显示或隐藏子孙窗口时,向其发送WM_SHOWWINDOW消息。有时,应用程序也可能在不最小化或隐藏属主窗口的情况下需要隐藏从属窗口,那么应用程序可以使用函数ShowOwnedPopups(隐藏本窗口所有子窗口。),这个函数设置或删除所有从属窗口的WS_VISIBLE风格,并在隐藏或显示拥有窗口之前向它们发送WM_SHOWWINDOW消息。 隐藏本窗口的所有子窗口还有另外一种实现方法:EnumChildWindows+ShowWindow