Как работает.
Использован механизм SetWindowsHookEx с флагом "WH_KEYBOARD_LL".
Механизм перехвата работает через отправку сообщений процессу приложения, установившему перехват. Это влечет за собой 2 последствия.
1.Внедрение DLL компоненты (т.е. нашего кода) для обработки callback-вызовов в адресное пространство других процессов не происходит.
2. При остановке потока 1с, установившего перехват (ошибка в коде, остановка отладчиком, зависание процесса 1с и т.д.) никаких негативных последствий ни для других процессов, ни для операционной системы не происходит. Субьективно это воспринимается пользователем как кратковременная (1-3 сек), однократная задержка реакции на нажатие кнопки. В этом случае операционка просто исключает обработчик зависшего процесса из цепочки обработчиков перехвата. Для возобновления перехвата - потребуется его перезапустить.
Механизм сильно Generic и поэтому теоретически будет работать довольно надежно на всех семействах операционок от Майки (Windows 9x, Windows NT, Windows Server) - 32 и 64-разрядных. Без каких-либо дополнительных фреймворков и сред.
Кроме очевидного использования - для перехвата нажатия кнопок в 1с - вне зависимости от положения фокуса, навскидку видятся еще следующие применения:
- в качестве перехватчика данных, получаемых от других устройств клавиатурного ввода (сканеры, ридеры и т.д.)
- иное применение :).
Установка компоненты:
&НаКлиенте
Процедура ПриОткрытии(Отказ)
УстановитьВнешнююКомпоненту("ОбщийМакет.KbdHook");
ПодключитьВнешнююКомпоненту("ОбщийМакет.KbdHook", "СС",ТипВнешнейКомпоненты.Native);
//ПодключитьВнешнююКомпоненту("C:\C++\KbdHook\x64\Release\KbdHook64.dll", "СС", ТипВнешнейКомпоненты.Native);
ВК = Новый("AddIn.СС.KbdHook");
КонецПроцедуры
Запуск перехвата:
&НаКлиенте
Процедура УстановитьКрючок(Команда)
//Если параметр Истина, то компонента возвращает - строчное представление шестнадцатеричного значения двойного слова (4 байта).
//Если ложь - системную интерпретацию названия нажатой кнопки (зависит от локализации ОС).
Сообщить(ВК.УстановитьПерехват("string",11,28)); //установка перехвата в режиме получения данных от эмуляторов клавиатуры
//второй параметр - целое положительное число - задает скан-код клавиши, с которой начинается накопление символов в буфере
//третий параметр - целые положительные числа - задает скан-код клавиши, при перехвате которой накопленная последовательность символов передается в 1С.
Сообщить(ВК.УстановитьПерехват("char",1,1)); //установка перехвата в посимвольном режиме
КонецПроцедуры
Остановка перехвата:
&НаКлиенте
Процедура СнятьКрючок(Команда)
Сообщить(ВК.СнятьПерехват());
КонецПроцедуры
Обработка callback-вызова:
//Модуль приложения
Процедура ОбработкаВнешнегоСобытия(Источник, Событие, Данные)
Оповестить(Событие,Данные,Источник)
КонецПроцедуры
//Модуль формы
&НаКлиенте
Процедура ОбработкаОповещения(ИмяСобытия, Параметр, Источник)
//в посимвольном режиме
ЧтениеДжей = новый ЧтениеJSON;
ЧтениеДжей.УстановитьСтроку(Параметр);
структура_= ПрочитатьJSON(ЧтениеДжей);
ЧтениеДжей.Закрыть();
ЭтаФорма.Реквизит1 = ЭтаФорма.Реквизит1 + структура_.key;
Сообщить(Параметр);
//в режиме перехвата эмуляции клавиатуры
ЧтениеДжей = новый ЧтениеJSON;
ЧтениеДжей.УстановитьСтроку(Параметр);
массив_ = ПрочитатьJSON(ЧтениеДжей);
ЧтениеДжей.Закрыть();
Для каждого эм из массив_ Цикл
Сообщить(Строка(эм.sc)); //Скан-код клавиши
КонецЦикла;
КонецПроцедуры
Json-структура, возвращаемая в 1с компонентой:
//В режиме посимвольного перехвата
{
"Alt" : false,
"Ctrl" : false,
"Key" : "0x00390001",
"Shift" : false
}
//В режиме перехвата эмуляции клавиатуры
[
{
"key" : "0", //печатный символ (если интерпретировать возможно)
"sc" : 11, //скан-код клавиши
"vk" : 48 //виртуальный код клавиши
},
{
"key" : "", //ОС не смогла интерпретировать скан-код
"sc" : 66, //скан-код кнопки F8 (нажатие этой кнопки промулировал Honeywell 1400d при сканировании символа "29" (0х1Dh))
"vk" : 119
},
{
"key" : "\r",
"sc" : 28, //скан-код кнопки ENTER
"vk" : 13
}
]
Немного о структуре поля Key.
Если включен режим возврата скан-кода нажатой клавиши, то возвращается строка вида:
0x00100001
Справа-налево:
байт 0: всегда = 1.
байт 1: всегда = 0.
байт 2: скан-код нажатой клавиши (https://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/scancode.doc)
байт 3: флаг - является-ли нажатая клавиша расширенной клавишей (клавиша Windows, Системное меню и т.д.), возможные значения: 0-1.
Самый старший байт - слева.
Режим перехвата данных от эмуляторов клавиатуры (от сканеров, кард-ридеров и т.п.).
Принцип работы.
После установки перехвата, компонента проверяет скан-код каждого введенного символа на совпадение со скан-кодом, установленным во 2м параметре функции установки перехвата (УстановитьПерехват()).
Если скан-коды совпадают, то все последующие символы будут записываться во внутренний буфер компоненты.
Запись будет происходить до тех пор, пока скан-код входящего символа не окажется равным скан-коду третьего параметра функции установки перехвата (УстановитьПерехват()).
Если скан-коды символов совпадут, то компонента передает последовательность накопленных символов в 1с (генерируется внешнее событие), очищает буфер и снова входит в режим ожидания совпадения скан-кода входящего символа с открывающим скан-кодом.
Таким образом, запрограммировав сканер на генерацию открывающего и закрывающего символов, можно получать в 1с данные не по-символьно, а массивом.
Кроме того, передача массива происходит из другого потока, который запускается специально для генерации внешнего события 1с. Такое решение исключает задержки с точки зрения операционной системы в обработчике перехвата (если, например, мы решили поотлаживать обработку перехвата в конфигураторе). При генерации внешнего события в основном потоке 1с (в случае слишком длительной задержки обработки перехвата) ОС может исключить наш обработчик из цепочки и его придется устанавливать заново.
По-хорошему, генерацию внешнего события в режиме по-символьного перехвата, тоже нужно реализовать в отдельном потоке.
Тестировалось на:
технологические платформы версии: 8.3.18.1334, 8.3.20.1674
ОС: Windows 10
Исходники: https://github.com/KotVezdehod/KbdHook