原文地址:http://www.cnblogs.com/youzai/archive/2008/05/19/1202732.htmlhtml
要實現一個屏幕鍵盤,須要監聽全部鍵盤事件,不管窗體是否被激活。所以須要一個全局的鉤子,也就
是系統範圍的鉤子。函數
什麼是鉤子(Hook)ui
鉤子(Hook)是Windows提供的一種消息處理機制平臺,是指在程序正常運行中接受信息以前預先
啓動的函數,用來檢查和修改傳給該程序的信息,(鉤子)其實是一個處理消息的程序段,通
過系統調用,把它掛入系統。每當特定的消息發出,在沒有到達目的窗口前,鉤子程序就先捕獲
該消息,亦即鉤子函數先獲得控制權。這時鉤子函數便可以加工處理(改變)該消息,也能夠不
做處理而繼續傳遞該消息,還能夠強制結束消息的傳遞。注意:安裝鉤子函數將會影響系統的性
能。監測「系統範圍事件」的系統鉤子特別明顯。由於系統在處理全部的相關事件時都將調用您的
鉤子函數,這樣您的系統將會明顯的減慢。因此應謹慎使用,用完後當即卸載。還有,因爲您可
以預先截獲其它進程的消息,因此一旦您的鉤子函數出了問題的話必將影響其它的進程。this
鉤子的做用範圍
一共有兩種範圍(類型)的鉤子,局部的和遠程的。局部鉤子僅鉤掛本身進程的事件。遠程的鉤
子還能夠將鉤掛其它進程發生的事件。遠程的鉤子又有兩種: 基於線程的鉤子將捕獲其它進程中
某一特定線程的事件。簡言之,就是能夠用來觀察其它進程中的某一特定線程將發生的事件。 系
統範圍的鉤子將捕捉系統中全部進程將發生的事件消息。 spa
Hook 類型
Windows共有14種Hooks,每一種類型的Hook可使應用程序可以監視不一樣類型的系統消息處理機
制。下面描述全部能夠利用的Hook類型的發生時機。詳細內容能夠查閱MSDN,這裏只介紹咱們將要
用到的兩種類型的鉤子。
(1)WH_KEYBOARD_LL Hook
WH_KEYBOARD_LL Hook監視輸入到線程消息隊列中的鍵盤消息。線程
(2)WH_MOUSE_LL Hook
WH_MOUSE_LL Hook監視輸入到線程消息隊列中的鼠標消息。設計
下面的 class 把 API 調用封裝起來以便調用。
code
1

// NativeMethods.cs
2

using System;
3

using System.Runtime.InteropServices;
4

using System.Drawing;
5
6

namespace CnBlogs.Youzai.ScreenKeyboard
{
7
[StructLayout(LayoutKind.Sequential)]
8
internal struct MOUSEINPUT {
9
public int dx;
10
public int dy;
11
public int mouseData;
12
public int dwFlags;
13
public int time;
14
public IntPtr dwExtraInfo;
15
}
16
17
[StructLayout(LayoutKind.Sequential)]
18
internal struct KEYBDINPUT {
19
public short wVk;
20
public short wScan;
21
public int dwFlags;
22
public int time;
23
public IntPtr dwExtraInfo;
24
}
25
26
[StructLayout(LayoutKind.Explicit)]
27
internal struct Input {
28
[FieldOffset(0)]
29
public int type;
30
[FieldOffset(4)]
31
public MOUSEINPUT mi;
32
[FieldOffset(4)]
33
public KEYBDINPUT ki;
34
[FieldOffset(4)]
35
public HARDWAREINPUT hi;
36
}
37
38
[StructLayout(LayoutKind.Sequential)]
39
internal struct HARDWAREINPUT {
40
public int uMsg;
41
public short wParamL;
42
public short wParamH;
43
}
44
45
internal class INPUT {
46
public const int MOUSE = 0;
47
public const int KEYBOARD = 1;
48
public const int HARDWARE = 2;
49
}
50
51
internal static class NativeMethods {
52
[DllImport("User32.dll", CharSet = CharSet.Auto, SetLastError = false)]
53
internal static extern IntPtr GetWindowLong(IntPtr hWnd, int nIndex);
54
55
[DllImport("User32.dll", CharSet = CharSet.Auto, SetLastError = false)]
56
internal static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
57
58
[DllImport("User32.dll", EntryPoint = "SendInput", CharSet = CharSet.Auto)]
59
internal static extern UInt32 SendInput(UInt32 nInputs, Input[] pInputs, Int32 cbSize);
60
61
[DllImport("Kernel32.dll", EntryPoint = "GetTickCount", CharSet = CharSet.Auto)]
62
internal static extern int GetTickCount();
63
64
[DllImport("User32.dll", EntryPoint = "GetKeyState", CharSet = CharSet.Auto)]
65
internal static extern short GetKeyState(int nVirtKey);
66
67
[DllImport("User32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
68
internal static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
69
}
70
}
安裝鉤子
使用SetWindowsHookEx函數(API函數),指定一個Hook類型、本身的Hook過程是全局仍是局部Hook,
同時給出Hook過程的進入點,就能夠輕鬆的安裝本身的Hook過程。SetWindowsHookEx老是將你的Hook函
數放置在Hook鏈的頂端。你可使用CallNextHookEx函數將系統消息傳遞給Hook鏈中的下一個函數。
對於某些類型的Hook,系統將向該類的全部Hook函數發送消息,這時,
Hook函數中的CallNextHookEx語句將被忽略。全局(遠程鉤子)Hook函數能夠攔截系統中全部線程的某
個特定的消息,爲了安裝一個全局Hook過程,必須在應用程序外創建一個DLL並將該Hook函數封裝到其中,
應用程序在安裝全局Hook過程時必須先獲得該DLL模塊的句柄。將Dll名傳遞給LoadLibrary 函數,就會得
到該DLL模塊的句柄;獲得該句柄 後,使用GetProcAddress函數能夠獲得Hook過程的地址。最後,使用
SetWindowsHookEx將 Hook過程的首址嵌入相應的Hook鏈中,SetWindowsHookEx傳遞一個模塊句柄,它爲
Hook過程的進入點,線程標識符置爲0,該Hook過程同系統中的全部線程關聯。若是是安裝局部Hook此時
該Hook函數能夠放置在DLL中,也能夠放置在應用程序的模塊段。在C#中經過平臺調用(前文已經介紹過)
來調用API函數。orm
1

public void Start(bool installMouseHook, bool installKeyboardHook)
{
2
if (hMouseHook == IntPtr.Zero && installMouseHook) {
3
MouseHookProcedure = new HookProc(MouseHookProc);
4
hMouseHook = SetWindowsHookEx(
5
WH_MOUSE_LL,
6
MouseHookProcedure,
7
Marshal.GetHINSTANCE(
8
Assembly.GetExecutingAssembly().GetModules()[0]),
9
0
10
);
11
12
if (hMouseHook == IntPtr.Zero) {
13
int errorCode = Marshal.GetLastWin32Error();
14
Stop(true, false, false);
15
16
throw new Win32Exception(errorCode);
17
}
18
}
19
20
if (hKeyboardHook == IntPtr.Zero && installKeyboardHook) {
21
KeyboardHookProcedure = new HookProc(KeyboardHookProc);
22
//install hook
23
hKeyboardHook = SetWindowsHookEx(
24
WH_KEYBOARD_LL,
25
KeyboardHookProcedure,
26
Marshal.GetHINSTANCE(
27
Assembly.GetExecutingAssembly().GetModules()[0]),
28
0);
29
// If SetWindowsHookEx fails.
30
if (hKeyboardHook == IntPtr.Zero) {
31
// Returns the error code returned by the last
32
// unmanaged function called using platform invoke
33
// that has the DllImportAttribute.SetLastError flag set.
34
int errorCode = Marshal.GetLastWin32Error();
35
//do cleanup
36
Stop(false, true, false);
37
//Initializes and throws a new instance of the
38
// Win32Exception class with the specified error.
39
throw new Win32Exception(errorCode);
40
}
41
}
42
}
使用完鉤子後,要進行卸載,這個能夠寫在析構函數中。htm
將這個文件編譯成一個dll,便可在應用程序中調用。經過它提供的事件,即可監聽全部的鍵盤事件。
可是,這隻能監聽鍵盤事件,沒有鍵盤的狀況下,怎麼會有鍵盤事件?其實很簡單,經過SendInput
API函數提供虛擬鍵盤代碼的調用便可模擬鍵盤輸入。下面的代碼模擬一個 KeyDown 和 KeyUp 過程,
把他們鏈接起來就是一次按鍵過程。
本身實現一個 KeyBoardButton 控件用做按鈕,用 Visual Studio 或者 SharpDevelop 爲屏幕鍵盤設計 UI,而後
在這些 Button 的 Click 事件裏面模擬一個按鍵過程。
其中 combinationVKButtonsMap 是一個 IDictionary<short, IList<KeyboardButton>>, key 存儲的是
VK_SHIFT, VK_CONTROL 等組合鍵的鍵盤碼。左右兩個按鈕對應同一個鍵盤碼,所以須要放在一個 List 裏。
標準鍵盤上的每個鍵都有虛擬鍵碼( VK_CODE)與之對應。還有一些其餘的常量,
把它寫在一個靜態 class 裏吧。
屏幕鍵盤必須是一個不能得到輸入焦點的窗體,在這個窗體的構造函數裏,能夠安裝
一個全局鼠標鉤子,再經過調用 SetWindowLong API 函數完成。
1

UserActivityHook hook = new UserActivityHook(true, true);
2

hook.MouseActivity += HookOnMouseActivity;
3
4

private void HookOnMouseActivity(object sener, HookEx.MouseExEventArgs e)
{
5
Point location = e.Location;
6
7
if (e.Button == MouseButtons.Left) {
8
Rectangle captionRect = new Rectangle(this.Location, new Size(this.Width,
9
SystemInformation.CaptionHeight));
10
if (captionRect.Contains(location)) {
11
NativeMethods.SetWindowLong(this.Handle, KeyboardConstaint.GWL_EXSTYLE,
12
(int)NativeMethods.GetWindowLong(this.Handle, KeyboardConstaint.GWL_EXSTYLE)
13
& (~KeyboardConstaint.WS_DISABLED));
14
NativeMethods.SendMessage(this.Handle, KeyboardConstaint.WM_SETFOCUS, IntPtr.Zero, IntPtr.Zero);
15
} else {
16
NativeMethods.SetWindowLong(this.Handle, KeyboardConstaint.GWL_EXSTYLE,
17
(int)NativeMethods.GetWindowLong(this.Handle, KeyboardConstaint.GWL_EXSTYLE) |
18
KeyboardConstaint.WS_DISABLED);
19
}
20
}
21
}
鼠標單擊標題欄,讓屏幕鍵盤能夠接收焦點,並激活,單擊其餘部分則不激活窗體(若是激活了,其餘程序必然取消激活,
輸入就沒法進行了),這樣才能夠進行輸入,而且保證了能夠拖動窗體到其餘位置。
至此,一個屏幕鍵盤程序差很少完成了,可以實現與實際鍵盤徹底同步。至於窗體,按鍵重繪,以及 Num Lock, Caps Lock,
Scroll Lock 等鍵盤燈的模擬,這裏就不講了,若是有興趣,能夠下載完整的代碼。最後咱們的屏幕鍵盤程序運行的效果如
下圖:

點擊下載完整源代碼
