HCLIENT

[This is preliminary documentation and subject to change.]

SUMMARY

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.

BUILDING THE SAMPLE

To build the HClient.exe sample, follow these instructions.

  1. Run the standard Windows NT®/Windows® 2000 DDK build environment (checked or free)
  2. Change to the .\src\wdm\hid\hclient directory
  3. Execute build
  4. The built HClient.exe will be found in .\lib\<i386|alpha>\<checked|free>

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

TOOLS

The only tools needed to work with HClient are HID devices. In the Windows NT/Windows 2000 system, file handles cannot be opened on mice and keyboards. However, all other HID devices will be available and recognized by HClient for testing purposes.

RESOURCES

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.

CODE TOUR

File Manifest

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

Programming Tour

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 it’s 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 device’s 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:

  • Zeroes out the current report buffer
  • Searches the array of HID_DATA structures looking all data values that match the report ID
  • For each data value structure found, it calls HidP_SetUsageValue to set the value currently stored in the structure into the report.
  • For each button structure found, it calls HidP_SetUsages with the corresponding usage value to set the button state in the report to "On".

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:

  • Extracts the report ID from the first byte of the report buffer
  • Searches the array of HID_DATA structures looking for all structures that match the report ID
  • For each data value structure found, it call HidP_GetUsageValue and HidP_GetScaledUsageValue to set the Value and ScaledValue fields.
  • For each button structure, UnpackReport() calls HidP_GetUsages() to retrieve all the "On" buttons for that data structure.

Once finished, the array of HID_DATA structures will contain the new settings based on the information returned in the report buffer.

Top of page

© 1999 Microsoft Corporation