控制台应用程序中的全局热键

39 c# pinvoke winapi console-application

有谁知道如何在控制台应用程序中使用RegisterHotKey/UnregisterHotKey API调用?我假设设置/删除热键是相同的,但是如何在按下键时收到回叫?

我看到的每个例子都是针对Winforms和使用的protected override void WndProc(ref Message m){...},我无法使用.


更新:我的内容如下,但事件永远不会被击中.我认为这可能是因为当你加载ConsoleShell时它会阻止进一步的执行,但即使我放入SetupHotkey一个不同的线程也没有任何反应.有什么想法吗?

class Program
{
    static void Main(string[] args)
    {
        new Hud().Init(args);
    }
}

class Hud
{
    int keyHookId;


    public void Init(string[] args)
    {
        SetupHotkey();
        InitPowershell(args);
        Cleanup();
    }

    private void Cleanup()
    {
        HotKeyManager.UnregisterHotKey(keyHookId);
    }

    private void SetupHotkey()
    {
        keyHookId = HotKeyManager.RegisterHotKey(Keys.Oemtilde, KeyModifiers.Control);
        HotKeyManager.HotKeyPressed += new EventHandler<HotKeyEventArgs>(HotKeyManager_HotKeyPressed);
    }

    void HotKeyManager_HotKeyPressed(object sender, HotKeyEventArgs e)
    {
        //never executed
        System.IO.File.WriteAllText("c:\\keyPressed.txt", "Hotkey pressed");
    }

    private static void InitPowershell(string[] args)
    {
        var config = RunspaceConfiguration.Create();
        ConsoleShell.Start(config, "", "", args);
    }
}
Run Code Online (Sandbox Code Playgroud)

Chr*_*lor 64

您可以做的是在控制台应用程序中创建一个隐藏窗口,用于处理热键通知并引发事件.

这里的代码演示了委托人.是一篇关于在控制台应用程序中处理消息的文章,使用它可以增强HotKeyManager以在控制台应用程序中运行.

以下对HotKeyManager的更新创建了一个后台线程,它运行消息循环并处理Windows消息.

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Threading;

namespace ConsoleHotKey
{
  public static class HotKeyManager
  {
    public static event EventHandler<HotKeyEventArgs> HotKeyPressed;

    public static int RegisterHotKey(Keys key, KeyModifiers modifiers)
    {
      _windowReadyEvent.WaitOne();
      int id = System.Threading.Interlocked.Increment(ref _id);
      _wnd.Invoke(new RegisterHotKeyDelegate(RegisterHotKeyInternal), _hwnd, id, (uint)modifiers, (uint)key);
      return id;
    }

    public static void UnregisterHotKey(int id)
    {
      _wnd.Invoke(new UnRegisterHotKeyDelegate(UnRegisterHotKeyInternal), _hwnd, id);
    }

    delegate void RegisterHotKeyDelegate(IntPtr hwnd, int id, uint modifiers, uint key);
    delegate void UnRegisterHotKeyDelegate(IntPtr hwnd, int id);

    private static void RegisterHotKeyInternal(IntPtr hwnd, int id, uint modifiers, uint key)
    {      
      RegisterHotKey(hwnd, id, modifiers, key);      
    }

    private static void UnRegisterHotKeyInternal(IntPtr hwnd, int id)
    {
      UnregisterHotKey(_hwnd, id);
    }    

    private static void OnHotKeyPressed(HotKeyEventArgs e)
    {
      if (HotKeyManager.HotKeyPressed != null)
      {
        HotKeyManager.HotKeyPressed(null, e);
      }
    }

    private static volatile MessageWindow _wnd;
    private static volatile IntPtr _hwnd;
    private static ManualResetEvent _windowReadyEvent = new ManualResetEvent(false);
    static HotKeyManager()
    {
      Thread messageLoop = new Thread(delegate()
        {
          Application.Run(new MessageWindow());
        });
      messageLoop.Name = "MessageLoopThread";
      messageLoop.IsBackground = true;
      messageLoop.Start();      
    }

    private class MessageWindow : Form
    {
      public MessageWindow()
      {
        _wnd = this;
        _hwnd = this.Handle;
        _windowReadyEvent.Set();
      }

      protected override void WndProc(ref Message m)
      {
        if (m.Msg == WM_HOTKEY)
        {
          HotKeyEventArgs e = new HotKeyEventArgs(m.LParam);
          HotKeyManager.OnHotKeyPressed(e);
        }

        base.WndProc(ref m);
      }

      protected override void SetVisibleCore(bool value)
      {
        // Ensure the window never becomes visible
        base.SetVisibleCore(false);
      }

      private const int WM_HOTKEY = 0x312;
    }

    [DllImport("user32", SetLastError=true)]
    private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);

    [DllImport("user32", SetLastError = true)]
    private static extern bool UnregisterHotKey(IntPtr hWnd, int id);

    private static int _id = 0;
  }


  public class HotKeyEventArgs : EventArgs
  {
    public readonly Keys Key;
    public readonly KeyModifiers Modifiers;

    public HotKeyEventArgs(Keys key, KeyModifiers modifiers)
    {
      this.Key = key;
      this.Modifiers = modifiers;
    }

    public HotKeyEventArgs(IntPtr hotKeyParam)
    {
      uint param = (uint)hotKeyParam.ToInt64();
      Key = (Keys)((param & 0xffff0000) >> 16);
      Modifiers = (KeyModifiers)(param & 0x0000ffff);
    }
  }

  [Flags]
  public enum KeyModifiers
  {
    Alt = 1,
    Control = 2,
    Shift = 4,
    Windows = 8,
    NoRepeat = 0x4000
  }
}
Run Code Online (Sandbox Code Playgroud)

以下是从Console应用程序中使用HotKeyManager的示例

using System;
using System.Windows.Forms;

namespace ConsoleHotKey
{
  class Program
  {
    static void Main(string[] args)
    {
      HotKeyManager.RegisterHotKey(Keys.A, KeyModifiers.Alt);
      HotKeyManager.HotKeyPressed += new EventHandler<HotKeyEventArgs>(HotKeyManager_HotKeyPressed);
      Console.ReadLine();      
    }

    static void HotKeyManager_HotKeyPressed(object sender, HotKeyEventArgs e)
    {
      Console.WriteLine("Hit me!");
    }
  }
}
Run Code Online (Sandbox Code Playgroud)


use*_*820 6

我只是想提供一种替代解决方案.

我正在为使用此脚本的人回答一个问题,我认为这可能有助于设置全局密钥钩子的其他人.

编辑:不要忘记添加引用 System.Windows.Forms

您可以通过选择ProjectAdd Reference和检查来完成此操作System.Windows.Forms

在此输入图像描述

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace ConsoleKeyhook
{
class Hooky
{
    ///////////////////////////////////////////////////////////
    //A bunch of DLL Imports to set a low level keyboard 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);

    ////////////////////////////////////////////////////////////////
    //Some constants to make handling our hook code easier to read
    ////////////////////////////////////////////////////////////////
    private const int WH_KEYBOARD_LL = 13;                    //Type of Hook - Low Level Keyboard
    private const int WM_KEYDOWN = 0x0100;                    //Value passed on KeyDown
    private const int WM_KEYUP = 0x0101;                      //Value passed on KeyUp
    private static LowLevelKeyboardProc _proc = HookCallback; //The function called when a key is pressed
    private static IntPtr _hookID = IntPtr.Zero;
    private static bool CONTROL_DOWN = false;                 //Bool to use as a flag for control key

    public static void Main()
    {
        _hookID = SetHook(_proc);  //Set our hook
        Application.Run();         //Start a standard application method loop
    }

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

    private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);

    private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
    {
        if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN) //A Key was pressed down
        {
            int vkCode = Marshal.ReadInt32(lParam);           //Get the keycode
            string theKey = ((Keys)vkCode).ToString();        //Name of the key
            Console.Write(theKey);                            //Display the name of the key
            if (theKey.Contains("ControlKey"))                //If they pressed control
            {
                CONTROL_DOWN = true;                          //Flag control as down
            }
            else if (CONTROL_DOWN && theKey == "B")           //If they held CTRL and pressed B
            {
                Console.WriteLine("\n***HOTKEY PRESSED***");  //Our hotkey was pressed
            }
            else if (theKey == "Escape")                      //If they press escape
            {
                UnhookWindowsHookEx(_hookID);                 //Release our hook
                Environment.Exit(0);                          //Exit our program
            }
        }
        else if (nCode >= 0 && wParam == (IntPtr)WM_KEYUP) //KeyUP
        {
            int vkCode = Marshal.ReadInt32(lParam);        //Get Keycode
            string theKey = ((Keys)vkCode).ToString();     //Get Key name
            if (theKey.Contains("ControlKey"))             //If they let go of control
            {
                CONTROL_DOWN = false;                      //Unflag control
            }
        }
        return CallNextHookEx(_hookID, nCode, wParam, lParam); //Call the next hook
    }
}
}
Run Code Online (Sandbox Code Playgroud)