简体   繁体   English

如何在 C# 中制作全局键盘挂钩

[英]How to make a global keyboard hook in C#

How can I make a global keyboard hook for an Electron.NET app in C#?如何在 C# 中为 Electron.NET 应用程序制作全局键盘挂钩? I believe as long as it works in a console app it should work properly in an Electron.Net app.我相信只要它在控制台应用程序中工作,它就应该在 Electron.Net 应用程序中正常工作。

I made a 'solution' for this problem, but It tends to use up a lot of CPU (7-10%).我为这个问题做了一个“解决方案”,但它往往会消耗大量的 CPU(7-10%)。 Maybe someone is somehow able to make it actually efficient if there is no other option:如果没有其他选择,也许有人能够以某种方式使其真正有效:

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();
}

A lot of things I found would only work if I was using WinForms or WPF.我发现的很多东西只有在我使用 WinForms 或 WPF 时才有效。

Edit:编辑:

I tried this answer by hanabanashiku, and a lot of others that I found online, but all of them seemed to just lag the keyboards input and their callback functions would seem to be never called.我尝试了 hanabanashiku 的这个答案,以及我在网上找到的许多其他答案,但它们似乎都只是滞后于键盘输入,而且它们的回调函数似乎永远不会被调用。

I decided to write the keyboard hook in C++, compile as a DLL and then reference that DLL in my C# code to hopefully make a keyboard hook that functioned properly and didn't cause any noticeable input lag, but that didn't work either. I decided to write the keyboard hook in C++, compile as a DLL and then reference that DLL in my C# code to hopefully make a keyboard hook that functioned properly and didn't cause any noticeable input lag, but that didn't work either.

The keyboard hook ran perfectly when run as an.exe in C++, but when I compiled it as a DLL and ran it in C# it caused the same issue as before - a lot of input lag and the callback function seemingly not being called. The keyboard hook ran perfectly when run as an.exe in C++, but when I compiled it as a DLL and ran it in C# it caused the same issue as before - a lot of input lag and the callback function seemingly not being called.

Heres the code if anyone wants to try it:如果有人想尝试,这是代码:

KeyboardHook.cpp键盘钩子.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\n";
}

void KeyUp(int key)
{
    std::cout << "KeyUp\n";
}

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\n";
}

void KeyboardHook::Uninstall() {
    // Unhook keyboard hook
    UnhookWindowsHookEx(keyboardHook);
}

KeyboardHook.h键盘钩子.h

#include <Windows.h>

HHOOK keyboardHook;

namespace Hooks 
{
    class KeyboardHook
    {
    public:
        __declspec(dllexport) void Install();
        __declspec(dllexport) void Uninstall();
    };
}

Program.cs程序.cs

using System;
using System.Runtime.InteropServices;

namespace HelloMyNameIsSpindiNiceToMeetYou
{
    class Program
    {
        private const string hooksPath = @"C:\Path\To\Hooks.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();
        }
    }
}

I am testing this stuff outside of an electron.net app now, just in a console app and things still don't work.我现在正在一个 electron.net 应用程序之外测试这些东西,只是在一个控制台应用程序中,事情仍然无法正常工作。 Everything I have found just leads back to using winforms, which I cannot use.我发现的一切都导致回到使用我无法使用的 winforms。

Electron has a way to create global shortcuts built-in, you just have to adapt the syntax a little to work with Electron.Net, you can search the repo to figure out function names (usually names are the same, just in PascalCase). Electron 有一种内置创建全局快捷方式的方法,您只需稍微调整语法即可使用 Electron.Net,您可以搜索存储以找出 ZC1C425268E68385D1AB5074C17A941F

Registering a shortcut:注册快捷方式:

Electron.GlobalShortcut.Register("CommandOrControl+X", () =>
{
    Console.WriteLine("CommandOrControl+X pressed");
});

Unregistering a shortcut:取消注册快捷方式:

// Unregister specific shortcut
Electron.GlobalShortcut.Unregister("CommandOrControl+X");

// Unregister all shortcuts
Electron.GlobalShortcut.UnregisterAll();

Your solution uses a lot of computing power, because every time the loop repeats, it's making 11 calls to the Windows API.您的解决方案使用了大量的计算能力,因为每次循环重复时,它都会对 Windows API 进行 11 次调用。 To more efficiently accomplish this, you will want to add a keyboard hook .为了更有效地完成此操作,您将需要添加一个键盘挂钩 A simple solution would be something like the following.一个简单的解决方案如下所示。

[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);
    }

The Callback function will fire every time a key is pressed down.每次按下一个键时都会触发回调 function。 Simply look for the virtual keys that you wish to query.只需查找您要查询的虚拟键。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM