第9章 目录和文件 对于编程来说,磁盘文件是最基本的内容。作为程序员,不论是为什么目的(商用、开 发、娱乐或科研)而编程,都必须涉及磁盘文件。于是大多数编程语言都提供了丰富的简单 方法来创建、打开、读、写关闭和删除文件。 从高级语言来处理文件非常简单,以致我们通常不用考虑在DOS级别上的文件操 作。借助C、BASIC和Pascal所提供的文件处理功能,我们能安全地完成工作而没有太多 的麻烦。但如果采用汇编语言,则必须熟悉DOS的文件操作功能。 即使不用汇编语言来编程,一些来自C、BASIC或Pascal函数库中的函数也并非是现 成可用的;某些类型的操作,除非使用DOS功能,否则不可能有效地完成它们。 读者也许会问为什么本书只提及DOS功能——而不提BIOS。因为文件和目录超出 了BIOS的范围。BIOS对文件一无所知。它没将结构(除磁道和扇区以外)分配给磁盘。磁 盘文件结构在DOS的直接控制之下,DOS包括访问磁盘文件和目录所必需的所有功能。 本章首先看看目录结构,然后讨论磁盘文件和文件功能。最后了解怎样利用最近掌握 的有关目录和文件的知识来建立一个程序,使读者能将某个特定文件安装在目录系统的 任何地方。 9.1磁盘目录 目录第一次出现时,它们代表了磁盘操作系统对磁盘文件操纵方式的一个重要改进。 较早的操作系统(CP/M、TRSDOS、Apple DOS和无数其它的系统)都以相同的方式来处 理文件。在这些平面文件系统中,所有的磁盘文件都借助单一目录便可工作。在DOS 2.0 版出现之前,DOS也采用这种方式来处理文件。 自DOS 2.0版开始,从UNIX和XENIX中借来了一个文件排序的概念。这种分级目 录方案允许对大量磁盘文件(通常是保存在硬盘上的,但并非总是如此)进行简易排序和 处理。在有分级目录的系统中,每个磁盘都有预先限定大小固定的根目录保存在磁盘的一 个已知位置上。像平面文件这个概念一样,根目录能包括一定数量的同样是可访问的文 件。 如果要停留在单个根目录的限制之中,那么不会比以前更好。2.0版磁盘的根目录不 能与1.0版磁盘的目录区分开来。但分级目录系统让用户在根目录中创建特别的表项(叫 作子目录),子目录与那些除它们的文件空间包含另外的目录项以外的文件相似。像根目 录一样,依它们自己的行为,子目录也是目录。 185页 但子目录又不像根目录,它没有任何大小的限制。如果必要,它们能扩展而容纳其它 的文件。更进一步,可以在于目录中创建子目录项,每一子目录各自包含一组该子目录管 理的文件。这种子目录的嵌套过程的唯一限制就是路径说明(从根目录到被访文件必须穿 过的目录所经过的路径)的最大大小为65个字符,这一限制使得不允许有超过32个层次 的嵌套。让我们先看看DOS追踪单一文件和目录所采用方法。 9.1.1根目录 根目录在磁盘上有固定的位置和大小,它们是在磁盘格式化过程中,由FORMAT程 序确定的,根目录的大小和在磁盘上的位置记录在磁盘引导扇区的BIOS参数块中(参见 (第8章“磁盘”)。 DOS1.0版中,根目录是磁盘中唯一的目录。 DOS2.0版开始支持了目录。正如读者 所知道的那样,子目录只是一种特殊类型的文件,它包括其它的目录项而不是平常的数 据。 根目录的头两个表项被保留起来,作为BIOS和DOS核心系统文件表项,磁盘自举 程序在系统启动过程中要利用这些表项(参见第3章“动态的DOS”)。图9.1显示了包括 操作系统的典型根目录的第一个扇区。 图9.1根目录中的内容显示 186页 根目录必须在一个明确定义的点上开始,这样对文件系统一无所知的程序才能找到 它。内核和BIOS文件必须是储存在磁盘上的最开始的内容,以便在引导磁盘的过程中不 必提供一个搜索例程而能将它们定位。自举装入程序假定这些文件处在根目录的最开始 位置,并且至少BIOS文件(1.0版中的文件)是连续保存在磁盘上的。 9.1.2目录项 如果想了解DOS怎样跟踪文件和目录,那么绝对有必要了解目录项的结构。了解目 录项以后,就能很容易地解释它们。 每个目录项(32个字节的数据)包含文件的识别信息:文件名、扩展名、属性、大小磁 盘上的起始位置,以及目录项最近更新的日期和时间。表9.1显示一个32字节的目录项 的基本结构。 表9.1目录项的结构 偏移值 大小 意义 00h 8个字节 文件名 08h 3个字节 扩展名 0Bh 字节 文件属性 0Ch 10个字节 保留(未用) 16h 字 最后更新的时间 18h 字 最后更新的日期 1Ah 字 起始的磁盘簇 1Ch 双字 文件大小 每个目录项都以这种方式格式化,文件信息的每一部分都存储在32字节项的固定偏 移值上。目录项中的每个域会告知有关文件的独一无二的事情。除了DOS所保存的10个 字节以外(在偏移值0Ch),本章要相对详细地讨论目录项中的其它各域。 一、文件名(偏移值00h) 该项的头8个字节是文件的根名(以ASCII文本串来保存)。现在读者能知道为什么 DOS中的文件名要限于8个字符——在目录项中只有这么多空间可用。 在许多DOS程序中,可以使用超过8个字符的文件名,但DOS会删简它们,以适于8 个字符限制。如果根文件名没有8个字符长,它就在域中向左对齐并插入一些空格。 DOS 会将所有的文件名都以大写的ASCII字符保存起来。 文件名域的第一个字节有许多特殊的意义(见表9.2)。 00h代码可避免DOS对未使用的目录项的搜索。在搜索文件名时,遇上了该代码,它 就会被解释成“目录的结束”,并结束搜索过程。 E5h字符(在1BM中显示为希腊字符Sigma)能用作文件名的第一个字符,但却存放 在目录项中,当作05h(为什么人们都想把它用在文件名中,这作为一个问题留给读者;但 自动翻译能做到这一点)。 187页 表9.2文件名的头一个字节的特殊含义 第一字节的值 意 义 00h 项从未用过;后面没有项。 05h 文件名的第一字符其实是E5h。 2Eh 该项是当前子目录的别名,如果下一个字节是2Eh,则目录的 起始磁盘簇包含当前目录的父目录的目录项。 E5h 文件已被删除。 E5h作为第一个字符表示已被删除的文件;在搜索的过程中,DOS会忽略这样的项; 或产生一个新文件时,DOS会再次使用这样的项。只有第一个字节变化了来表示一个被 删除的项;目录项其它部分仍保持不变。从理论上讲,可以把第一个字符变成有效的 AsCII字符,从而使删除的文件复活。但是,被删除的文件占据磁盘空间之前,如果另一个 文件已重新占用磁盘空间,那么就只能彻底删除这个文件了。 2Eh项是一个句号。尽管只在子目录中发现它。但它表示的是当前目录的一个目录 项。如果下一个字节也是一个句号,那么该项会指向当前目录的父目录。每当使用DOS的 DIR命令,都可以看到每个子目录起始处的点(.)。和点点(..)项;当子目录存在和不能删 除时,就会自动产生这些项。 任何对ERASE.的尝试都解释成对子目录的ERASE*.*,并且ERASE..变成了对 父目录的ERASE*.*。标准DOS会询问Are you sure(Y/N)?这没提供什么保护,因为 它没有指出将发生大量的文件破坏。在4.0版中,会给出信息明确地告诉你整个目录将被 删除。 二、文件扩展名(偏移值08h) 偏移值08h处开始的3个字符是文件类型或扩展名(也作为ASCII字符来保存)。如 果文件没有扩展名,或者扩展名少于3个字符,那么该扩展名就在域中向左对齐并且在名 字中插入一些空格。注意在文件名中的句号,它通常用作根目录和扩展名之间的界线,它 不与目录项中的文件名一起保存。 DOS假定目录项的第8和第9字节之间存在一个字句 号。 三、文件属性(偏移值0Bh) 文件属性(偏移值0Bh处的字节)指示目录项所代表的文件的类型和其可访问性。该 属性的每一位都表示了文件的一个特点或特性(见表9.3)。 只读特性意味着该文件只能从其中读,而不能向它里面写。如果设置了这个位,该文 件就不能被删除。尽管这项预防措施能为文件提供一定的安全性,但文件还是能重新命名 进行修改的。 其它的属性怎么样呢?隐藏起来的文件对DIR、COPY或许多其它的DOS命令都是 没用的。尽管DOS不能提供工具来完成这种工作,但许多第三方的实用程序还允许用户 在目录项中设置该隐藏位的,它使目录对于DIR也是不可见的。但可用CD命令来进入其 中,然后,就能看到该文件的内部了。 188页 表9.3一个属性字节 位 意义 76543210 .......x 只读文件 ......x. 隐藏的文件 .....x.. 系统文件 ....x... 卷标文件 ...x.... 子目录文件 ..X..... 归档文件 xx...... 保留(未用) 系统这个词是特指DOS内核和BIOS文件的,但它也能用于其它文件。例如,可以标 记COMMAND.COM和其它的系统实用程序作为系统和最初由非技术方面的人员所使 用的系统中的隐藏文件;该过程储存了用户试图打扫磁盘“房间”时清除掉,但必须恢复的 文件。 卷标则为所给的磁盘来识别包含该磁盘标记(或名字)的一个目录项。尽管有张磁盘 应该有一个卷标,但直到DOS4.0中才可在大多数磁盘中发挥作用时检查其标记。卷标 记将在本章后面更详细地介绍。 子目录位表示该文件是一个包含其它目录项的特殊文件。子目录是从属于当前目录 的目录(目录和子目录将在本章后面详细讨论)。 归档位(文件的状态位)在文件更新时设置。特别地,硬盘备份程序用该位来指示哪个 文件需要备份。 四、上次更新的时间(偏移值16h) 偏移值16h处开始的字(2个字节)是文件上次更新的时间。有时叫作文件的时间标 记,它的低字节保存在前面。 当产生文件时就设置它的时间域,并且不论什么时候关闭该文件,其时间域就会得到 更新;但是只有向该文件中写入了信息,其时间域才会真正地更新。如果该文件只是被读、 拷贝(DOS COPY命令)或被重新命名(用DOS命令),那么该域应当不会更新。当更新时 间域时,新的时间由系统时钟来获得。 表9.4显示时间域的各个位的含义。小时包含在5个位(24小时时钟)中,而分钟则 包含在6个位中。因为这只留下5个位来存放秒数,所以必须将数除以2。 注意因为秒数除以2,所以时间标记只对秒的偶数是精确的(在大多数应用程序中这种 限制并不显著)。 一个鲜为人知的有关文件时间域的事实是:在两个字节中的所有位都是0,则DIR命 令不会显示任何时间。但是如果秒域包括一个1并且其它位都是0,则文件时间显示为12 :00a(午夜)。一些软件发行者利用这一小技巧来帮助识别他们发布的磁盘的修正版本。 189页 表9.4时间域的编码 位 FEDCBA98 7 6543210 含义 xxxxx... ........ 小时 .....xxx xxx..... 分钟 ........ ...xxxxx 2秒的增量 五、上次更新的日期(偏移值18h) 偏移值18h处开始的字(2个字节)是文件上次更新的日期,有时也把这叫文件的时 间标记。它的低字节保存在前面。 日期域与时间域想似——产生文件时就设置日期域,并且只有向文件中写信息后再 关闭文件时,该域才会更新。如果只是读、拷贝(用DOS COPY命令)、或重新命令(用DOS REN命令)文件,则其日期域不会更新。日期域更新后,它对文件最近修改的日期进行编 码(通过系统时钟)。 表9.5显示了日期域的布局。年份保存在7个位中,月份保存在4个位中,而日期则 保存在5个位中。 表9.5日期域的编码 位 FEDCBA98 76543210 含义 xxxxxxx. .......x 年计数(相对1980年) .......x xxx..... 月 ........ ...xxxxx 日 注意年份是相对于1980年的。换句话说,它是相对于1980的差,而不是绝对值(如 1988)。例如1988年是以8来保存的,要识别绝对的年份,.只需将差值加上1980。年份占 据了7个位;因为7个位能代表的最大值是127,所以年可从1980(差为0)到2107(差为 127)。 六、起始磁盘簇号(偏移值1Ah) 偏移值1Ah处开始的字(2个字节)是文件的起始磁盘簇号,该字的低字节保存在前 面。 该字只给出了文件的起始点。要分配第二个簇和后面的簇,该域的值也用来计算与文 件分配表(FAT)的偏移值。通过FAT,文件所使用的任何其它簇号也都能定位(见第8章 有关FAT的详细讨论)。 七、文件大小(偏移值1Ch) 有4个字节的文件大小域包含了文件的精确长度,以字节来表示。于是DOS所能操 纵的最大大小是4294967295个字节。因为这个数目比现今最大的MS-DOS硬盘还要大 13倍,所以要达到这个限制数还有几年。 190页 一些高级语言(特别是BASIC的早期版本)以将这个值设置成与文件所占有的扇区 中的字节总数相等而著名。如果该文件包含520个字节并占有2个512个字节大小的扇 区,那么文件大小会设置成明显误导的1024。但通常这个项会精确的以字节来反映出文 件的大小。 9.1.3子目录 前面已经提到,每个目录项都包含文件的一个属性字节。这个属性字节的第4位表示 它是作为子目录指针的目录项。 子目录是一个文件,就像系统的其它文件一样。子目录项指向目录文件的起始簇号, 目录文件可包含16个项;可以搜索随后的文件簇来找到后面的项。图9.2显示了一个典 型子目录的数据。 图9.2一个子目录的数据显示 子目录的结构确切地像根目录的结构,只有一个除外:在每个子目录的开始处,有两 个特殊的项,它们带有.和..文件名。 第一项(.)指向当前目录;在这个目录项中的起始簇域指向当前子目录的第一簇。第 二项(..)在父目录中完成同样的工作;这个项的起始簇域指向父目录的第一簇。如果该簇 号为0,那么父目录就是根目录。这两个特殊的目录项都不能删除。相反,想这样做的话, 就会删除掉相应目录中的所有文件。 不能像处理一般的文件那样来处理子目录。 DOS功能43h不能设置子目录的属性 位。子目录的系统位和隐藏位可被设置以使目录不能被正常地列出,但它的可访问性都不 能改变。 CHDIR还是能到达它那里。 9.1.4卷标 卷标是一个目录项,在其中设置了文件属性字节的第三位。要解释这一项,所有必需 做的就是将它定位——文件名就是卷标。 尽管在通常情况下卷标不被DOS 4.0版之前的大多数软件所使用,但要编写利用它 们的软件,它们就极为有用。程序可用独一无二的卷标来当作磁盘识别者。程序记住哪些 191页 磁盘正在使用以后,它能借助名字而不是一个通用的标题来激活某个特定的磁盘。 例如,Macintosh能在单个驱动器系统中使用多张磁盘时做到了这一点。在Macintosh 便携式计算机中,显示了系统已识别的磁盘名字,并且如果需要其中的一张磁盘,可用名 字来进行请求。能告诉用户是否插入了错误盘的系统可以用名字来继续要求正确的磁盘。 尽管大多数PC程序不能以这种方式来操纵磁盘,但现在它们还是能的——感谢卷 标记。产生磁盘标记的唯一方式是通过扩展的FCB功能,本章“扩展的文件控制块”一节 将讨论它们(有关所有FCB操作的DOS功能表请见本书结尾“DOS参考手册”一节)。 DOS 4.0版发表后,卷标就在世界上传播开来。现在它拷贝到引导扇区中,还有根目 录中,并且DOS在一定程度上利用它来快速确定磁盘是否已改变(如果卷标没变,还要进 行更详细的检查)。 尽管因为没有办法保证两个不同的磁盘不带有相同的卷标,所以增添了新的东西来 增加独一无二个性的可能性:卷系列号。当磁盘格式化时产生这个系列号,它以从系统时 钟获得的时间和日期标记为基础,并与标记一起保存在引导扇区中。卷标和系列号一起提 供高度可靠的磁盘变化的指示物,因为系列号每2秒种便会产生一次变化。 9.2什么是文件 我们在各种事情中都用到了文件。简而言之,文件是一个用来保存信息的有组织的地 方。文件这个词也用来指设备。在UNIX的带领之下,DOS的2.0版也引入了文件句柄这 个概念。文件系统能分配独特的数字(叫作句柄)给设备如打印机、RS232端口、键盘和屏 幕。我们中的许多人也习惯于将这些设备看作特殊的物体,但文件句柄改变了这一点。 使用文件句柄这个概念比许多人意识到的要有力一些。借助文件句柄,可以用同样的 技术来访问文件和设备。例如,编写一个涉及键盘和视频屏幕的程序(使用sTDIN和 sTDOUT),就不仅可以使输入重定向,以便输入来自文件;而且可以使输出重定向到一 个文件。同样的程序在每个实例中发挥作用——而用户只需改变输入输出所用的句柄。 9.3 DOS处理文件的方式 DOS提供了两种方式来处理文件:FCB方式或句柄功能方式。 DOS 2.0发表之前,文件控制块(FCB)方式(旧的CP/M系统的派生物)是唯一的一 种访问文件的方式。 FCB功能是围绕程序员直接控制的文件控制块的存在而建立起来 的。 我们不必掌握文件的所有细节来用于大多数的操作,如打开、关闭、读、写或保存一个 文件(重新命名或删除文件)。对于这些类型的文件操作,可以使用别的方式——句柄功 能。 句柄功能只给程序员对文件信息的有限访问;DOS内在地控制着文件。程序员借助 某个特定的文件名(打开或创建文件)或利用一个文件句柄(读、写或关闭文件)来请求文 件操作。 DOS使用句柄查看文件的信息。 192页 句柄功能比FCB功能有许多的优越性: ·因为句柄功能更易于使用,所以它更易于防止错误或改正错误。 ·句柄功能将保留与DOS及OS/2中变化的兼容性。 ·它能利用DOS的分级目录结构。 ·它能减少程序员大量的登记簿工作(登记薄登记文件位置和保存在FCB中的每 样东西——是在DOS内核中完成的)。 我们建议,无论什么时候,只要可能,都应利用句柄功能来访问文件。必须使用FCB 功能来创建磁盘卷标,但对于其它文件操作,句柄是处理文件的更好方式。 不管是否使用FCB或句柄功能,DOS都会熟练地告诉用户它可能检测到的任何错 误。有关DOS错误代码的表,可以参看本书“DOS参考手册”一节中的内容(特别要看看 Int 21h的功能59h)。 9.3.1标准文件控制块 所有FCB功能所使用的标准FCB,几乎直接来自原始的CP/M环境,它的36个字节 构成了11个域,这一点可从表9.6所显示的标准FCB的布局中看出来。 表9.6标准文件控制块的布局 偏移值 长度 意义 备注 00h 1 驱动器说明 0=默认值,1=A,2=B,依此类推 01h 8 文件名 向左对齐的ASCII码;再插入空格 09h 3 扩展名 向左对齐的ASCII码;再插入空格 0Ch 2 当前块号 0Eh 2 记录大小 默认值80h字节,以及DOS OPEN或CREATE功能 10h 4 文件大小 14h 2 创建或更新日期 与目录表项的格式相同 16h 2 创建或更新时间 与目录表项的格式相同 18h 8 保留 20h 1 当前记录号 21h 4 随机记录号 如记录大小少于64个字节,则只用3个字节 文件控制块由DOS所提供的信息所组成,有些直接来自文件目录项中的值。注意不 允许与FCB一起使用路径名。所有FCB功能都在当前目录的范围内操作。 文件名、扩展名、文件大小以及上次更新的日期和时间都是对文件目录项的反映。其 他域要么被FCB功能初始化,要么被程序员修改来向DOS指示程序员希望的是什么。 9.3.2扩展的文件控制块 扩展的FCB允许在FCB中包含其它的文件信息。 FCB的扩展部分由7个普通FCB 的起始处添加的7个字节(3个域)组成。 检查一下FCB的第一个字节,DOS就会告知用户所使用的FCB类型。如果第一个字 节是FFh,DOS就假定它是一个扩展的FCB(标准FCB的第一个字节代表磁盘驱动器标 193页 识符。 FFh用作磁盘驱动器号,它是非法值)。 所有DOS FCB功能都使用扩展的FCB。如果决定使用FCB功能,那么就应使用扩展 的FCB,以便只需追踪一个结构化的FCB区域。 在扩展FCB中的大多数信息与标准FCB中的信息是相同的。如果将表9.6与表9.7 比较,表9.7详细介绍扩展FCB的布局。从这个比较可看出(从偏移值07h字节开始)两 者的布局是一致的。 表9.7扩展的文件控制块 偏移值 长度 意义 备注 00h 1 FFh 告诉DOS这是扩展FCB 01h 5 保留 由DOS使用,通常为OS 06h 1 属性字节 与目录项相同的意思 07h 1 驱动器说明 0=默认值,1=A,2=B,依此类推 08h 8 文件名 向左对齐的ASCII码;再加上空格 10h 3 扩展名 向左对齐的ASCII码;再加上空格 13h 2 当前块号 15h 2 记录大小 默认值80h字节,以及DOS OPEN或CREATE功能 17h 4 文件大小 1Bh 2 创建或更新的日期 与目录项的格式相同 1Dh 2 创建或更新的时间 与目录项的格式相同 1Fh 8 保留 27h 1 当前记录号 如果记录大小少于64个字节,就只用3个字节 28h 4 随机记录号 9.3.3基本的FCB文件处理 要用FCB来成功地工作,可按下列基本的步骤来进行: 1.将FCB的所有字节设置为0。 2.获得文件名信息。要做到这一点,可能需要利用DOS的析取功能(29h)。 3.打开(功能0Fh)或创建(功能16h)文件。 4.如果记录大小域不是80h,就改变它。 5.如果要进行随机访问操作,那么就设置记录号域。 6.设置DTA地址(如果它还未设置)。 7.执行适当的功能。 8.完成以后,关闭文件 9.3.4什么时候使用FCB功能 甚至在DOS 4.0版中,也是因为下列原因而合理地使用FCB功能的: ·使用FCB时,可以有数量不受限制的打开文件。 · FCB提供了一条途径来产生磁盘的卷标。 194页 · FCB保证访问文件的方法与DOS1.0版兼容。 这第一点是真实的,因为我们有对与文件I/O相关的“内务管理”的全部控制(在最近 的DOS版本中,用户可在CONFIG.SYS文件中指定可同时打开多少个文件)。 第二点是很重要的,且不论所使用的操作系统是哪个版本的。如果程序要产生磁盘的 卷标,那么就必须使用FCB。 第三点可能是最重要的:FcB是唯一的证明与使用DOS1.0版的系统兼容性的途 径。如果肯定软件将用于DOS 2.0版或更迟的系统中,或者如果对较早系统的兼容性感 到满意,那么总是应该选择句柄功能。 9.3.5句柄功能 前面已提到,句柄功能是编程技术中的一个提高。将它们引入DOS,就为文件提供了 与在UNIX中实现的相似的控制。事实上,可将UNIX应用程序(用C编写)移植进DOS。 这些应用程序运行起来就像是它们的UNIX副本。 应该意识到句柄功能有下面两个基本特性: . 在句柄功能下,连续的和随机的访问文件之间没有区别。所有的文件都看作字节 串,很像一个数组。对文件的这种看法对UNIX文件来说是标准的。 .句柄是被DOS内部保存的。程序所需的唯一信息就是文件名和句柄号。 并非所有的程序员都赞成这些特性是优越的。一些程序员反对不得不放弃FCB所提 供的控制;其它的程序员认为不提供随意的访问记录结构,这个系统会降低威力。 哪种看法正确呢?都不对。对于简单的编程,也可以为了能轻松地使用,而利用句柄 功能。不考虑一些程序员的反对,程序也很少需要做它们自己的文件登记簿记录工作。随 机的文件访问还是有用的,但是必须用不同的方式来采用它。 句柄只是保存了所有关打开的文件的相关信息的内部DOS表的一个指针。程序员 不必保存对信息的详细说明,他们使用FCB 功能时,也是这样的;相反,句柄功能放弃属 于DOS的所有登记簿功能。对于大多数编程应用程序来说,这个性能显著减轻了程序员 的负担。因为不必操纵或担心特殊的文件控制块,所以理论上,程序更简单、更易于调试并 且更易于保持与DOS的未来版本之间的兼容性。 9.3.6基本的句柄文件处理技术 使用句柄的技术要比使用对应的FCB 功能的技术简单得多。因为系统操纵了基本的 细节,所以只用识别所需要的文件并让DOS完成余下的工作。在句柄文件处理方法中,可 以遵循下列步骤: 1.产生一个ASCII文件名字符串。 2.打开(Int 21h的功能3Dh)或创建(int21h的功能3Ch)文件(3.0版和4.0版提供 了另外的OPEN/CREATE功能;或参看参考手册一节)。 3.在文件中设置文件指针(Int 21h的功能42h)。 4.完成所需要的操作。 195页 5.关闭文件 ASCIIZ字符串只是一个以NUL字符(ASCII码0)作为结尾的ASCII文字串。在高 级语言中,如C中,字符串,通常作为ASCIIZ字符串来保存的。 要设置文件大小,可在结尾处设置文件指针。然后完成0字节的书写。要想文件达到 精确的某个大小,就需要增加或移去一定的空间)。 9.3.7何时使用句柄功能 某些例子的产生是由DOS 2.0版或更近的版本提供的改进功能而带来的,在这些例 子中必须使用文件句柄而不是FCB。例如,在下列情况下,必须使用文件句柄: ·无论什么时候使用路径名时 ·无论何时I/O重定向和管道很重要时 ·支持文件共享和死锁 ·支持网络环境 ·使用增强的错误报告 ·为了能容易地访问文件中的任意地址。 ·在程序控制之下设置文件大小 我们建议:无论何时何地,只要可能,都使用文件句柄(但一定要用FCB去产生卷 标)。在所有的程序中使用文件句柄,就能马上获得未来环境的方便,在这样的环境中 FCB不会存在了。更重要的是,可以简化编程任务。 通常,用高级语言如C或BASIC时,不会考虑下降到本章所讨论的内容的水平上。总 之,高级语言能提供优秀的文件控制操作。但使用者却要使用兼容的功能——句柄。最近 的C、BASIC和Pascal发行物都在它的文件访问例程中使用了句柄功能。 9.3.8练习:目录搜索 要分析DOS目录功能的使用,让我们开发一个简单的能指出文件在分级目录系统的 位置的应用程序。必须知道的是文件名。本章后面所介绍的程序是用来在文件系统的任 何地方找到文件(给出其名字)。 这个程序,叫作find.c ,它通过文件结构来搜索(C的循环特性允许向下搜索文件系 统而不使程序过分复杂)。该程序分析了文件句柄功能用来快速而自然地访问信息的方 式。 这一简单程序的基本技术是对于命令行中的每个变量,从文件系统中搜索拥有那个 名字的所有文件。程序的执行如列表9.1所示。 列表9.1 /* Find.c Listing 9.1 of DOS Programmer'S Reference*/ #include<Stdio.h> 196页 /* Prototypes */ VOid depth_search(char*dir,char *name); void main(argc, argv) /* find.c This search program locates file names in the directory structure of a hard disk. It illustrates the use of the DOS directory functions from a high-level language. */ int argc; char *argv[]; { int i; for(i=1; i<argc; i++) depth_search(" " , argv[i]); } 使用与要求的文件名相符的文件搜索过程,搜索例程depth_search()检查文件系统 中每个目录寻找指定文件。 基本的算法是: 检查当前目录,找与所期望的文件名相匹配的文件。 在当前目录中定位每个子目录并继续搜索。 每当进入这个循环例程,都会产生一个新的磁盘传输区域(DTA)。无论功能何时返 回,前面使用过的DTA又会变成当前的。不是必须只用一个DTA;可以拥有所希望的那 么多的DTA,这决定于解决问题需要的是什么。在这种情况下,这个问题能用下列程序来 得到最好的解决。 列表9.2 /* depsrch.C Listing 9.2 of DOS Programmer'S Reference*/ #include<stdio.h> #include #include /* FIRST or NEXT search flags*/ #define FIRST 0 #define NEXT 1 /* File attribute for search*/ #define FILE 0 #define DIR 16 /* Prototypes */ void depth_search(char *dir, Char * name); int search(char*fname, int flag, int type); void set_dta(char*ptr); int Streql(char *str1, char*str2); void depth_search(dir, name) char *dir, *name; 197页 { char filename[256]; /* File name to search for*/ char dirname[256]; /* Directory name to search*/ char dta[43]; /* Disk transfer area */ int flag; /* Search type flag */ sprintf(filename,"%s\\%s",dir,name) ; /*Set the DTA to the local DTA buffer*/ set_dta(dta); flag = FIRST; while(search(filename,flag,FILE)) { printf("DEPTH:FOUND: %s\\%s\n",dir,dta+30); flag = NEXT; } sprintf(filename,"%s\\*." ,dir); flag = FIRST; while(search(filename,flag,DIR)){ flag = NEXT; /* Specifically exclude “.” and “..” from searching */ if(!streql(".",dta+30) && !streql("..", dta+30)){ sprintf(dirname,"%s\\%s",dir,dta+30); depth_Search(dirname, name); } /* Return to local DTA buffer for next directory*/ set_dta(dta); } } void set_dta(ptr) char *ptr; { union REGS regs; regs.h.ah = 26; regs.x.dx=(int)ptr; intdos(&regs, &regs); } int streql(str1, str2) char *str1, *str2; { return (strcmp(str1, str2)==0); } 该程序控制了对目录结构的搜索,但利用DOS找到第一个文件的功能(4Eh),根据 特定的搜索准则来定位文件。每当找到一个文件,DOS都用文件信息来更新DTA。从文 件的目录项获得的信息用在depth_search()中来打印文件名(或访问目录)。 列表9.3显示例程search(),它与DOS功能之间有相互作用。 列表9.3 /* search.c Listing 9.3 of DOS Programmer's Reference*/ #include #include 198页 #define FALSE 0 #define TRUE !FALSE int search(fname,flag,type) char*fname; int flag; int type; { union REGS regs; regs.h.ah = 0x4e+flag; regs.x.cx=type; regs.x.dx=(int)fname; intdos(&regs,&regs); if(regs.x.cflag==1) return (FALSE); return (TRUE); } 注意在该例程中给每个功能调用设置了相同的寄存器,不管是在寻找过程中文件的 第一次出现,还是在寻找过程中文件的又一次出现,它都是这样。唯一的差异存在于AH 中的设置(4Eh用于发现第一次,4Fh则用于发现下一次)。尽管所有的设置信息只为 FindFirstFile功能(4Eh)而需要,但它不会制止FIndNextFile功能(4Fh)的操作。有关这两 个功能的更详细情况,请参考“DOS参考手册”一节。 当搜索到达分级系统的另一级时,必须从前一级来保留DTA以继续搜索下列例程, set_dta()可标记一个缓冲区来作为当前DTA(该例程包含在列表9.2中): void set_dta(ptr) char * ptr; { union REGS regs; regs.h.ah=26; regs.x.dx=(int)ptr; intdos(&regs,&regs); } 所保存的例程streql()分析了用C功能进行编程的便利。在这个例程中,strcmp()(标 准的C库函数用来比较两个字符串)在字符串相同时返回0。但有时,特别是在建立一个 程序的早期开发阶段,产生你真正希望的那种助记提醒信号的函数是便利的。例如,根据 两个字符串是否等同,streql()返回TRUE或FALSE。利用streql()函数,可以使逻辑更清 楚并且能将精力集中到需要解决的问题上。通过限定streql()宏可以从该例程中挤出一点 速度: define streql(x,y)(strcmp(x,y)==0) 199页 使用这个宏可以消除一个功能调用的内部开销。那么为什么不作呢?完全可以作—— 依据程序员的意图而定。将streql()定义为一个函数,就可以把它包含进一个库中,并且 在需要它的时候,连接程序能肯定它在那里。如果将它定义为一个宏,必须在程序或一个 包含文件中定义这个宏,以使它有用。一种方法易于开发,一种则能消除一些开销。 对于大多数程序,选择使用哪种形式取决于程序员。一些程序员喜欢函数;其他人则 喜欢宏。程序开发过程中唯一的关键因素是明确性。需要使各样东西都尽可能地清楚以 使开发问题变得最小。 streql()例程的代码表如下(该例程包含在列表9.2中): int streql(str1,str2) char * str1, * str2; { return (strcmp(str1, str2)==0); } 既然已详细地介绍了find.c 的各个部分,那么让我们把这个程序用于测试驱动器,下 面的测试运行搜索了autoexec.*文件的所有发生的情形: C>find autoexec.* DEPTH:FOUND:\AUTOEXEC.BAT DEPTH:FOUND:\AUTOEXEC.DV DEPTH:FOUND:\AUTOEXEC.BAK DEPTh:FOUND:\AUTOEXEC.WIN DEPTH:FOUND:\BIN\LOTUS\INSTALL\AUTOEXEC.BAT DEPTH:FOUND:\SYS\AUTOEXEC.DV DEPTH:FOUND:\SYS\AUTOEXEC.OLD DEPTH:FOUND:\SYS\AUTOEXEC.BAT 9.4小 结 从本章我们了解了文件访问由下列两种方法之一来处理:文件控制块(FCB)或文件 句柄。FCB是较老的形式,但总是能与DOS 1.0版兼容。磁盘卷标也只可用FCB来书写, 但其它各种文件访问都能由文件句柄功能来完成。 句柄功能在DOS 2.0版引入的分级目录结构中工作。它们也能使程序设计变得简单 得多,因为它们让操作系统完成文件的登记薄工作。 基于我们已经了解的知识,我们准备开始学习下一章“程序和内存管理”,它涉及到程 序执行的一些知识。