This document and associated sample code describe how to write a user-mode client application to communicate with devices that conform to the HID device class specification. This article is useful to application writers who need to develop a user-mode application which communicates with and extracts information from a HID-compatible device. This sample illustrates the method for detecting HID devices, opening those HID devices for communication, and extracting and/or formatting the data into/from device reports.
The HID class consists primarily of devices that are used by humans to control the operation of computer systems. Typical HID devices include keyboards, mice, and joysticks. Non-typical devices might include front-panel controls (knobs, switches, or buttons) or controls found on devices such as telephones, VCR remote controls, games, and simulation devices. The underlying common feature of all HID devices is the need for guaranteed delivery of small amounts of non-periodic data.
The basic communication mechanism for HID class devices is the HID report. Every HID device must supply a report descriptor that details the format of the different reports that it creates for its device. The HID class drivers and HID.DLL is provide an interface for extracting the relevant data from these reports.
Although the HClient sample is a user-mode application, many of the functions available in HID.DLL are available to kernel-mode HID clients as well. The functions exported by HID.DLL have a prefix of either HidD_ or HidP_. All functions with a HidP_ prefix are available to kernel-mode clients. However, the mechanism for opening HID devices and obtaining the necessary information such as preparsed data is different in this context.
To build the HClient.exe sample, follow these instructions.
The HClient sources are dependent on the following system include files and libraries.
HIDSDI.H User-mode only definitions and declarations HIDPI.H Definitions and declarations for user-mode and kernel-mode HID clients HIDUSAGE.H Macro definitions for predefined usage table and usage values as defined in the HID Spec 1.0 and HID Usage Table Spec 1.0 HID.LIB Library file needed to resolve exported HID.DLL functions
Kernel-mode clients may also need the following files:
HIDPDDI.H Declarations and definitions for features available through the HID ioctl interface HIDPARSE.LIB Library file needed to resolve exported HIDPARSE.SYS functions
See the Universal Serial Bus Device Class Definition for Human Interface Devices (HID) Version 1.0 and Universal Serial Bus HID Usage Tables 1.0.
Files Description HCLIENT.HTM The documentation for this sample (this file) SOURCES The generic file for building the code sample BUFFERS.C Code for displaying HID report buffers in the extended calls dialog box. BUFFERS.H Function and structure declaration visible to other modules DEBUG.C Contains the function definitions for debug memory allocation tracking DEBUG.H Contains public macro definitions and function declarations for the debugging routines to deal with asserts, traps, and memory allocations ECDISP.C Code to handle the extended calls dialog box ECDISP.H Contains public declarations for the extended calls dialog box HCLIENT.C Code for handling HClient's main dialog box HCLIENT.H Contains public declarations and definitions for HCLIENT.C and visible to other modules HCLIENT.RC Visual C++ generated resource file for HClient HID.H Contains declarations and definitions for handling devices and data within HClient LIST.H Contains public macro definitions for manipulating doubly-linked lists LOGPNP.C Code for finding, loading and building logical HID device structures LOGPNP.H Contains public function declarations for LOGPNP.C MAKEFILE NT DDK build environment makefile PNP.C Contains the code for finding, adding, removing, and identifying hid devices. REPORT.C Contains the code for reading/writing hid reports and translating those HID reports into useful information. RESOURCE.H Visual C++ generated resource definition file STRINGS.C Code for converting data buffers and integer values to and from string representation for display. STRINGS.H Contains public function definitions for STRINGS.C
The core functionality relevant to HID client applications is contained in the files REPORT.C and PNP.C. The code in these files implements the basic features that most clients will require. Most of the other source files contain code for handling the user interface and calling the main tasks that are in the above two files. This section covers those relevant topics.
The major topics covered in this tour are:
Detecting Installed HID Devices
A necessary component of a HID client application is the detection of installed HID devices. The function FindKnownHidDevices() in PNP.C details how to do that work. The basic steps for identifying attached HID devices are:
The remainder of the code implemented in the function deals with creating a list of HID_DEVICE structures which contain information for each HID device in the system. This sample client accesses all HID devices in the system. A more specific implementation may only be looking for a certain type of HID device such as a joystick/gamepad.
Opening HID Devices
After detecting a HID device, HClient proceeds to open that device. When opening the device, HClient creates a HID_DEVICE structure to contain any information about the device that further routines might use. HID_DEVICE is defined as follows in HID.H:
typedef struct _HID_DEVICE { HANDLE HidDevice; // A file handle to the hid device. PHIDP_PREPARSED_DATA Ppd; // The opaque parser info describing this device HIDP_CAPS Caps; // The Capabilities of this hid device. HIDD_ATTRIBUTES Attributes; PCHAR InputReportBuffer; PHID_DATA InputData; // array of hid data structures ULONG InputDataLength; // Num elements in this array. 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; |
In addition to storing the basic HID structures as defined in HIDPI.H and HIDSDI.H, the HID_DEVICE structure also maintains an array of HID_DATA structures for each report type. These structures contain the most recent value used for each of the controls defined in the HIDP_VALUE_CAPS list and HIDP_BUTTON_CAPS list for the given report type. The fields within this structure are used by routines that pack/unpack data reports. The usage of these fields is discussed further in the section entitled Building/Interpreting HID Reports.
The function OpenHidDevice() in PNP.C performs the necessary steps to fill a HID_DEVICE structure for a device. In order to do so, it performs the following steps:
A given client application may not need to perform all of the above steps. For instance, if a client application only operates with a specific HID device (ie. vendor ID/product ID), it may forego any more processing after retrieving the attributes if the device it is currently opening is not the one its interested in.
A client application may also work only for a given usage page/usage combination. For example, a monitor control application would only need to open devices that match the monitor usage page and usage combination.
Lastly, a HID client may require only a subset of the detailed information currently stored in the HID_DEVICE structure. This sample application performs a broad range of functionality to provide a detailed sample.
Communicating with HID Devices
As mentioned above, a HID devices basic method of communication is through reports. A HID device can contain as many as 255 reports for each report type (Input, Output, and Feature). In order to properly communicate with a device, a client application must be able to create reports (when sending data) or extract data from reports (when receiving data). See the section titled: Building/Interpreting HID Reports for information on how to manipulate these report buffers.
A HID device reports information to the host through Input reports or Feature Reports. Typically, input reports contain the data generated by user interaction, such as a button press. Feature reports, on the other hand, report the current state or settings for a device. The methods for obtaining these two different reports are implemented in the functions Read() and GetFeature() in REPORT.C.
In order to receive data, HClient must use the file handle created in OpenHidDevice that has READ access and the buffer allocated for the given report type. For input reports, Read() calls the Win32 API ReadFile() and waits for the device to return a report. For feature reports, GetFeature() sets the first byte of the report buffer to the desired report ID and calls HidD_GetFeature() to obtain that feature report. After receiving a report back from the underlying drivers, these routines call UnpackReport() to fill in the corresponding HID_DATA structures.
Outputting data to a device is a bit more complicated. Once again, there are two different types of reports that can be sent to a device, Output and Feature. The corresponding functions for outputting data are Write() and SetFeature() in REPORT.C.
The first step that both of these functions perform is creating the desired report buffer. First, the HID_DATA structure with the desired report ID value is found. Then, these functions call PackReport() to set the data values within the allocated report buffer for that report ID. Once the report buffer has been created, either WriteFile() or HidD_SetFeature() is called to send the report packet to the device.
It is important to understand that all access to a HID device file handle is synchronized. Therefore, all threads using that handle will block until all previous requests to the device have completed. One possible client implementation would have one thread continuously read input reports while another thread sends or receives feature reports. Since the HidD_GetFeature() and HidD_SetFeature functions are implemented as DeviceIoControl calls, they are also synchronized with ReadFile and WriteFile(). There are two solutions to this scenario. One solutions uses overlapped I/O for asynchronous control. The second is to open two handles for the device, one for the read thread and one for the feature thread.
Building/Interpreting HID Reports
The last important idea when dealing with reports is extracting/setting data values from/in a given report buffer. The section covers the details of the functions PackReport() and UnpackReport() as implemented in REPORT.C.
As mentioned above, in order to communicate with a HID device, the client application must be able to either create the appropriate report to send to the device or extract the relevant information from a report received from the device. When Hclient initially opened the HID device and created the HID_DEVICE structure for that device, it also created an array of HID_DATA structures for each of the report types. The format of this HID_DATA structure is as follows:
typedef struct _HID_DATA { BOOLEAN IsButtonData; UCHAR Reserved; USAGE UsagePage; // The usage page for which we are looking. ULONG Status; // The last status returned from the accessor function // when updating this field. ULONG ReportID; // ReportID for this given data structure BOOLEAN IsDataSet; // Variable to track whether a given data structure // has already been added to a report structure union { struct { ULONG UsageMin; // Variables to track the usage minimum and max ULONG UsageMax; // If equal, then only a single usage ULONG MaxUsageLength; // Usages buffer length. PUSAGE Usages; // list of usages (buttons ``down'' on the device. } ButtonData; struct { USAGE Usage; // The usage describing this value; USHORT Reserved; ULONG Value; LONG ScaledValue; } ValueData; }; } HID_DATA, *PHID_DATA; |
When creating the HID reports for sending to the device, the function PackReport() is used. PackReport takes as input a pointer to a HID_DATA structure and the appropriate report buffer. The pointer should point to the first HID_DATA structure in the array that contains the report ID for the report to be created. With this information, PackReport() performs the following steps:
After having looped through the array of HID_DATA structures, the report buffer will have the appropriate report ID set as the first byte in the buffer and is ready to be sent to the device.
In a similar manner, UnpackReport() extracts data from a given report. Like PackReport(), this function receives a report buffer returned by the device and an array of HID_DATA structures that could possibly be filled in. This routine performs the following steps:
Once finished, the array of HID_DATA structures will contain the new settings based on the information returned in the report buffer.
© 1999 Microsoft Corporation