汇编语言编写DOS下的内存驻留程序(5)
2007-04-25 09:30:37 来源:WEB开发网核心提示:为了解决这个问题可以使用RET的参数来设置.这个参数是用来指示从堆栈中取出多少个字节.通常这是用在高级语言的子程序返回时,用来从堆栈中除去一些参数或是变数.在这里我们希望用来移去原先中断时堆栈的CPU状态,这样才有办法把改变的ZF传回,因此在这里使用了RET 2这个指令.上面的程序码中调用到Keyread和KeySta
为了解决这个问题可以使用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.这两者的差别在于前者是错误的,因为它的意思是把一个字节的内容移到一个字节之中,汇编器会强迫两者的形式吻合.后者则是正确的,因为 我们希望做的是把式string指向的地址值移到current之中.
当我们在进行扩充时,就把指针current所指的字节内容移到AL中,只要AL的内容不是0,就不必管AH的内容是什么.如果AL是0的话,就表示已经到了扩充字符的结尾了.这表示不应该传回0,而必须重新调用Old_Keyboard_IO ,以便从键盘取得输入字符.
在程序包KeyRead中有一行指令比较特殊,你也许注意到了,在进入KeyRead,当确定current为0时,接下来又把current设定成0.这样做虽然有些奇怪,却没有任何伤害;但是对于扩充字符串到达结尾时,却很有用.当我们到达扩充字符串的结尾时,current的内容将指到字符串结尾的下一个位置,而不是0.因此必把current设定为0,可以先跳到某一位置把current设定为0,然后再跳到ReadChar.而采取前面程序的做法时,只是浪费一行毫无伤害的指令,却可以使程序变得简明.
在这个程序中,每次使用到内存的内容时,都必须牵涉到段值,这一点相当重要.当计算机的控制权转移到我们的程序中时,我们对于DS的内容是不知道的.但是有两件事可以确定:第一,DS的内容对我们的程序几乎没有任何用;第二,DS的内容对于被中断的程序可能很重要.因此我们必须保证每次使用到内存位置时,都是使用目前的段,亦即以目前的CS值为标准.必须要确定:如果使用到任何寄存器的话那么在程序结束前,必须恢复其值.
5.2 多键扩充程序
上面的程序是把某一个特殊键扩充成一个字符串.如果要把一组特殊键扩充成其个别的扩充字符串,该如何做呢?
一个比较常见的做法是,修改上面的程序,让它接受被扩充字符以被扩充字符串为参数.譬如,如果这个程序名为MACRO,那么可以在AUTOEXEC.BAT中定义以下的指令:
........
MACRO F1 DIR
MACRO F2 DIR/W
MACRO F3 DIR *.ASM
MACRO F4 DIR *.COM
MACRO F5 DIR *.EXE
........
这种做法是把MACRO这程序一个个留在内存中,至于每一个所做的扩充字符串则分别定义在AUTOEXEC.BAT中,因此可以AUTOEXEC.BAT以的内容.来改变扩充字符的意思.每当执行AUTOEXEC.BAT的MACRO时,就把一个新的键盘程序和BIOS中的键盘处理程序连结起来.第二次执行MACRO则是在新的键盘处理程序上加上第二层的键盘处理程序,以后依次类推.每一个输入字符都必须经过一层一层的键盘处理程序,以过滤出被扩充字符.
这种键盘程序一层一层加上去的做法只能使用在希望被扩充字符不多时,因为 每一个希望被扩充字符需要将近一百个字节的驻留程序代码,如果要为128个功能键产生个别的扩充字符时,那么就要耗费13K字节的内存,显然可以采纳别的比较节省内存的方法.
更多精彩
赞助商链接