• 当前位置:首页>>汇编语言>>汇编语言实例分析>>用汇编编写DOS下的内存驻留程序(5)
  • 用汇编编写DOS下的内存驻留程序(5)
  • 五 键盘输入扩充程序
    有了前一节的基本驻留程序为基础,就可以建立起不同的应用程序.接下来,就写一个驻留程序,把用户敲入的字符,用一系列的字符来取代.这样可以减少用户的击键次数.
    首先,先复习一下前一节的驻留程序的格式,如下所示:
    cseg segment
    assume cs:cseg,ds:cseg
    org 100h
    start:
    jmp Initialize
    Old_Keyboard_IO dd ?
    ;Section 1
    new_keyboard_io proc far
    sti
    ;Section 2
    pushf
    assume ds:nothing
    call Old_Keyboard_IO
    nop
    iret
    new_keyboard_io endp
    ;Section 3
    Initialize:
    assume cs:cseg,ds:cseg
    mov bx,cs
    mov ds,bx
    mov al,16h
    mov ah,35h
    int 21h
    mov word ptr Old_Keyboard_IO,bx
    mov word ptr Old_Keyboard_IO[2],es
    ;End Section 3
    mov dx,offset new_keyboard_io
    mov al,16h
    mov ah,25h
    int 21h
    mov dx,offset Initialize
    int 27h
    cseg ends
    end start
    只要New_keyboard_IO这个程序,就可以把以上的程序变成许多不同的键盘应用程序.在开始设计之前,必须解决一些问题.
    首先,必须决定哪些键可以用来加以扩充.如果把一般的英文字母或是数目字做为扩充字符的话可能会出现一些问题.如果是对控制字符做扩充,应该不会有什么问题,但是DOS把某些控制字符视为特殊的功能.譬如Control_H,IBM PC本身有一组自己独有和增加字符(extended character),譬如:功能键(F1到F10),以及ALT键和其它组合所产生的字符等.这些增加字符通常都是使用在文书编辑程序中,这些字符比较适合用来作为扩充字符用.这组字符是由两个码组成,前面一个码永远是0,因此DOS可以很容易加以分辨.而且使用这些字符作为扩充字符对DOS的使用也不会产生太大的影响.下面是扩充字符组的第二个码大小:
    1 2 Paoudo_NULL 3 4 5
    6 7 8 9 10
    11 12 13 14 15 Shift_Tab 16 Alt_Q 17 Alt_W 18 Alt_E 19 Alt_R 20 Alt_T 21 Alt_Y 22 Alt_U 23 Alt_I 24 Alt_O 25 Alt_P 26 27 28 29 30 Alt_A
    31 Alt_S 32 Alt_D 33 Alt_F 34 Alt_G 35 Alt_H
    36 Alt_J 37 Alt_K 38 Alt_L 39 40
    41 42 43 44 Alt_Z 45 Alt_X
    46 Alt_C 47 Alt_V 48 49 50
    51 52 53 54 55
    56 57 58 59 F1 60 F2
    61 F3 62 F4 63 F5 64 F6 65 F7
    66 F8 67 F9 68 F10 69 70
    71 HOME 72 UpArrow 73 PgUp 74 75 LeftArrow
    76 77 RightArrow 78 79 End 80 DownArrow
    81 PgDn 82 Insert 83 Delete 84 Shift_F1 85 Shift_F2
    86 Shift_F3 87 Shift_F4 88 Shift_F5 89 Shift_F6 90 Shift_F7
    91 Shift_F8 92 Shift_F9 93 Shift_F10 94 Control_F1 95 Control_F2
    96 Control_F3 97 Control_F4 98 Control_F5 99 Control_F6 100 Control_F7
    101 Control_F8 102 Control_F9 103 Control_F10 104 Alt_F1 105 Alt_F2
    106 Alt_F3 107 Alt_F4 108 Alt_F5 109 Alt_F6 110 Alt_F7
    111 Alt_F8 112 Alt_F9 113 Alt_F10 114 Control_PrtSc 115 Control_LArrow
    116 Control_RArrow 117 Control_End 118 Control_PgDn 119 Control_Home 120 Alt_1
    121 Alt_2 122 Alt_3 123 Alt_4 124 Alt_5 125 Alt_6
    126 Alt_7 127 Alt_8 128 Alt_9 129 Alt_0 130 Alt_Hyphan
    131 Alt_Space 132 Control_PgUp
    接下来,需要决定把扩充字符扩充成什么样的字符串.譬如,所扩充的字符串以什么作结尾?有一个可能的选择是:回车键(Carriage Return,ASCII码0DH).这种选择很合乎逻辑,因为一般的指令都能是以回车键做结尾.但是,如果选择回车键名做扩充字符串的结尾,那么就很难表示许多行的扩充字符串.另外一个选择是使用$作为扩充字符串的结尾.但是,因为有些DOS的系统调用使用$作为字符结尾;因此如果采用$时,那么扩充字符串中就不能有$出现.
    C语言中都是采用ASCII码的0做为字符串的结尾,这种形式的字符串称为ASCII字符串(ASCII零结尾).使用ASCII字符串格式,就可以表示所有的可见字符和不可见字符,因为从键盘不可能输入ASCII码为0的字符.
    下面的例子中,把F1这个键(扩充码59)定义为DIR指令.也可以把F1定义成以下的指令:
    MASM MACRO;
    LINK MACRO;
    EXE2BIN MACRO.EXE MACRO.COM;
    上面的指令中,每一行都是以回车键作结尾的.
    最后要做的是,解决将扩充的字符返回给DOS的问题.通常每当在键盘敲入一个键时,DOS就会从键盘输入队列取得一个字符.因此必须设法欺骗DOS,让它接受一连串的字符.
    DOS借检查键盘的状态来判断,是否有字符输入,ROM BIOS上的键盘输入功能在没有输入字符时就把ZF(Zero Flag)设定为1,否则就把ZF设定为0.如果可以控制这个功能,反复地欺骗DOS目前有字符要输入,然后把预的字符串传回给DOS,那么就可以让DOS接受任何数量的字符.
    5.1 基本的扩充程序
    可以把上面的空的New_Keyboard_IO程序,改用以下的程序来代替.
    New_Keyboard_IO proc far
    sti
    cmp ah,0 ;A read request?
    je ksread
    cmp ah,1 ;A status request?
    je ksstat
    assume ds:nothing ;Let original routine
    jmp Old_Keyboard_IO ;Do remaining subfunction
    ksRead:
    call keyRead ;Get next char to return
    iret
    ksstat:
    call keyStat ;GetStatus
    ret 2 ;It's important!!
    New_Keyboard_IO endp
    上面的New_Keyboard_IO程序中,把0H(读取字符)和1H(取得键盘状态)这两项功能自行处理.这个程序很简单,但是其中有一个关键点.当我们处理取得键盘状态的功能时,因为原先的键盘中断处理程序是利用ZF返回键盘状态,因此程序包中也必须保有这种特性,如果使用IRET返回的话,那么设定好ZF就会因为CPU状态标志从堆栈中取出,而恢复成未中断前的状态.
    为了解决这个问题可以使用RET的参数来设置.这个参数是用来指示从堆栈中取出多少个字节.通常这是用在高级语言的子程序返回时,用来从堆栈中除去一些参数或是变数.在这里我们希望用来移去原先中断时堆栈的CPU状态,这样才有办法把改变的ZF传回,因此在这里使用了RET 2这个指令.
    上面的程序码中调用到Keyread和KeyStat这两个子程序,其内容如下所示:
    assume ds:nothing
    ;If expansion is in progress,return a fake status
    ;of ZF=0,indicatin gthat a character is ready to be
    ;read,If expansion is not in progress,then return
    ;the actual status from the keyboard
    KeyStat proc
    cmp cs:current,0
    jne FakeStat
    pushf ;Let original routine
    call Old_Keyboard_IO ;get keyboard status
    ret
    FakeStat:
    mov bx,1 ;Fake a "char ready"
    cmp bx,0 ;by clearing ZF
    KeyStat endp
    ;Read a character from the keyboard input queue,
    ;if not expanding or the expansion string.
    ;if expansion is in progress
    KeyRead proc
    cmp cs:current,0
    jne ExpandChar
    ReadChar:
    mov cs:current,0 ;Slightly peculiar
    pushf ;Let original routine
    call Old_Keyboard_IO ;Get keyboard status
    cmp al,0
    je Extended
    ReadDone:
    ret
    Expanded:
    cmp ah,59 ;Is this character to expand?
    jne ReadDone ;If not,then return it normally
    ;If so,then start expanding
    mov cs:current,offset string
    ExpandChar:
    push si
    mov si,cs:current
    mov al,cs:[si]
    inc cs:current
    pop si
    cmp al,0 ;Is this end of string?
    je ReadChar ;If so,then read a real char?
    ret
    KeyRead endp
    ;Pointer to where we are in the expansion string
    current dw 0
    ;String we will return when an F1 is typed
    ;0DH is ASCII carriage return
    string db 'DIR',0dh,0
    上面的程序中,使用了一个指针current,这个指针指向传给DOS的下一个字符.如果current等于0时,就表示扩充字符没了.如果current不等于0,那么current所指的字符就会被传回,除非所指到的字符是ASCII 0,如果current所指到的字符是ASCII 0,那么就必须把current设定成0.
    状态检查程序KeyStat和字符输入程序KeyRead都各有两个部分,一部分是当current等于0,另一部分则是当current等于0.
    如果current等于0,也就是没有扩充字符时,那么状态检查程序就需调用旧的键盘输入程序,来检查目前键盘输入队列的状态.如果current不等于0,ZF就必须设定成0,以表示目前有字符输入.ZF要设定成0或1,可以先执行某一运算让结果为0或非0即可.
    键盘输入程序是整个程序最复杂的部分.这个程序决定了下个送给DOS的字符是什么.如果扩充字符送完时,就调用旧的键盘输入程序取得下一个输入的字符.无论从键盘输入的字符是什么,都必须检查是否是希望扩充的字符.键盘输入程序是把输入的结果放在寄存器AL中.如果输入的字符是增加字符时(如F1),那么AL的内容是0,增加的字符码则放在AH中.
    如果读到的字符是希望扩充的字符F1,那么就必须开始进行扩充工作.这时候就必须把指针current指到扩充字符串的开头.大多数人常犯的一个错误是:使用mov cs:current,string而不是mov cs:current,offset string.这两者的差别在
  • 上一篇:用汇编编写DOS下的内存驻留程序(1)
    下一篇:鼠标控制CD-Audio播放程序