键盘监听器c#控制台应用程序的部署

本文关键字:部署 应用程序 控制台 监听器 键盘 | 更新日期: 2023-09-27 18:13:29

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

在我的开发机器上,我可以在没有visual studio的情况下运行build 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);

键盘监听器c#控制台应用程序的部署

您的代码中有大量琐碎的问题,所有这些问题共同导致您不知道为什么它不能在另一台机器上运行。浏览源码:

    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()。您可以毫不费力地在谷歌上搜索示例代码。自动解决"其他应用无论如何都能看到按键"的问题。

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

是否尝试将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);
}

这可能不是你期望的解决方案,但我还是会把它贴出来。

我用一种不同的方法来捕获这类事件,Windows提供了注册一个"全局热键"的可能性,无论目标应用程序是否正在运行,它都会在任何地方被拦截(实际上它是与操作系统相关联的,而不是单个应用程序)。

您可以尝试使用以下代码片段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);

从快速参考的链接片段:

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