C#模擬鍵盤輸入、鍵狀態和監聽鍵盤消息
模擬鍵盤輸入
模擬鍵盤輸入的功能需要依賴Windows函數實現,這個函數是SendInput,它是專門用來模擬鍵盤、鼠標等設備輸入的函數。
另外和鍵盤輸入相關的函數還有SendKeys,它是System.Windows.Forms. SendKeys,只能在WinFrom項目中使用,并且它的所有功能都可以由SendInput來實現。
另一個是keybd_event函數,這個函數依然是有用的,但是目前官方已經推薦使用SendInput替代它了。
SendInput的定義
[DllImport("user32.dll")]
static extern uint SendInput(int nInputs,INPUT[] pInputs,int cbSize);
INPUT對象中保存了輸入內容,nInputs和cbSize代表pInputs的長度和INPUT結構的大小,這兩個參數能幫助SendInput正確解析INPUT對象。返回值0表示失敗,非零表示正確執行。
INPUT的定義
[StructLayout(LayoutKind.Sequential)]
struct KEYBDINPUT {
public ushort wVk;
public ushort wScan;
public uint dwFlags;
public uint time;
public IntPtr dwExtraInfo;
}
[StructLayout(LayoutKind.Sequential)]
struct HARDWAREINPUT {
public uint uMsg;
public ushort wParamL;
public ushort wParamH;
}
[StructLayout(LayoutKind.Sequential)]
struct MOUSEINPUT {
public int dx;
public int dy;
public uint mouseData;
public uint dwFlags;
public uint time;
public IntPtr dwExtraInfo;
}
[StructLayout(LayoutKind.Explicit)]
struct MOUSEKEYBDHARDWAREINPUT {
[FieldOffset(0)]
public HARDWAREINPUT hi;
[FieldOffset(0)]
public KEYBDINPUT ki;
[FieldOffset(0)]
public MOUSEINPUT mi;
}
[StructLayout(LayoutKind.Sequential)]
struct INPUT {
public uint type;
public MOUSEKEYBDHARDWAREINPUT mkhi;
}
INPUT結構中的type表示消息類型,值為1表示鍵盤消息。mkhi表示具體的消息內容,它可以模擬三類消息,其中鍵盤消息使用KEYBDINPUT表示,其它消息類型的結構不在這里介紹(雖然用不到MOUSEINPUT等結構,但是它們的定義不能省略,否則SendInput無法正確解析INPUT中的具體內容)。
?FieldOffset(0)將三個結構的起始都放在0位置,所以只能使用其中一個內容,因為一個INPUT也只能表示一個消息,這樣設計可以節省空間。
KEYBDINPUT結構中的wVK表示虛擬鍵碼 ,dwFlags的第一位bit默認0表示鍵盤按下事件,1表示鍵盤釋放事件。
虛擬鍵碼是一種能讓Windows以與設備無關的方式處理鍵盤的技術,可以簡單理解為:鍵盤上的每個鍵用一個數字來表示。
模擬A鍵
INPUT[] inputs = new INPUT[2];
inputs[0]=new INPUT {
type=1,
mkhi=new MOUSEKEYBDHARDWAREINPUT {
ki=new KEYBDINPUT {
wVk=0x41
}
}
};
inputs[1]=new INPUT {
type=1,
mkhi=new MOUSEKEYBDHARDWAREINPUT {
ki=new KEYBDINPUT {
wVk=0x41,
dwFlags=2
}
}
};
SendInput(inputs.Length,inputs,Marshal.SizeOf(inputs[0]));
A鍵的虛擬鍵碼是0x41。type=1表示這是鍵盤消息,dwFlags=2表示鍵盤釋放事件。
這里INPUT數組模擬的就是使用物理鍵盤A鍵的過程。inputs[0]模擬A鍵按下,inputs[1]模擬A鍵釋放。
模擬Ctrl+A
INPUT[] inputs = new INPUT[4];
inputs[0]=new INPUT {
type=1,
mkhi=new MOUSEKEYBDHARDWAREINPUT {
ki=new KEYBDINPUT {
wVk=0x11
}
}
};
inputs[1]=new INPUT {
type=1,
mkhi=new MOUSEKEYBDHARDWAREINPUT {
ki=new KEYBDINPUT {
wVk=0x41
}
}
};
inputs[2]=new INPUT {
type=1,
mkhi=new MOUSEKEYBDHARDWAREINPUT {
ki=new KEYBDINPUT {
wVk=0x41,
dwFlags=2
}
}
};
inputs[3]=new INPUT {
type=1,
mkhi=new MOUSEKEYBDHARDWAREINPUT {
ki=new KEYBDINPUT {
wVk=0x11,
dwFlags=2,
}
}
};
SendInput(inputs.Length,inputs,Marshal.SizeOf(inputs[0]));
0x11是Ctrl的虛擬鍵碼,這里模擬了按下Ctrl鍵,按下A鍵,釋放A鍵,釋放Ctrl鍵的過程,實現了Ctrl+A的組合鍵效果。
SendInput除了能模擬擊鍵消息外還可以在文本輸入中模擬字符消息。
KEYBDINPUT結構的wScan表示字符內容,將dwFlags的第二位bit置1表示使用wScan屬性而非wVK。
文本輸入
string ntext = "你好";
INPUT[] inputs = new INPUT[ntext.Length*2];
for(int i = 0;i<ntext.Length;i++) {
ushort ch = ntext[i];
inputs[i*2]=new INPUT {
type=1,
mkhi=new MOUSEKEYBDHARDWAREINPUT {
ki=new KEYBDINPUT {
wScan=ch,
dwFlags=4
}
}
};
inputs[i*2+1]=new INPUT {
type=1,
mkhi=new MOUSEKEYBDHARDWAREINPUT {
ki=new KEYBDINPUT {
wScan=ch,
dwFlags=4|2
}
}
};
}
SendInput(inputs.Length,inputs,Marshal.SizeOf(inputs[0]));
鍵狀態
有時需要知道鍵盤按鍵的當前狀態,可以使用GetKeyState函數。
GetKeyState的定義
[DllImport("user32.dll")]
static extern short GetKeyState(int VKey);
參數是鍵的虛擬碼,對于開關鍵(Caps Look、Num Lock和Scroll Lock),返回值1表示開啟狀態。對于其它鍵返回負數表示按下狀態。
CapsLock鍵狀態
short iState = GetKeyState(0x14);
監聽鍵盤消息
對于WinForm和WPF程序,要監聽輸入到本程序的鍵盤消息直接使用窗口的KeyDown和KeyUp事件即可。
對于其它鍵盤消息(即給本程序以外的鍵盤消息),需要使用鉤子(hook)。
鉤子是Windows系統消息處理機制中的一個節點,可以安裝鉤子來監聽系統中的Windows消息。
Windows消息分很多種,對于特定的一類消息需要使用對應的特定類型的鉤子,這里只介紹鍵盤消息的鉤子。
鉤子的安裝需要調用系統SetWindowsHookEx方法。
SetWindowsHookEx的定義
[DllImport("user32.dll")]
static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hmod, int threadID);
idHook等于13表示全局鍵盤消息鉤子,lpfn代表鍵盤消息處理程序,返回非IntPtr.Zero表示安裝成功。
安裝鉤子
delegate int HookProc(int code,IntPtr wParam,IntPtr lParam);
static HookProc KeyboardProc;
static void InstallKeyboardHook() {
KeyboardProc=KeyboardHookCallback;
pKeyboardHook=SetWindowsHookEx(13,keyboardProc,IntPtr.Zero,0);
}
KeyboardHookCallback就是自定義的具體處理鍵盤消息的方法。
消息處理
[DllImport("user32.dll")]
static extern int CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
static int KeyboardHookCallback(int code,IntPtr wParam,IntPtr lParam) {
if(code<0)
return CallNextHookEx(IntPtr.Zero,code,wParam,lParam);
int vkCode = Marshal.ReadInt32(lParam);
System.Diagnostics.Debug.Write(vkCode+" ");
long downup = (long)wParam;
switch(downup) {
case 256:
System.Diagnostics.Debug.WriteLine("down");
break;
case 257:
System.Diagnostics.Debug.WriteLine("up");
break;
case 260:
System.Diagnostics.Debug.WriteLine("sys_down");
break;
case 261:
System.Diagnostics.Debug.WriteLine("sys_up");
break;
}
return CallNextHookEx(IntPtr.Zero,code,wParam,lParam);
}
從lParam中讀取鍵的虛擬碼(lParam其實是指向類似前文提到的KEYBDINPUT結構的指針),wParam表示擊鍵事件的類型。CallNextHookEx將消息傳遞給下一個消息處理節點。
?使用前文提到的SendInput方法模擬鍵盤輸入也能被鉤子監聽到。
?應避免在消息處理過程中進行耗時操作。
卸載鉤子需要使用UnhookWindowsHookEx
UnhookWindowsHookEx的定義
[DllImport("user32.dll")]
static extern bool UnhookWindowsHookEx(IntPtr pHookHandle);
傳入SetWindowsHookEx的返回值即可,返回true則卸載成功。
浙公網安備 33010602011771號