如何在C#中制作全局键盘钩子
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何在C#中制作全局键盘钩子相关的知识,希望对你有一定的参考价值。
如何在C#中为Electron.NET应用制作全局键盘挂钩?我相信,只要它可以在控制台应用程序中运行,就应该可以在Electron.Net应用程序中正常运行。
我为这个问题提供了“解决方案”,但是它往往会占用大量CPU(7-10%)。如果没有其他选择,也许有人可以以某种方式使其真正有效:
using System;
using System.Runtime.InteropServices;
using System.Threading;
[DllImport("User32.dll")]
public static extern short GetAsyncKeyState(int vKey);
// Other VKey codes: https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
public enum VKeys {
LBUTTON = 0x01, // Left mouse button
RBUTTON = 0x02, // Right mouse button
KEY_0 = 0x30, // 0 key
KEY_1 = 0x31, // 1 key
KEY_2 = 0x32, // 2 key
KEY_3 = 0x33, // 3 key
KEY_4 = 0x34, // 4 key
KEY_5 = 0x35, // 5 key
KEY_6 = 0x36, // 6 key
KEY_7 = 0x37, // 7 key
KEY_8 = 0x38, // 8 key
KEY_9 = 0x39 // 9 key
}
public void Start()
{
Thread HookThread = new Thread(delegate ()
{
var keys = Enum.GetValues(typeof(VKeys));
while (true)
{
foreach (int key in keys)
{
var ks = GetAsyncKeyState(key);
if (ks < 0)
{
Console.WriteLine($"pressed {key}");
//Thread.Sleep(100);
}
//Thread.Sleep(1); // Even sleeping for '1ms' will delay it too much
}
}
});
HookThread.Start();
}
我发现的很多东西只有在使用WinForms或WPF时才有效。
编辑:
[我尝试了hanabanashiku的this答案,以及我在网上找到的许多其他答案,但是它们似乎都落后于键盘输入,并且似乎永远不会调用它们的回调函数。
我决定用C ++编写键盘钩子,编译为DLL,然后在我的C#代码中引用该DLL,以期使键盘钩子正常运行,并且不会引起任何明显的输入滞后,但是那没用也可以。
键盘钩在C ++中作为.exe运行时运行完美,但是当我将其编译为DLL并在C#中运行时,它引起了与以前相同的问题-大量的输入滞后和回调函数似乎没有被调用。
如果有人想尝试,请在这里找到代码:
KeyboardHook.cpp
#include "KeyboardHook.h"
#include <iostream>
#define __event void KeyDown(int key), KeyUp(int key);
using namespace Hooks;
void KeyDown(int key)
{
std::cout << "KeyDown
";
}
void KeyUp(int key)
{
std::cout << "KeyUp
";
}
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode == HC_ACTION)
{
PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT)lParam;
switch (wParam)
{
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
KeyDown(p->vkCode);
break;
case WM_KEYUP:
case WM_SYSKEYUP:
KeyUp(p->vkCode);
break;
}
}
// Not processing keys so always return CallNextHookEx
return(CallNextHookEx(NULL, nCode, wParam, lParam));
}
void KeyboardHook::Install() {
// Install keyboard hook
keyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProc, 0, 0);
std::cout << "Installed
";
}
void KeyboardHook::Uninstall() {
// Unhook keyboard hook
UnhookWindowsHookEx(keyboardHook);
}
KeyboardHook.h
#include <Windows.h>
HHOOK keyboardHook;
namespace Hooks
{
class KeyboardHook
{
public:
__declspec(dllexport) void Install();
__declspec(dllexport) void Uninstall();
};
}
Program.cs
using System;
using System.Runtime.InteropServices;
namespace HelloMyNameIsSpindiNiceToMeetYou
{
class Program
{
private const string hooksPath = @"C:PathToHooks.dll";
// If EntryPoint doesn't work, yours might be different
// https://docs.microsoft.com/en-us/dotnet/framework/interop/identifying-functions-in-dlls
//
// "For example, you can use dumpbin /exports Hooks.dll [...] to obtain function names."
// you need to be in the folder with the dll for above to work in Command Prompt for VS
[DllImport(hooksPath, EntryPoint = "?Install@KeyboardHook@Hooks@@QEAAXXZ", CallingConvention = CallingConvention.Cdecl)]
private extern static void Install();
static void Main(string[] args)
{
Install();
// keep console app running
while (true)
{
continue;
}
// or keep it running with this
// Console.ReadKey();
}
}
}
您的解决方案使用了大量的计算能力,因为每次循环重复时,它都会对Windows API进行11次调用。为了更有效地完成此操作,您将需要添加键盘hook。一个简单的解决方案如下所示。
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);
private const int WH_KEYBOARD_LL = 13;
private const int WM_KEYDOWN = 0x0100;
private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
private static InPtr hook_ = IntPtr.Zero;
private static LowLevelHookProc _proc = KeyboardProc;
public void Start() {
using (var process = Process.GetCurrentProcess())
using (var module = process.MainModule)
{
_hook = SetWindowsHookEx(WH_KEYBOARD_LL, _proc,
GetModuleHandle(module.ModuleName), 0);
}
private static IntPtr KeyboardProc(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN) {
switch(nCode) {
// Look for keys
}
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
每按下一个键,就会触发回调函数。只需查找您要查询的虚拟键即可。
以上是关于如何在C#中制作全局键盘钩子的主要内容,如果未能解决你的问题,请参考以下文章
用于 C# 和 WPF 的高级全局键盘钩子,用于读取键盘楔形卡扫描仪