ITEEDU

为了搞清楚这个问题,我们来看下面两个程序:

STARMAP.ASM
        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会把整个参数字符串原样放在这个区域中,不对其进行任何的处理。