C# - Xây Dựng Ứng Dụng Đơn Giản Với Hook


Trong Windows, các ứng sử của người dùng và các ứng dụng liên lạc với hệ điều hành(HĐH)  thông qua các thông điệp, và "kỹ thuật" Hook chính là chặn bắc các thông điệp ấy phục vụ mục đích cho từng ứng dụng. TRong bài viếc này tôi sẽ làm một ví dụ cụ thể về việc lắng nghe sự kiện nhấn phím(keyboard).




Để hiểu về khái niệm, các bạn có thể tham khảo tài liệu Microsoft tại đây hoặc của một blogger khác tại đây


Dưới đây là hình ảnh một ứng dụng ghi chú có sử dụng phím tắt(hook), ở bấc cứ đâu nếu bạn nhấn ALT->C là dữ liệu trong vùng nhớ đệm(Clipboard) sẽ được Add vào ứng dụng này, để coppy dữ liệu bạn chỉ cần chọn dòng và có thể Ctrl+C để dán, dùng Del/ESC để xóa,  khi tắc đi dữ liệu sẽ được lưu ra file và đọc lên cho lần sau. Do nhu cầu công việc nên tôi chọn cách ghi chú theo từng dòng. Dùng thử tại đây




[caption id="attachment_319" align="aligncenter" width="566"]HoverNote HoverNote[/caption]

Tôi có một ví dụ sau, tôi làm một ứng dụng WPF về cột đèn giao thông(3 màu), và tôi muốn khi nhấn phím R thì đèn đỏ sáng và tương tự với các phím G, Y. Để làm được điều này quả là vô cùng đơn giản với các bạn đã từng làm một App nào đó trên .NET. Xong khi mà App của bạn không được Focus(hoặc không Active) thì xem như không thể sử dụng phím tắt. Chúng ta khắc phục bằng cách dùng Hook, tại ứng dụng của mình, ta đăng ký HOOK_KEYBOARD với HĐH để lắng nghe các thông điệp về Phím, như vậy khi có sự kiện nào về Phím chúng ta sẽ nhận được thông báo từ HĐH dù App không được Focus.


Tôi xây dựng 3 class như sau, để hiểu nó các bạn tham khảo link đầu bài và nhớ reference các thư viện cần thiết.



InterceptKeys: Đăng ký Hook hoặc hủy đăng ký


[code language="csharp"]
#region WINAPI Helper class
/// <summary>
/// Winapi Key interception helper class.
/// </summary>
internal static class InterceptKeys
{
public delegate IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam);
public static int WH_KEYBOARD_LL = 13;

public enum KeyEvent : int
{
WM_KEYDOWN = 256,
WM_KEYUP = 257,
WM_SYSKEYUP = 261,
WM_SYSKEYDOWN = 260
}

public static IntPtr SetHook(LowLevelKeyboardProc proc)
{
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
GetModuleHandle(curModule.ModuleName), 0);
}
}

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool UnhookWindowsHookEx(IntPtr hhk);

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, UIntPtr wParam, IntPtr lParam);

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr GetModuleHandle(string lpModuleName);
}
#endregion
[/code]

RawKeyEventArgs: Truyền tải thông tin cho các sự kiện Keynoard


[code language="csharp"]
/// <summary>
/// Raw KeyEvent arguments.
/// </summary>
public class RawKeyEventArgs : EventArgs
{
/// <summary>
/// VKCode of the key.
/// </summary>
public int VKCode;

/// <summary>
/// WPF Key of the key.
/// </summary>
public Key Key;

/// <summary>
/// Is the hitted key system key.
/// </summary>
public bool IsSysKey;

/// <summary>
/// Create raw keyevent arguments.
/// </summary>
/// <param name="VKCode"></param>
/// <param name="isSysKey"></param>
public RawKeyEventArgs(int VKCode, bool isSysKey)
{
this.VKCode = VKCode;
this.IsSysKey = isSysKey;
this.Key = System.Windows.Input.KeyInterop.KeyFromVirtualKey(VKCode);
}
}
[/code]

KeyboardListener: Quản lý Hook và các event


[code language="csharp"]
public class KeyboardListener : IDisposable
{
/// <summary>
/// Fired when any of the keys is pressed down.
/// </summary>
public event RawKeyEventHandler KeyDown;

/// <summary>
/// Fired when any of the keys is released.
/// </summary>
public event RawKeyEventHandler KeyUp;

/// <summary>
/// Creates global keyboard listener.
/// </summary>
public KeyboardListener()
{
// We have to store the HookCallback, so that it is not garbage collected runtime
hookedLowLevelKeyboardProc = (InterceptKeys.LowLevelKeyboardProc)LowLevelKeyboardProc;

// Set the hook
hookId = InterceptKeys.SetHook(hookedLowLevelKeyboardProc);

// Assign the asynchronous callback event
hookedKeyboardCallbackAsync = new KeyboardCallbackAsync(KeyboardListener_KeyboardCallbackAsync);
}

/// <summary>
/// Destroys global keyboard listener.
/// </summary>
~KeyboardListener()
{
Dispose();
}

#region Inner workings
/// <summary>
/// Hook ID
/// </summary>
private IntPtr hookId = IntPtr.Zero;

/// <summary>
/// Asynchronous callback hook.
/// </summary>
/// <param name="nCode"></param>
/// <param name="wParam"></param>
/// <param name="lParam"></param>
private delegate void KeyboardCallbackAsync(InterceptKeys.KeyEvent keyEvent, int vkCode);

/// <summary>
/// Actual callback hook.
///
/// <remarks>Calls asynchronously the asyncCallback.</remarks>
/// </summary>
/// <param name="nCode"></param>
/// <param name="wParam"></param>
/// <param name="lParam"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.NoInlining)]
private IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam)
{
if (nCode >= 0)
if (wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_KEYDOWN ||
wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_KEYUP ||
wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_SYSKEYDOWN ||
wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_SYSKEYUP)
hookedKeyboardCallbackAsync.BeginInvoke((InterceptKeys.KeyEvent)wParam.ToUInt32(), Marshal.ReadInt32(lParam), null, null);

return InterceptKeys.CallNextHookEx(hookId, nCode, wParam, lParam);
}

/// <summary>
/// Event to be invoked asynchronously (BeginInvoke) each time key is pressed.
/// </summary>
private KeyboardCallbackAsync hookedKeyboardCallbackAsync;

/// <summary>
/// Contains the hooked callback in runtime.
/// </summary>
private InterceptKeys.LowLevelKeyboardProc hookedLowLevelKeyboardProc;

/// <summary>
/// HookCallbackAsync procedure that calls accordingly the KeyDown or KeyUp events.
/// </summary>
/// <param name="keyEvent">Keyboard event</param>
/// <param name="vkCode">VKCode</param>
void KeyboardListener_KeyboardCallbackAsync(InterceptKeys.KeyEvent keyEvent, int vkCode)
{
switch (keyEvent)
{
// KeyDown events
case InterceptKeys.KeyEvent.WM_KEYDOWN:
if (KeyDown != null)
KeyDown(this, new RawKeyEventArgs(vkCode, false));
break;
case InterceptKeys.KeyEvent.WM_SYSKEYDOWN:
if (KeyDown != null)
KeyDown(this, new RawKeyEventArgs(vkCode, true));
break;

// KeyUp events
case InterceptKeys.KeyEvent.WM_KEYUP:
if (KeyUp != null)
KeyUp(this, new RawKeyEventArgs(vkCode, false));
break;
case InterceptKeys.KeyEvent.WM_SYSKEYUP:
if (KeyUp != null)
KeyUp(this, new RawKeyEventArgs(vkCode, true));
break;

default:
break;
}
}

#endregion

#region IDisposable Members

/// <summary>
/// Disposes the hook.
/// <remarks>This call is required as it calls the UnhookWindowsHookEx.</remarks>
/// </summary>
public void Dispose()
{
InterceptKeys.UnhookWindowsHookEx(hookId);
}

#endregion
}
[/code]

3 class trên là tất cả những gì chúng ta cần cho ứng dụng của mình. Xong chưa đủ, dưới đây là KeyCode cho các phím, tất cả đều dùng được nhé.




[code language="csharp"]
public class VKCodeConst
{
public const int LEFTCTRL = 162;
public const int RIGHTCTRL = 163;
public const int LEFTALT = 164;
public const int RIGHTALT = 165;
//public const int LButton = 1; //Left mouse button
//public const int RButton = 2; //Right mouse button
//public const int Cancel = 3; //CANCEL key
//public const int MButton = 4; //Middle mouse button
//public const int Back = 8; //BACKSPACE key
//public const int Tab = 9; //TAB key
//public const int Clear = 12; //CLEAR key
//public const int Return = 13; //ENTER key
public const int SHIFT = 160; //SHIFT key
//public const int Control = 17; //CTRL key
//public const int Menu = 18; //MENU key
//public const int Pause = 19; //PAUSE key
//public const int Capital = 20; //CAPS LOCK key
//public const int Escape = 27; //ESC key
//public const int Space = 32; //SPACEBAR key
//public const int PageUp = 33; //PAGE UP key
//public const int PageDown = 34; //PAGE DOWN key
//public const int End = 35; //END key
//public const int Home = 36; //HOME key
//public const int Left = 37; //LEFT ARROW key
//public const int Up = 38; // ARROW key Up
//public const int Right = 39; //RIGHT ARROW key
//public const int Down = 40; //DOWN ARROW key
//public const int Select = 41; //SELECT key
//public const int Print =42; //PRINT SCREEN key
//public const int Execute = 43; //EXECUTE key
//public const int Snapshot = 44; //SNAPSHOT key
//public const int Insert = 45; //INS key
//public const int Delete = 46; //DEL key
//public const int Help = 47; //HELP key
//public const int Numlock = 144; //NUM LOCK key

public const int NUM0 = 48;
public const int NUMPAD0 = 96;

//public const int F1 = 112;
//public const int F2 = 113;
//public const int F3 = 114;
//public const int F4 = 115;
//public const int F5 = 116;
//public const int F6 = 117;
//public const int F7 = 118;
//public const int F8 = 119;
//public const int F9 = 120;
//public const int F10 = 121;
//public const int F11 = 122;
//public const int F12 = 123;
//public const int F13 = 124;
//public const int F14 = 125;
//public const int F15 = 126;
//public const int F16 = 127;

public const int A = 65;
//public const int B = 66;
public const int C = 67;
//public const int D = 68;
//public const int E = 69;
public const int F = 70;
//public const int G = 71;
//public const int H = 72;
//public const int I = 73;
//public const int J = 74;
//public const int K = 75;
//public const int L = 76;
public const int M = 77;
public const int N = 78;
//public const int O = 79;
public const int P = 80;
//public const int Q = 81;
//public const int R = 82;
//public const int S = 83;
//public const int T = 84;
//public const int U = 85;
public const int V = 86;
//public const int W = 87;
//public const int X = 88;
//public const int Y = 89;
//public const int Z = 90;
}
[/code]

Việc sử dụng hết sức đơn giản, các bạn hãy thử xem - tạo cho ứng dụng của mình những phím tắt nếu cần thiết.




[code language="csharp"]
class UsingHookKey
{
KeyboardListener KListener;

public UsingHookKey()
{
KListener = new KeyboardListener();

KListener.KeyDown += KListener_KeyDown;
}

void KListener_KeyDown(object sender, RawKeyEventArgs args)
{
switch (args.VKCode)
{
case VKCodeConst.N: //Key N
break;
case VKCodeConst.P: //Key P
break;
case VKCodeConst.V: //Key V
break;
}
}
}
[/code]

Chúc các bạn thành công!
Phạm Tuân


Chúc các bạn thành công!
PHẠM TUÂN