📄 chap2_3.htm
字号:
<html><head><meta http-equiv="Content-Type" content="text/html; charset=gb2312"><meta name="GENERATOR" content="Microsoft FrontPage 3.0"><title> WIN32开发</title></head><body link="#3973DE" alink="#3973DE" background="../bg.gif"><font SIZE="5"><b><div align="center"><center><table border="0" width="85%" cellspacing="0" cellpadding="0" bgcolor="#FFFFFF"> <tr> </b><td><div align="center"><center><table border="0" width="615" cellpadding="0" cellspacing="0" height="20"> </table> </center></div><p align="center"><font size="4" color="#3973DE"><br> </font><font color="#3973DE"><font FACE="Times New Roman" size="5">2.3 WIN32</font><font size="5">开发</font></font></p> <font FACE="Times New Roman" size="5"><p ALIGN="JUSTIFY"></font><span style="font-size: 9pt">Visual C++5.0是一个全32位的软件开发工具,它完全支持32位的Win32平台开发。Win32平台包括32位的Windows操作系统和软件开发系统Win32 API。所谓API(应用程序接口)指的是一组由操作系统提供的函数。Win32 API是Windows平台上的一个32位的软件开发系统,它使应用程序可以充分利用32位Windows操作系统的能力。使用Win32 API写成的应用程序可以在Windows95或更高版本以及Windows NT上运行。</span></p> <p ALIGN="JUSTIFY"><span style="font-size: 9pt">由于Microsoft在Windows 3.x及其Win16 API上取得巨大的成功,因此,在研制Win32 API时,首先考虑的就是保证Win32与Win16 API兼容,只有让软件开发者能将Win16代码很容易移植到Win32 API上,才有实际意义。Win32 API在语法上只作了极小的改动,API的命名与Windows 的Win16 API相同,语义也相同,消息序号也相同。事实上,完全可以保存独立的源代码,并选择编译成16位的Win16程序或32位的Win32程序。</span></p> <p><span style="font-size: 9pt">其次,如其名所示,在设计Win32 API时考虑到了充分利用32位处理器的能力。随着硬件的发展,内存和CPU价格的降低和性能的提高,32位CPU的486、Pentium已成为主流。据有关数据显示,目前在我国家用计算机用户中,使用Pentium系列处理器的计算机已占80%以上。如何充分利用当前32位(和64位)处理器的能力,并预见将来处理器的发展,就成为Win32设计时考虑的重要因素之一。</span></p> <p><span style="font-size: 9pt">再次,为了摆脱操作系统对Intel处理器的依赖,使应用程序可以运行于各种处理器平台上,Win32设计时增强了它的可移植性,提供了Microsoft Windows95和Windows NT之间的透明的移植能力。虽然Windows95只能运行于Intel平台上,但是Win32还支持Windows NT,而Windows NT已经被移植到许多非Intel的处理器上,如Alpha、RISC硬件平台等。</span></p> <p><span style="font-size: 9pt">Win32可以应用于特定的操作系统,这种系统可以直接控制和处理PC硬件资源,而不必象Win16 API那样依赖于MS-DOS系统服务。然而,Win32不是简单的由Win16从16位到32位的升级,更重要的在于它支持:</span><ul> <li><span style="font-size: 9pt">高性能的抢先式多任务和多线程</span></li> <li><span style="font-size: 9pt">连续的32位地址空间和先进的内存管理</span></li> <li><span style="font-size: 9pt">对所有的可为进程共享的对象,解决了它的安全性问题</span></li> <li><span style="font-size: 9pt">内存映射文件</span></li> </ul> <b><p></b><span style="font-size: 9pt"><font color="#3973DE">2.3.1 抢先式多任务和多线程</font></span></p> <p><span style="font-size: 9pt">我们知道Windows是一个多任务操作系统,它提供了一次运行多个应用程序的能力。但是,Windows 3.x和Windows95在多任务的实现上有所不同。</span></p> <p ALIGN="JUSTIFY"><span style="font-size: 9pt">Windows 3.x的多任务是一种由协作、软件方式产生的有限的非抢先式的多任务。它是借助于每个应用程序的消息循环这种软件协议方式来实现多任务的。Windows 3.x管理所有的消息,并存放于系统的消息队列中。操作系统判断消息应归哪一个窗口去处理,再将消息发送给该窗口。每个应用程序窗口处于等待消息状态,直到有消息来,然后进行处理,处理完毕将控制权交给操作系统。在对消息进行处理时,对于用户用键盘或者鼠标输入的任何命令,Win16都不会理睬。比如,我们用WORD载入一个文件时,其他程序都得等待文件I/O操作完成才能获得响应。而且,一个应用程序切换到另一个应用程序时,需要较长的等待时间。各应用程序在取得消息、处理消息时是平等的,无优先级的,系统无法设置应用程序的优先级和时间片的大小。</span></p> <p><span style="font-size: 9pt">Windows95的多任务是一种抢先式多任务。比如,我们在用资源管理器复制一个文件的同时,还可以启动另外一个应用程序,如纸牌游戏,而且随时都可以切换回资源管理器,察看文件复制进度,系统始终保持较好的响应和灵活性。Windows95的抢先式多任务机制不是用Windows 3.x下的软件调度来实现的。要了解抢先式多任务,我们需要首先了解一下进程和线程的概念。调入内存准备执行的应用程序叫做进程(process)。每个进程至少有一条线程,叫做主线程(primary thread)。一个进程包含代码、数据和其他属于应用程序的资源。一条线程包含一组指令,相关的CPU寄存器值和一个堆栈。</span></p> <p ALIGN="JUSTIFY"><span style="font-size: 9pt">在抢先式多任务操作系统中,系统在所有运行的所有进程之间对CPU时间进行共享,从而保证每个进程都能频繁的访问处理器,并且实现指令的连续执行。这样,每个Win32进程都需要分配一个优先级,系统调度程序利用这种优先级来决定哪一时刻该运行哪一个进程。具有高优先级的进程(严格的说应当是线程)就是当前运行的哪一个。更高优先级的线程可以中断当前进程的执行。同一优先级的线程通过时间片来调度。一个线程处于以下三种状态之一:正在执行,挂起,准备运行。在单处理器环境下(如Windows 95),同一时刻只能运行一个线程。有关多线程,我们还将在后面的章节里作专门介绍。</span></p> <p><span style="font-size: 9pt">为了在Win32中支持多线程进程结构,Win32在原来Win16基础上增加了:</span></p> <blockquote> <p><span style="font-size: 9pt">对进程以及线程创建、操纵的支持</span></p> <p><span style="font-size: 9pt">对一个进程内线程之间的同步和同步对象的支持</span></p> <p><span style="font-size: 9pt">一个统一的共享机制。</span></p> </blockquote> <p><b> </p> <p></b><span style="font-size: 9pt"><font color="#3973DE">2.3.2 连续的地址空间和先进的内存管理</font></span></p> <p><span style="font-size: 9pt">对于各种操作系统和平台来说,内存管理都是一个非常重要的问题。在Windows3.1下,有两种形式的内存管理函数调用:局部的和全局的。全局内存管理函数从物理内存中分配一段,然后返回一个句柄值。该句柄可以转换为一个GlobalLock函数所使用的远指针。基本处理过程如下:</span><ol> <li><span style="font-size: 9pt">申请一块可移动的内存块</span></li> <li><span style="font-size: 9pt">锁定该内存块。因为Windows引入了虚拟内存管理,可以把内存块移动到硬盘交换文件中,所以在使用内存块之前,必须将它锁定在真正的内存RAM之中,也就是告诉操作系统,现在这块内存暂时由应用程序来管理。</span></li> <li><span style="font-size: 9pt">对该内存块进行各种操作:如复制数据到内存块。</span></li> <li><span style="font-size: 9pt">解锁内存,应用程序将对该内存的控制交与Windows。</span></li> </ol> <p> </p> <p><span style="font-size: 9pt">下面给出一个程序片段,来说明内存管理函数的用法。</span></p> <p><span style="font-size: 9pt">HGLOBAL memHandle;//内存句柄</span></p> <p><span style="font-size: 9pt">char far* lpMem;//假设长度为memLen</span></p> <p><span style="font-size: 9pt">memHandle=GlobalAlloc(GHND,memLen+1);//申请内存块,此处未做返回结果检查,</span></p> <p><span style="font-size: 9pt">//事实上,申请内存有时会失败</span></p> <p><span style="font-size: 9pt">memcpy(lpMem,string,textLen);//拷贝数据,其中string为一字符串变量,textLen是这个</span></p> <p><span style="font-size: 9pt">//字符串的长度</span></p> <p><span style="font-size: 9pt">GlobalUnlock(memHandle);//解锁内存</span></p> <p><span style="font-size: 9pt">...</span></p> <p><span style="font-size: 9pt">GlobalFree((HGLOBAL) memHandle);//释放内存</span></p> <p ALIGN="JUSTIFY"><span style="font-size: 9pt">全局内存对所有的应用程序都是可见的,不管是显式的还是隐式的请求。因为Windows 3.x的实现方式就是所有的进程在同一地址空间中运行。局部内存管理则是从64KB的段内分配对象并返回所分配内存的16位偏移量。</span></p> <p ALIGN="JUSTIFY"><span style="font-size: 9pt">在Win32下,局部和全局内存管理函数基本相同,仍然可以使用可移动和可丢弃选项。但是它引入了连续(flat)的32位内存管理概念。</span></p> <p ALIGN="JUSTIFY"><span style="font-size: 9pt">在Win32中,每个进程都有其特有的32位虚拟地址空间,该空间最大可达4GB。如图所示,低端内存的2GB是用户可用的,高端内存的2GB为内核(Kernel)保留。其中,最高的1GB用于VxD、内存管理和文件系统。下面的1GB用于共享的Win32 DLL、内存映射文件和共享内存区域。进程所使用的虚拟地址不代表一个对象在内存的实际的物理位置(事实上,我们大部分的PC还没有配置4GB内存)。操作系统为每个进程维护一个映射表,根据该表将虚拟地址映射到真正的物理位置处(RAM或者交换页文件中)。</span></p> <p ALIGN="CENTER"><span style="font-size: 9pt"><img src="T2_7.gif" alt="T2_7.tif (136561 bytes)" WIDTH="385" HEIGHT="325"></span></p> <p ALIGN="CENTER"><span style="font-size: 9pt">图2.7 Windows95的内存映射</span></p> <p ALIGN="CENTER"> </p> <p><span style="font-size: 9pt">在Win32下局部内存对象有一个32位句柄而不是Windows 3.x下的16位句柄,而且这个句柄是一个实际指针而不是一个相对于段的偏移量。</span></p> <p><span style="font-size: 9pt">Win32和16位Windows一个重要区别是:在Win32下,所有的进程都有自己独立的地址空间(在进程内部的线程仍然共享进程的内存变量),全局内存不再对所有的Windows应用程序都可见。由于每个应用程序都有自己的地址空间,一个进程分配的内存在该进程的地址之外就不再可见。DDE会话中使用的内存对接收者进程来说是透明的。这样,进程的安全性就得到大大提高,程序更加强壮。一个进程崩溃一般不会影响另外一个进程的执行。但是,这也给多个应用程序共享内存带来了困难。在许多情况下,需要在多个应用程序之间进行通讯和数据交换,这时,该怎么办呢?Win32引入了内存映射文件,很好的解决了这个问题。</span><b></p> <p></b><span style="font-size: 9pt"><font color="#3973DE">2.3.3 内存映射文件</font></span></p> <p><span style="font-size: 9pt">内存映射文件是由一个文件到一块内存的映射。Win32提供了允许应用程序把文件映射到一个进程的函数(CreateFileMapping)。这样,文件内的数据就可以用内存读/写指令来访问,而不是用ReadFile和WriteFile这样的I/O系统函数,从而提高了文件存取速度。</span></p> <p><span style="font-size: 9pt">这种函数最适用于需要读取文件并且对文件内包含的信息做语法分析的应用程序,如对输入文件进行语法分析的彩色语法编辑器,编译器等。把文件映射后进行读和分析,能让应用程序使用内存操作来操纵文件,而不必在文件里来回地读、写、移动文件指针。</span></p> <p><span style="font-size: 9pt">有些操作,如放弃“读”一个字符,在以前是相当复杂的,用户需要处理缓冲区的刷新问题。在引入了映射文件之后,就简单的多了。应用程序要做的只是使指针减少一个值。</span></p> <p><span style="font-size: 9pt">映射文件的另一个重要应用就是用来支持永久命名的共享内存。要在两个应用程序之间共享内存,可以在一个应用程序中创建一个文件并映射之,然后另一个应用程序可以通过打开和映射此文件把它作为共享的内存来使用。</span><b></p> <p></b><span style="font-size: 9pt"><font color="#3973DE">2.3.4 Win32s:Windows 3.x对Win32 API的支持</font></span></p> <p ALIGN="JUSTIFY"><span style="font-size: 9pt">我们经常会遇到Win32s这个词,它与Win32是有区别的。Win32s的s的含义是指子集(subset)。它指的是,在一个Win32程序中移入一些DLLs和一个VxD,使它运行于配置80386以上处理器的Windows 3.x系统之上,并且以一种增强模式运行(但有一定限制)。运行在Windows 3.x/Win32s系统上的Win32程序支持32位指针和32位寄存器,只需要在系统调用之前稍作形式替换。如果程序中使用大的数据结构或很多的计算时,Win32s性能明显优于16位Windows版本,根据Microsoft的测试,性能可以提高两倍左右;如果程序只是大量的调用Windows API,则16位版本的性能可能会强于32位版本,因为Win32s会对每一次API调用作一个从16位到32位的转换。</span></p> <p><span style="font-size: 9pt">Win32s子集同Win32相比,不支持:多线程,高级图形API,异步文件I/O,Unicode和安全性;而且它是运行于16位的Windows系统上的。但是同Win16相比,有它的优越之处,目前在16位Windows程序开发方面有相当的潜力。</span></p> <p><span style="font-size: 9pt">Visual C++4.1及以前版本支持Win32s,但Visual C++5.0不再支持Win32s。</span></p> <b><p></b><span style="font-size: 9pt"><font color="#3973DE">2.3.5 Win32编程基础</font></span><b></p> <p><span style="font-size: 9pt">Win32数据类型</span></b></p> <p><span style="font-size: 9pt">这里的数据类型指的是一些关键字,这些关键字定义了Win32中的函数中的有关参数和返回值的大小和意义。Win32常用的数据类型有:</span></p> <p> </p> <table BORDER="1" CELLSPACING="2" BORDERCOLOR="#7f7f7f" CELLPADDING="1" WIDTH="557"> <tr> <td WIDTH="16%"><span style="font-size: 9pt">数据类型</span></td> <td WIDTH="84%"><span style="font-size: 9pt">描述</span></td> </tr> <tr> <td WIDTH="16%"><span style="font-size: 9pt">HANDLE</span></td> <td WIDTH="84%"><span style="font-size: 9pt">定义一个32位无符号的整数,用作句柄</span></td> </tr> <tr> <td WIDTH="16%"><span style="font-size: 9pt">HINSTANCE</span></td> <td WIDTH="84%"><span style="font-size: 9pt">定义一个32位的无符号整数,用作实例句柄</span></td> </tr> <tr> <td WIDTH="16%"><span style="font-size: 9pt">HWND</span></td> <td WIDTH="84%"><span style="font-size: 9pt">定义一个32位的无符号整数,用作窗口句柄</span></td> </tr> <tr> <td WIDTH="16%"><span style="font-size: 9pt">HDC</span></td> <td WIDTH="84%"><span style="font-size: 9pt">一个设备描述背景的句柄</span></td> </tr> <tr> <td WIDTH="16%"><span style="font-size: 9pt">LONG</span></td> <td WIDTH="84%"><span style="font-size: 9pt">说明一个32位带符号整数</span></td> </tr> <tr> <td WIDTH="16%"><span style="font-size: 9pt">LPSTR</span></td> <td WIDTH="84%"><span style="font-size: 9pt">定义一个线性的32位字符串指针</span></td> </tr> <tr> <td WIDTH="16%"><span style="font-size: 9pt">UINT</span></td> <td WIDTH="84%"><span style="font-size: 9pt">定义一个新的Win32数据类型,它会把一个参数强制转换成Windows3.x应用中的16位值或Win32应用中的32位</span></td>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -