http://www.cnblogs.com/bjxsky/p/3656076.htmlhtml
本文目錄以下編程
1、基於windows 消息機制的鼠標鍵盤模擬windows
(一)、應用程序級模擬數組
(二)、系統級模擬安全
1、 用API函數keybd_event 模擬鍵盤事件ide
2、 SendInput函數模擬全局鍵盤鼠標事件函數
3、用全局鉤子模擬鍵盤消息post
2、驅動級模擬性能
*******************************************************************************************this
1、基於windows 消息機制的鼠標鍵盤模擬
咱們怎樣才能用Delphi來寫一個程序,用來代替人們按鍵的方法呢?那就讓咱們來先了解一下windows中響應鍵盤事件的機制。
當用戶按下鍵盤上的一個鍵時,鍵盤內的芯片會檢測到這個動做,並把這個信號傳送到計算機。如何區別是哪個鍵被按下了呢?鍵盤上的全部按鍵都有一個編碼,稱做鍵盤掃描碼。當你按下一個鍵時,這個鍵的掃描碼就被傳給系統。掃描碼是跟具體的硬件相關的,同一個鍵,在不一樣鍵盤上的掃描碼有可能不一樣。鍵盤控制器就是將這個掃描碼傳給計算機,而後交給鍵盤驅動程序。鍵盤驅動程序會完成相關的工做,並把這個掃描碼轉換爲鍵盤虛擬碼。什麼是虛擬碼呢?由於掃描碼與硬件相關,不具備通用性,爲了統一鍵盤上全部鍵的編碼,因而就提出了虛擬碼概念。不管什麼鍵盤,同一個按鍵的虛擬碼老是相同的,這樣程序就能夠識別了。簡單點說,虛擬碼就是咱們常常能夠看到的像VK_A,VK_B這樣的常數,好比鍵A的虛擬碼是65,寫成16進制就是&H41,注意,人們常常用16進制來表示虛擬碼。當鍵盤驅動程序把掃描碼轉換爲虛擬碼後,會把這個鍵盤操做的掃描碼和虛擬碼還有其它信息一塊兒傳遞給操做系統。而後操做系統則會把這些信息封裝在一個消息中,並把這個鍵盤消息插入到消息列隊。最後,要是不出意外的話,這個鍵盤消息最終會被送到當前的活動窗口那裏,活動窗口所在的應用程序接收到這個消息後,就知道鍵盤上哪一個鍵被按下,也就能夠決定該做出什麼響應給用戶了。
這個過程能夠簡單的以下表示:
用戶按下鍵盤上的一個鍵 >>>>> 鍵盤控制器就把這個鍵的掃描碼傳給計算機,而後交給鍵盤驅動程序 >>>>> 鍵盤驅動程序會把這個掃描碼轉換爲鍵盤虛擬碼(VK_A,VK_B這樣的常數,好比鍵A的虛擬碼是65,寫成16進制就是&H41)傳給操做系統 >>>>> 操操做系統則會把這些信息封裝在一個消息中,並把這個鍵盤消息插入到消息列隊 >>>>> 鍵盤消息被髮送到當前活動窗口
明白了這個過程,咱們就能夠編程實如今其中的某個環節來模擬鍵盤操做了。在Delphi中,有多種方法能夠實現鍵盤模擬,咱們就介紹幾種比較典型的。
(一)、應用程序級模擬(只針對某個程序,我稱之爲局部模擬)
windows提供了幾個這樣的API函數能夠實現直接向目標程序發送消息的功能,經常使用的有SendMessage和PostMessage,它們的區別是PostMessage函數直接把消息仍給目標程序就無論了,而SendMessage把消息發出去後,還要等待目標程序返回些什麼東西纔好。這裏要注意的是,模擬鍵盤消息必定要用PostMessage函數纔好,用SendMessage是不正確的(由於模擬鍵盤消息是不須要返回值的,否則目標程序會沒反應),切記切記!
PostMessage函數的delphi聲明以下:
PostMessage(
hWnd: HWND; {目標程序上某個控件的句柄}
Msg: UINT; {消息的類型}
wParam: WPARAM; {32位指定附加的消息特定的信息}
lParam: LPARAM {32位指定附加的消息特定的信息}
): BOOL;
參數hwnd 是你要發送消息的目標程序上某個控件的句柄,參數Msg 是消息的類型,表示你要發送什麼樣的消息,最後wParam 和lParam這兩個參數是隨消息附加的數據,具體內容要由消息決定。
再來看看Msg 這個參數,要模擬按鍵就靠這個了。
鍵盤消息經常使用的有以下幾個:
WM_KEYDOWN 表示一個普通鍵被按下
WM_KEYUP 表示一個普通鍵被釋放
WM_SYSKEYDOWN 表示一個系統鍵被按下,好比Alt鍵
WM_SYSKEYUP 表示一個系統鍵被釋放,好比Alt鍵
若是你肯定要發送以上幾個鍵盤消息,那麼再來看看如何肯定鍵盤消息中的wParam 和lParam 這兩個參數。在一個鍵盤消息中,wParam 參數的含義較簡單,它表示你要發送的鍵盤事件的按鍵虛擬碼,好比你要對目標程序模擬按下A鍵,那麼wParam 參數的值就設爲VK_A ,至於lParam 這個參數就比較複雜了,由於它包含了多個信息,通常能夠把它設爲0。即
PostMessage (MyHwnd, WM_KEYDOWN, key, 0);
可是若是你想要你的模擬更真實一些,那麼建議你仍是設置一下這個參數。那麼咱們就詳細瞭解一下lParam 吧。
lParam 是一個32 bit的參數,它在內存中佔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碼。它的delphi 聲明以下:
MapVirtualKey(
uCode: UINT; {key code, scan code or virtual key}
uMapType: UINT {flags for translation mode}
): UINT; {returns translated key code}
參數uCode 表示待轉換的碼,參數uMapType 表示從什麼轉換爲何,若是是虛擬碼轉掃描碼,則uMapType 設置爲0,若是是虛擬掃描碼轉虛擬碼,則wMapType 設置爲1,若是是虛擬碼轉ASCII碼,則uMapType 設置爲2.相信有了這些,咱們就能夠構造鍵盤事件的lParam參數了。
下面給出一個構造lParam參數的函數:
function VKB_param(VirtualKey:Integer;flag:Integer):Integer; //函數名
var
s,Firstbyte,Secondbyte:String;
S_code:Integer;
Begin
if flag=1 then //按下鍵
begin
Firstbyte :='00'
end
else //彈起鍵
begin
Firstbyte :='C0'
end;
S_code:= MapVirtualKey(VirtualKey, 0);
Secondbyte:='00'+inttostr(s_code);
Secondbyte:=copy(Secondbyte,Length(Secondbyte)-1,2);
s:='$'+Firstbyte + Secondbyte + '0001';
Result:=strtoint(s);
End;
使用按鍵的方法:
說明:key爲鍵值,如1鍵[不是數字鍵的1]的值是$31,flag傳遞的是按鍵狀態,1是按下,0是彈起。
lparam := VKB_param(key, 1); {按下鍵}
PostMessage (MyHwnd, WM_KEYDOWN, key, lParam);
lParam := VKB_param(key, 0); {鬆開鍵}
PostMessage (MyHwnd, WM_KEYUP, key, lParam);
var
hwnd, lparam:Cardinal;
begin
hwnd:=FindWindowEx(FindWindow(nil,'無標題 - 記事本'),0,'edit',nil);
lparam := VKB_param(97, 1); {按下鍵}
PostMessage (hwnd,WM_KEYDOWN,vk_F3,lparam) ; //按下F3鍵
//PostMessage (hwnd,WM_CHAR,97,lparam); //輸入字符A (edit控件接收字符)
lParam := VKB_param(key, 0); {鬆開鍵}
PostMessage (hwnd,WM_KEYUP,vk_F3,lparam); //釋放F3鍵
end;
模擬鼠標點擊
Var
P1:Tpoint;
Lparam:integer;
begin
GetCursorPos(P1); // 獲取屏幕座標
P1.X:= P1.X+100;
P1.Y:=P1.Y+100;
lparam:=p1.X+ p1.Y shl 16;
sendmessage(h,messages.WM_LBUTTONDOWN ,0,lparam);// 按下鼠標左鍵
sendmessage(h,messages.WM_LBUTTONUP ,0, lparam); //擡起鼠標左鍵
End;
(二)、系統級模擬(針對全部程序的窗口,我稱之爲全局模擬)
模擬全局鍵盤消息常見的能夠有如下一些方法:
1、 用API函數keybd_event,這個函數能夠用來模擬一個鍵盤事件
keybd_event(
bVk: Byte; {virtual-key code}
bScan: Byte; {scan-code}
dwFlags: DWORD; {option flags}
dwExtraInfo: DWORD {additional information about the key}
); {this procedure does not return a value}
keybd_event(VK_A, 0, 0, 0) '按下A鍵
keybd_event (VK_A, 0, KEYEVENTF_KEYUP, 0) '釋放A鍵
注意有時候按鍵的速度不要太快,不然會出問題,能夠用API函數Sleep來進行延時,delphi聲明以下:
Sleep(
dwMilliseconds: DWORD {specifies the number of milliseconds to pause}
);
參數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函數來模擬鍵盤事件。
模擬按下鼠標左鍵
mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);//鼠標左鍵按下mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0);//鼠標左鍵彈起
2、 SendInput函數也能夠模擬全局鍵盤鼠標事件。
SendInput能夠直接把一條消息插入到消息隊列中,算是比較底層的了。
SendInput的參數其實很簡單,在Windows.pas就有函數的聲明以下:
function SendInput (
cInputs: UINT; pInputs中記錄數組的元素數目
var pInputs: TInput; TInput類型記錄數組的第1個元素
cbSize: Integer 定義TInput的大小,通常爲SizeOf(TInput)
): UINT; stdcall;
cInputs:定義pInputs中記錄數組的元素數目。pInputs:TInput類型記錄數組的第1個元素。每一個元素表明插人到系統消息隊列的鍵盤或鼠標事件。cbSize:定義TInput的大小,通常爲SizeOf(TInput)。函數返回成功插入系統消息隊列中事件的數目,失敗返回0。調用SendInput關鍵的就是要搞清楚它的幾個記錄結構的意思,在Windows.pas中對TInput的聲明以下:
tagINPUT = packed record
Itype: DWORD;
case Integer of
0: (mi: TMouseInput);
1: (ki: TKeybdInput);
2: (hi: THardwareInput);
end;
TInput = tagINPUT;
其中mi、ki、hi是3個共用型的記錄結構,Itype指出記錄結構中所使用的類型,它有3個值。INPUT_MOUSE:表示使用mi記錄結構,忽略ki和hi;INPUT_KEYBOARD:表示使用ki記錄結構,忽略mi和hi。
(1)、鍵盤模擬
TKeybdInput記錄結構的聲明以下:
tagKEYBDINPUT = packed record
wVk: WORD; 是將要操做的按鍵的虛鍵碼
wScan: WORD; 是安全碼,通常不用
dwFlags: DWORD; 指定鍵盤所進行的操做,爲0時表示按下某鍵,KEYEVENTF_KEYUP表示放開某鍵
time: DWORD;
dwExtraInfo: DWORD; 是擴展信息,可使用API函數GetMessageExtraInfo的返回值
end;
TKeybdInput = tagKEYBDINPUT;
其中wVk是將要操做的按鍵的虛鍵碼。wScan是安全碼,通常不用。dwFlags指定鍵盤所進行的操做,爲0時表示按下某鍵,KEYEVENTF_KEYUP表示放開某鍵。time是時間戳,可使用API函數GetTickCount的返回值。dwExtraInfo是擴展信息,可使用API函數GetMessageExtraInfo的返回值。例如擊鍵「A」的程序以下:
procedure KeyPressA;
var
Inputs : array [0..1] of TInput;
begin
Inputs[0].Itype:=INPUT_KEYBOARD;
with Inputs[0].ki do
begin
wVk:=VK_A;
wScan:=0;
dwFlags:=0;
time:=GetTickCount;
dwExtraInfo:=GetMessageExtraInfo;
end;
Inputs[1].Itype:=INPUT_KEYBOARD;
with Inputs[1].ki do
begin
wVk:=VK_A;
wScan:=0;
dwFlags:=KEYEVENTF_KEYUP;
time:=GetTickCount;
dwExtraInfo:=GetMessageExtraInfo;
end;
SendInput(2,Inputs[0],SizeOf(TInput));
end;
注意:在Windows.pas單元中並無字母和數字的虛鍵碼的聲明,在我寫的SIMouseKeyboard.pas單元文件中對全部的虛鍵碼進行了從新聲明,包含了字母、數字和標點符號。
(2)、鼠標模擬
TMouseInput記錄結構的聲明以下:
tagMOUSEINPUT = packed record
dx: Longint;
dy: Longint;
mouseData: DWORD;
dwFlags: DWORD;
time: DWORD;
dwExtraInfo: DWORD;
end;
TMouseInput = tagMOUSEINPUT;
其中dx、dy是鼠標移動時的座標差(不是象素單位),在鼠標移動時有效。mouseData是鼠標滾輪滾動值,在滾動鼠標滾輪時有效。當mouseData小於0時向下滾動,當mouseData大於0時向上滾動,mouseData的絕對值通常設爲120。dwFlags指定鼠標所進行的操做,例如,MOUSEEVENTF_MOVE表示移動鼠標,MOUSEEVENTF_LEFTDOWN表示按下鼠標左鍵,MOUSEEVENTF_LEFTUP表示放開鼠標左鍵。time是時間戳,可使用API函數GetTickCount的返回值。dwExtraInfo是擴展信息,可使用API函數GetMessageExtraInfo的返回值。例如單擊鼠標左鍵的程序以下:
procedure MouseClick;
var
Inputs : array [0..1] of TInput;
begin
Inputs[0].Itype:=INPUT_MOUSE;
with Inputs[0].mi do
begin
dx:=0;
dy:=0;
mouseData:=0;
dwFlags:=MOUSEEVENTF_LEFTDOWN;
time:=GetTickCount;
dwExtraInfo:=GetMessageExtraInfo;
end;
Inputs[1].Itype:=INPUT_MOUSE;
with Inputs[1].mi do
begin
dx:=0;
dy:=0;
mouseData:=0;
dwFlags:=MOUSEEVENTF_LEFTUP;
time:=GetTickCount;
dwExtraInfo:=GetMessageExtraInfo;
end;
SendInput(2,Inputs[0],SizeOf(TInput));
end;
鼠標的移動老是很麻煩,上面的dx、dy不是以象素爲單位的,而是以鼠標設備移動量爲單位的,它們之間的比值受鼠標移動速度設置的影響。具體的解決方法我已經在《Delphi下利用WinIo模擬鼠標鍵盤詳解》一文中進行了討論,這裏再也不重複。dwFlags能夠設置一個MOUSEEVENTF_ABSOLUTE標誌,這使得能夠用另一種方法移動鼠標。當dwFlags設置了MOUSEEVENTF_ABSOLUTE標誌,dx、dy爲屏幕座標值,表示將鼠標移動到dx,dy的位置。可是這個座標值也不是以象素爲單位的。這個值的範圍是0到65535($FFFF),當dx等於0、dy等於0時表示屏幕的最左上角,當dx等於6553五、dy等於65535時表示屏幕的最右下角,至關於將屏幕的寬和高分別65536等分。API函數GetSystemMetrics(SM_CXSCREEN)能夠返回屏幕的寬度,函數GetSystemMetrics(SM_CYSCREEN)能夠返回屏幕的高度,利用屏幕的寬度和高度就能夠將象素座標換算成相應的dx、dy。注意:這種換算最多會出現1象素的偏差。例如:將鼠標指針移動到屏幕150,120座標處的程序以下:
procedure MouseMove;
var
Input : TInput;
begin
Input.Itype:=INPUT_MOUSE;
with Input.mi do
begin
dx:=($FFFF div (GetSystemMetrics(SM_CXSCREEN)-1)) * 150;
dy:=($FFFF div (GetSystemMetrics(SM_CYSCREEN)-1)) * 120;
mouseData:=0;
dwFlags:=MOUSEEVENTF_MOVE or MOUSEEVENTF_ABSOLUTE;
time:=GetTickCount;
dwExtraInfo:=GetMessageExtraInfo;
end;
SendInput(1,Input,SizeOf(TInput));
end;
(3)、SendInput與WInIo的對比
在《Delphi下利用WinIo模擬鼠標鍵盤詳解》一文中我已經說了WinIo的不少缺點,SendInput幾乎沒有這些缺點。SendInput的模擬要比WinIo簡單的多。事件是被直接插入到系統消息隊列的,因此它的速度比WinIo要快。系統也會保證數據的完整性,不會出現數據包混亂的狀況。利用「絕對移動」能夠將鼠標指針移動到準確的位置,同鼠標的配置隔離不會出現兼容性的問題。SendInput的缺點也是最要命的,它會被一些程序屏蔽。因此說在SendInput與WInIo均可以使用的狀況下優先考慮SendInput。另外SendInput與WInIo能夠接合使用,一些程序對鼠標左鍵單擊敏感,可使用WinIo模擬鼠標左鍵單擊,其它操做由SendInput模擬。
3、用全局鉤子也能夠模擬鍵盤消息。若是你對windows中消息鉤子的用法已經有所瞭解,那麼你能夠經過設置一個全局HOOK來模擬鍵盤消息,好比,你能夠用WH_JOURNALPLAYBACK這個鉤子來模擬按鍵。WH_JOURNALPLAYBACK是一個系統級的全局鉤子,它和WH_JOURNALRECORD的功能是相對的,經常使用它們來記錄並回放鍵盤鼠標操做。WH_JOURNALRECORD鉤子用來將鍵盤鼠標的操做忠實地記錄下來,記錄下來的信息能夠保存到文件中,而WH_JOURNALPLAYBACK則能夠重現這些操做。固然亦能夠單獨使用WH_JOURNALPLAYBACK來模擬鍵盤操做。
SetWindowsHookEx函數,它能夠用來安裝消息鉤子:
SetWindowsHookEx(
idHook: Integer; {hook type flag}
lpfn: TFNHookProc; {a pointer to the hook function}
hmod: HINST; {a handle to the module containing the hook function}
dwThreadId: DWORD {the identifier of the associated thread}
): HHOOK;
先安裝WH_JOURNALPLAYBACK這個鉤子,而後你須要本身寫一個鉤子函數,在系統調用它時,把你要模擬的事件傳遞給鉤子參數lParam所指向的EVENTMSG區域,就能夠達到模擬按鍵的效果。不過用這個鉤子模擬鍵盤事件有一個反作用,就是它會鎖定真實的鼠標鍵盤,不過若是你就是想在模擬的時候不會受真實鍵盤操做的干擾,那麼用用它卻是個不錯的主意。
在不須要監視系統消息時須要調用提供UnHookWindowsHookEx來解除對消息的監視。 下面來創建程序,在Delphi中創建一個工程,在Form1上添加3個按鈕用於程序操做。另外再添加一個按鈕控件和一個Edit控件用於驗證操做。
下面是Form1的所有代碼
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
Button3: TButton;
Edit1: TEdit;
Button4: TButton;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
EventArr:array[0..1000]of EVENTMSG;
EventLog:Integer;
PlayLog:Integer;
hHook,hPlay:Integer;
recOK:Integer;
canPlay:Integer;
bDelay:Bool;
implementation
{$R *.DFM}
Function PlayProc(iCode:Integer;wParam:wParam;lParam:lParam):LRESULT;stdcall;
begin
canPlay:=1;
Result:=0;
if iCode < 0 then // 必須將消息傳遞到消息鏈的下一個接受單元
Result := CallNextHookEx(hPlay,iCode,wParam,lParam)
else if iCode = HC_SYSMODALON then
canPlay:=0
else if iCode = HC_SYSMODALOFF then
canPlay:=1
else if ((canPlay =1 )and(iCode=HC_GETNEXT)) then begin
if bDelay then begin
bDelay:=False;
Result:=50;
end;
pEventMSG(lParam)^:=EventArr[PlayLog];
end
else if ((canPlay = 1)and(iCode = HC_SKIP))then begin
bDelay := True;
PlayLog:=PlayLog+1;
end;
if PlayLog>=EventLog then begin
UNHookWindowsHookEx(hPlay);
end;
end;
function HookProc(iCode:Integer;wParam:wParam;lParam:lParam):LRESULT;stdcall;
begin
recOK:=1;
Result:=0;
if iCode < 0 then
Result := CallNextHookEx(hHook,iCode,wParam,lParam)
else if iCode = HC_SYSMODALON then
recOK:=0
else if iCode = HC_SYSMODALOFF then
recOK:=1
else if ((recOK>0) and (iCode = HC_ACTION)) then begin
EventArr[EventLog]:=pEventMSG(lParam)^;
EventLog:=EventLog+1;
if EventLog>=1000 then begin
UnHookWindowsHookEx(hHook);
end;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Button1.Caption:= '紀錄';
Button2.Caption:= '中止';
Button3.Caption:= '回放';
Button4.Caption:= '範例';
Button2.Enabled:=False;
Button3.Enabled:=False;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
EventLog:=0; // 創建鍵盤鼠標操做消息紀錄鏈
hHook:=SetwindowsHookEx(WH_JOURNALRECORD,HookProc,HInstance,0);
Button2.Enabled:=True;
Button1.Enabled:=False;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
UnHookWindowsHookEx(hHook);
hHook:=0;
Button1.Enabled:=True;
Button2.Enabled:=False;
Button3.Enabled:=True;
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
PlayLog:=0; //創建鍵盤鼠標操做消息紀錄回放鏈
hPlay:=SetwindowsHookEx(WH_JOURNALPLAYBACK,PlayProc,
HInstance,0);
Button3.Enabled:=False;
end;
end.
2、驅動級模擬 (繞過了windows消息)
有一些使用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,獲得斷碼,表示釋放這個鍵
好了,如今的問題就是在delphi中如何向端口寫入數據了。由於在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個文件拷貝到你的程序的文件夾下。
下面給出使用WINIO模擬按鍵的單元和使用方法:
{****************************************************************************}
unit MNwinio;
interface
const
KBC_KEY_CMD = $64; //鍵盤命令端口
KBC_KEY_DATA = $60; //鍵盤數據端口
implementation
function InitializeWinIo: Boolean; stdcall; external 'WinIo.dll' name 'InitializeWinIo';
{
本函數初始化WioIO函數庫。
必須在調用全部其它功能函數以前調用本函數。
若是函數調用成功,返回值爲非零值。
若是調用失敗,則返回值爲0。
procedure TForm1.FormActivate(Sender: TObject); //一般在程序啓動時調用
begin
if InitializeWinIo = False then
begin
Messagebox(handle, '初始化失敗!', '提示', MB_OK + MB_IconError)
end;
end;
}
function InstallWinIoDriver(pszWinIoDriverPath: PString; IsDemandLoaded: boolean
= false): Boolean; stdcall; external 'WinIo.dll' name 'InstallWinIoDriver';
function RemoveWinIoDriver: Boolean; stdcall; external 'WinIo.dll' name
'RemoveWinIoDriver';
function GetPortVal(PortAddr: Word; PortVal: PDWord; bSize: Byte): Boolean;
stdcall; external 'WinIo.dll' name 'GetPortVal';
function SetPortVal(PortAddr: Word; PortVal: DWord; bSize: Byte): Boolean;
stdcall; external 'WinIo.dll' name 'SetPortVal';
function GetPhysLong(PhysAddr: PByte; PhysVal: PDWord): Boolean; stdcall;
external 'WinIo.dll' name 'GetPhysLong';
function SetPhysLong(PhysAddr: PByte; PhysVal: DWord): Boolean; stdcall; external
'WinIo.dll' name 'SetPhysLong';
function MapPhysToLin(PhysAddr: PByte; PhysSize: DWord; PhysMemHandle: PHandle):
PByte; stdcall; external 'WinIo.dll' name 'MapPhysToLin';
function UnMapPhysicalMemory(PhysMemHandle: THandle; LinAddr: PByte): Boolean;
stdcall; external 'WinIo.dll' name 'UnmapPhysicalMemory';
procedure ShutdownWinIo; stdcall; external 'WinIo.dll' name 'ShutdownWinIo';
{ 本函數在內存中清除WinIO庫
本函數必須在停止應用函數以前或者再也不須要WinIO庫時調用
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); //一般在程序關閉時調用
begin
ShutdownWinIo;
end;
}
{**********以上爲WINIO.dll中API函數的調用***************}
procedure KBCWait4IBE; //等待鍵盤緩衝區爲空
var
dwVal: DWord;
begin
repeat
GetPortVal($64, @dwVal, 1);
{這句表示從&H64端口讀取一個字節並把讀出的數據放到變量dwVal中. GetPortVal函數的用法是 GetPortVal (端口號,存放讀出數據的變量地址,讀入的長度}
until (dwVal and $2) = 0;
{上面的是一個根據KBC規範寫的過程,它的做用是在向鍵盤端口寫入數據前等待一段時間,後面將會用到。}
end;
procedure MyKeyDown(vKeyCoad: Integer); // 這個用來模擬按下鍵,參數vKeyCoad傳入按鍵的虛擬碼
var
btScancode: DWord;
begin
btScancode := MapVirtualKey(vKeyCoad, 0);
KBCWait4IBE; // 發送數據前應該先等待鍵盤緩衝區爲空
SetPortVal($64, $D2, 1); // 發送鍵盤寫入命令
{SetPortVal函數用於向端口寫入數據,它的用法是:SetPortVal(端口號,欲寫入的數據,寫入數據的長度)}
KBCWait4IBE;
SetPortVal($60, btScancode, 1); //寫入按鍵信息,按下鍵
end;
procedure MyKeyUp(vKeyCoad: Integer); //這個用來模擬釋放鍵,參數vKeyCoad傳入按鍵的虛擬碼
var
btScancode: DWord;
begin
btScancode := MapVirtualKey(vKeyCoad, 0);
KBCWait4IBE;
SetPortVal($64, $D2, 1);
KBCWait4IBE;
SetPortVal($64, (btScancode or $80), 1);
end;
end.
{
使用方法:
如模擬按鍵 1
MyKeyDown($31);
Sleep(50);
MyKeyUp($31);
}