Развертывание консольного приложения C # для Keyboard Listener

1

У меня есть консольное приложение С#, предназначенное для запуска в фоновом режиме и захвата ключевых событий нажатия клавиши, и если оно соответствует моей горячей клавише, я хочу сделать какое-то действие и НЕ передать этот ключ активному приложению.

На моей машине разработки я могу запустить exe файл build без визуальной студии, и моя программа работает так, как ожидалось. Когда я набираю горячую клавишу (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);
  • 1
    Не заставляйте людей нажимать на ссылки на ваш код и не размещайте так много кода в вопросе. Пожалуйста, попробуйте создать MCVE и опубликуйте это в своем вопросе.
  • 0
    Я обновил свой вопрос в соответствии с вашим полезным вкладом. Спасибо за объяснение.
Показать ещё 1 комментарий
Теги:
console
listener
keypress

3 ответа

3
Лучший ответ

Вы пытались установить lParam равным нулю? Прошло некоторое время с тех пор, как я сделал ключевую обработку, поэтому не уверен, что лучше установить значение 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);
}
  • 0
    Я дам это выстрел и обновлю с результатами.
  • 0
    Это было все, что было. Спасибо за ответ! Я бился головой об этом в течение некоторого времени. Извините, я не успел вовремя присуждать вам награду.
5

В вашем коде большое количество проблем с niggly, все из которых сговариваются, чтобы не иметь понятия, почему он не запускается на другой машине. Пройдя через источник:

    static bool debug = false;

У вас нет возможности диагностировать что-либо с этой переменной, равной false. Вы перехватываете исключения и не показываете сообщение об исключении и трассировку стека. Ты слепой, как летучая мышь. Это должен быть элемент конфигурации.

    static string _com = "COM3";

Никогда не серийный номер жесткого кода. Шансы, что он все еще COM3 на другой машине с другим эмулятором USB и другим драйвером, очень низки. Вы должны сделать его элементом конфигурации.

    setConsoleWindowVisibility(false);

Вы потеряли единственный шанс увидеть диагностику. Это нужно, если (! Debug) перед ним.

    _hookID = SetHook(_proc);

Проверка ошибок вообще отсутствует. Если SetWindowsHookEx() терпит неудачу, вы никогда не узнаете. Принудительные функции winapi всегда требуют проверки ошибок, у вас больше нет дружественных исключений.NET, чтобы не допустить неприятностей. Бросьте исключение Win32Exception, когда вызов winapi завершится с ошибкой.

    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, она принимает нулевое значение для дескриптора модуля, но проблема в более ранних версиях.

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

Это хорошо, но что, если другой крюк клавиатуры сначала перехватывает нажатия клавиш? Вы, конечно, проиграете.

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

Я бы настоятельно рекомендовал вам не использовать низкоуровневый клавиатурный крючок, чтобы просто уловить два нажатия клавиш. Значительно лучше мышеловка - RegisterHotKey(). У вас не будет проблем с кодом примера для Google. Автоматически решает проблему "другое приложение видит в любом случае".

Ну, это, наверное, один из тех, я не могу догадаться, что :) Удачи.

  • 0
    Это не мой полный код. Я подключаюсь к очень конкретным устройствам, и COM3 по умолчанию на большинстве из них. У меня есть опция конфигурации, нажав F1 что позволяет изменять настройки последовательного порта. Эти последовательные устройства не используют протоколы рукопожатия. Я внимательно рассмотрю ваши комментарии и уточню, где это имеет смысл. Я проделал большую работу с последовательными устройствами и никогда не сталкивался с проблемами с некоторыми поднятыми вопросами. Я обновлю свой вопрос с результатами. Спасибо за ответ.
1

Возможно, это не решение, которое вы ожидаете, но я все равно отправлю его.

Я использую другой подход для захвата такого рода событий, 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);

где yourForm - это System.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);

Ещё вопросы

Сообщество Overcoder
Наверх
Меню