這年頭,在這個論壇里面已經沒有什么技術貼了...呵呵~發一篇驚天地,泣鬼神的帖子.當然這個只是模擬鍵盤的終極模擬.呵呵~
?????
鍵盤是我們使用計算機的一個很重要的輸入設備了,即使在鼠標大行其道的今天,很多程序依然離不開鍵盤來操作。但是有時候,一些重復性的,很繁瑣的鍵盤操作
總會讓人疲憊,于是就有了用程序來代替人們按鍵的方法,這樣可以把很多重復性的鍵盤操作交給程序來模擬,省了很多精力,按鍵精靈就是這樣的一個軟件。那么
我們怎樣才能用VB來寫一個程序,達到與按鍵精靈類似的功能呢?那就讓我們來先了解一下windows中響應鍵盤事件的機制。
????當用戶按下
鍵盤上的一個鍵時,鍵盤內的芯片會檢測到這個動作,并把這個信號傳送到計算機。如何區別是哪一個鍵被按下了呢?鍵盤上的所有按鍵都有一個編碼,稱作鍵盤掃
描碼。當你按下一個鍵時,這個鍵的掃描碼就被傳給系統。掃描碼是跟具體的硬件相關的,同一個鍵,在不同鍵盤上的掃描碼有可能不同。鍵盤控制器就是將這個掃
描碼傳給計算機,然后交給鍵盤驅動程序。鍵盤驅動程序會完成相關的工作,并把這個掃描碼轉換為鍵盤虛擬碼。什么是虛擬碼呢?因為掃描碼與硬件相關,不具有
通用性,為了統一鍵盤上所有鍵的編碼,于是就提出了虛擬碼概念。無論什么鍵盤,同一個按鍵的虛擬碼總是相同的,這樣程序就可以識別了。簡單點說,虛擬碼就
是我們經常可以看到的像VK_A,VK_B這樣的常數,比如鍵A的虛擬碼是65,寫成16進制就是&H41,注意,人們經常用16進制來表示虛擬
碼。當鍵盤驅動程序把掃描碼轉換為虛擬碼后,會把這個鍵盤操作的掃描碼和虛擬碼還有其它信息一起傳遞給操作系統。然后操作系統則會把這些信息封裝在一個消
息中,并把這個鍵盤消息插入到消息列隊。最后,要是不出意外的話,這個鍵盤消息最終會被送到當前的活動窗口那里,活動窗口所在的應用程序接收到這個消息
后,就知道鍵盤上哪個鍵被按下,也就可以決定該作出什么響應給用戶了。這個過程可以簡單的如下表示:
用戶按下按鍵-----鍵盤驅動程序將此事件傳遞給操作系統-----操作系統將鍵盤事件插入消息隊列-----鍵盤消息被發送到當前活動窗口
明白了這個過程,我們就可以編程實現在其中的某個環節來模擬鍵盤操作了。在VB中,有多種方法可以實現鍵盤模擬,我們就介紹幾種比較典型的。
1.局部級模擬
????
從上面的流程可以看出,鍵盤事件是最終被送到活動窗口,然后才引起目標程序響應的。那么最直接的模擬方法就是:直接偽造一個鍵盤消息發給目標程序。哈哈,
這實在是很簡單,windows提供了幾個這樣的API函數可以實現直接向目標程序發送消息的功能,常用的有SendMessage和
PostMessage,它們的區別是PostMessage函數直接把消息仍給目標程序就不管了,而SendMessage把消息發出去后,還要等待目
標程序返回些什么東西才好。這里要注意的是,模擬鍵盤消息一定要用PostMessage函數才好,用SendMessage是不正確的(因為模擬鍵盤消
息是不需要返回值的,不然目標程序會沒反應),切記切記!PostMessage函數的VB聲明如下:
Declare?Function?PostMessage?Lib?"user32"?Alias?"PostMessageA"?(ByVal?hwnd?As?Long,?ByVal?wMsg?As?Long,?ByVal?wParam?As?Long,?lParam?As?Any)?As?Long
參數hwnd?是你要發送消息的目標程序上某個控件的句柄,參數wMsg?是消息的類型,表示你要發送什么樣的消息,最后wParam?和lParam?這兩個參數是隨消息附加的數據,具體內容要由消息決定。
再來看看wMsg?這個參數,要模擬按鍵就靠這個了。鍵盤消息常用的有如下幾個:
WM_KEYDOWN?????表示一個普通鍵被按下
WM_KEYUP???????表示一個普通鍵被釋放
WM_SYSKEYDOWN??表示一個系統鍵被按下,比如Alt鍵
WM_SYSKEYUP????表示一個系統鍵被釋放,比如Alt鍵
如
果你確定要發送以上幾個鍵盤消息,那么再來看看如何確定鍵盤消息中的wParam?和lParam?這兩個參數。在一個鍵盤消息中,wParam?參數的
含義較簡單,它表示你要發送的鍵盤事件的按鍵虛擬碼,比如你要對目標程序模擬按下A鍵,那么wParam?參數的值就設為VK_A?,至于lParam?
這個參數就比較復雜了,因為它包含了多個信息,一般可以把它設為0,但是如果你想要你的模擬更真實一些,那么建議你還是設置一下這個參數。那么我們就詳細
了解一下lParam?吧。lParam?是一個long類型的參數,它在內存中占4個字節,寫成二進制就是
00000000?00000000?00000000?00000000??一共是32位,我們從右向左數,假設最右邊那位為第0位(注意是從0而不是
從1開始計數),最左邊的就是第31位,那么該參數的的0-15位表示鍵的發送次數等擴展信息,16-23位為按鍵的掃描碼,24-31位表示是按下鍵還
是釋放鍵。大家一般習慣寫成16進制的,那么就應該是&H00?00?00?00?,第0-15位一般為&H0001,如果是按下鍵,那
么24-31位為&H00,釋放鍵則為&HC0,那么16-23位的掃描碼怎么會得呢?這需要用到一個API函數
MapVirtualKey,這個函數可以將虛擬碼轉換為掃描碼,或將掃描碼轉換為虛擬碼,還可以把虛擬碼轉換為對應字符的ASCII碼。它的VB聲明如
下:
Declare?Function?MapVirtualKey?Lib?"user32"?Alias?"MapVirtualKeyA"?(ByVal?wCode?As?Long,?ByVal?wMapType?As?Long)?As?Long
參
數wCode?表示待轉換的碼,參數wMapType?表示從什么轉換為什么,如果是虛擬碼轉掃描碼,則wMapType?設置為0,如果是虛擬掃描碼轉
虛擬碼,則wMapType?設置為1,如果是虛擬碼轉ASCII碼,則wMapType?設置為2.相信有了這些,我們就可以構造鍵盤事件的
lParam參數了。下面給出一個構造lParam參數的函數:
Declare?Function?MapVirtualKey?Lib?"user32"?Alias?"MapVirtualKeyA"?(ByVal?wCode?As?Long,?ByVal?wMapType?As?Long)?As?Long
Function?MakeKeyLparam(ByVal?VirtualKey?As?Long,?ByVal?flag?As?Long)?As?Long
'參數VirtualKey表示按鍵虛擬碼,flag表示是按下鍵還是釋放鍵,用WM_KEYDOWN和WM_KEYUP這兩個常數表示
????Dim?s?As?String
????Dim?Firstbyte?As?String????'lparam參數的24-31位
????If?flag?=?WM_KEYDOWN??Then?'如果是按下鍵
????????Firstbyte?=?"00"
????Else
????????Firstbyte?=?"C0"???????'如果是釋放鍵
????End?If
????Dim?Scancode?As?Long
????'獲得鍵的掃描碼
????Scancode?=?MapVirtualKey(VirtualKey,?0)
????Dim?Secondbyte?As?String???'lparam參數的16-23位,即虛擬鍵掃描碼
????Secondbyte?=?Right("00"?&?Hex(Scancode),?2)
????s?=?Firstbyte?&?Secondbyte?&?"0001"??'0001為lparam參數的0-15位,即發送次數和其它擴展信息
????MakeKeyLparam?=?Val("&H"?&?s)
End?Function
這
個函數像這樣調用,比如按下A鍵,那么lParam=MakeKeyLparam(VK_A,WM_KEYDOWN)?,很簡單吧。值得注意的是,即使你
發送消息時設置了lParam參數的值,但是系統在傳遞消息時仍然可能會根據當時的情況重新設置該參數,那么目標程序收到的消息中lParam的值可能會
和你發送時的有所不同。所以,如果你很懶的話,還是直接把它設為0吧,對大多數程序不會有影響的,呵呵。
????好了,做完以上的事情,現在我們可以向目標程序發送鍵盤消息了。首先取得目標程序接受這個消息的控件的句柄,比如目標句柄是12345,那么我們來對目標模擬按下并釋放A鍵,像這樣:(為了簡單起見,lParam這個參數就不構造了,直接傳0)
PostMessage?12345,WM_KEYDOWN,VK_A,0&???'按下A鍵
PostMessage?12345,WM_UP,VK_A,0&????????'釋放A鍵
好
了,一次按鍵就完成了?,F在你可以迫不及待的打開記事本做實驗,先用FindWindowEx這類API函數找到記事本程序的句柄,再向它發送鍵盤消息,
期望記事本里能詭異的自動出現字符??墒悄泷R上就是失望了,咦,怎么一點反應也沒有?你欺騙感情啊~~~~~~~~~~55555555555555??
不是的哦,接著往下看啊。
一般目標程序都會含有多個控件,并不是每個控件都會對鍵盤消息作出反應,只有把鍵盤消息發送給接受它的控件才會得到期望
的反應。那記事本來說,它的編輯框其實是個edit類,只有這個控件才對鍵盤事件有反應,如果只是把消息發給記事本的窗體,那是沒有用的?,F在你找出記事
本那個編輯框的句柄,比如是54321,那么寫如下代碼:
PostMessage?54321,WM_KEYDOWN,VK_F1,0&???'按下F1鍵
PostMessage?54321,WM_UP,VK_F1,0&????????'釋放F1鍵
怎么樣,是不是打開了記事本的“幫助”信息?這說明目標程序已經收到了你發的消息,還不錯吧~~~~~~~~
可以馬上新問題就來了,你想模擬向記事本按下A這個鍵,好在記事本里自動輸入字符,可是,沒有任何反應!這是怎么一回事呢?
原
來,如果要向目標程序發送字符,光靠WM_KEYDOWN和WM_UP這兩個事件還不行,還需要一個事件:WM_CHAR,這個消息表示一個字符,程序需
靠它看來接受輸入的字符。一般只有A,B,C等這樣的按鍵才有WM_CHAR消息,別的鍵(比如方向鍵和功能鍵)是沒有這個消息的,WM_CHAR消息一
般發生在WM_KEYDOWN消息之后。WM_CHAR消息的lParam參數的含義與其它鍵盤消息一樣,而它的wParam則表示相應字符的ASCII
編碼(可以輸入中文的哦^_^),現在你可以寫出一個完整的向記事本里自動寫入字符的程序了,下面是一個例子,并附有這些消息常數的具體值:
Declare?Function?PostMessage?Lib?"user32"?Alias?"PostMessageA"?(ByVal?hwnd?As?Long,?ByVal?wMsg?As?Long,?ByVal?wParam?As?Long,?lParam?As?Any)?As?Long
Declare?Function?MapVirtualKey?Lib?"user32"?Alias?"MapVirtualKeyA"?(ByVal?wCode?As?Long,?ByVal?wMapType?As?Long)?As?Long
Public?Const?WM_KEYDOWN?=?&H100
Public?Const?WM_KEYUP?=?&H101
Public?Const?WM_CHAR?=?&H102
Public?Const?VK_A?=?&H41?
Function?MakeKeyLparam(ByVal?VirtualKey?As?Long,?ByVal?flag?As?Long)?As?Long
????Dim?s?As?String
????Dim?Firstbyte?As?String????'lparam參數的24-31位
????If?flag?=?WM_KEYDOWN??Then?'如果是按下鍵
????????Firstbyte?=?"00"
????Else
????????Firstbyte?=?"C0"???????'如果是釋放鍵
????End?If
????Dim?Scancode?As?Long
????'獲得鍵的掃描碼
????Scancode?=?MapVirtualKey(VirtualKey,?0)
????Dim?Secondbyte?As?String???'lparam參數的16-23位,即虛擬鍵掃描碼
????Secondbyte?=?Right("00"?&?Hex(Scancode),?2)
????s?=?Firstbyte?&?Secondbyte?&?"0001"??'0001為lparam參數的0-15位,即發送次數和其它擴展信息
????MakeKeyLparam?=?Val("&H"?&?s)
End?Function
Private?Sub?Form_Load()
????dim?hwnd?as?long
????hwnd?=?XXXXXX??'XXXXX表示記事本編輯框的句柄
????PostMessage?hwnd,WM_KEYDOWN,VK_A,MakeKeyLparam(VK_A,WM_KEYDOWN)??'按下A鍵
????PostMessage?hwnd,WM_CHAR,ASC("A"),MakeKeyLparam(VK_A,WM_KEYDOWN)??'輸入字符A
????PostMessage?hwnd,WM_UP,VK_A,MakeKeyLparam(VK_A,WM_UP)???????'釋放A鍵
End?Sub
這
就是通過局部鍵盤消息來模擬按鍵。這個方法有一個極大的好處,就是:它可以實現后臺按鍵,也就是說他對你的前臺操作不會有什么影響。比如,你可以用這個方
法做個程序在游戲中模擬按鍵來不斷地執行某些重復的操作,而你則一邊喝茶一邊與QQ上的MM們聊得火熱,它絲毫不會影響你的前臺操作。無論目標程序是否獲
得焦點都沒有影響,這就是后臺模擬按鍵的原理啦~~~~
2.全局級模擬
????你會發現,用上面的方法模擬按鍵并不
是對所有程序都有效的,有的程序啊,你向它發了一大堆消息,可是它卻一點反應也沒有。這是怎么回事呢?這就要看具體的情況了,有些程序(特別是一些游戲)
出于某些原因,會禁止用戶對它使用模擬按鍵程序,這個怎么實現呢?比如可以在程序中檢查一下,如果發現自己不是活動窗口,就不接受鍵盤消息。或者仔細檢查
一下收到的鍵盤消息,你會發現真實的按鍵和模擬的按鍵消息總是有一些小差別,從這些小差別上,目標程序就能判斷出:這是假的!是偽造的??!因此,如果用
PostMessage發送局部消息模擬按鍵不成功的話,你可以試一試全局級的鍵盤消息,看看能不能騙過目標程序。
模擬全局鍵盤消息常見的可以有以下一些方法:
(1)?用API函數keybd_event,這個函數可以用來模擬一個鍵盤事件,它的VB聲明為:
Declare?Sub?keybd_event?Lib?"user32"?(ByVal?bVk?As?Byte,?ByVal?bScan?As?Byte,?ByVal?dwFlags?As?Long,?ByVal?dwExtraInfo?As?Long)
參數bVk表示要模擬的按鍵的虛擬碼,bScan表示該按鍵的掃描碼(一般可以傳0),dwFlags表示是按下鍵還是釋放鍵(按下鍵為0,釋放鍵為2),dwExtraInfo是擴展標志,一般沒有用。比如要模擬按下A鍵,可以這樣:
Const?KEYEVENTF_KEYUP?=?&H2
keybd_event?VK_A,?0,?0,?0???'按下A鍵
keybd_event?VK_A,?0,?KEYEVENTF_KEYUP,?0???'釋放A鍵
注意有時候按鍵的速度不要太快,否則會出問題,可以用API函數Sleep來進行延時,聲明如下:
Declare?Sub?Sleep?Lib?"kernel32"?(ByVal?dwMilliseconds?As?Long)
參數dwMilliseconds表示延時的時間,以毫秒為單位。
那么如果要模擬按下功能鍵怎么做呢?比如要按下Ctrl+C實現拷貝這個功能,可以這樣:
keybd_event?VK_Ctrl,?0,?0,?0???'按下Ctrl鍵
keybd_event?VK_C,?0,?0,?0??????'按下C鍵
Sleep?500????????????'延時500毫秒
keybd_event?VK_C,?0,?KEYEVENTF_KEYUP,?0???'釋放C鍵
keybd_event?VK_Ctrl,?0,?KEYEVENTF_KEYUP,?0???'釋放Ctrl鍵
好
了,現在你可以試試是不是可以騙過目標程序了,這個函數對大部分的窗口程序都有效,可是仍然有一部分游戲對它產生的鍵盤事件熟視無睹,這時候,你就要用上
bScan這個參數了。一般的,bScan都傳0,但是如果目標程序是一些DirectX游戲,那么你就需要正確使用這個參數傳入掃描碼,用了它可以產生
正確的硬件事件消息,以被游戲識別。這樣的話,就可以寫成這樣:
keybd_event?VK_A,?MapVirtualKey(VK_A,?0),?0,?0???'按下A鍵
keybd_event?VK_A,?MapVirtualKey(VK_A,?0),?KEYEVENTF_KEYUP,?0???'釋放A鍵
以上就是用keybd_event函數來模擬鍵盤事件。除了這個函數,SendInput函數也可以模擬全局鍵盤事件。SendInput可以直接把一條消息插入到消息隊列中,算是比較底層的了。它的VB聲明如下:
Declare?Function?SendInput?Lib?"user32.dll"?(ByVal?nInputs?As?Long,?pInputs?As?GENERALINPUT,?ByVal?cbSize?As?Long)?As?Long
參數:?
nlnprts:定義plnputs指向的結構的數目。
plnputs:指向INPUT結構數組的指針。每個結構代表插人到鍵盤或鼠標輸入流中的一個事件。
cbSize:定義INPUT結構的大小。若cbSize不是INPUT結構的大小,則函數調用失敗。
返回值:函數返回被成功地插人鍵盤或鼠標輸入流中的事件的數目。若要獲得更多的錯誤信息,可以調用GetlastError函數。
備注:Sendlnput函數將INPUT結構中的事件順序地插入鍵盤或鼠標的輸入流中。這些事件與用戶插入的(用鼠標或鍵盤)或調用keybd_event,mouse_event,或另外的Sendlnput插人的鍵盤或鼠標的輸入流不兼容。
嗯,這個函數用起來蠻復雜的,因為它的參數都是指針一類的東西。要用它來模擬鍵盤輸入,先要構造一組數據結構,把你要模擬的鍵盤消息裝進去,然后傳給它。為了方便起見,把它做在一個過程里面,要用的時候直接調用好了,代碼如下:
Declare?Function?SendInput?Lib?"user32.dll"?(ByVal?nInputs?As?Long,?pInputs?As?GENERALINPUT,?ByVal?cbSize?As?Long)?As?Long
Declare?Sub?CopyMemory?Lib?"kernel32"?Alias?"RtlMoveMemory"?(pDst?As?Any,?pSrc?As?Any,?ByVal?ByteLen?As?Long)
?Type?GENERALINPUT
???dwType?As?Long
???xi(0?To?23)?As?Byte
?End?Type
?Type?KEYBDINPUT
??wVk?As?Integer
??wScan?As?Integer
??dwFlags?As?Long
??time?As?Long
??dwExtraInfo?As?Long
?End?Type
Const?INPUT_KEYBOARD?=?1
Sub?MySendKey(bkey?As?Long)
'參數bkey傳入要模擬按鍵的虛擬碼即可模擬按下指定鍵
Dim?GInput(0?To?1)?As?GENERALINPUT
Dim?KInput?As?KEYBDINPUT
?KInput.wVk?=?bkey??'你要模擬的按鍵
?KInput.dwFlags?=?0?'按下鍵標志
?GInput(0).dwType?=?INPUT_KEYBOARD
?CopyMemory?GInput(0).xi(0),?KInput,?Len(KInput)?'這個函數用來把內存中KInput的數據復制到GInput
?KInput.wVk?=?bkey??
?KInput.dwFlags?=?KEYEVENTF_KEYUP??'?釋放按鍵
?GInput(1).dwType?=?INPUT_KEYBOARD?'?表示該消息為鍵盤消息
?CopyMemory?GInput(1).xi(0),?KInput,?Len(KInput)
'以上工作把按下鍵和釋放鍵共2條鍵盤消息加入到GInput數據結構中
?SendInput?2,?GInput(0),?Len(GInput(0))????'把GInput中存放的消息插入到消息列隊
End?Sub
????
除了以上這些,用全局鉤子也可以模擬鍵盤消息。如果你對windows中消息鉤子的用法已經有所了解,那么你可以通過設置一個全局HOOK來模擬鍵盤消
息,比如,你可以用WH_JOURNALPLAYBACK這個鉤子來模擬按鍵。WH_JOURNALPLAYBACK是一個系統級的全局鉤子,它和
WH_JOURNALRECORD的功能是相對的,常用它們來記錄并回放鍵盤鼠標操作。WH_JOURNALRECORD鉤子用來將鍵盤鼠標的操作忠實地
記錄下來,記錄下來的信息可以保存到文件中,而WH_JOURNALPLAYBACK則可以重現這些操作。當然亦可以單獨使用
WH_JOURNALPLAYBACK來模擬鍵盤操作。你需要首先聲明SetWindowsHookEx函數,它可以用來安裝消息鉤子:
Declare?Function?SetWindowsHookEx?Lib?"user32"?Alias?"SetWindowsHookExA"?(ByVal?idHook?As?Long,ByVal?lpfn?As?Long,?ByVal?hmod?As?Long,?ByVal?dwThreadId?As?Long)?As?Long
先
安裝WH_JOURNALPLAYBACK這個鉤子,然后你需要自己寫一個鉤子函數,在系統調用它時,把你要模擬的事件傳遞給鉤子參數lParam所指向
的EVENTMSG區域,就可以達到模擬按鍵的效果。不過用這個鉤子模擬鍵盤事件有一個副作用,就是它會鎖定真實的鼠標鍵盤,不過如果你就是想在模擬的時
候不會受真實鍵盤操作的干擾,那么用用它倒是個不錯的主意。
3.驅動級模擬
????如果上面的方法你都試過了,可是你發現目標程序卻仍然頑固的不接受你模擬的消息,寒~~~~~~~~~還好,我還剩下最后一招,這就是驅動級模擬:直接讀寫鍵盤的硬件端口!
????
有一些使用DirectX接口的游戲程序,它們在讀取鍵盤操作時繞過了windows的消息機制,而使用DirectInput.這是因為有些游戲對實時
性控制的要求比較高,比如賽車游戲,要求以最快速度響應鍵盤輸入。而windows消息由于是隊列形式的,消息在傳遞時會有不少延遲,有時1秒鐘也就傳遞
十幾條消息,這個速度達不到游戲的要求。而DirectInput則繞過了windows消息,直接與鍵盤驅動程序打交道,效率當然提高了不少。因此也就
造成,對這樣的程序無論用PostMessage或者是keybd_event都不會有反應,因為這些函數都在較高層。對于這樣的程序,只好用直接讀寫鍵
盤端口的方法來模擬硬件事件了。要用這個方法來模擬鍵盤,需要先了解一下鍵盤編程的相關知識。
????在DOS時代,當用戶按下或者放開一個鍵
時,就會產生一個鍵盤中斷(如果鍵盤中斷是允許的),這樣程序會跳轉到BIOS中的鍵盤中斷處理程序去執行。打開windows的設備管理器,可以查看到
鍵盤控制器由兩個端口控制。其中&H60是數據端口,可以讀出鍵盤數據,而&H64是控制端口,用來發出控制信號。也就是,從&
H60號端口可以讀此鍵盤的按鍵信息,當從這個端口讀取一個字節,該字節的低7位就是按鍵的掃描碼,而高1位則表示是按下鍵還是釋放鍵。當按下鍵時,最高
位為0,稱為通碼,當釋放鍵時,最高位為1,稱為斷碼。既然從這個端口讀數據可以獲得按鍵信息,那么向這個端口寫入數據就可以模擬按鍵了!用過
QbASIC4.5的朋友可能知道,QB中有個OUT命令可以向指定端口寫入數據,而INP函數可以讀取指定端口的數據。那我們先看看如果用QB該怎么寫
代碼:
假如你想模擬按下一個鍵,這個鍵的掃描碼為&H50,那就這樣
OUT?&H64,&HD2???'把數據&HD2發送到&H64端口。這是一個KBC指令,表示將要向鍵盤寫入數據
OUT?&H60,&H50???'把掃描碼&H50發送到&H60端口,表示模擬按下掃描碼為&H50的這個鍵
那么要釋放這個鍵呢?像這樣,發送該鍵的斷碼:
OUT?&H64,&HD2???'把數據&HD2發送到&H64端口。這是一個KBC指令,表示將要向鍵盤寫入數據
OUT?&H60,(&H50?OR?&H80)???'把掃描碼&H50與數據&H80進行或運算,可以把它的高位置1,得到斷碼,表示釋放這個鍵
????
好了,現在的問題就是在VB中如何向端口寫入數據了。因為在windows中,普通應用程序是無權操作端口的,于是我們就需要一個驅動程序來幫助我們實
現。在這里我們可以使用一個組件WINIO來完成讀寫端口操作。什么是WINIO?WINIO是一個全免費的、無需注冊的、含源程序的
WINDOWS2000端口操作驅動程序組件(可以到http://www.internals.com/上
去下載)。它不僅可以操作端口,還可以操作內存;不僅能在VB下用,還可以在DELPHI、VC等其它環境下使用,性能特別優異。下載該組件,解壓縮后可
以看到幾個文件夾,其中Release文件夾下的3個文件就是我們需要的,這3個文件是WinIo.sys(用于win?xp下的驅動程序),
WINIO.VXD(用于win?98下的驅動程序),WinIo.dll(封裝函數的動態鏈接庫),我們只需要調用WinIo.dll中的函數,然后
WinIo.dll就會安裝并調用驅動程序來完成相應的功能。值得一提的是這個組件完全是綠色的,無需安裝,你只需要把這3個文件復制到與你的程序相同的
文件夾下就可以使用了。用法很簡單,先用里面的InitializeWinIo函數安裝驅動程序,然后就可以用GetPortVal來讀取端口或者用
SetPortVal來寫入端口了。好,讓我們來做一個驅動級的鍵盤模擬吧。先把winio的3個文件拷貝到你的程序的文件夾下,然后在VB中新建一個工
程,添加一個模塊,在模塊中加入下面的winio函數聲明:
Declare?Function?MapPhysToLin?Lib?"WinIo.dll"?(ByVal?PhysAddr?As?Long,?ByVal?PhysSize?As?Long,?ByRef?PhysMemHandle)?As?Long
Declare?Function?UnmapPhysicalMemory?Lib?"WinIo.dll"?(ByVal?PhysMemHandle,?ByVal?LinAddr)?As?Boolean
Declare?Function?GetPhysLong?Lib?"WinIo.dll"?(ByVal?PhysAddr?As?Long,?ByRef?PhysVal?As?Long)?As?Boolean
Declare?Function?SetPhysLong?Lib?"WinIo.dll"?(ByVal?PhysAddr?As?Long,?ByVal?PhysVal?As?Long)?As?Boolean
Declare?Function?GetPortVal?Lib?"WinIo.dll"?(ByVal?PortAddr?As?Integer,?ByRef?PortVal?As?Long,?ByVal?bSize?As?Byte)?As?Boolean
Declare?Function?SetPortVal?Lib?"WinIo.dll"?(ByVal?PortAddr?As?Integer,?ByVal?PortVal?As?Long,?ByVal?bSize?As?Byte)?As?Boolean
Declare?Function?InitializeWinIo?Lib?"WinIo.dll"?()?As?Boolean
Declare?Function?ShutdownWinIo?Lib?"WinIo.dll"?()?As?Boolean
Declare?Function?InstallWinIoDriver?Lib?"WinIo.dll"?(ByVal?DriverPath?As?String,?ByVal?Mode?As?Integer)?As?Boolean
Declare?Function?RemoveWinIoDriver?Lib?"WinIo.dll"?()?As?Boolean
'?------------------------------------以上是WINIO函數聲明-------------------------------------------
Declare?Function?MapVirtualKey?Lib?"user32"?Alias?"MapVirtualKeyA"?(ByVal?wCode?As?Long,?ByVal?wMapType?As?Long)?As?Long
'-----------------------------------以上是WIN32?API函數聲明-----------------------------------------
再添加下面這個過程:
Sub?KBCWait4IBE()???'等待鍵盤緩沖區為空
Dim?dwVal?As?Long
??Do
??GetPortVal?&H64,?dwVal,?1
'這句表示從&H64端口讀取一個字節并把讀出的數據放到變量dwVal中
'GetPortVal函數的用法是GetPortVal?端口號,存放讀出數據的變量,讀入的長度
??Loop?While?(dwVal?And?&H2)
End?Sub
上面的是一個根據KBC規范寫的過程,它的作用是在向鍵盤端口寫入數據前等待一段時間,后面將會用到。
然后再添加如下過程,這2個過程用來模擬按鍵:
Public?Const?KBC_KEY_CMD?=?&H64????'鍵盤命令端口
Public?Const?KBC_KEY_DATA?=?&H60???'鍵盤數據端口
Sub?MyKeyDown(ByVal?vKeyCoad?As?Long)???
'這個用來模擬按下鍵,參數vKeyCoad傳入按鍵的虛擬碼
Dim?btScancode?As?Long
btScancode?=?MapVirtualKey(vKeyCoad,?0)
??
????KBCWait4IBE???'發送數據前應該先等待鍵盤緩沖區為空
????SetPortVal?KBC_KEY_CMD,?&HD2,?1?????'發送鍵盤寫入命令
'SetPortVal函數用于向端口寫入數據,它的用法是SetPortVal?端口號,欲寫入的數據,寫入數據的長度
????KBCWait4IBE
????SetPortVal?KBC_KEY_DATA,?btScancode,?1??'寫入按鍵信息,按下鍵
????
End?Sub
?Sub?MyKeyUp(ByVal?vKeyCoad?As?Long)???
'這個用來模擬釋放鍵,參數vKeyCoad傳入按鍵的虛擬碼
Dim?btScancode?As?Long
btScancode?=?MapVirtualKey(vKeyCoad,?0)
??
????KBCWait4IBE???'等待鍵盤緩沖區為空
????SetPortVal?KBC_KEY_CMD,?&HD2,?1??'發送鍵盤寫入命令
????KBCWait4IBE
????SetPortVal?KBC_KEY_DATA,?(btScancode?Or?&H80),?1??'寫入按鍵信息,釋放鍵
End?Sub
定義了上面的過程后,就可以用它來模擬鍵盤輸入了。在窗體模塊中添加一個定時器控件,然后加入以下代碼:
Private?Sub?Form_Load() ?If?InitializeWinIo?=?False?Then??? ??'用InitializeWinIo函數加載驅動程序,如果成功會返回true,否則返回false ????MsgBox?"驅動程序加載失敗!" ????Unload?Me ?End?If Timer1.Interval=3000 Timer1.Enabled=True End?Sub Private?Sub?Form_Unload(Cancel?As?Integer) ?ShutdownWinIo?'程序結束時記得用ShutdownWinIo函數卸載驅動程序 End?Sub Private?Sub?Timer1_Timer() Dim?VK_A?as?Long?=?&H41? MyKeyDown?VK_A???? MyKeyUp?VK_A????'模擬按下并釋放A鍵 End?Sub [/quote] 運行上面的程序,就會每隔3秒鐘模擬按下一次A鍵,試試看,怎么樣,是不是對所有程序都有效果了? 需要注意的問題: 要在VB的調試模式下使用WINIO,需要把那3個文件拷貝到VB的安裝目錄中。 鍵盤上有些鍵屬于擴展鍵(比如鍵盤上的方向鍵就是擴展鍵),對于擴展鍵不應該用上面的MyKeyDown和MyKeyUp過程來模擬,可以使用下面的2個過程來準確模擬擴展鍵: [quote]Sub?MyKeyDownEx(ByVal?vKeyCoad?As?Long)???'模擬擴展鍵按下,參數vKeyCoad是擴展鍵的虛擬碼 Dim?btScancode?As?Long btScancode?=?MapVirtualKey(vKeyCoad,?0) ????KBCWait4IBE???'等待鍵盤緩沖區為空 ????SetPortVal?KBC_KEY_CMD,?&HD2,?1?????'發送鍵盤寫入命令 ????KBCWait4IBE ????SetPortVal?KBC_KEY_DATA,?&HE0,?1??'寫入擴展鍵標志信息 ???? ???? ????KBCWait4IBE???'等待鍵盤緩沖區為空 ????SetPortVal?KBC_KEY_CMD,?&HD2,?1?????'發送鍵盤寫入命令 ????KBCWait4IBE ????SetPortVal?KBC_KEY_DATA,?btScancode,?1??'寫入按鍵信息,按下鍵 ???? ???? End?Sub Sub?MyKeyUpEx(ByVal?vKeyCoad?As?Long)???'模擬擴展鍵彈起 Dim?btScancode?As?Long btScancode?=?MapVirtualKey(vKeyCoad,?0)
????KBCWait4IBE???'等待鍵盤緩沖區為空 ????SetPortVal?KBC_KEY_CMD,?&HD2,?1?????'發送鍵盤寫入命令 ????KBCWait4IBE ????SetPortVal?KBC_KEY_DATA,?&HE0,?1??'寫入擴展鍵標志信息 ???? ???? ????KBCWait4IBE???'等待鍵盤緩沖區為空 ????SetPortVal?KBC_KEY_CMD,?&HD2,?1?????'發送鍵盤寫入命令 ????KBCWait4IBE ????SetPortVal?KBC_KEY_DATA,?(btScancode?Or?&H80),?1??'寫入按鍵信息,釋放鍵 ???? End?Sub [/quote] 還
應該注意的是,如果要從擴展鍵轉換到普通鍵,那么普通鍵的KeyDown事件應該發送兩次。也就是說,如果我想模擬先按下一個擴展鍵,再按下一個普通鍵,
那么就應該向端口發送兩次該普通鍵被按下的信息。比如,我想模擬先按下左方向鍵,再按下空格鍵這個事件,由于左方向鍵是擴展鍵,空格鍵是普通鍵,那么流程
就應該是這樣的: [quote]MyKeyDownEx?VK_LEFT???'按下左方向鍵 Sleep?200?????????????'延時200毫秒 MyKeyUpEx?VK_LEFT?????'釋放左方向鍵 Sleep?500 MyKeyDown?VK_SPACE???'按下空格鍵,注意要發送兩次 MyKeyDown?VK_SPACE Sleep?200 MyKeyUp?VK_SPACE?????'釋放空格鍵
|
好了,相信到這里,你的模擬按鍵程序也就差不多了,測試一下,是不是很有效呢,嘿嘿~~~~
WINIO組件的下載地址:
http://www.114vip.com.cn/download/winio.zip4.骨灰級模擬
????方法3算是很底層的模擬了,我現在還沒有發現有它模擬無效的程序。但是如果你用盡上面所有的方法,仍然無效的話,那么還有最后一個方法,絕對對任何程序都會有效,那就是:把鍵盤拿出來,老老實實地按下去吧。~~~~