FCB功能的存在是出于历史的原因,虽然它也有一些很显著的优点,但随着DOS版本的升级,FCB功能逐渐被另一套文件处理功能所取代。即使是DOS的设计者Microsoft公司也并不提倡程序员使用FCB功能。因此在本章我们将研究DOS提供的另一套功能--"文件句柄功能"的应用。
我们在前面讨论过,使用FCB处理文件有一个十分显著的缺点,那就是无法处理"树形目录"。这并不能怪FCB的功能弱,FCB功能源于CP/M操作系统,而树形目录是在DOS2.0版中才引入PC中的,所以利用FCB无法处理也就不足为奇了。
所谓树形目录,是Microsoft从UNIX/XENIX操作系统中移植到DOS中的,它引入了"子目录"的的概念,使磁盘在有限的空间内能够更合理地容纳更多的文件,方便了用户对文件的管理工作。
为了使程序设计人员能够有效的处理树形目录,DOS提供了一套称之为"文件句柄功能调用"的API接口,和FCB功能相比,文件句柄功能的一个突出特点就是将FCB中保存的文件特征信息隐藏在DOS内部,而对于我们设计的程序,DOS只提供了一个16位的数据,这个数据就是我们所说的"文件句柄"。我们现在就来研究一下有关"文件句柄"功能所需的一个很重要的基础知识--文件名与路径名。
我们使用FCB功能打开文件时,需要先初始化一个"未打开"的FCB,也就是在FCB的前12个字节中填入相应驱动器符和文件名。这实际上是给DOS提供执行打开操作的依据。而使用文件句柄功能处理文件也有这样一个步骤。但是我们需要提供给DOS的数据就不象FCB那么死板了。主要的区别在于我们所给出的数据不仅有驱动器符和文件名,还可以包含路径名。DOS规定我们给出的数据应具有下面这样的结构:
[Drive][路径名]<文件名>.<扩展名>,00H
其中Drive项是驱动器名,同FCB的规定不同,DOS不再接受0,1,2,3这样的数字表示,而要求我们直接使用A:,B:,C:,D:这样的表示法;[路径名]一项和我们一般理解的路径名是一致的,即一系列用"\"隔离开的子目录名。文件名和扩展名没什么更多说的,需注意的是其中的那个"."不能省去;至于未尾那个"00H",这是DOS所规定必须要有的表示结尾的标志。所以我们说我们提供给DOS的数据是一个由ASCII字符组成的字符串加上一个"0"字节构成,习惯上我们把这串数据称为ASCIIZ串(ASCII ZERO)。比如我们需要处理WINDOWS系统中名为WIN386.EXE的文件,那么我们将这样定义ASCIIZ串:
FILENAME DB'C:\WINDOWS\WIN386.EXE',0
这里还有几点我要提醒大家,凡是"[ ]"中的项都是可以省略的,DOS会按"当前情况"处理。另外,DOS不要求我们定义象"文件控制块"那样的数据结构,因此,ASCIIZ串可以放在内存中任意地方。同时,在存取文件时DOS也不需要我们定义DTA,不过DOS要求我们给出指向数据缓冲区的指针。简而言之,有关FCB功能的那一套并未"传染"给文件句柄功能。关于这些问题我们将在稍后做更深入的讨论。
有了这些初步的认识,我们下面所做的工作就是研究文件句柄功能的具体应用方法。表7-1给出了DOS提供的最常用的文件句柄功能调用,仿照前半章的内容,我们仍按照"打开关闭--读出写入--其它功能"的顺序来分别讨论。
表7-1 文件句柄功能调用 | |||||||||||||||||
功能用途 | 参数 | 返回 | |||||||||||||||
3CH建立文件 | CX=文件属性 DS:DX指向ASCIIZ串 |
成功:CF标志清0,AX=文件句柄 失败:CF标志置1,AX=错误代码* |
|||||||||||||||
3DH打开文件 | AL=访问方式 DS:DX指向ASCIIZ串 |
成功:CF标志清0,AX=文件句柄 失败:CF标志置1,AX=错误代码 |
|||||||||||||||
3EH关闭文件 | BX=文件句柄 | 成功:CF标志清0,AX=文件句柄 失败:CF标志置1,AX=错误代码 |
|||||||||||||||
3FH读文件 | BX=文件句柄 CX=要读入的字节数 DS:DX指向缓冲区 |
成功:CF标志清0,AX=实际读入的字节数 失败:CF标志置1,AX=错误代码 |
|||||||||||||||
40H写文件 | BX=文件句柄 CX=要写的字节数 DS:DX指向缓冲区 |
成功:CF标志清0,AX=实际写出的字节数 失败:CF标志置1,AX=错误代码 |
|||||||||||||||
41H删除文件 | BX=文件句柄 | 成功:CF标志清0 失败:CF标志置1 | |||||||||||||||
42H移动文件读写指针 | AL=方式代码 BX=文件句柄 CX:DX=偏移值 |
成功:CF标志清0,DX:AX=新的文件指针 失败:CF标志置1,AX=错误代码 |
|||||||||||||||
* 注:错误代码在后面讨论。 |
在这一节我们主要讨论文件的打开与关闭功能,借此以加深对文件句柄功能的理解。请大家把我们的老朋友--DEBUG搬入内存,并用"A"命令输入下面这段小程序:
C:\ASM\>DEBUG[Enter] -a100[Enter] 123D:0100 JMP 0124 ;跳过数据区 123D:0102 DB 'C:\COMMAND.COM',0 ;定义一个ASCIIZ串 123D:0111 DB 'OK...',0D,0A,24 ;程序执行正确的信息 123D:0119 DB 'ERROR...',0D,0A,24 ;程序出错信息 123D:0124 MOV AX,3D00 ;按读方式打开文件 123D:0127 MOV DX,0102 ;DX指向ASCIIZ串 123D:012A INT 21 ;打开文件 123D:012C JB 0139 ;出错,转0139 123D:012E MOV BX,AX ;文件句柄送入BX寄存器 123D:0130 MOV DX,0111 ;显示程序正确执行的信息 123D:0133 MOV AH,09 123D:0135 INT 21 123D:0137 JMP 0140 ;转0140结束进程 123D:0139 MOV DX,0119 ;显示程序出错信息 123D:013C MOV AH,09 123D:013E INT 21 123D:0140 MOV AH,3E ;用3EH功能 123D:0142 INT 21 ;关闭文件 123D:0140 MOV AH,4C ;结束进程 123D:0142 INT 21
通过这个实验程序大家应该掌握两点:其一,ASCIIZ串的定义方法;其二,3DH功能的应用。第一个问题可以对照我们前面讨论过的内容,而第二个问题可以参考后面对3DH功能的说明。我们现在来实际跟踪一下这个程序,看看还能发现什么新鲜的东西。
-g=100 12c[Enter] AX=0005 BX=0000 CX=0042 DX=0102 SP=FFFE BP=0000 SI=0000 DI=0000 DS=123D ES=123D SS=123D CS=123D IP=012C NV UP EI PL NZ NA PO NC 123D:012C 7209 JB 0139
用"G"命令在012CH处打一个断点,则DEBUG会在执行INT 21指令后为我们列出各寄存器的情况。可以看到AX寄存器返回一个16位的数字05H,这就是我们所需要的那个所谓的"句柄"。这一个数字所代表的就是我们刚刚打开的那个文件,从此以后我们对这个文件进行其它操作时必须要给DOS提供这个数字。
前面几节使用FCB功能打开文件后,DOS会在文件控制块中填入好多的信息供程序使用。而使用句柄功能却只能得到一个简单的数字。这不能不说是句柄功能与FCB功能的最大差别了。当然,事实上DOS仍然要取得有关文件的特征信息的,只不过这些信息都存于DOS内部,由DOS私用而已。
标志寄存器中的CF位为0,表明DOS成功的打开了要处理的文件,如果CF标志为1则说明功能调用执行出错。因此应用指令依据CF标志的状态转移即可区分文件操作是否正确。指令"JNC"恰好能达到此目的:
助记符:JNC(Jump if Not Carry)
用 途:依据CF标志状态转移
格 式:JNC 目的地址
执 行:若CF标志等于0,此指令将使CPU转移至目的地址处继续执行程序,
否则CPU执行JNC指令下面的程序
这是一个条件转移指令,注意它只能进行短程转移。与此指令相对的指令是JC,第六章里已经介绍过。句柄功能返回错误状态的方法与FCB功能不同,应加以注意。
先别忙着"GO"这个程序,我想问诸位一句大家是否对AX寄存器中的"句柄"有什么疑问?问题并不难发现,为什么这个句柄不是"00H"呢?我们只打开了一个文件呀?这里面有个不大不小的秘密,我打算在下一节中将其"曝光"。
程序中使用的3EH功能用于关闭文件,所谓关闭,其含义和我们在前面所讲的一样,也是将BUFFER中的数据存入磁盘同时更新目录项,这里我们就不多讨论了,要注意的是使用这个功能前别忘记在BX寄存器中放入要关闭的文件对应的"句柄"。
附带说明一下,关于打开文件时在AL寄存器中填入的"存取方式"一项,这是一个取值为0,1,2的数据,分别表示"读"、"写"和"读/写"三种方式。如果大家熟悉C语言,肯定知道用FOPEN函数打开文件时也要指明存取方式:r表示"读取";rw表示"读/写"。如果大家不清楚C语言为什么要有这样的要求,那么学到这里,我想你应该对这个问题有了比较明白的认识--C语言也要求助于DOS为其打开文件,它也要遵守DOS的规定。
事实上DOS允许我们同时打开多个文件,每打开一个文件DOS都会给我们返回一个句柄,各个文件对应的句柄都是不同的。我们知道每打开一个文件后DOS都会取得与这个文件相关的一些特征信息保存在自己内部,而DOS用于保存这些信息的空间可不是无穷无尽的,所以能够同时打开的文件数就有限制。DOS启动后究竟为打开文件准备了多少内存呢?这个问题的答案在CONFIG.SYS文件中。还记得我们前面讨论的BUFFERS设置吗?在CONFIG文件中还有一项"FILES=???"的配置项,这一项就是告诉DOS启动时要准备出打开"???"个文件所需的内存空间。而我们的程序所能同时打开的文件个数是"???-5"个,这是因为文件句柄号是从05H开始的。
在本节的最后,我们来讨论一下有关"建立文件"的内容。下面给出了3CH功能的用法,利用句柄功能建立一个文件和用FCB功能并无太大区别,要注意的有这样几点:
第一,不要忘记用ASCIIZ串给出路径和文件名;
第二,如果指定目录下无重名的目录项,则DOS会新建一个目录项并返回句柄。如果指定目录下有一个与待建立的文件重名的目录项,则DOS会将旧的文件长度截为0。此一点和FCB功能相同。
第三,CX寄存器中要设定待建文件的属性。属性的定义和FCB功能相同,但是句柄功能无法建立具有卷标属性或子目录属性的目录项。同FCB功能相比这可以说是句柄功能的一个弱项。
第四,若出错,则CF标志置位,同时AX寄存器返回错误码供程序处理。这其实是所有句柄功能的一个共同特点。这些错误代码都出自一个统一的错误代码表,见表7-2。
第五,文件成功建立之后我们即可对其进行写操作,3CH功能不涉及"打开方式"的问题。建立文件功能的用法并不难,大家可自己编制实验程序将其掌握。
01 | 非法功能号 | 10 | 非法环境 |
02 | 文件未找到 | 11 | 非法格式 |
03 | 路径名不正确 | 12 | 非法存取代码 |
04 | 同时打开的文件太多 | 13 | 非法数据 |
05 | 拒绝存取 | 14 | 未定义 |
06 | 非法文件句柄 | 15 | 非法指定设备 |
07 | 内存控制块被破坏 | 16 | 试图删除当前的目录 |
08 | 内存不够 | 17 | 设备不一致 |
09 | 非法存储块地址 | 18 | 已没有文件 |