【问题标题】:Deployment of C# console application for Keyboard Listener为键盘侦听器部署 C# 控制台应用程序
【发布时间】:2014-09-10 04:30:38
【问题描述】:

我有一个 C# 控制台应用程序,该应用程序旨在在后台运行并捕获按键事件,如果它与我的热键匹配,我想做一些操作,而不是将该键传递给活动的应用程序。

在我的开发机器上,我可以在没有 Visual Studio 的情况下运行构建 exe 文件,并且我的程序按预期工作。当我在任何应用程序中的任何位置键入热键(f11 或 f12)时,该键事件被捕获并且不会传递到活动应用程序。当我将 exe 部署到另一台机器、相同的操作系统(Windows 8.1 Pro)时,检测到按键并且我可以“做某事”(参见代码),但随后它被传递到活动应用程序。这不是我想要的操作,也不是我在我的开发机器上遇到的。我的具体问题是,为了将这个应用程序部署到其他机器上,我还需要做些什么,以便它们不仅捕获按键事件,而且还不会传递给活动的应用程序?

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

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

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

            if (pressedKey == Keys.F11 || pressedKey == Keys.F12)
            {
                // Do something...

                // Don't pass the key press on to the system
                return (System.IntPtr)1;
            }
        }
        return CallNextHookEx(_hookID, nCode, wParam, lParam);
    }

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

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

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

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

【问题讨论】:

  • 不要让人们点击你的代码链接,也不要在问题中发布太多代码。请尝试创建一个MCVE 并将其发布在您的问题中。
  • 我已根据您的有用输入更新了我的问题。谢谢你的解释。
  • 这是一个更好的问题,它可能会被重新打开。

标签: c# console listener keypress


【解决方案1】:

这可能不是您期望的解决方案,但我还是会发布它。

我使用不同的方法来捕获此类事件,Windows 提供了注册“全局热键”的可能性,无论目标应用程序是否正在运行(实际上它与操作系统,而不是单个应用程序)。

你可以试试这个可以这样调用的 sn-p https://gist.github.com/bruce965/87caacfb0289d0ad1b3c

new HotkeyHandler(false, false, false, false, Keys.F11).Register(yourForm);
new HotkeyHandler(false, false, false, false, Keys.F12).Register(yourForm);

其中yourFormSystem.Windows.Forms.Form,代码如下:

protected override void WndProc(ref Message m) {
    if(m.Msg == HotkeyHandler.WM_HOTKEY_MSG_ID) {
        // Handle hotkey
    }

    base.WndProc(ref m);
}

您不需要打开或显示表单,只要确保它正在运行:

Application.Run(yourForm);

来自链接的 sn-p 以供快速参考:

[DllImport("user32.dll")]
static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vk);

// make sure the second parameter '1234' is unique for every registered hotkey
RegisterHotKey(yourForm.Handle, 1234, 0, (int) Keys.F11);

【讨论】:

    【解决方案2】:

    your code 中有大量琐碎的问题,所有这些都让人不知道为什么它不能在另一台机器上运行。遍历源:

        static bool debug = false;
    

    如果将该变量设置为 false,您将没有机会诊断任何内容。您捕获异常并且不显示异常消息和堆栈跟踪。你像蝙蝠一样盲目。这应该是一个配置项。

        static string _com = "COM3";
    

    从不硬编码串行端口号。在具有另一个 USB 仿真器和另一个驱动程序的另一台机器上它仍然是 COM3 的几率非常低。你必须让它成为一个配置项。

        setConsoleWindowVisibility(false);
    

    您失去了查看诊断程序的唯一机会。这需要在它前面加上 if (!debug)。

        _hookID = SetHook(_proc);
    

    根本没有错误检查。如果 SetWindowsHookEx() 失败,那么你永远不会知道。调用 winapi 函数总是需要错误检查,您不再有友好的 .NET 异常来避免麻烦。 winapi调用失败时抛出Win32Exception。

        scaleSerialPort.Handshake = Handshake.None;
    

    串行端口设备始终使用握手。他们会注意您的 DTR 和 RTS 信号,并且在关闭时不会发送任何内容。调试代码时很容易看不到这一点,您将使用打开这些信号的终端仿真器程序。不会在另一台机器上工作。您必须将 DtrEnable 和 RtsEnable 属性显式设置为 true

        if (scaleSerialPort.IsOpen)
            scaleSerialPort.Close();
        try
        {
            scaleSerialPort.Open();
            open = true;
        }
    

    这永远不会正确,MSDN 文章 SerialPort.Close() 对此特别提出警告。 SerialPort 使用工作线程来引发其事件,Close() 方法不会等到该线程完成。尝试立即再次打开它总是会失败,该端口仍然由该工作线程打开。永远不要这样做,在您的应用程序启动时打开端口,直到它结束才关闭它。

        scaleSerialPort.ReadTimeout = 200; 
    

    强烈避免使用接近您的程序可能进入紧张状态的时间量的超时,因为机器变得繁忙或您的进程的重要部分被分页。您根本不应该使用超时,因为您依赖 DataReceived。如果你无论如何都想使用它们,那么总是让它们比最坏的情况大 10 倍。不要低于5000。

        catch (Exception e)
        {
            if (debug) Console.WriteLine(e);
            Console.WriteLine("Could not connect to scale.");
            SendKeys.SendWait("^a");
            SendKeys.SendWait("Error-C23");
        }
    

    永远不要这样做。如果您无法打开端口,那么保持程序运行绝对没有意义。一定要大声崩溃,为 AppDomain.CurrentDomain.UnhandledException 编写一个事件处理程序来讲述这个故事。 永远不要那样敲击键盘,你根本不知道它们去了哪里。

        Double.TryParse(...);
    

    这个方法的返回值是可选的。继续努力并忽略失败的转换只会产生无法诊断的错误行为。

        return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0);
    

    .NET 4.0 不再模拟托管代码的模块句柄。您应该使用 LoadLibrary("user32") 来获得有效的句柄。在 Windows 8 上不是问题,它接受模块句柄的 null 值,但在早期版本中会出现问题。

        if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
    

    没关系,但是如果另一个键盘钩子先拦截击键怎么办?你当然会输。

        if (pressedKey == Keys.F11 || pressedKey == Keys.F12)
    

    我强烈建议您不要使用低级键盘挂钩来捕获两次击键。更好的捕鼠器是RegisterHotKey()。谷歌搜索示例代码不会有任何问题。自动解决“其他应用仍然看到击键”的问题。

    嗯,它可能是其中之一,我猜不出是哪一个 :) 祝你好运。

    【讨论】:

    • 这不是我的完整代码。我正在连接到非常特定的设备,COM3 是大多数设备的默认设置。通过按F1,我有一个配置选项,允许对串行端口进行设置更改。这些串行设备不使用任何握手协议。不过,我会仔细审查您的 cmets 并在有意义的地方进行调整。我在串行设备方面做了很多工作,并且从未遇到过提出的一些问题。我会用结果更新我的问题。感谢您的回答。
    【解决方案3】:

    您是否尝试过将 lParam 设置为 null?自从我完成密钥处理以来已经有一段时间了,所以不确定设置为 null 还是 IntPtr.Zero 是否更好。两者之一应该可以工作:

    public static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
    {
        if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
        {
            Keys pressedKey = (Keys)Marshal.ReadInt32(lParam);
    
            if (pressedKey == Keys.F11 || pressedKey == Keys.F12)
            {
                // Do something...
    
                // Don't pass the key press on to the system
                lParam = null;
            }
        }
        return CallNextHookEx(_hookID, nCode, wParam, lParam);
    }
    

    【讨论】:

    • 就是这样。谢谢你的回答!一段时间以来,我一直在努力解决这个问题。抱歉,我没能及时给你奖励。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多