至于本节最后一个问题--命令行参数则是一个十分重要的内容,所谓重要主要表现为这个技术很常用。那么究竟何为"命令行参数"呢?这是我们首先要讨论的问题。
我们知道DOS的所有功能都是以"命令"的形式提供给我们的,当我们需要DOS为我们做一些事情的时候我们要在DOS提示符后面打入一个命令,如COPY或TYPE等。而我们如果需要运行一个可执行程序时也要在提示符后打入程序名,比如FORMAT等。一般情况下我们把这样一种人机交互方式称为"命令行"方式。
所谓的"命令行参数"指得是在命令或程序名后面打入的一些具有某种特殊意义的字符。以DIR命令为例,如果我们仅仅打入"DIR"这命令,DOS会按顺序显示磁盘目录;如果我们打入"DIR/P",那么DOS就会在显示一个屏幕的目录名后暂停并等待按键。这个命令中出现的"/P"就是我们所说的命令行参数。再比如程序FORMAT.COM,我们使用这个程序格式化磁盘时必须要指出相应的驱动器,假如我们要格式化的软盘在"A:"中,这时我们需要打入"FORMAT A:"命令,其中的"A:"也是命令行参数。
需要指出的是有些参考书把"/"后面的字符称为"开关",把用空格隔开的字符称为"参数"。比如FORMAT程序的这样一个用法--"FORMAT A:/S",其中的"A:"是参数,"/S"是开关。这两种叫法是比较正规的,不过笔者并没有采用这样的叫法,原因是对于设计程序来说它们的处理方法没什么区别。
说了这么多东西大家好象觉得"命令行参数"是个很神秘的家伙,其实不然。命令行参数很容易处理,至少不是那么难以寻觅。我们现在就来在内存中找一找,看看它究竟出现在哪儿。请大家在DOS提示符后打入下面这个命令:
C:\ASM\>debug tasm.exe aaa bbb[Enter]
笔者使用了"TASM.EXE"这个程序作为"调试"对象,其实可以选择任意一个".EXE"的文件。现在请大家回想一下当一个".EXE"文件被DEBUG调入后,段寄存器DS和ES指向谁所在的段?
段寄存器DS和ES指向"PSP"所在的段。我们现在就来看一看PSP中还有什么有用的数据:
-d0[Enter] 1027:0000 CD 20 FF 9F 00 9A F0 FE-1D F0 4F 03 92 09 8A 03 . ........O..... 1027:0010 92 09 17 03 92 09 81 09-01 04 01 00 02 FF FF FF ................ 1027:0020 FF FF FF FF FF FF FF FF-FF FF FF FF 18 10 4C 01 ..............L. 1027:0030 97 0D 14 00 18 00 27 10-FF FF FF FF 00 00 00 00 ......'......... 1027:0040 07 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 1027:0050 CD 21 CB 00 00 00 00 00-00 00 00 00 00 41 41 41 .!...........AAA 1027:0060 20 20 20 20 20 20 20 20-00 00 00 00 00 42 42 42 .....BBB 1027:0070 20 20 20 20 20 20 20 20-00 00 00 00 00 00 00 00 ........
"金矿"并不难发现,注意PSP中偏移5CH和偏移6CH处的数据,我们给"TASM.EXE"的两个参数"aaa"和"bbb"就在其中。我们由此得出了第一个结论:程序所需的两个参数出现在PSP偏移5CH和6CH处,而且是以大写形式出现的。
通过这个实验我们也能发觉三个问题:第一,PSP偏移5CH和6CH处存放的数据都是"0",并不是真正的命令行参数,为什么要说参数出现在偏移5CH和6CH处呢?如果这个"0"是参数的一部分的话,那么它又有什么含义?第二,如果有更多的参数会有什么情况?第三,如果有开关符"/"又会有什么情况?为搞清这三个问题,我们再做三次实验。先看看那个奇怪的"0"是怎么回事,注意命令中加有下划线的内容:
C:\ASM\>debug tasm.exe a:xxx c:yyy[Enter] -d0[Enter] 1028:0000 CD 20 FF 9F 00 9A F0 FE-1D F0 4F 03 92 09 8A 03 . ........O..... 1028:0010 92 09 17 03 92 09 81 09-01 04 01 00 02 FF FF FF ................ 1028:0020 FF FF FF FF FF FF FF FF-FF FF FF FF 18 10 4C 01 ..............L. 1028:0030 97 0D 14 00 18 00 27 10-FF FF FF FF 00 00 00 00 ......'......... 1028:0040 07 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 1028:0050 CD 21 CB 00 00 00 00 00-00 00 00 00 01 58 58 58 .!...........xxx 1028:0060 20 20 20 20 20 20 20 20-00 00 00 00 03 59 59 59 .....yyy 1028:0070 20 20 20 20 20 20 20 20-00 00 00 00 00 00 00 00 ........
问题清楚了,偏移5CH和6CH处存放的数据原来是驱动器号,我们做第一次实验时没有指定驱动器,所以DOS给我们填入了"默认"驱动器号。这个问题就有些奇怪了,难道DOS会把我们给出的参数都当做文件名来处理吗?说到此,笔者认为已经到了真正搞清这两个数据结构的时候了。
在PSP偏移5CH和6CH处有两个DOS提供给我们的十分重要的数据结构,这两个数据结构我们称之为"格式化未打开的FCB"。实际上每一个FCB的长度只有16个字节,其结构与一般的FCB相同。注意这两个FCB都是"未打开的",如果我们将第一个FCB打开了,那么第二个FCB就会被覆盖。
我们给出的命令行参数均被DOS作为文件名放在这两个FCB中,由于只有两个FCB,所以DOS只能处理两个参数,而且如果参数中出现了"/"之类的字符,那么DOS就会认为给出的参数是不合法的文件名而不予处理。请做下面的实验:
C:\ASM\>debug tasm.exe aaa /b ccc[Enter] -d0[Enter] 1027:0000 CD 20 FF 9F 00 9A F0 FE-1D F0 4F 03 92 09 8A 03 . ........O..... 1027:0010 92 09 17 03 92 09 81 09-01 04 01 00 02 FF FF FF ................ 1027:0020 FF FF FF FF FF FF FF FF-FF FF FF FF 18 10 4C 01 ..............L. 1027:0030 97 0D 14 00 18 00 27 10-FF FF FF FF 00 00 00 00 ......'......... 1027:0040 07 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 1027:0050 CD 21 CB 00 00 00 00 00-00 00 00 00 00 41 41 41 .!...........AAA 1027:0060 20 20 20 20 20 20 20 20-00 00 00 00 00 20 20 20 .....? 1027:0070 20 20 20 20 20 20 20 20-00 00 00 00 00 00 00 00 ........
很明显,"/b"和"ccc"均未出现。由此我们可以看出,使用这两个FCB处理命令行参数是有很多限制的。事实上很多程序根本不使用这样的方法处理命令行参数,而是采用了另一种方法。我们将在本章的后面详细讨论,这里我们给出一个示例程序,以说明这两个FCB的应用方法:
DATA SEGMENT ASSUME DS:DATA LINE DB ? MSG DB 'Press any key to conti DATA ENDS CODE SEGMENT ASSUME CS:CODE MAIN PROC FAR MOV AX,DATA ;初始化ES寄存器指向数据段 MOV ES,AX MOV DX,5CH ;DX指向第一个未打开的FCB MOV AH,0FH ;打开这个FCB INT 21H OR AL,AL ;文件正确打开了吗? JNZ ERR_EXIT ;若没有打开文件,转ERR_EXIT MOV BX,DX ;BX指向已经打开的FCB MOV WORD PTR [BX+0EH],1 ;设置记录长度为1字节 MOV SI,80H ;SI寄存器指向缺省的DTA READ_NEXT: MOV AH,14H ;从文件中读取记录 MOV DX,5CH ;DX指向打开的FCB INT 21H OR AL,AL ;记录正确地读入了吗? JNZ ERR_EXIT ;若读取操作不正确,转ERR_EXIT MOV AL,[SI] ;从DTA中取得一个字节 CMP AL,0AH ;取得的是换行符吗? JZ READ_NEXT ;若是换行符,转READ_NEXT MOV AH,0EH ;利用10H中断的0EH功能 INT 10H ;输出AL寄存器中的字符 CMP AL,0DH ;输出的字符是回车符吗? JNZ READ_NEXT ;若不是回车符则继续处理下一个字符 MOV AL,0AH ;若是回车符则同时输出一个换行符 INT 10H INC ES:[LINE] ;并且将行数计数器加1 CMP ES:[LINE],23 ;判断是否已经输出了23行字符 JNZ READ_NEXT ;若输出不足23行,则继续处理下一个字符 MOV DX,OFFSET MSG ;DX寄存器指向MSG PUSH DS ;暂存DS寄存器 PUSH ES ;通过堆栈将ES寄存器送入DS POP DS MOV AH,9 ;输出字符串MSG INT 21H MOV AH,0 ;等待键盘输入 INT 16H MOV AX,0E0DH ;将光标移至下一行首部 INT 10H MOV AL,0AH INT 10H MOV [LINE],0 ;行计数器清0 POP DS ;恢复DS寄存器 JMP READ_NEXT ;处理下一个字符 ERR_EXIT: MOV AH,4CH ;结束进程 INT 21H MAIN ENDP CODE ENDS END MAIN
这个程序可以取代DOS提供的TYPE命令,它用于显示文本文件的内容,用法与TYPE命令一致。不过这个程序每显示一屏信息就会暂停一次,按一键后继续显示下一屏。当然,被显示的文件每一行的字符数要小于80个。
至此,有关FCB文件操作功能我们已经基本上讨论完了,当然还有一些很不常用的功能我们没有涉及,这些内容可以在其它的参考书上找到。通过对FCB功能的研究,我们可以发现FCB功能所具有的一些特点,归纳如下:
(1)FCB结构源于CP/M操作系统,所以在CP/M系统下编制的程序很容易移植到DOS系统下,这一点在DOS发展的初期具有重要的作用;
(2)FCB的结构对于我们编程来讲是可见的,一个FCB打开后,所有有关于这个文件的信息都保存于FCB中,可以供应用程序使用;
(3)应用扩展FCB可以存取具有特殊属性的文件,而且可以处理象卷标这样的目录;这是其它文件功能所不具备的能力;
(4)FCB功能将文件划分为记录,程序员可以根据需要任意设置记录长度;
(5)一般情况下使用FCB功能打开文件的数目没有限制,除非是在网络环境下装入了文件共享程序(SHARE命令)。不过要打开众多的FCB,需要在内存中开辟相应数量的文件控制块,这些都增加了内存的开销;
(6)FCB功能不能处理树型目录,也就是不能处理"路径名",因此FCB功能的应用范围受到了限制;
(7)FCB功能返回的错误信息很少,不利于程序处理错误。DOS的版本3对此做了改进,提供了一个新的功能调用59H以获得更多的错误信息,但应用起来仍不方便;