用匯編編寫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 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/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 照片記憶編寫軟體:Memory Pictures for MacMac
- 一個golang編寫的redis記憶體分析工具rma4goGolangRedis記憶體
- 用PHP編寫Android應用程式PHPAndroid
- php常駐程式記憶體洩露的簡單解決PHP記憶體洩露
- 編寫友好的命令列應用程式命令列
- 用Rust編寫的快如閃電的程式碼編輯器:lapceRust
- Linux下的 sniff-andthen-spoof程式編寫Linux
- linux 設定預留記憶體Linux記憶體
- 5 個用 Python 編寫 web 爬蟲的方法PythonWeb爬蟲
- 5.編寫recipe
- io記憶體,io埠,彙編 in out 指令記憶體
- Java程式編寫Java
- Vue3,用組合的方式來編寫更好的程式碼(1/5)Vue
- Sublime 編寫編譯 swift程式碼編譯Swift
- 來安利一下,編寫現代JavaScript程式碼的5個小技巧!JavaScript
- 如何學習用Java編寫程式碼?Java
- 編寫執行緒安全的JSP應用程式執行緒JS
- Java程式編寫 • 【第5章 程式:賭博遊戲】Java遊戲
- vue專案編譯node記憶體溢位Vue編譯記憶體溢位
- Netweaver工作程式的記憶體限制 VS CloudFoundry應用的記憶體限制記憶體Cloud
- Java應用程式中的記憶體洩漏及記憶體管理Java記憶體
- 7.7 實現程式記憶體讀寫記憶體
- 用vim編寫markdown
- PHP 編寫基本的 Socket 程式PHP
- ida pro 7.5反編匯程式
- Python字串駐留的原理探究Python字串
- 軟體測試用例編寫(含思路)
- 用Java編寫一個最簡單的桌面程式Java
- MixPHP:基於 Swoole 的常駐記憶體型 PHP 框架PHP記憶體框架
- Vue3,用組合編寫更好的程式碼:靈活的引數(2/5)Vue
- 駐點日記之記憶碎片
- 5. SQL 編寫規範SQL
- Python編寫守護程式程式Python
- 程式設計師筆記|如何編寫優雅的Dockerfile程式設計師筆記Docker
- 使用 TypeScript 編寫 SAP UI5 應用的準備工作TypeScriptUI
- 使用GoogleAppEngine、GoogleClosureLibrary與Clojure編寫HTML5應用GoAPPHTML
- ekzhang/rustpad:使用Rust編寫的高效程式碼編輯器Rust
- 軟體編寫風格
- 用Python編寫自己的微型RedisPythonRedis