Get key from any process

2020-02-29 16:03发布

问题:

Ive seen many solutions online but none does exactly what I want. What is the best/simplest way to get any keys pressed in a given process (not my console applicaton) while my application is running in background. I dont need the modifiers or anything.

回答1:

If you don't particularly care which process the keys are being pressed in the easiest method would be to call GetAsyncKeyState. It's rather limited though as it does not hook the keyboard and requires you to call it continuously. The best approach in my opinion is to hook the keyboard.

Using SetWindowsHookEx you can actually explicitly specify the identifier of the thread with which the hook procedure is to be associated so you can hook keys for a specific process (see dwThreadId).

Here's a class that you can use (originally found on a Micrsoft blog but I cannot seem to find the authors name at the moment!)

public delegate IntPtr KeyboardProcess(int nCode, IntPtr wParam, IntPtr lParam);

public sealed class KeyboardHook
{
    public static event EventHandler<KeyPressedEventArgs> KeyPressed;
    private const int WH_KEYBOARD = 13;
    private const int WM_KEYDOWN = 0x0100;
    private static KeyboardProcess keyboardProc = HookCallback;
    private static IntPtr hookID = IntPtr.Zero;

    public static void CreateHook()
    {
        hookID = SetHook(keyboardProc);
    }

    public static void DisposeHook()
    {
        UnhookWindowsHookEx(hookID);
    }

    private static IntPtr SetHook(KeyboardProcess keyboardProc)
    {
        using (Process currentProcess = Process.GetCurrentProcess())
        using (ProcessModule currentProcessModule = currentProcess.MainModule)
        {
            return SetWindowsHookEx(WH_KEYBOARD, keyboardProc, GetModuleHandle(currentProcessModule.ModuleName), 0);
        }
    }

    private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
    {
        if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
        {
            int vkCode = Marshal.ReadInt32(lParam);

            if (KeyPressed != null)
                KeyPressed(null, new KeyPressedEventArgs((Keys)vkCode));
        }
        return CallNextHookEx(hookID, nCode, wParam, lParam);
    }

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

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

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

}

public class KeyPressedEventArgs : EventArgs
{
    public Keys KeyCode { get; set; }
    public KeyPressedEventArgs(Keys Key)
    {
        KeyCode = Key;
    }
}

Implementation via Console Application:

class Program
{
    static void Main(string[] args)
    {
        KeyboardHook.CreateHook();
        KeyboardHook.KeyPressed += KeyboardHook_KeyPressed;
        Application.Run();
        KeyboardHook.DisposeHook();
    }

    static void KeyboardHook_KeyPressed(object sender, KeyPressedEventArgs e)
    {
        Console.WriteLine(e.KeyCode.ToString());
    }
}


回答2:

What you're looking for is called a global keyboard hook. You can find more information and examples on MSDN.



回答3:

Oh, so you're looking for "Autofire" in old-school gaming terms?

Instead of writing your own keyboard hook app (unless you're doing it for fun/the thrill of it/the exercise) you might want to look at AutoIt or AutoHotkey, which are both pretty good for keyboard/mouse automation.

See this thread for instance... http://www.autohotkey.com/board/topic/40598-autofire-keyboard/



回答4:

I have found a way to hook only for a process. You may need it.

int ProcessId = GetProcessesByName("Your_app_here").FirstOrDefault().Id;
private IntPtr SetHook(KeyboardHookHandler proc)
    {
        return SetWindowsHookEx(13, proc, GetModuleHandle(Process.GetProcessById(ProcessId).MainModule.ModuleName), GetWindowThreadProcessId(GetModuleHandle(Process.GetProcessById(ProcessId).MainModule.ModuleName), out int MainThreadId));
    }

Remember to import these methods.

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

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr GetModuleHandle(string lpModuleName);

[DllImport("user32.dll", SetLastError = true)]
    static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);