Click Here to Install Silverlight*
United StatesChange|All Microsoft Sites
MSDN
|Developer Centers|Library|Downloads|Code Center|Subscriptions|MSDN Worldwide
Search for


Advanced Search
MSDN Home > MSJ > February 1998
February 1998

Microsoft Systems Journal Homepage

May the Force Feedback Be with You: Grappling with DirectX and DirectInput

Jason Clark

DirectX is a set of components that can provide your Windows applications with more intimate control of a system's hardware. DirectX components consist of many fairly high-level APIs that make complicated tasks, such as blitting bitmaps and playing sounds, fairly simple.

This article assumes you're familiar with C++, COM

Code for this article: FeelForce.exe (16KB)
Jason Clark supports software core development for Microsoft. He believes that logic is a pure art and a pure science. He can be reached at jclark@microsft.com.

In case you haven't noticed, games and multimedia programs for Windows® have really started to rock. Hardware is getting faster, and Windows has become more flexible. Since Microsoft released DirectX®, game developers have had increasingly less incentive to develop their software for other platforms. Many software publishers have migrated their game development completely to Windows.
Developing games for PCs has never been easy. With the myriad of graphics and sound cards available, developers have learned the art of balancing functionality with compatibility. They've had to deal with nasty issues such as page swapping, segmented memory architecture, and bit manipulations. And with the growing popularity of multiplayer games, developers must also deal with considerations such as networking and communications. Game development has become easier with the introduction of DirectX. Much of this grunt work has been greatly simplified by the DirectX objects now available to developers.
I would like to take some of the mystery out of DirectX. Is a DirectX-based program a normal Windows-based application? Do you have to know COM? Is it worth using DirectX for a simple program? Do you have to use all of DirectX? I'm sure there are more questions, but you get the point.
This article, the first in a series, will introduce DirectX and then let you get your feet wet with one of its components, DirectInput
®. A demo application illustrates DirectInput, focusing on force feedback functionality. Each successive article will expand on the demo app until it has touched on each major component of DirectX.

DirectX Demystified

DirectX is a set of components that provides Windows-based applications with more intimate control of a system's hardware. That's it. (For a list of the components and their uses under DirectX 5.0, see Figure 1.) But what do I mean by intimate control?
I have often heard the hardware control that DirectX offers described as low-level. This makes me picture bit manipulations and other nasty programming feats. Actually, DirectX components consist of many high-level APIs that make complicated tasks, such as blitting bitmaps and playing sounds, fairly simple. It's more accurate to characterize DirectX as giving your process better hardware control than in the past. This is a noteworthy feature in Windows, where physical resources are largely shared or controlled by the operating system. The power behind DirectX definitely lies in the control that it offers.
The DirectX components follow the industry standard for binary objects known as COM. I'm going to assume you have some knowledge of COM. If you don't, there are plenty of resources available to get you up to speed.

Getting Started with DirectX

Let's start with the installation of DirectX. Most likely, one of your favorite games has installed DirectX on your system already. In the interest of being up-to-date you should probably install it on your system from the latest Microsoft Platform SDK. You can get the platform SDK from http://www.microsoft.com/msdn or your MSDN subscription CDs. By default, the Microsoft Platform SDK is installed in the \MSSDK directory in the root of your default drive. Header files for DirectX (and the rest of the SDK) can be found in \MSSDK\INCLUDE. Lib files are found in \MSSDK\LIB.
The Platform SDK contains some excellent DirectX samples and documentation. While early releases of DirectX documentation were sketchy and sometimes incorrect, this problem has been largely eliminated with the current version of DirectX. I strongly suggest becoming familiar with the documentation.
Now you are ready to begin putting together an application that takes advantage of DirectX. Fortunately, you don't have to deal with everything at once. DirectX is a set of components that can be used separately. In fact, in programming terms the different parts of DirectX have nothing to do with each other. They simply share a common design style and a common goal: making game programming for Windows easier.
Is there anything special about an application that uses a DirectX component? Not really. Allow me to explain. Applications that use DirectX are Win32
®-based applications. They use the normal Win32 API set and have access to all of the operating system facilities available to processes. In fact, you can use DirectX in either a regular GUI Windows-based application or you can choose to make your application a console application. You can develop your application using straight Petzold-style SDK programming, or you can use a foundation class library such as MFC. In general, the only requirement is that most components of DirectX require an HWND from your process, so you need to have at least one window. I'll get into these details shortly.
Although the components of DirectX are separate, implementation style and usage are similar for each. DirectInput is a great starting point for learning DirectX because it is one of the simpler components.

Use the Force

OK, that's the only Star Wars reference I'll subject you to, but I used it because I am really happy about the addition of force feedback support to DirectInput. Previous to DirectX 5.0, DirectInput provided support for reading input from the mouse and keyboard. It was a decidedly useful-but-boring feature of DirectX. That support has been expanded to devices that have the ability to relay feedback back to the user in the form of physical force.
If you aren't immediately sold on this idea, let me set you up with a scenario. You have just started up your favorite super-realistic 3D offroad racing game. You have your force feedback joystick in hand. You are at the starting line, and you can hear your buggy's engine idling. You can also feel your buggy's engine idling through your joystick! But it doesn't end there. Now you are racing. You feel your engine's buzz at a higher RPM. You are busy kicking your opponent's electronic butt when you come to a rough spot in the track. Your buggy is bouncing all over the place, and so is your joystick. Your tire finds a rut that causes your buggy to pull to the left. You guessed it! Your joystick also pulls to the left. You can feel every bump, scrape, slam, and crash in the entire course. As you can see, this thing has potential.
At the moment, the only force feedback device with a Windows driver that supports DirectInput is the Microsoft
® SideWinder Force Feedback Pro. This will not be true for long; new devices and drivers for existing devices will be hitting the market soon. As I am writing this, Thanksgiving turkeys are being purchased and folks are starting to get the holiday jitters. I have a sneaking suspicion that force feedback devices will find their way into many a household before this article makes it to print. Hopefully, one has found its way into yours, as it will make the sample code a lot more fun.

Dissecting DirectInput

DirectInput is composed of three objects: DirectInput, DirectInputDevice, and DirectInputEffect (see Figure 2). DirectInput is a high-level object that allows you to do some basic initialization and fact-finding regarding input devices. It is ultimately used to create the lower-level DirectInputDevice object, which actually works with specific input devices attached to the system. Get used to this design. Each major component of DirectX takes a similar approach. You create a high-level object, such as a DirectInput or DirectSound
® object, that in turn creates lower-level objects that actually communicate with specific hardware.
The DirectInput object is the easiest of the three objects to understand. In fact, it exposes only five functions in the form of its one interface: IDirectInput (see Figure 3). It is a very important part of DirectInput because it's your starting point. Let's get started with creating a DirectInput object.

Creating a DirectInputObject

Your goal is to create a DirectInput object and retrieve a pointer to its IDirectInput interface. This should be done during your application's initialization, in one of two ways.
The first approach is fairly simple. DirectX provides a helper function, DirectInputCreate, to create and initialize a DirectInput object. It's declared in the DINPUT.H header file along with all of DirectInput's function, interface, and macro definitions. The actual function is located in the DINPUT.LIB file.
DirectInputCreate is defined as follows:

 HRESULT WINAPI DirectInputCreate(
   HINSTANCE hinst, 
   DWORD dwVersion, 
   LPDIRECTINPUT * lplpDirectInput, 
   LPUNKNOWN punkOuter 
 );
The first parameter is the instance of your process. The second is the version of DirectInput that your application requires. Normally you pass the DIRECTINPUT_VERSION macro, which will be defined as the current version. The third parameter is the most important, and it's potentially confusing if COM is fairly new to you. It is the address of a pointer to an IDirectInput interface. Your application should define a variable (possibly global) of type LPDIRECTINPUT and pass its address as the third parameter to DirectInputCreate.
The last parameter is known as punkOuter, and has to do with a COM technique known as aggregation. You can safely ignore this parameter and pass NULL. The return value is an HRESULT, which is a standard return type for COM. It can be compared to the possible return values for the function, or you can apply one of the COM macros SUCCESS or FAILED to check it.
Using DirectInputCreate, you can easily create your high-level object and retrieve a pointer to its main interface. This is another design approach of DirectX that you should get used to. Each DirectX component provides a helper function, such as DirectInputCreate or DirectDrawCreate, which can be used to create your high-level object. You will probably use these helper functions to create DirectX objects in your own applications. However, these functions are actually creating COM objects, which can also be done by using a standard Win32 API function called CoCreateInstance. This leads to the second way that you can create a DirectInput object.
Using CoCreateInstance to create a COM object in Win32 is very common. If your application is already using CoCreateInstance to create other COM objects, you may want to use it to create your DirectX objects as well. Because COM objects are registered to the system when they are installed, all you need to know is the object's GUID to create an instance of it. All of the GUIDs that you need to create DirectX objects are declared in the header files, such as DINPUT.H, and they are defined in the DXGUID.LIB lib file. You can pass one of these predefined GUIDs to CoCreateInstance and have Windows create the object for you.
CoCreateInstance is defined as follows:

 STDAPI CoCreateInstance(
   REFCLSID rclsid, 
   LPUNKNOWN pUnkOuter, 
   DWORD dwClsContext, 
   REFIID riid,
   LPVOID * ppv
 );
The first parameter is the GUID for the object you want to create. In this case, DirectX defines the GUID for you as the GUID structure variable named CLSID_DirectInput. The second parameter is the familiar pUnkOuter. Again, you can ignore this feature and pass NULL. The third parameter, dwClsContext, defines where the COM object is to be created. DirectX only supports inproc servers, so you will pass the value CLSCTX_INPROC_SERVER.
The fourth parameter to CoCreateInstance is where the real difference in the two approaches comes in. Remember that COM objects expose interfaces. Like the objects themselves, the interfaces are identified using a GUID. Using the first approach, you have no choice about which interface you get; you always get IDirectInput. Using CoCreateInstance, you can request any interface supported by the object that you are requesting simply by passing the predefined GUID for that interface. In the case of DirectInput, this is all but moot because the DirectInput object's only useful interface is IDirectInput. I brought up this approach because other DirectX components support more than one useful interface. (For example, the DirectDraw
® object can be manipulated by its IDirectDraw or IDirectDraw2 interface.)
The last parameter to CoCreateInstance is the actual address of your application's interface pointer variable.
You now have the object and an interface to the object. The CoCreateInstance approach requires one more step: you must make your first call into an interface function to initialize the object. DirectInputCreate provides you with an already-initialized DirectInput object, but CoCreateInstance has no specific knowledge of DirectInput, so you must call the Initialize member function of the IDirectInput interface. Assuming you defined your pointer to the IDirectInput interface variable as follows

 LPDIRECTINPUT g_lpDI 
your call to Initialize would look like this:

 g_lpDI->Initialize( hInstance, DIRECTINPUT_VERSION);
Now you have effectively done, using CoCreateInstance, what CreateDirectInput did in the first approach. Though if you choose to take this "standard" approach to creating your objects, you will also have to pay attention to other standard COM needs such as the required call to CoInitialize and CoUninitialize.

Using the DirectInput Object

Once you have your DirectInput object, you can use it to create DirectInputDevice objects to be used with specific devices on your system. This is done using the CreateDevice function, which is one of the five functions that are a part of the IDirectInput interface. CreateDevice requires a GUID passed for the device in question, and it returns a pointer to the IDirectInputDevice interface for your new DirectInputDevice object.

 HRESULT CreateDevice(
   REFGUID rguid, 
   LPDIRECTINPUTDEVICE *lplpDirectInputDevice, 
   LPUNKNOWN pUnkOuter 
 );
This should look familiar, as it's similar to the CoCreateInstance and DirectInputCreate APIs. But I am not yet completely ready to move on to the DirectInputDevice object. The reason is that you need the GUID for a device before you can create a DirectInputDevice object to use it.
The DirectInput library predefines two GUIDs for creating DirectInputDevice objects: GUID_SysKeyboard and GUID_SysMouse. Either of these can be passed directly to the CreateDevice function and you will be given a DirectInputDevice for that device.
Notice the surprising lack of a predefined GUID for joysticks. In Windows, you usually have a system keyboard and a system mouse. Even if a system is an oddball and has two mice, it still has one system mouse defined as the composite movements of both mice. This is also the case with keyboards. On the other hand, the system itself has no use for a joystick. You can install a joystick or two (or three, or four), and the system doesn't care beyond the driver level. It will not assign one of these devices a special system status, and it will not use one of the devices in its daily business. So it would not be logical (or truthful) for DirectInput to define a GUID for a system joystick.
So how do you find the GUIDs for the joysticks attached to the system? To find them, you have to enumerate devices. Enumerating system devices and capabilities is common in DirectX. To enumerate input devices in your system, use EnumDevices, part of the IDirectInput interface. EnumDevices is defined as follows:

 HRESULT EnumDevices(
   DWORD dwDevType, 
   LPDIENUMCALLBACK lpCallback, 
   LPVOID pvRef, 
   DWORD dwFlags 
 );
Notice that this function is similar to other enumeration APIs in Windows, such as EnumWindows. You pass the function a callback as the second parameter, and an application-defined 32-bit value, which is passed to the callback function as the third parameter. You also pass the type of device you wish to enumerate as the first parameter. For joysticks this would be DIDEVTYPE_JOYSTICK (see Figure 4 for a complete list of device types). Pass flags that detail what you want to enumerate as the last parameter. Currently supported flags are DIEDFL_ATTACHEDONLY and DIEDFL_ALLDEVICES, which are mutually exclusive, and DIEDFL_FORCEFEEDBACK, which indicates force feedback devices and can be bitwise ORed with the other two flags.
While EnumDevices is enumerating input devices for the system, it is repeatedly calling your callback function. This function is defined as follows:

 BOOL CALLBACK EnumProc(LPCDIDEVICEINSTANCE lpddi, 
   LPVOID pvRef) ;
Since this function is defined by your application and passed to EnumDevices, this is an excellent place for you to call CreateDevice until you have created enough DirectInputDevice objects to suit your needs. But the function does not have to be implemented this way. It could just as easily store all of the GUIDs for the enumerated devices in a list to be used by the code following the call to EnumDevices.
Your callback function receives two parameters. The second is the application-defined 32-bit value passed to EnumDevices. More importantly, the first parameter passed is a pointer to a structure containing lots of useful information about a single device that matches the criteria for the enumeration. This is the DIDEVICEINSTANCE structure. The most important piece of information for this purpose is the device's GUID, which is stored in the guidInstance member of the structure.
After your application is completely finished with DirectInput, it should call the Release member of the IDirectInput interface. This tells the DirectInput object that it can free itself. With DirectX, it is a good idea to get used to freeing your objects, starting with the lower-level objects and ending with your high-level objects. Normally an application will call Release as part of its cleanup or shutdown routine. Once again, get used to this because it is a necessary part of using every DirectX component, and every COM component for that matter.
Now that you have used the CreateDevice member function to get an interface to a DirectInputDevice object, you are ready to begin dealing with actual physical devices connected to the system.

Using the DirectInputDevice Object

Each instance of a DirectInputDevice object is related to a specific device on the system. As a result, this object keeps the promise of DirectX by giving you more power and control in dealing with the systems hardware. Let's talk about what to do once you have your DirectInputDevice object.
You have an interface pointer to the IDirectInputDevice interface; what now? First, set the data format for the device. Do this by calling SetDataFormat, which is an interface member function. Setting the data format includes a myriad of possible decisions, including axis information, relative or absolute coordinate information, and so on. All of these details are relayed to the function via a structure called DIDATAFORMAT. In fact, SetDataFormat's only parameter is a pointer to this structure.
Filling in the details of this structure can be daunting, to say the least. Thank goodness it's rarely necessary, because DirectInput has defined several DIDATAFORMAT structure variables that can be used for the more common input devices: c_dfDIKeyboard, c_dfDIMouse, c_dfDIJoystick, and c_dfDIJoystick2. To set the data format for a garden-variety force feedback joystick, you'd make a call something like this:

 lpdid->SetDataFormat( &c_dfDIJoystick ) ;
In this example, lpdid would be your pointer to the IDirectInputDevice interface.
Once you have set the data format for the device object, you need to set the cooperation level of the device. Cooperation levels warrant some explaining since they are common throughout DirectX. Most DirectX objects that deal directly with system hardware have a function called SetCooperativeLevel as a member of one of their interfaces. This is important because it defines the level of control that your application wields over the hardware relative to other processes in the system. Like other DirectX objects, very little can be done with a DirectInputDevice object until its cooperation level has been set. To understand cooperation levels, you need to be acquainted with the Acquire function. This function is called to obtain actual access to the physical device (not to be confused with the logical DirectInputDevice object). Conversely, the Unacquire function releases access to the physical device.
Now back to SetCooperativeLevel. Here is the function's definition:

 HRESULT SetCooperativeLevel(
   HWND hwnd,     
   DWORD dwFlags  
 );
The hwnd is your application's main window (more on this in a moment). The flags are some combination of the following values ORed together: DISCL_BACKGROUND, DISCL_FOREGROUND, DISCL_EXCLUSIVE, DISCL_ NONEXCLUSIVE.
If DISCL_EXCLUSIVE is ORed into the flags parameter, then your process will be the only one granted access to the physical device when you have acquired the device. On the other hand, if DISCL_NONEXCLUSIVE is selected, then multiple processes in the system can acquire and use the device concurrently. If DISCL_BACKGROUND is ORed into the flags parameter, your process will not lose a physical device to another process. However, a system event such as the Ctrl+Alt+ Del combination can still implicitly "unacquire" a device for your process. If DISCL_ FOREGROUND is used, your process will automatically unacquire the physical device if your main window ceases to be the active window. This is the significance of passing your application's main window handle to SetCooperativeLevel. DirectX coordinates device sharing automatically based on whether the provided window is currently the active window in the system.
So what is the significance of all of this? Let me give you some examples. If the cooperative mode for a force feedback joystick device is DISCL_FOREGROUND | DISCL_EXCLUSIVE, the application will be able to read from the joystick and cause force feedback effects to play (force feedback requires exclusive-level cooperation) as long as the application is active. As soon as the user selects another application, your application loses control of the physical device and the new active application can access the device if it wants to. This means that if you are debugging an application and you switch to the debugger's window, your application loses the joystick because its window became inactive.
What would happen if your cooperative level for the same joystick was DISCL_BACKGROUND | DISCL_EXCLUSIVE? Your application would have access to the joystick at all times, regardless of the state of its window. But now other processes in the system cannot acquire the joystick until your process unacquires it—regardless of what the user does!
It would seem obvious that you would always choose DISCL_FOREGROUND | DISCL_EXCLUSIVE for a release product, and DISCL_BACKGROUND|DISCL_EXCLUSIVE for the debug version. But this is not always an option. For example, DirectInputDevice will fail a call to SetCooperativeLevel for exclusive use if the device is the system keyboard. This is because the operating system wants to allow the user the freedom to switch from one process to the next, no matter what. Similarly, DirectInputDevice won't allow a cooperative level of DISCL_BACKGROUND|DISCL_EXCLUSIVE for the system mouse. Windows does not want an application to be able to completely shut a user out from the operating system.
I touched on the Acquire function briefly. Let's finish that discussion. The physical device must be acquired, using Acquire, before you can read from it or send information to it. To explicitly unacquire a device, which you do when you are temporarily or permanently finished with the device, use the Unacquire function. But Unacquire is not the only way that you can lose control of a device.
A device can be implicitly unacquired if the application's main window ceases to be the active window in the system and the DISCL_FOREGROUND flag was used when setting the cooperation level. This means that between the time that the application calls Acquire and the time that it actually tries to read from the device, it could have lost its acquire. You need to catch such errors by checking return values, and you should be prepared to reacquire the device using Acquire at any time.
One final point regarding Acquire and Unacquire: while your application has a device acquired at the exclusive cooperative level, DirectX owns the device. For example, a button on your application's window will not respond to the mouse, if the mouse is acquired (exclusively) by DirectX. This means that you should unacquire a device if you want Windows to respond to the device. Said another way, call Unacquire if you don't want DirectInput to be reading data from a device.
After setting the cooperation level of the device, you then configure other settings for the device. Acquire the device, then begin regular polling for input data using the GetDeviceState function. When finished with the device object, call Unacquire, and then Release to free up the DirectInputDevice object. Details differ from device to device; I'll cover the joystick and keyboard here, which should provide enough base knowledge for you to read input from other devices.

The Keyboard

The keyboard is by far the simplest of the devices to read. In fact, after setting the data format, setting the cooperative level, and acquiring the device, you are ready to read the keyboard state. This is done using the GetDeviceState member of the IDirectInputDevice interface. GetDeviceState populates a structure with state information about the physical device. The type of structure it populates is decided by a previous call to SetDataFormat. For the keyboard, the data structure is simply a 256-element array of bytes. Each byte corresponds to a key on the keyboard, and the high bit for the byte is set if the key is pressed.
DirectInput defines a set of constants prefixed by DIK_XXX, which can be used to index into the array of bytes to find data about a particular key. For example, to find out if the right Shift key is currently pressed, you can use the DIK_RSHIFT define as follows:

 GetDeviceState(256,(LPVOID) cKeyboardData) ;
 if(cKeyboardData[DIK_ RSHIFT]&0x80)
     DoWhatever() ;
cKeyboardData is the 256-char buffer. It's almost that simple. Do remember that if GetDeviceState ever returns a value of DIERR_INPUTLOST, you must acquire the device using Acquire. This is going to happen every time the user switches away from your application.
In the interest of being complete, it's important that I mention that it is possible to request that DirectInput buffer keyboard information. This requires you to supply a buffer and set the buffer size for the device using SetProperty. I don't have room to cover this technique here, this would be useful if your application is not able to check the keyboard state fairly often. It is possible that the user could press and release a key between two calls to GetDeviceState by the application. This keypress would be lost if DirectInput were not buffering keyboard data for you.
So that is the keyboard in a nutshell. Now let's discuss the joystick.

The Joystick

Joysticks are fun. True to their cheesy name, these devices add a lot of joy to the gaming experience. They also add something to the programmer's experience. Normally, you retrieve your IDirectInputDevice interface (and object) via a call to the CreateDevice member of the IDirectInput interface. This is still true with a joystick; but you want to immediately upgrade your interface to IDirectInputDevice2. You can ask the interface returned by CreateDevice for the new interface by using a call to QueryInterface such as this one:

 hr = lpDIDeviceJoystickTemp->QueryInterface(
     IID_IDirectInputDevice2, 
     (void **) &g_lpDIDeviceJoystick);
Assuming success, you can then call release on your first interface and use your shiny new IDirectInputDevice2 interface. But why do you need this? The IDirectInputDevice2 interface provides all of the functionality of IDirectInputDevice with two important added features: Support for polling devices and support for force feedback devices. I will cover both of these in a minute.
Next, there are some setup considerations. Remember that SetDataFormat defines the type of data later returned by GetDeviceState. For a joystick device, pass one of the predefined variables: c_dfDIJoystick or c_dfDIJoystick2. These set the DIJOYSTATE or DIJOYSTATE2 structures as the return data type. Which one you choose depends largely on what type of joystick features you intend to use. Reading through the elements of these structures should help to clarify this. Here I will use the DIJOYSTATE structure, and will therefore set the data format of the device with the predefined c_dfDIJoystick structure variable.
Like all input devices, you set the data format and cooperative levels for joysticks. The joystick tends to require a bit more attention than the keyboard. This is because there are roughly a gazillion joysticks available today so your application should make sure that the device at its disposal is suitable for its needs. If not, it can adjust its needs or alert the user to the fact that the joystick is lame! Either way, a device's capabilities can and should be ascertained with a call to the GetCapabilities member function of the IDirectInputDevice interface.
This brings me to another point of discussion that applies to all of DirectX. DirectX offers a wide array of support for a variety of devices. To avoid defaulting to lowest common denominator support, the DirectX objects support features or groups of features that have been predefined for a given device. Odds are that you develop software on a vastly different machine from mine. Our machines are going to support differing levels of DirectX functionality. Writing good software using DirectX requires that you check the capabilities of the hardware, period. At the very least you can fail the application if a feature is missing. Best case scenario, of course, would be that your application intelligently adjusts itself for a missing feature.
Before you begin retrieving input from your device, you need to set properties for the device. This includes such details as the range of values you want, to what portion of the joystick is the deadzone (middle position). This is all done with the SetProperty function. This function is potentially confusing, so let's take it slowly.
SetProperty sets one feature of the device. First, you must fill in a data structure with the information regarding the setting you wish to change. Consult the documentation in the Platform SDK for all of the structures available. Each possible structure starts out with the elements of a DIPROPHEADER structure, which you fill in with information describing the feature you wish to change. Then you fill in the remainder of the structure with data specific to the setting you're changing. Finally, call SetProperty, passing the GUID for the change and a pointer to the DIPROPHEADER portion of your structure. The following code snippet would set the vertical range of a joystick to –100 by 100:

 DIPROPRANGE  dipRange ;
 dipRange.diph.dwSize       = sizeof(dipRange); 
 dipRange.diph.dwHeaderSize = sizeof(dipRange.diph); 
 dipRange.diph.dwObj        = DIJOFS_Y; 
 dipRange.diph.dwHow        = DIPH_BYOFFSET; 
 dipRange.lMin              = -100; 
 dipRange.lMax              = +100;
 g_lpDIDeviceJoystick->SetProperty( DIPROP_RANGE,
                                    &dipRange.diph) ;
The most confusing parts of this structure are the diph.dwObj and diph.dwHow elements. The diph.dwHow member describes what kind of info the diph.dwObj member holds. The diph.dwObj member actually describes which property is being set. Most of the time, the "How" value will be DIPH_BYOFFSET, and the "Obj" value will be a predefined offset into the structure that was passed into SetDataFormat.
That said, I should point out that it is possible to enumerate objects of a device, which include buttons and other features. This is done with the EnumObjects function. In doing so, you are provided with an object ID. You can pass this ID in the diph.dwObj member, at which point you would fill the diph.dwHow member with DIPH_BYID. If this method is confusing, use the "by offset" approach with preexisting values such as DIJOFS_Y until you have the need to take the second approach.
Before reading data from the device, at the very least you need to set the min and max values for the X and Y axis of your device. Once your device's properties have been set, you can acquire the device and begin retrieving data from it. Retrieving data from a joystick is different than from a keyboard or a mouse because it is a polled device.
Keyboards and mice cause hardware interrupts, which are handled by drivers in the system and are used to update the data that DirectInput returns via calls to GetDeviceState. Polled devices (such as most joysticks) do not create hardware interrupts. As a result, DirectInput must be told to retrieve state information from the device. This is done by making a call to the Poll member function of the IDirectInputDevice2 interface. This is also an appropriate time to check if your device is in need of reacquiring. Once your device has been successfully polled, you can retrieve the state data by calling GetDeviceState.
If you used the c_dfDIJoystick variable when calling SetDataFormat, then GetDeviceState is going to fill in a DIJOYSTATE structure with information about your joystick's current state. The contents of this structure depend largely on the features of the physical device and the settings you specified through SetProperty. For example, if the lY member of the structure is equal to –50, and you have set your Y axis range from –100 to 100, the joystick is vertical halfway between the center and the top of its range. You will notice that this structure contains members for rudders, sliders, thrusts, and point-of-view hats, as well as support for up to 32 buttons. Your application should make sure that the device's ranges are set to values that are logical for the needs of the application. As for retrieving data from a joystick device, an application should simply poll the device in a regular period.
Now you're ready to move on to making force feedback effects for a joystick. Although this process begins with the DirectInputDevice object, much of the work is done with an object called DirectInputEffect. For the most part, DirectInputDevice retrieves input from a device and creates instances of the DirectInputEffect object. The guts of force feedback exist in the DirectInputEffect object.

Using DirectInputEffect

First of all, I should explain some force feedback terminology. A force feedback device is a physical device capable of creating a force that the user can feel. These forces are called effects. You could have a bump effect or an effect that's a constant force pulling the stick to the upper-right. Either way, these effects are "played." An effect is played programmatically, in response to a function call, or automatically in response to a user firing a button.
DirectInput currently supports about a dozen different effect types (see Figure 5). These effects range from the low-level constant force effect, which must be managed entirely by the application, to the higher-level ramp or wave effects, which are managed by DirectInput or the device itself. There are four basic types of effects: constant force, ramp effect, periodic effect, and conditions. A constant force is a force in a single direction that does not change in strength. A ramp effect is a constant force that changes in strength linearly over time. A periodic effect fluctuates repeatedly along a given axis, whose magnitude or strength of force is defined by the type of periodic effect. A condition is an effect that responds to user interaction with the joystick. This can be an effect such as a spring that grows stronger the farther the stick is pushed in a given direction.
Let's get started with creating an effect. All of my work with force feedback has been with the Microsoft SideWinder Force Feedback Pro. This means that some of the details in this article may be more or less true for other devices. For example, I found that it was necessary to turn off the auto-center feature for my joystick if I wanted any level of noticeable force feedback. This can be done using the SetProperty member of the IDirectInputDevice interface. Whether this is true for other devices, I don't know.
It isn't a bad idea to acquire a device before creating forces for the device. Although this is not necessary, it is necessary to acquire the device before the effect can be downloaded to the device. This point becomes particularly important for forces that are played in response to the user pressing a joystick button. I will discuss this in more detail in a moment, but first let's cover the creation of an effect.
First, create an instance of the DirectInputEffect object for each effect that you plan to use with the device. This is done with a call to the CreateEffect member function of the IDirectInputDevice2 interface. This function requires a GUID for the desired effect, as well as a pointer to a DIEFFECT structure that has been filled in with the details about your effect. Finally, CreateEffect returns a pointer to the IDirectInputEffect interface in a variable whose address you pass to the function. As you may have guessed, the guts of this call fall in the filling out of DIEFFECT structure.
The DIEFFECT structure is defined as follows:
 typedef struct { 
     DWORD dwSize; 
     DWORD dwFlags; 
     DWORD dwDuration; 
     DWORD dwSamplePeriod; 
     DWORD dwGain; 
     DWORD dwTriggerButton; 
     DWORD dwTriggerRepeatInterval;
     DWORD cAxes; 
     LPDWORD rgdwAxes; 
     LPLONG rglDirection; 
     LPDIENVELOPE lpEnvelope; 
     DWORD cbTypeSpecificParams; 
     LPVOID lpvTypeSpecificParams; 
 } DIEFFECT, *LPDIEFFECT;
The dwSize member is the size in bytes of the structure. The dwFlags tells what type of coordinate style you will use for the effect, as well as whether you will use the by offset or by ID approach to describing axes (exactly like SetProperty described previously). Normally, you will set the flags to DIEFF_CARTESIAN|DIEFF_OBJECTOFFSETS. This means that axes are described by offset and are in terms of XYZ coordinates.
The dwDuration describes how long the effect will play in microseconds. Note that dwDuration can be set to INFINITE. The dwSamplePeriod describes, in microseconds, how long it takes the effect to play one cycle. Different devices support different sample periods. In practice, the SideWinder joystick seems to support periods no longer than one second and no shorter than 1/80 second. The dwGain value can be viewed as a master volume for the effect, in that it describes in relative terms how forceful the effect is. The range for this value is from 0 to 10000.
The dwTriggerButton and dwTriggerRepeatInterval values are used to set the button that causes the effect to be played, and how often it should repeat itself. Of course, an effect can be set with no button association by filling the dwTriggerButton value with DIEB_NOTRIGGER. Otherwise, the dwFlags member defines its value type in that it can describe a button by ID or by offset. Because the by offset approach does not require a call to EnumObjects, you will commonly be assigning values such as DIJOFS_ BUTTON0 and DIJOFS_BUTTON1.
The cAxes member tells how many axes should be affected by this effect. rgdwAxes points to an array of DWORDs that describe the axes involved. This array should have one element per axis. Like the buttons before them, the axes are described by offset or by ID. Common by offset values include DIJOFS_X and DIJOFS_Y.
Likewise, the rglDirection member points to an array of longs, one per axis. For Cartesian values, a Y value of –1 and an X value of 1 would be the same as a Y value of –10 and an X value of 10. That is to say, it would describe a diagonal force direction that moves the joystick away from the user and to the right. You should adjust the relative magnitudes of the values if you want a force in a direction that is not a multiple of 45 degrees. For example, a Y value of –10 and an X value of 1 would describe a direction in the same quadrant as the last example, but it would be significantly closer to the Y axis.
Effects can also have an envelope described for them. Filling in a DIENVELOPE structure and then filling in the lpEnvelope member with its address does this. An envelope causes the effect's magnitude or strength to be affected over a period of time, where the attack level is a starting modifier to the effect, and the attack time describes in microseconds how long it takes the effect to reach the sustain or unmodified strength. The fade level is the level of the effect at the end of the envelope, and the fade time is the number of microseconds that it takes to begin fading. An envelope can be used to make an effect initially strong, and then slowly fade, for example. See Figure 6 for an example of how an envelope changes an effect.
Figure 6 Envelope Effects
Figure 6 Envelope Effects


The last two members of the DIEFFECT structure are cbTypeSpecificParams and lpvTypeSpecificParams. They hold the size in bytes and the address of a structure specific to the type of effect being created. For information on which structures to use for a given type of effect, see Figure 5.
After filling in this structure, and calling CreateEffect, you will receive a pointer to an IDirectInputEffect interface. You can now use this interface to play the effect, change the effect, and so on. If you have not associated an effect with a button, you must use the Start and Stop members of the IDirectInputEffect interface to play and stop it. Effects are automatically downloaded to a device when they are played, unless they are associated with a button in which case they are downloaded upon creation. If the application must reacquire the device, then all effects that are associated with buttons must be downloaded explicitly by calling the Download member for that effect.
Effects can be unloaded with the Unload member, and they can also be given a new set of parameters by passing a new DIEFFECT structure to an effect's SetParameters member function. When the application is finished with an effect, it must call the Release member of the interface.

The Demo Sample

So now do you know enough about force feedback to be dangerous. To help solidify the information in this article, let's look at a sample application. This application serves two purposes. First, it forms the groundwork from which my next few DirectX articles will build their samples into a full-blown DirectX demo. It also provides specific coding samples for DirectInput.
First, you should build the demo code and run it. You should see a stick figure in a window (see Figure 7). Using the cursor keys or a joystick, you can move the man around. You will also see coordinate and input state information updated in the upper-left corner of the window. If you have a force feedback joystick, you should be able to feel a couple of different forces by pressing buttons 1 and 2 of your joystick. You should also feel a bumping effect if you slam the little stick figure into the edge of the window.
Figure 7 Demo App
Figure 7 Demo App


This demo illustrates the use of DirectInput. There is still a fair amount of code here that's not directly related to DirectInput. This code has been broken into modules based on function. Main.cpp is your basic boilerplate WinMain and window creation code. Aside from calls to initialization functions, this module has nearly nothing to do with the rest of this article. It creates a window, and enters into a message pump. WndProc.cpp contains the window procedure for the application's window.
Demo.cpp starts the interesting code. Whenever you see a reference to the "demo", it is referring to this application's game logic. For example, the InitDemo function sets up the state data for the little stick figure and creates some needed events and threads. Aside from initialization, all of the demo logic is running in a second thread. The thread attempts to take input, and then updates the "reality" or state data of the demo 32 times a second. It then invalidates the window and allows the main thread to redraw the window at its leisure. This means that one iteration of input and state change, or one demo "moment," should have a period of approximately 1/32 of a second. As a result, input responsiveness will remain more consistent regardless of how often the display is updated.
DX.cpp contains the very little initialization and uninitialization needed for DirectX, and then calls functions to do specific DirectInput work. Aside from CoInitialize and CoUninitialize, the DXInput module contains code for everything covered in this article. The functions are listed in the order that they are called by the demo, assuming one pass through. Notice that the great majority of work in DirectInput is done on initialization. This lengthy task is broken into several functions shown in Figure 8.
Another point of interest in this module would be the functions that are called repeatedly by the demo logic. ForceEffect plays an existing effect, GetKeyboardInput retrieves keyboard input, and GetJoystickInput retrieves joystick input. And finally, UnInitDirectInput tears the whole thing down.
Future articles will discuss other components of DirectX. My admittedly simple little stick person will upgrade from GDI to DirectDraw. I will introduce sound and network play as well. The DirectX libraries provide some very exciting capabilities. Take advantage of them and watch your application spring to life.

From the February 1998 issue of Microsoft Systems Journal. Get it at your local newsstand, or better yet, subscribe.


For related information see: Basics of DirectDraw Game Programming, http://premium.microsoft.com/msdn/library/
techart/extdirect.htm
.
Also check http://www.microsoft.com/msdn for daily updates on developer programs, resources and events.

© 1997 Microsoft Corporation. All rights reserved.
Terms of Use
.

© 2014 Microsoft Corporation. All rights reserved. Contact Us |Terms of Use |Trademarks |Privacy & Cookies
Microsoft