用匯編編寫DOS下的記憶體駐留程式(5) (轉)

gugu99發表於2007-12-05
用匯編編寫DOS下的記憶體駐留程式(5) (轉)[@more@]

 五 鍵盤輸入擴充
 有了前一節的基本駐留程式為基礎,就可以建立起不同的應用程式.接下來,就寫一個駐留程式,把敲入的字元,用一系列的字元來取代.這樣可以減少使用者的擊鍵次數.
 首先,先複習一下前一節的駐留程式的格式,如下所示:
 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  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 
 接下來,需要決定把擴充字元擴充成什麼樣的字串.譬如,所擴充的字串以什麼作結尾?有一個可能的選擇是:Enter鍵(Carriage Return,ASCII碼0DH).這種選擇很合乎邏輯,因為一般的指令都能是以Enter鍵做結尾.但是,如果選擇Enter鍵名做擴充字串的結尾,那麼就很難表示許多行的擴充字串.另外一個選擇是使用$作為擴充字串的結尾.但是,因為有些DOS的使用$作為字元結尾;因此如果採用$時,那麼擴充字串中就不能有$出現.
 C語言中都是採用ASCII碼的0做為字串的結尾,這種形式的字串稱為ASCII字串(ASCII零結尾).使用ASCII字串格式,就可以表示所有的可見字元和不可見字元,因為從鍵盤不可能輸入ASCII碼為0的字元.
 下面的例子中,把F1這個鍵(擴充碼59)定義為DIR指令.也可以把F1定義成以下的指令:
 MASM MACRO;
 LINK  MACRO;
 EXE2BIN MACRO.EXE MACRO.COM;
 上面的指令中,每一行都是以Enter鍵作結尾的.
 最後要做的是,解決將擴充的字元返回給DOS的問題.通常每當在鍵盤敲入一個鍵時,DOS就會從鍵盤輸入佇列取得一個字元.因此必須設法欺騙DOS,讓它接受一連串的字元.
 DOS借檢查鍵盤的狀態來判斷,是否有字元輸入,ROM 上的鍵盤輸入功能在沒有輸入字元時就把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就會因為狀態標誌從堆疊中取出,而恢復成未中斷前的狀態.
 為了解決這個問題可以使用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  '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位元組的記憶體,顯然可以採納別的比較節省記憶體的方法.
 如果可以在一個小程式中辨認出一個字元,那麼也應該可以辨認出一個以上的字元.然後使用所辨認出的字元轉換成值.再從一個由字串所組成的表格中,找出所擴充的字串.
 一個字串本身佔用一個位元組,而指到字串的指標則佔用兩個位元組,如果有128個字元需要擴充時,則總共需要284個位元組.另外原先的程式大約需要增加50個位元組.因此整個程式的大小就變成大約半K位元組.假設每一個擴充字串佔用20個位元組,那麼128個擴充鍵就需2.5K位元組,這和程式程式碼的0.5K位元組加起來,總共也不過3K位元組,還比前一種方法少10K位元組.
 上面的單鍵擴充程式轉換成多鍵擴充程式時,只要修改其中的KeyRead這個程式以及資料區的內容即可.以下就是修改後的內容:
 ;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
 jmp ReadDone
 Extended:
 cmp byte ptr cs:[si],0 ;Is this end of table?
 je ReadDone
 cmp ah,cs:[si]
 je StartExpand
 add si,3
 jmp NextExt
 StartExtend:
 push  bx
 add si,1
 mov bx,cs:[si]
 mov cs:current,bx ;If so,start expanding 
 ExpandChar: 
 mov si,cs:current
 mov al,cs:[si]
 inc cs:current
 cmp al,0 ;Is this end of string?
 je ReadChar ;If so,then read a real char?
 ReadDone:
 pop si
 ret
 KeyRead endp 
 current dw 0
 KeyTab db 59
 dw dir_cmd
 db 60
 dw dir_w
 db 61
 dw dir_asm
 db 62
 dw dir_com
 db 63
 dw dir_exe
 db 50
 dw make_macro
 db 0 ;This must be last in key table
 dir_cmp db 'DIR',0dh,0
 dir_wide db 'DIR/W',0dh,0
 dir_asm db 'DIR *.ASM',0dh,0
 dir_com db 'DIR *.COM',0dh,0
 dir_exe db 'DIR *.EXE',0dh,0
 make_macro db 'MASM MACRO;',0dh,0
 db 'LINK MACRO;',0dh,0
 db 'EXE2BIN MACRO.EXE MACRO.COM',0dh,0
 上面的程式是節省了一點的時間,但是對於和使用者介面而言則變得比較不方便,因為把功能鍵的定義移到組合語言的程式中.但是可以高法改寫這個程式,讓它在初次執行時從一個裝載所定義的字元患上 .這樣做並不會改變駐留程式程式碼的大小,因為裝載檔案的起始碼可以在執行完後拋棄,因此不必佔用駐留程式程式碼的位置.
5.3 單鍵擴充程式
 以下是單鍵擴充成命令字串的程式內容:
 cseg segment
 assume cs:cseg,ds:cseg
 org 100h
 Start:
 jmp Initialize
 Old_Keyboard_IO dd ?
 assume ds:nothing
 New_Keyboard_IO proc far
 sti
 cmp ah,0 ;Is this call a read request?
 je ksRead
 cmp ah,1 ;Is it a status request?
 je ksStat ;Let original routine
 jmp Old_Keyboard_IO ;handle remianing subfunction
 ksRead:
 call KeyRead ;Get next character to return
 iret
 ksStat:
 call KeyStat ;Return appropriate status
 ret 2 ;Important!!!
 New_Keyboard_IO endp
 KeyRead Proc near
 cmp cs:current,0
 jne ExpandChar
 ReadChar:
 mov cs:current,0 ;Slightly peculiar
 pushf ;Let original routine
 call Old_Keyboard_IO ;Detene keyboard status
 cmp al,0
 je Extended
 ReadDone:
 ret
 Extended:
 cmp ah,59 ;Is this character to expand?
 jne ReadDone ;If not,return it normally
 ;If so,start expanding
 mov cs:current,offset String
 ExpandChar:
 push si
 mov si,cs:current
 mov si,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
 KeyStat proc near
 cmp cs:current,0
 jne FakeStat
 pushf ;Let original routine
 call Old_Keyboard_IO ;Determine keyboard
 ret
 FakeStat:
 mov bx,1 ;Fake a "Character ready" by clearing ZF
 cmp bx,0
 ret
 KeyStat endp
 current dw 0
 string db 'masm macro;',0dh
 db 'link macro;',0dh
 db 'exe2bin macro.exe macro.com',0dh,0
 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,es
 mov dx,offset New_Keyboard_IO
 mov al,16h
 mov ah,25h
 int 21h
 mov dx,offset Initialize
 int 27h
 cseg ends
 end Start
5.4 一般的鍵盤擴充程式Mactab.asm
  以下和程式可以把由表的查詢,將任意婁的擴充鍵擴充成命令字串:
 cseg segment
 assume cs:cseg,ds:cseg
 org 100h
 Start:
 jmp Initialize
 Old_Keyboard_IO dd ?
 assume ds:nothing
 cmp byte ptr cs:[si],0 ;end of table
 je ReadDone
 cmp ah,cs:[si]
 je StartExpand
 add si,3
 jmp NextExt
 StartExpand:
 add si,1
 push bx
 mov bx,cs:[si]
 mov cs:current,bx
 pop bx
 ExpandChar:
 mov si,cs:current
 mov al,cs:[si]
 inc cs:current
 cmp al,0 ;end of string 2
 je ReadChar ;then read real char
 ReadDone:
 pop si
 ret 3
 KeyRead endp
 current dw 0
 KeyTab db 59
 dw dir_cmd
 db 60
 dw dir_wide
 db 61
 dw dir_asm
 db 62
 dw dir_com
 db 63
 dw dir_exe
 db 50
 dw make_macro
 db 0 ;This must be last in key table
 dir_cmp db 'DIR',0dh,0
 dir_wide db 'DIR/W',0dh,0
 dir_asm db 'DIR *.ASM',0dh,0
 dir_com db 'DIR *.COM',0dh,0
 dir_exe db 'DIR *.EXE',0dh,0
 make_macro db 'MASM MACRO;',0dh,0
 db 'LINK MACRO;',0dh,0
 db 'EXE2BIN MACRO.EXE MACRO.COM',0dh,0
 New_Keyboard_IO proc far
 sti
 cmp ah,0 ;Is this call a read request?
 je ksRead
 cmp ah,1 ;Is it a status request?
 je ksStat ;Let original routine
 jmp Old_Keyboard_IO ;handle remianing subfunction
 ksRead:
 call KeyRead ;Get next character to return
 iret
 ksStat:
 call KeyStat ;Return appropriate status
 ret 2 ;Important!!!
 New_Keyboard_IO endp
 KeyStat proc near
 cmp cs:current,0
 jne FakeStat
 pushf ;Let original routine
 call Old_Keyboard_IO ;Determine keyboard
 ret
 FakeStat:
 mov bx,1 ;Fake a "Character ready" by clearing ZF
 cmp bx,0
 ret
 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
 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,es
 mov dx,offset New_Keyboard_IO
 mov al,16h
 mov ah,25h
 int 21h
 mov dx,offset Initialize
 int 27h
 cseg ends
 end Start
 未完待續......

 

 


 

 


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10748419/viewspace-988596/,如需轉載,請註明出處,否則將追究法律責任。

相關文章