Пример работы с USB HID из Windows DDK |
![]() |
Добавил(а) microsin |
Эта статья содержит дословный перевод комментария к программе HClient - пример (hclient.htm) работы с устройствами USB HID, который можно найти в Windows DDK (см. папку c:\WINDDK2003\3790.1830\src\wdm\hid\hclient). HClient представляет из себя, кроме примера, полезную программу, дающую информацию обо всех подключенных в системе устройствах USB HID. Этот документ и связанный с ним код примера показывает, как написать приложение-клиент пользовательского режима (user-mode client application), предназначенное для обмена данными с устройствами, которые отвечают спецификации HID device class. Эта статья полезна для программистов, кому нужно разработать приложение пользовательского режима, которое работает с HID-совместимыми устройствами, получает и декодирует информацию от них. Этот пример иллюстрирует метод детектирования устройств HID, открытие этих устройств HID для обмена, и распаковку данных из репортов или форматирование данных в репорты устройства HID. Класс HID состоит главным образом из устройств, которые используются людьми для управления компьютерными системами. Типичными устройствами HID являются клавиатуры, мыши и джойстики. Нетипичные устройства могут включать управляющие элементы передней панели (ручки, переключатели или кнопки) или элементы управления, которые можно найти на таких устройствах, как телефоны, пульты управления видеомагнитофонами, игры и эмулирующие устройства. Основополагающая особенность всех устройств HID – необходимость гарантированной доставки маленьких порций данных, поступающих не периодично (в произвольные моменты времени). Основной механизм обмена данными для устройств HID class - HID report (далее эти слова я иногда не буду переводить). Каждое устройство HID должно поддерживать report descriptor (дескриптор репорта, это тоже я не буду переводить, т. к. по английски звучит куда лучше) который описывает формат различных репортов, которые он создает для этих устройств. Драйверы HID class и HID.DLL предоставляют интерфейс для распаковки подходящих данных из этих репортов. Несмотря на то, что пример HClient является приложением пользовательского режима, многие функции HID.DLL также доступны для HID клиентов режима ядра (kernel-mode HID clients). Функции, экспортируемые из HID.DLL, имеют префикс либо HidD_, либо HidP_. Все функции с префиксом HidP_ доступны для клиентов режима ядра. Однако механизм для открытия устройств HID и получения нужной информации в этом контексте отличается - как, например, для получения интерпретируемых данных. СБОРКА ПРИМЕРА 1. Запустите «standard Windows NT®/Windows® 2000 DDK build environment (checked or free)» - это просто командная строка, у которой в Path прописаны пути до утилит компиляции DDK (это все ставится вместе с DDK). HIDSDI.H Определения и декларации только для пользовательского режима Клиенты kernel-mode могут также нуждаться в следующих файлах: ИНСТРУМЕНТЫ (TOOLS) РЕСУРСЫ ОБЗОР КОДА (CODE TOUR) HCLIENT.HTM Документация по примеру (этот текст) Обзор программирования Основные темы, раскрываемые в этой секции: Детектирование установленных устройств HID Остальная часть кода реализована в функции, которая занимается созданием списка структур HID_DEVICE, которые содержат информацию по каждому HID устройству в системе. Этот пример получает доступ ко всем устройствам HID, работающим с системе. Более специфичная реализация программы может работать только с определенным типом устройства HID, такими как джойстик или геймпад. Открытие устройств HID typedef struct _HID_DEVICE { HANDLE HidDevice; // дескриптор (handle) устройства HID PHIDP_PREPARSED_DATA Ppd; // тупой парсер информации, описывающей это устройство HIDP_CAPS Caps; // возможности (Capabilities) этого HID устройства HIDD_ATTRIBUTES Attributes; PCHAR InputReportBuffer; PHID_DATA InputData; // массив структур hid data ULONG InputDataLength; // количество элементов в массиве PHIDP_BUTTON_CAPS InputButtonCaps; PHIDP_VALUE_CAPS InputValueCaps; PCHAR OutputReportBuffer; PHID_DATA OutputData; ULONG OutputDataLength; PHIDP_BUTTON_CAPS OutputButtonCaps; PHIDP_VALUE_CAPS OutputValueCaps; PCHAR FeatureReportBuffer; PHID_DATA FeatureData; ULONG FeatureDataLength; PHIDP_BUTTON_CAPS FeatureButtonCaps; PHIDP_VALUE_CAPS FeatureValueCaps; } HID_DEVICE, *PHID_DEVICE; Помимо хранения основных HID структур, как это определено в HIDPI.H и HIDSDI.H, в структуре HID_DEVICE содержится также целый ряд HID_DATA структур для каждого типа репорта. Эти структуры содержат самые последние значения, используемые для каждого из органов управления (controls), определенных в списках HIDP_VALUE_CAPS и HIDP_BUTTON_CAPS для данного типа отчета. Поля в этой структуре используются подпрограммами, которые пакуют/распаковывают репорты данных (data reports). Как использовать эти поля рассмотрено в секции Building/Interpreting HID Reports (Построение/интерпретирование репортов HID). Функция OpenHidDevice() в PNP.C выполняет необходимые шаги по заполнению структуры HID_DEVICE для устройства: Разрабатываемое приложение HID-клиента может и не нуждаться во всех перечисленных выполняемых шагах. Например, если приложение-клиент работает только с конкретным HID device (определяемым по vendor ID/product ID), оно может отказаться от обработки других устройств, кроме того, которое интересно. Приложение клиента может также работать только для требуемой комбинации usage page/usage. Например, приложение управления монитором может нуждаться только в открытии устройств, которые подходят к комбинации usage page и usage монитора. В завершении HID клиент может нуждаться только в части полной информации, сохраненной в настоящий момент в структуре HID_DEVICE. Этот пример приложения выполняет широкий диапазон функциональности, чтобы предоставить более подробный пример. Обмен данными с устройствами HID Устройство HID поставляет информацию для хоста (для приложения HID-клиента, в частности) через репорты Input Reports или Feature Reports. Обычно input reports содержат данные, которые образовались при работе пользователя с HID-устройством (нажатия кнопок и т. п.). С другой стороны, Feature reports содержат текущее состояние или установки для устройства. Методы получения этих двух различных репортов реализованы в функциях Read() и GetFeature(), находящиеся в файле REPORT.C. В порядке приема данных, HClient должен использовать дескриптор файла (file handle), созданный в функции OpenHidDevice, которая имеет доступ на ЧТЕНИЕ (READ) и буфер, выделенный для нужного типа репорта. Для input reports функция Read() делает вызовы функции Win32 API ReadFile() и ожидает от устройства возврата репорта. Для feature reports функция GetFeature() устанавливает первый байт буфера репорта в значение требуемого report ID и вызывает функцию HidD_GetFeature() для получения feature report. После приема репорта обратно от нижележащих драйверов, эти подпрограммы вызывают UnpackReport() для заполнения соответствующих структур HID_DATA. Вывод данных в устройство несколько сложнее. Тут также имеются два типа репортов, которые можно посылать в устройство - Output и Feature. Соответствующие функции для вывода данных - Write() и SetFeature() в файле REPORT.C. Первый шаг, который выполняют обе из этих функций – создание нужного буфера репорта. Сначала находится структура HID_DATA с требуемым значением report ID. Далее, эти функции вызывают PackReport() для установки величин данных внутри выделенного буфера репорта для этого ID. Как только report buffer создан, вызываются либо WriteFile(), либо HidD_SetFeature() для отправки пакета репорта в устройство. Важно понимать, что все обращения к файловому дескриптору устройства HID (file handle) синхронизированы. Таким образом, все потоки, которые используют этот дескриптор, будут блокированы, пока все предыдущие запросы к устройству не будут выполнены. Одна из возможных реализаций клиента должна иметь один поток, непрерывно читающий input reports, пока другой поток отправляет или принимает feature reports. Поскольку функции HidD_GetFeature() и HidD_SetFeature реализованы как вызовы DeviceIoControl, они также синхронизированы с ReadFile() и WriteFile(). В этом сценарии есть два решения. Один вариант использует перекрывающийся ввод/вывод (overlapped I/O) для асинхронного управления. Второй вариант – открытие двух дескрипторов (handles) для устройства, один для потока чтения (read thread), другой для потока feature (feature thread). Построение/интерпретация репортов HID Как упоминалось ранее, в процессе коммуникации с устройством HID приложение клиента должно быть способно либо создавать подходящий репорт для отправки в устройство, либо распаковывать нужную информацию из репорта, полученного от устройства. Когда HClient первоначально открыл устройство HID и создал структуру HID_DEVICE, он также создал массив структур HID_DATA для каждого типа репорта. Формат этой структуры HID_DATA следующий: typedef struct _HID_DATA { BOOLEAN IsButtonData; UCHAR Reserved; USAGE UsagePage; // usage page, которая нам нужна ULONG Status; // последний статус, возвращенный из функции accessor, // когда это поде обновляется ULONG ReportID; // ReportID для предоставляемой структуры данных BOOLEAN IsDataSet; // Переменная для отслеживания – добавлена ли предоставляемая // структура данных уже в структуру репорта union { struct { ULONG UsageMin; // переменные для отслеживания usage min и max ULONG UsageMax; // Если равны, то только одиночное usage ULONG MaxUsageLength; // Длина буфера usage PUSAGE Usages; // Список usage (кнопки "down" на устройстве) } ButtonData; struct { USAGE Usage; // Использование описанием этого значения USHORT Reserved; ULONG Value; LONG ScaledValue; } ValueData; }; } HID_DATA, *PHID_DATA; Когда создаются репорты HID для отправки в устройство, используется функция PackReport(). PackReport получает в качестве входного параметра указатель на структуру HID_DATA и соответствующий буфер репорта. Указатель должен указывать на первую структуру HID_DATA в массиве, которая содержит report ID для создаваемого репорта. С этой информацией PackReport() выполняет следующие шаги: • Сбрасывает содержимое текущего буфера репорта После прохождения по циклу через массив структур HID_DATA, буфер репорта будет иметь соответствующий report ID, установленный в первом байте буфера, и он будет готов к отправке в устройство. Аналогичным образом UnpackReport() распаковывает данные из данного репорта. Как и PackReport(), эта функция принимает буфер репорта, возвращенный от устройства и массив структур HID_DATA, который может быть заполнен. Эта подпрограмма выполняет следующие шаги: По завершении массив структур HID_DATA будет содержать новые установки, основанные на информации, возвращенной в буфере репорта. Исходный код и скомпилированный выполняемый файл можно скачать в архиве hclient.zip. [Ссылки] 1. Исходный код и скомпилированный выполняемый файл проекта, описанного в статье. |