螢幕鍵盤需監聽所有鍵盤事件,無論視窗是否啟用。唯有系統級 (全域) 鉤子能攔截所有執行緒訊息。
Windows 訊息處理機制:在訊息到達目標視窗前,鉤子函數先捕獲,可修改/攔截/傳遞訊息。注意:影響系統效能,用畢即卸。
本專案使用兩種低階鉤子 (LL = Low Level):
// NativeMethods.cs
using System;
using System.Runtime.InteropServices;
using System.Drawing;
namespace CnBlogs.Youzai.ScreenKeyboard
{
[StructLayout(LayoutKind.Sequential)]
internal struct MOUSEINPUT
{
public int dx, dy, mouseData, dwFlags, time;
public IntPtr dwExtraInfo;
}
[StructLayout(LayoutKind.Sequential)]
internal struct KEYBDINPUT
{
public short wVk, wScan;
public int dwFlags, time;
public IntPtr dwExtraInfo;
}
[StructLayout(LayoutKind.Explicit)]
internal struct Input
{
[FieldOffset(0)] public int type;
[FieldOffset(4)] public MOUSEINPUT mi;
[FieldOffset(4)] public KEYBDINPUT ki;
[FieldOffset(4)] public HARDWAREINPUT hi;
}
[StructLayout(LayoutKind.Sequential)]
internal struct HARDWAREINPUT
{
public int uMsg;
public short wParamL, wParamH;
}
internal static class INPUT
{
public const int MOUSE = 0;
public const int KEYBOARD = 1;
public const int HARDWARE = 2;
}
internal static class NativeMethods
{
[DllImport("User32.dll", CharSet = CharSet.Auto, SetLastError = false)]
internal static extern IntPtr GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("User32.dll", CharSet = CharSet.Auto, SetLastError = false)]
internal static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("User32.dll", EntryPoint = "SendInput", CharSet = CharSet.Auto)]
internal static extern UInt32 SendInput(UInt32 nInputs, Input[] pInputs, Int32 cbSize);
[DllImport("Kernel32.dll", EntryPoint = "GetTickCount", CharSet = CharSet.Auto)]
internal static extern int GetTickCount();
[DllImport("User32.dll", EntryPoint = "GetKeyState", CharSet = CharSet.Auto)]
internal static extern short GetKeyState(int nVirtKey);
[DllImport("User32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
internal static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
}
}
LayoutKind.Explicit + FieldOffset(4) 實現聯集,同一記憶體區塊切換鍵盤/滑鼠/硬體輸入keybd_event/mouse_eventGetWindowLong/SetWindowLong 不需錯誤碼