为了搞清楚这个问题,我们来看下面两个程序:
data segment
assume ds:data
fname db 'hzk16',0
buffer db 32 dup(0)
off dw 1520h,0002h,3340h,0001h,18e0h,0000h,74a0h,0002h,2520h,0001h
msg db 'Press any key to display the next star-map.',0dh,0ah,24h
data ends
code segment
assume cs:code
main proc far
mov ax,data ;初始化DS寄存器
mov ds,ax
mov dx,offset fname ;DX指向待处理的文件名
mov ax,3d00h ;按"读取"的方式打开文件
int 21h
jc exit ;若文件打开出错,转EXIT结束
mov cx,5 ;准备读取5组数据
mov bx,ax ;BX寄存器获得文件句柄
mov si,offset off ;SI寄存器指向偏移值数组
loop1:
push cx ;暂存CX寄存器
mov ax,0003h ;重置显示模式,清除屏幕
int 10h
mov ah,09h ;显示字符串MSG
mov dx,offset msg
int 21h
lodsw ;取得第一个偏移量的低字
mov dx,ax ;将偏移量低字送入DX寄存器
lodsw ;取得第一个偏移量的高字
mov cx,ax ;将偏移量高字送入CX寄存器
mov ah,42h ;调用42H移动文件指针
mov al,00h ;从文件首部计数偏移量
int 21h
jc exit ;若移动指针出错,转EXIT结束
mov ah,3fh ;准备读取数据
mov dx,offset buffer ;DX寄存器指向文件缓冲区
mov cx,32 ;读入32个字节
int 21h
jc exit ;若读取出错,转EXIT结束
call disp ;调用DISP子过程
mov ah,0 ;等待键盘输入
int 16h
pop cx ;恢复CX寄存器
loop loop1 ;转LOOP1继续读取下一组数据
exit:
mov ah,4ch ;结束进程
int 21h
main endp
disp proc near ;DISP子过程
push bx ;保存要使用的寄存器
push si
mov cx,16 ;处理16个字(32字节)
mov si,offset buffer ;SI寄存器指向文件缓冲区
mov dh,3 ;设置光标初始行、列位置
mov dl,32
loop2:
mov ah,2 ;设置光标位置
mov bh,0
int 10h
lodsw ;取得一个字的数据
xchg ah,al ;交换高低字节
push cx ;暂存CX寄存器
mov cx,16 ;处理16个数位
loop3:
shl ax,1 ;取得的数据左移1位
push ax ;暂存移位后的结果
mov ah,0eh ;选择10H中断的0EH功能
jnc next_dot ;若移出的数位为0,转NEXT_DOT输出空格
mov al,2ah ;AL寄存器送入字符"*"
jmp output ;转OUTPUT输出AL寄存器中的字符
next_dot:
mov al,20h ;AL寄存器送入空格符
output:
int 10h ;显示输出
pop ax ;恢复移位后的结果
loop loop3 ;继续处理下一个数位
inc dh ;光标行号加1
pop cx ;恢复CX寄存器
loop loop2 ;转LOOP2继续处理下一个字
pop si ;恢复入口处保存的寄存器
pop bx
ret ;返回主过程
disp endp
code ends
end main
在这个程序中我们使用DW伪指令定义了5个偏移量,当然这样做是比较累的,事实上我们们完全可以使用"DD"(Define Double word)伪指令直接定义32位的数据。不过考虑到某些早期版本的编译程序可能不支持这个伪指令,所以还是采用了DW伪指令,这并不是问题的关键。下面给出了这个程序跟踪执行的结果:
-g=0 2e[Enter] Press any key to display the next star-map. AX=4200 BX=0005 CX=0002 DX=1520 SP=FFFE BP=0000 SI=002A DI=0000 DS=124D ES=123D SS=124D CS=1254 IP=002E NV UP EI PL NZ NA PO NC 1254:002E CD21 INT 21
以上是执行42H功能调用之前各个寄存器的情况,"CX:DX"中是文件读写指针的新位置,AL寄存器指定方式0,BX寄存器为文件句柄。下面执行指令INT 21:
-p[Enter] AX=1520 BX=0005 CX=0002 DX=0002 SP=FFFE BP=0000 SI=002A DI=0000 DS=124D ES=123D SS=124D CS=1254 IP=0030 NV UP EI PL NZ NA PO NC 1254:0030 7216 JB 0048
可以看到"DX:AX"寄存器返回了文件指针的新指向。
STARMAP程序现在有了两个版本,我们搞的第一个版本只能从头至尾按顺序显示国标汉字,而这个新版本可以显示任意的汉字,关键在于正确地定位文件读写指针。这个程序中还有一些问题将在下一章进行更深入讨论,现在如果有什么不明白的地方可以先放一放,大家只要知道从文件HZK16中的某个位置读取32个字节加以处理就可以看到一个国标汉字或符号即可。
至于文件指针的相对移动问题笔者不准备给出程序例,大家可以自己研究。但是需要特别讨论的是文件指针的"绝对倒移"问题。这样的移动方式初看起来好象没什么用途,它不符合人们的习惯。但是这个方式在实践中还是有应用的,请思考这样一个问题:如果程序设定偏移量为0,而使用方式2移动文件读写指针,那么DOS在执行完功能调用后通过"DX:AX"寄存器给我们返回的数据有什么特殊的意义?
毫无疑问那是文件读写指针指向的新位置,即文件的结尾。但是如果换个方向思考这个问题就会发现那个32位的数据其实就是文件的长度。下面这个程序可以查出 "CONFIG.SYS"文件的长度:
data segment
assume ds:data
fname db c:\config.sys',0e '
msg db 'Your config.sys includ ;定义一个用于保存数字的缓冲区
tbuff db 5 dup (20h)
db ' Bytes',0dh,0ah,24h
data ends
code segment
assume cs:code
main proc far
mov ax,data ;初始化DS寄存器
mov ds,ax
mov dx,offset fname ;按"读取"方式打开文件
mov ax,3d00h
int 21h
jc exit ;若文件未能正确打开,转EXIT
mov bx,ax ;BX寄存器获得文件句柄
mov cx,0 ;设置新的文件指针偏移量为0
mov dx,0
mov ah,42h ;选42H功能
mov al,02h ;从文件尾部计数偏移量
int 21h ;移动文件读写指针
jc exit ;若没有正确移动文件指针,转EXIT结束
;准备输出DX:AX中的文件长度
mov cx,10 ;CX寄存器送入10
mov si,offset tbuff+4 ;SI寄存器指向数字的缓冲区尾部
outdec:
div cx ;DX:AX中的长度值除以10
xchg ax,dx ;将余数送入AX寄存器,商送入DX寄存器
add al,30h ;将余数加上30H转换成数字的ASCII码
mov byte ptr [si],al ;将个位数字送入缓冲区
dec si ;SI寄存器指向十位数字的位置
mov ax,dx ;将商转换成32位数并存入DX:AX
xor dx,dx
cmp ax,0 ;商是0吗?
jnz outdec ;若商不是0,则转OUTDEC继续计算数位
mov ah,9 ;输出带有长度数的字符串MSG
mov dx,offset msg
int 21h
exit:
mov ah,4ch ;结束进程
int 21h
main endp
code ends
end main
如何将一个32位的二进制数据转换成十进制的形式显示出来?这个问题大家倒是可以深入思考一下,上面这个程序给出了一种算法,还有没有更好的方法呢?
还有一个不常用的功能需要讨论一下,那就是文件的删除。DOS提供了41H号功能调用完成这个工作,这个功能可以参考表7-1中的有关说明,有关的程序例不再给出。需要提醒大家的是不要使用"*.*"这样的通配符,句柄能不支持通配符;另外凡是具有"只读"、"隐含"等属性的文件也不能用此功能删除。
在本节的最后我准备给出另一种处理命令行参数的方法,然后开始新一节的讨论。我们在讨论FCB功能时曾经给出过利用"缺省FCB"处理命令行参数的方法,那种方法缺乏灵活性,如果我们需要处理多个参数或树型目录使用缺省FCB就不行了。解决这个问题就要使用下面介绍的知识了。仿照前面的形式,我们先来看看在程序段前缀中还有什么值得注意的内容,我们还是要求助于DEBUG:
C:\ASM\>debug tasm.exe aaa\bbb\ccc ddd[Enter]
我们知道缺省的FCB在PSP的前128个字节中,后128个字节中有些什么内容我们还不清楚,现在就让我们列出后面的128个字节来看一看:
-d80[Enter] 0F6F:0080 10 20 61 61 61 5C 62 62-62 5C 63 63 63 20 64 64 . aaa\bbb\ccc dd 0F6F:0090 64 0D 20 64 64 64 0D 00-00 00 00 00 00 00 00 00 d. ddd.......... 0F6F:00A0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 0F6F:00B0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 0F6F:00C0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 0F6F:00D0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 0F6F:00E0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 0F6F:00F0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
这一下问题全清楚了,对于FCB功能来讲这后128个字节用作缺省的DTA空间,由于句柄功能不再使用DTA传输数据,所以DOS把命令行参数放在这个区域中供程序使用。与放在缺省FCB中的参数不同的是DOS会把整个参数字符串原样放在这个区域中,不对其进行任何的处理。