DirectX COM Documentation
conventions
Requesting Additional Interfaces
Managing a COM Object's Lifetime
Incrementing and Decrementing the
Reference Count
Using macros to call DirectX
methods.
Implementing a Callback Function
Checking the operating System version.
Most DirectX
application development with lcc-win32 involves conventional programming
techniques. However, there are several aspects of DirectX programming that may
be unfamiliar to users. This section provides a brief overview of several
specialized topics, along with some guidelines for compiling and debugging
DirectX applications.
·
Using COM
·
Using Callback
Functions
·
Version
Checking
·
Compiling
DirectX Samples and Other DirectX Applications
· Debugging DirectX Applications
The Component
Object Model (COM) is an object-oriented programming model used by numerous applications.
Because the bulk of the DirectX run time is in the form of COM-compliant
objects, all DirectX developers need to have at least a basic understanding of
COM principles and programming techniques. However, although COM has a
reputation for being difficult and complex, the COM programming required by
most DirectX applications is straightforward.
There are two
distinct types of COM programming:
·
Using existing
COM objects. This is not much more difficult than using C++ objects.
·
Implementing
your own COM objects. This can be a complicated and demanding task. Much of
COM's reputation for complexity comes from this type of COM programming.
Most DirectX
applications need to use only the COM objects provided by DirectX. They do not
need to implement their own COM objects. In other words, most DirectX
developers will need to concern themselves only with the first, and easiest,
type of COM programming.
This section
provides a brief introduction to using the COM objects provided by DirectX. It
is primarily intended for novice COM programmers. For a more detailed
discussion of how to use COM objects, or for information on how to implement
your own COM objects, see Further Information.
·
What is a COM
Object?
·
Creating a COM
Object
·
Using COM
Interfaces
·
Managing a COM
Object's Lifetime
·
Using C to
Access COM Objects
·
Using Macros
to Call DirectX COM Methods
·
DirectX COM Documentation Conventions
·
IUnknown
Interface
By convention, COM methods and interfaces are referred to in the documentation by their equivalent C++ class names. The Initialize method of the IDirectPlay8Peer interface is thus referred to as IDirectPlay8Peer::Initialize. The primary reason for this convention is that different interfaces may export a method with the same name but with entirely different functionality and syntax. For example, many interfaces have an Init or Initialize method, but the functionality and parameters may be quite different. Using the C++ class name is a convenient way to uniquely identify the method. Note that this is used here for easy of use only, you shouldn’t try to use this C++ syntax in code for lcc-win32.
COM objects are basically black boxes that can be used by applications to perform one or more tasks. They are most commonly implemented as DLLs. Like a conventional DLL, COM objects expose methods that your application can call to perform any of the supported tasks.
COM objects contain procedures (methods), and some of them contain data (properties). They expose an interface, i.e. a set of data and procedures, that can be called or set/unset by the calling application.
A COM object's public methods are grouped into one or more interfaces. To use a method, you must create the object and obtain the appropriate interface from the object. An interface typically contains a related set of methods that provide access to a particular feature of the object. For example, the IDirect3DCubeTexture8 interface contains methods that enable you to manipulate cube texture resources. Any methods that are not part of an interface are not accessible.
COM objects are not created in the same way as C structures. There are several ways to create a COM object, but all involve COM-specific techniques. The DirectX API includes a variety of helper functions and methods that simplify creating most of the DirectX objects.
You must use COM-specific techniques to control the lifetime of the object. You should call the AddRef method, at the beginning, and always call Release when you are done using an object.
COM objects do not need to be explicitly loaded. COM objects are typically contained in a DLL. However, you do not need to explicitly load the DLL or link to a static library in order to use a COM object. Each COM object has a unique registered identifier that is used to create the object. The COM runtime automatically loads the correct DLL.
Here is, to put a concrete example, a code snippet that will call the COM Internet Explorer object:
CLSID clsExcelApp;
…
CLSIDFromProgID(
L"InternetExplorer.Application",
&clsExplorerApp);
The COM API CLSIDFromProgID will look in the registry and call the correct DLL for you.
COM is a binary specification. COM objects can be written in and accessed from a variety of languages. You don't need to know anything about the object's source code. For example, lcc-win32 applications routinely use COM objects that were written in C++.
It is important to
understand the distinction between objects and interfaces. In casual usage, an
object may sometimes be referred to by the name of its principle interface.
However, strictly speaking, the two terms are not interchangeable.
An object may expose any number of
interfaces. For example, while all objects must expose the IUnknown interface, they normally expose at least one additional interface, and
they might expose many. In order to use a particular method, you must not only
create the object, you must also obtain the correct interface.
More than one
object might expose the same interface. An interface is a group of methods that
perform a specified set of operations. The interface definition specifies only
the syntax of the methods and their general functionality. Any COM object that
needs to support a particular set of operations can do so by exposing a
suitable interface. Some interfaces are highly specialized and are exposed only
by a single object. Others are useful in a variety of circumstances and are
exposed by many objects. The extreme case is the IUnknown
interface, which must be exposed by all COM objects.[1]
The COM standard
requires that an interface definition must not change once it has been
published. You cannot, for example, add a new method to an existing, published
interface. You must instead create a new interface. While there are no
restrictions on what methods must be in an interface, a common practice is to
have the next-generation interface include all the of the old interface's
methods, plus any new methods.
It is not unusual for
an interface to have several generations. Typically, all generations perform
essentially the same overall task, but they will be different in detail. Often,
an object will expose every generation of interface. Doing so allows older
applications to continue using the object's older interfaces, while newer
applications can take advantage of the features of the newer interfaces.
Typically, a family
of interfaces will all have the same name, plus an integer indicating the
generation. For example, if the original interface were named IMyInterface, the next two generations would be called IMyInterface2 and IMyInterface3. DirectX typically labels successive
generations of interfaces with the DirectX version number.
Globally Unique Identifiers
(GUIDs) are a key part of the COM programming model. At its most basic, a GUID
is a 128-bit structure. However, GUIDs are created in such as way as to
guarantee that no two GUIDs are the same. COM uses GUIDS extensively for two
primary purposes:
For convenience,
documentation normally refers to objects and interfaces by a descriptive name
such as IDirect3D8. In the context of the documentation, there
is rarely any danger of confusion. However, strictly speaking, there is no
guarantee that another object or interface does not have the same descriptive
name. The only unambiguous way to refer to a particular object or interface is
by its GUID.
Although GUIDs are
structures, they are often expressed as an equivalent string. The general
format of the string form of a GUID is "{VVVVVVVV-WWWW-XXXX-YYYY-ZZZZZZZZZZZZ}", where each letter corresponds to a
hexadecimal integer. For example, the string form of the IID for the IDirect3D8
interface is:
{1DD9E8DA-1C77-4D40-B0CF-98FEFDFF9512}
Because the actual
GUID is somewhat clumsy to use and easy to mistype, an equivalent name is
usually provided as well. You can use this name instead of the actual structure
when you call functions such as CoCreateInstance. The customary naming convention is to
prepend either IID_ or CLSID_ to the descriptive name of the interface or
object, respectively. For example, the name of the IDirect3D8 interface's IID is IID_IDirect3D8.
All COM methods
return a 32-bit integer called an HRESULT. With most methods, the HRESULT is
essentially a structure that contains two separate pieces of information:
Some methods return
HRESULT values only from the standard set defined in win.h. However, methods
are free to return custom HRESULT values with more specialized information.
These values are normally documented on the method's reference page.[2]
While HRESULT
values are often used to return error information, you should not think of them
as error codes. The fact that the bit that indicates success or failure is
stored separately from the bits that contain the detailed information allows
HRESULT values to have any number of success and failure codes. By convention, success
codes are given names with an S_ prefix, and failure codes with an E_ prefix.
For example, the two most commonly used codes are S_OK and E_FAIL, which
indicate simple success or failure, respectively.
The fact that COM
methods may return a variety of success or failure codes means that you have to
be careful how you test the HRESULT value. For example, consider a hypothetical
method with documented return values of S_OK if successful and E_FAIL if not.
However, remember that the method may also return other failure or success
codes. The following code fragment illustrates the danger of using a simple
test. The hr value is the HRESULT that was returned by the method.
if(hr == E_FAIL)
{
//Handle the failure
}
else
{
//Handle the success
}
As long as the
method returns only E_FAIL to indicate failure, this test will work properly.
However, the method might also return an error value such as E_NOTIMPL or
E_INVALIDARG. That value would be interpreted as a success, perhaps causing your
application to fail.
If you need
detailed information on the outcome of the method call, you will need to test
each relevant HRESULT value. However, you may be interested only in whether the
method succeeded or failed. A robust way to test whether an HRESULT value
indicates success or failure is to pass the value to the one of the following
macros, defined in Win.h.
You can fix the
preceding code fragment by using the FAILED macro.
if(FAILED(hr))
{
//Handle the failure
}
else
{
//Handle the success
}
This code fragment
properly treats E_NOTIMPL and E_INVALIDARG as failures.
Although most COM
methods return structured HRESULT values, a small number use the HRESULT to
return a simple integer. Implicitly, these methods are always successful. If
you pass an HRESULT of this sort to the SUCCESS macro, the macro will always
return TRUE. A commonly used example is the IUnknown::Release method. This
method decrements an object's reference count by one and returns the current
reference count. See Managing the Object's Lifetime for a discussion of
reference counting.
If you look at a
few COM method reference pages, you will probably run across something like the
following:
HRESULT
CreateDevice(
...,
IDirect3DDevice8** ppReturnedDeviceInterface
);
While a normal
pointer is quite familiar to any lcc-win32 developer, COM often uses an
additional level of indirection. This second level of indirection is indicated
by a "**" following the type declaration, and
the variable name typically has a "pp" prefix. For the example given
above, ppReturnedDeviceInterface parameter is typically referred to as the
address of a pointer to an IDirect3DDevice8 interface.
You do not access a
COM object's methods directly. Instead, you must obtain a pointer to an
interface that exposes the method. To invoke the method, you use essentially
the lpVtbl member of the interface structure, and then you dereference that
pointer. For example, to invoke the IMyInterface::DoSomething method, you would use the following syntax.
IMyInterface
*pMyIface;
...
pMyIface->lpVtbl->DoSomething(pMyIface,
...);
Note that this is
slightly different to the documentation for C++. In C++ you would write:
IMyInterface
*pMyIface;
...
pMyIface->DoSomething(...);
In C++, the
compiler makes the lpVtbl indirection, and adds an invisible parameter to the
method(s call. Since in C we surely do not WANT the compiler writing code
behind our backs, we do that ourselves when we want it.
The need for a
second level of indirection comes from the fact that you do not create
interface pointers directly. You must call one of variety of methods, such as
the CreateDevice method shown above. To use such a method to
obtain an interface pointer, you declare a variable as a pointer to the desired
interface, and pass the address of that variable to the method. In other words,
you pass the method the address of a pointer. When the method returns, the
variable will point to the requested interface, and you can use that pointer to
call any of the interface's methods. See Obtaining and Using COM Interfaces for
further discussion of how to use interface pointers.
There are several
ways to create COM objects. The two most common ones used in DirectX
programming are:
Before you create
any objects, COM must be initialized by calling the “CoInitialize” function. If you are creating objects indirectly, the object creation
method will handle this task. If you need to create an object with “CoCreateInstance”, you must call CoInitialize explicitly. When you are finished, COM must be uninitialized by calling
CoUninitialize. If you make a call to CoInitialize you must match it with a call to CoUninitialize. Typically, applications that need to
explicitly initialize COM do so in their startup routine, and they uninitialize
COM in their cleanup routine, or at the beginning and end of the WinMain
procedure.
To create a new
instance of a COM object with CoCreateInstance, you must have the object's CLSID. If this
CLSID is in the header files, or otherwise publicly available, you will find it
in the reference documentation or the appropriate header file.
The CoCreateInstance function has five parameters. For the COM
objects you will be using with DirectX, you can normally set the parameters as
follows
For example, the
following code fragment creates a new instance of the DirectPlay8 object, and it returns a pointer to the IDirectPlay8Peer
interface in the g_pDP variable. If an error occurs, a message box
is displayed, and the application terminates.
IDirectPlay8Peer* g_pDP = NULL;
...
CoInitialize( NULL
);
...
hr =
CoCreateInstance(&CLSID_DirectPlay8,
NULL,
CLSCTX_INPROC_SERVER,
&IID_IDirectPlay8Peer, (LPVOID*) &g_pDP );
if( FAILED( hr ) )
{
MessageBox( NULL,
TEXT("Failed
Creating IDirectPlay8Peer. "),
TEXT("DirectPlay Sample"),
MB_OK | MB_ICONERROR );
return FALSE;
}
Creating an object indirectly
is usually much simpler. You pass the object creation method the address of an
interface pointer. The method then creates the object and returns an interface
pointer. When you create an object indirectly, you typically cannot choose
which interface the method will return. However, you can often specify a
variety of things about how the object should be created. For example, the
following code fragment calls the IDirect3D8::CreateDevice method discussed earlier to create a device
object to represent a display adapter. It returns a pointer to the object's IDirect3DDevice8 interface. The first four parameters
provide a variety of information needed to create the object, and the fifth
parameter receives the interface pointer. See the reference documentation for
details.
IDirect3DDevice8
*g_pd3dDevice = NULL;
...
if(FAILED(g_pD3D->lpVtbl->CreateDevice(g_pD3D,
D3DADAPTER_DEFAULT,
3DDEVTYPE_HAL,
hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp,
&g_pd3dDevice )))
return E_FAIL;
When the object is
created, the creation method returns an interface pointer. You can then use
that pointer to access any of the interface's methods. The syntax is identical
to that used with a pointer to a C++ method. The following code fragment
extends the example given in the previous section. After creating the
DirectPlay8 object, the example uses the IDirectPlay8Peer interface pointer
returned by CoCreateInstance to initialize the object by calling the IDirectPlay8Peer::Initialize
method. Error correction code is omitted for clarity.
IDirectPlay8Peer* g_pDP = NULL;
...
CoInitialize( NULL
);
...
hr =
CoCreateInstance(CLSID_DirectPlay8,NULL,
CLSCTX_INPROC_SERVER,
IID_IDirectPlay8Peer, (LPVOID*) &g_pDP
);
hr =
g_pDP->lpVtbl->Initialize(g_pDP, NULL,
DirectPlayMessageHandler, 0 );
In many cases, the interface pointer that you receive from the creation method may be the only one that you need. In fact, it is not uncommon for an object to export only one interface other than IUnknown. However, many objects export multiple interfaces, and you may need pointers to several of them. If you need more interfaces than the one returned by the creation method, there is no need to create a new object. Instead, you request another interface pointer by using the object's IUnknown::QueryInterface method.
If you create your object with CoCreateInstance, you can request an IUnknown interface pointer, and then call IUnknown::QueryInterface to request every interface you need. However, this approach is inconvenient if you need only a single interface, and it doesn't work at all if you use an object creation method that does not allow you to specify which interface pointer should be returned. In practice, you usually don't need to obtain an explicit IUnknown pointer because all COM interfaces inherit from or extend the IUnknown interface.
Extending an interface is similar to inheriting in object oriented programming. The child interface exposes all of the parent interface's methods, plus one or more of its own. In fact, you will often see "inherits from" used instead of "extends". What you need to remember is that the inheritance is internal to the object. You can use the child interface to call any of the child's or the parent's methods.
Because all interfaces are children of IUnknown you can use any of the interface pointers you already have for the object to call QueryInterface. When you do so, you must provide the IID of the interface you are requesting and the address of a pointer that will contain the interface pointer when the method returns.
For example, the following code fragment calls IDirectSound8::CreateSoundBuffer to create a primary sound buffer object. This object exposes several interfaces. The CreateSoundBuffer method returns an IDirectSoundBuffer8 interface. The subsequent code then uses the IDirectSoundBuffer8 interface to call QueryInterface to request an IDirectSound3DListener8 interface.
IDirectSoundBuffer8*
pDSBPrimary = NULL;
IDirectSound3DListener8*
pDSListener;
...
if(FAILED(hr
= g_pDS->lpVtbl->CreateSoundBuffer(g_pDS,
&dsbd, &pDSBPrimary, NULL )))
return hr;
if(FAILED(hr
= pDSBPrimary->lpVtbl->QueryInterface(
gDSBPrimary,&IID_IDirectSound3DListener8,
(LPVOID *)&pDSListener)))
return hr;
When an object is created, the system allocates the necessary memory resources. When an object is no longer needed, it should be destroyed. The system can use that memory for other purposes. COM does not enable you to create or destroy objects directly. The reason for this practice is that the same object may be used by more than one application. If one of those applications were to destroy the object, the other applications would probably fail. Instead COM uses a system of reference counting, to control an object's lifetime.
An object's reference count is the number of times one of its interfaces has been requested. Each time an interface is requested, the reference count is incremented. An application releases an interface when that interface is no longer needed, decrementing the reference count. As long as the reference count is greater than zero, the object remains in memory. When the reference count reaches zero, the object destroys itself. You don't need to know anything about the reference count of an object. As long as you obtain and release an object's interfaces properly, the object will have the appropriate lifetime.[3]
Whenever you obtain a new interface pointer, the reference count must be incremented by a call to IUnknown::AddRef. However, your application does not usually need to call this method. If you obtain an interface pointer by calling an object creation method, or by calling IUnknown::QueryInterface, the object will automatically increment the reference count. However, if you create an interface pointer in some other way, such as copying an existing pointer, you must explicitly call IUnknown::AddRef. Otherwise, when you release the original interface pointer, the object may be destroyed even though you may still need to use the copy of the pointer.
You must release all interface pointers, regardless of whether you or the object incremented the reference count. When you no longer need an interface pointer, call IUnknown::Release to decrement the reference count. A common practice is to initialize all interface pointers to NULL, and set them back to NULL when they are released. That allows you to test all interface pointers in your cleanup code. Those that are non-NULL are still active and must be released before you terminate the application.
The following code fragment extends the sample discussed in Requesting Additional Interfaces to illustrate how to handle reference counting.
IDirectSoundBuffer8*
pDSBPrimary = NULL;
IDirectSound3DListener8*
pDSListener = NULL;
IDirectSound3DListener8*
pDSListener2 = NULL;
...
//Create the
object and obtain an additional interface.
//The object
increments the reference count.
if(FAILED(hr
= g_pDS->lpVtbl->CreateSoundBuffer(g_pDS,
&dsbd,
&pDSBPrimary, NULL )))
return hr;
if(FAILED(hr=pDSBPrimary->lpVtbl->QueryInterface(
pDSBPrimary,&IID_IDirectSound3DListener8,
(LPVOID *)&pDSListener)))
return hr;
//Make a
copy of the IDirectSound3DListener8 interface pointer.
//Call
AddRef to increment the reference count and to ensure that
//the object
is not destroyed prematurely
pDSListener2
= pDSListener;
pDSListener2->lpVtbl->AddRef(pDSListener2);
...
//Cleanup
code. Check to see if the pointers are still active.
//If they
are, call Release to release the interface.
if(pDSBPrimary
!= NULL)
{
pDSBPrimary->lpVtbl->Release(pDSBPrimary);
pDSBPrimary = NULL;
}
if(pDSListener
!= NULL)
{
pDSListener->lpVtbl->Release(pDSListener);
pDSListener = NULL;
}
if(pDSListener2
!= NULL)
{
pDSListener2->lpVtbl->Release(pDSListener2);
pDSListener2 = NULL;
}
Many of the DirectX interfaces have macros defined for each method that make using the methods in your applications simpler.
For example, the following code fragment from the d3d8.h header file shows the definitions of the C macros for the IDirect3D8::GetAdapterIdentifier method.
#define IDirect3D8_GetAdapterIdentifier(p,a,b,c)
\
(p)-lpVtbl->GetAdapterIdentifier(a,b,c)
...
To use one of these macros, you must first obtain a pointer to the associated interface. The first parameter of the macro must be set to that pointer. The remaining parameters map to the method's parameters. The macro's return value is the HRESULT value that is returned by the method. The following code fragment uses a macro to call the IDirect3D8::GetAdapterIdentifier method. pD3D is a pointer to an IDirect3D8 interface.
hr = IDirect3D8_GetAdapterIdentifier(pD3D,
Adapter,
dwFlags,
pIdentifier);
All COM objects support an interface called IUnknown. This interface provides DirectX with control of the object's lifetime and the ability to retrieve other interfaces implemented by the object. IUnknown has three methods.
The AddRef and Release methods maintain an object's reference count. For example, if you create a Direct3D object, the object's reference count is set to 1. Every time a function returns a pointer to an interface for that object, the function must call AddRef through that pointer to increment the reference count. Match each AddRef call with a call to Release. Before the pointer can be destroyed, you must call Release through that pointer. After an object's reference count reaches 0, the object is destroyed, and all interfaces to it become invalid.
The QueryInterface method determines whether an object supports a specific interface. If an object supports an interface, QueryInterface returns a pointer to that interface. You then can use the methods of that interface to communicate with the object. If QueryInterface successfully returns a pointer to an interface, it implicitly calls AddRef to increment the reference count, so your application must call Release to decrement the reference count before destroying the pointer to the interface.
A callback function is essentially an event handler that is implemented by an application and called by the system. Windows applications typically implement multiple callback functions, each one designed for a particular set of events. When an event occurs, the system notifies the application by calling the appropriate callback function. The callback function also usually has a parameter list that the system can use to pass the application more detailed information about the event. The most common example of a callback function is the window procedure. This function is used by the system to pass Windows messages to the applications that owns the window.
DirectX uses callback functions for a variety of purposes. For example, your system supports multiple devices. DirectInput represents each device by a device object, that contains the details of the device's capabilities. Your application will typically need to enumerate the available devices and to examine the device objects in order to handle user input properly. To do this enumeration, you must implement a DIEnumDeviceObjectsCallback callback function.
You start the enumeration process by calling IDirectInputDevice8::EnumObjects and passing the method a pointer to your DIEnumDeviceObjectsCallback callback function. The system will call this function once for each device, and it will pass in a DIDEVICEOBJECTINSTANCE structure containing information about the device's capabilities. After your callback function has processed the information, it can return DIENUM_CONTINUE to request the next device object, or DIENUM_STOP to stop the enumeration.
DirectX uses a number of other callback functions for a variety of purposes. For details, see the documentation for the particular DirectX component.
Because callback functions have different purposes and usage, they are documented in the appropriate Reference section in much the same way as a regular function. However, a callback function reference is essentially a template that describes how to implement the function, not a normal API reference. The callback function reference provides the following information:
You should declare the function as a CALLBACK or WINAPI type. Either type is acceptable. You can use any function name that you wish. The name used in the documentation is simply a convenient label for a particular callback function.
Implement the function according to the description in the reference. The implementation details will depend on the particular function and the requirements of your application. See the sample code for some examples of how to implement various callback functions.
Pass a pointer to the function to the appropriate DirectX component. The DirectX component can then use the function pointer to call the function. The way in which you pass this pointer varies from function to function, so you should see the particular function's reference for details.
The following code fragment is borrowed from the DirectInput Joystick sample. It sketches out the essential elements of a DIEnumDeviceObjectsCallback implementation that is used to enumerate the axes of a joystick.
//The function declaration
BOOL CALLBACK
EnumAxesCallback(
const DIDEVICEOBJECTINSTANCE* pdidoi,
VOID* pContext );
...
//Pass the function pointer to DirectInput by
calling the
//IDirectInputDevice8::EnumObjects method
if ( FAILED( hr =
g_pJoystick->lpVtbl->EnumObjects(
g_pJoystick,
EnumAxesCallback,
(VOID*)hDlg,
DIDFT_AXIS )));
...
//The function
implementation
BOOL CALLBACK
EnumAxesCallback(
const DIDEVICEOBJECTINSTANCE* pdidoi,
VOID* pContext )
{
//Process the information passed in through
the two parameters
//Return DIENUM_CONTINUE to request the next
device object
//Return DIENUM_STOP to stop the enumeration
}
Applications
sometimes need to know which version of DirectX is currently available on the
system. For example, if an older version of DirectX is on the system, your
application may need to scale itself to the capabilities of that version or
install the most recent version.
There is no direct
way to obtain the DirectX version number. However, each version has a
characteristic set of objects and interfaces. Because any version of DirectX
supports all previous versions, this set of interfaces and objects will be
supported by the version in which they are introduced and all subsequent
versions. Thus, the preferred way to determine whether your desired version is
available is to test for its characteristic objects or interfaces. As long as
those are present, your application will work normally even though you might be
using a more recent version of DirectX.
For example,
suppose you need version 6.1 support. The DirectMusic object
(CLSID_DirectMusic) was introduced in DirectX version 6.1. You can test for the
presence of the DirectMusic object by attempting to create it with
CoCreateInstance. If you are successful, you have version 6.1 or later, and you
will be able to use all the DirectX 6.1 capabilities.
Here is a function that makes all the tests for you. You will find the complete application in the samples distributed with lcc-win32, in the directory lcc\examples\directx\dxver
//-----------------------------------------------------------------------------
// Name: GetDXVersion()
// Desc: This function returns the DirectX version
number as follows:
//
0x0000 = No DirectX installed
// 0x0100
= DirectX version 1 installed
//
0x0200 = DirectX 2 installed
//
0x0300 = DirectX 3 installed
//
0x0500 = At least DirectX 5 installed.
//
0x0600 = At least DirectX 6 installed.
//
0x0601 = At least DirectX 6.1 installed.
//
0x0700 = At least DirectX 7 installed.
//
0x0800 = At least DirectX 8 installed.
//
//
Please note that this code is intended as a general guideline. Your
// app
will probably be able to simply query for functionality (via
//
QueryInterface) for one or two components.
//
//
Please also note:
//
"if( dwDXVersion != 0x500 ) return FALSE;" is VERY BAD.
//
"if( dwDXVersion < 0x500
) return FALSE;" is MUCH BETTER.
// to
ensure your app will run on future releases of DirectX.
//-----------------------------------------------------------------------------
DWORD GetDXVersion()
{
DIRECTDRAWCREATE
DirectDrawCreate = NULL;
DIRECTDRAWCREATEEX
DirectDrawCreateEx = NULL;
DIRECTINPUTCREATE
DirectInputCreate = NULL;
HINSTANCE
hDDrawDLL = NULL;
HINSTANCE
hDInputDLL = NULL;
HINSTANCE
hD3D8DLL = NULL;
LPDIRECTDRAW pDDraw = NULL;
LPDIRECTDRAW2 pDDraw2 = NULL;
LPDIRECTDRAWSURFACE pSurf = NULL;
LPDIRECTDRAWSURFACE3 pSurf3
= NULL;
LPDIRECTDRAWSURFACE4 pSurf4
= NULL;
DWORD
dwDXVersion = 0;
HRESULT hr;
// First
see if DDRAW.DLL even exists.
hDDrawDLL
= LoadLibrary( "DDRAW.DLL" );
if(
hDDrawDLL == NULL )
{
dwDXVersion = 0;
return dwDXVersion;
}
// See if
we can create the DirectDraw object.
DirectDrawCreate
=(DIRECTDRAWCREATE)GetProcAddress(hDDrawDLL,"DirectDrawCreate" );
if(
DirectDrawCreate == NULL )
{
dwDXVersion = 0;
FreeLibrary( hDDrawDLL );
OutputDebugString( "Couldn't LoadLibrary DDraw\r\n" );
return dwDXVersion;
}
hr =
DirectDrawCreate( NULL, &pDDraw, NULL );
if(
FAILED(hr) )
{
dwDXVersion = 0;
FreeLibrary( hDDrawDLL );
OutputDebugString( "Couldn't create DDraw\r\n" );
return
dwDXVersion;
}
// So
DirectDraw exists. We are at least DX1.
dwDXVersion = 0x100;
// Let's
see if IID_IDirectDraw2 exists.
hr =
pDDraw->lpVtbl->QueryInterface(pDDraw,& IID_IDirectDraw2,
(VOID**)&pDDraw2 );
if(
FAILED(hr) )
{
// No
IDirectDraw2 exists... must be DX1
pDDraw->lpVtbl->Release(pDDraw);
FreeLibrary( hDDrawDLL );
OutputDebugString( "Couldn't QI DDraw2\r\n" );
return dwDXVersion;
}
//
IDirectDraw2 exists. We must be at least DX2
pDDraw2->lpVtbl->Release(pDDraw2);
dwDXVersion = 0x200;
//-------------------------------------------------------------------------
//
DirectX 3.0 Checks
//-------------------------------------------------------------------------
//
DirectInput was added for DX3
hDInputDLL = LoadLibrary( "DINPUT.DLL" );
if(
hDInputDLL == NULL )
{
// No
DInput... must not be DX3
OutputDebugString( "Couldn't LoadLibrary DInput\r\n" );
pDDraw->lpVtbl->Release(pDDraw);
return dwDXVersion;
}
DirectInputCreate = (DIRECTINPUTCREATE)GetProcAddress( hDInputDLL,
"DirectInputCreateA" );
if(
DirectInputCreate == NULL )
{
// No
DInput... must be DX2
FreeLibrary( hDInputDLL );
FreeLibrary( hDDrawDLL );
pDDraw->lpVtbl->Release(pDDraw);
OutputDebugString( "Couldn't GetProcAddress DInputCreate\r\n"
);
return dwDXVersion;
}
// DirectInputCreate
exists. We are at least DX3
dwDXVersion = 0x300;
FreeLibrary( hDInputDLL );
// Can do
checks for 3a vs 3b here
//-------------------------------------------------------------------------
//
DirectX 5.0 Checks
//-------------------------------------------------------------------------
// We can
tell if DX5 is present by checking for the existence of
//
IDirectDrawSurface3. First, we need a surface to QI off of.
DDSURFACEDESC ddsd;
ZeroMemory( &ddsd, sizeof(ddsd) );
ddsd.dwSize =
sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
hr =
pDDraw->lpVtbl->SetCooperativeLevel(pDDraw, NULL, DDSCL_NORMAL );
if(
FAILED(hr) )
{
// Failure. This means DDraw isn't properly
installed.
pDDraw->lpVtbl->Release(pDDraw);
FreeLibrary( hDDrawDLL );
dwDXVersion = 0;
OutputDebugString( "Couldn't Set coop level\r\n" );
return dwDXVersion;
}
hr =
pDDraw->lpVtbl->CreateSurface(pDDraw, &ddsd, &pSurf, NULL );
if(
FAILED(hr) )
{
//
Failure. This means DDraw isn't properly installed.
pDDraw->lpVtbl->Release(pDDraw);
FreeLibrary( hDDrawDLL );
dwDXVersion = 0;
OutputDebugString( "Couldn't CreateSurface\r\n" );
return dwDXVersion;
}
// Query
for the IDirectDrawSurface3 interface
if(
FAILED( pSurf->lpVtbl->QueryInterface(pSurf,&IID_IDirectDrawSurface3,
(VOID**)&pSurf3 ) ) )
{
pDDraw->lpVtbl->Release(pDDraw);
FreeLibrary( hDDrawDLL );
return dwDXVersion;
}
// QI for
IDirectDrawSurface3 succeeded. We must be at least DX5
dwDXVersion = 0x500;
//-------------------------------------------------------------------------
//
DirectX 6.0 Checks
//-------------------------------------------------------------------------
// The
IDirectDrawSurface4 interface was introduced with DX 6.0
if(
FAILED( pSurf->lpVtbl->QueryInterface(pSurf,&
IID_IDirectDrawSurface4,
(VOID**)&pSurf4 ) ) )
{
pDDraw->lpVtbl->Release(pDDraw);
FreeLibrary( hDDrawDLL );
return dwDXVersion;
}
// IDirectDrawSurface4
was create successfully. We must be at least DX6
dwDXVersion = 0x600;
pSurf->lpVtbl->Release(pSurf);
pDDraw->lpVtbl->Release(pDDraw);
//-------------------------------------------------------------------------
// DirectX
6.1 Checks
//-------------------------------------------------------------------------
// Check
for DMusic, which was introduced with DX6.1
LPDIRECTMUSIC pDMusic = NULL;
CoInitialize( NULL );
hr =
CoCreateInstance(&CLSID_DirectMusic, NULL, CLSCTX_INPROC_SERVER,
&IID_IDirectMusic,
(VOID**)&pDMusic );
if(
FAILED(hr) )
{
OutputDebugString( "Couldn't create CLSID_DirectMusic\r\n" );
FreeLibrary( hDDrawDLL );
return dwDXVersion;
}
//
DirectMusic was created successfully. We must be at least DX6.1
dwDXVersion = 0x601;
pDMusic->lpVtbl->Release(pDMusic);
CoUninitialize();
//-------------------------------------------------------------------------
// DirectX 7.0 Checks
//-------------------------------------------------------------------------
// Check
for DirectX 7 by creating a DDraw7 object
LPDIRECTDRAW7 pDD7;
DirectDrawCreateEx = (DIRECTDRAWCREATEEX)GetProcAddress( hDDrawDLL,
"DirectDrawCreateEx" );
if( NULL
== DirectDrawCreateEx )
{
FreeLibrary( hDDrawDLL );
return dwDXVersion;
}
if(
FAILED( DirectDrawCreateEx( NULL, (VOID**)&pDD7,& IID_IDirectDraw7,
NULL ) ) )
{
FreeLibrary( hDDrawDLL );
return dwDXVersion;
}
// DDraw7
was created successfully. We must be at least DX7.0
dwDXVersion = 0x700;
pDD7->lpVtbl->Release(pDD7);
//-------------------------------------------------------------------------
//
DirectX 8.0 Checks
//-------------------------------------------------------------------------
// Simply
see if D3D8.dll exists.
hD3D8DLL
= LoadLibrary( "D3D8.DLL" );
if(
hD3D8DLL == NULL )
{
FreeLibrary( hDDrawDLL );
return dwDXVersion;
}
//
D3D8.dll exists. We must be at least DX8.0
dwDXVersion = 0x800;
//-------------------------------------------------------------------------
// End of
checking for versions of DirectX
//-------------------------------------------------------------------------
// Close
open libraries and return
FreeLibrary( hDDrawDLL );
FreeLibrary( hD3D8DLL );
return dwDXVersion;
}
In addition to determining the DirectX version, applications may also need to know what operating system they are running on, and perhaps which Service Pack has been installed. The preferred way to check the operating system version is to use the Windows API function, GetVersionEx. This function returns an OSVERSIONINFO structure that contains a variety of information including:
Whether the system is Microsoft
Windows NT®-based or Windows 95/98/ME
The major and minor version numbers
The service pack number, for
Windows NT-based systems
The general procedure is to determine the earliest version of the operating system that your application is compliant with. If that version or later is installed, you can safely install and run your application.[4]
The following sample function
illustrates how to use GetVersionEx to
test the operating system version. If the installed version is identical to or
more recent than the version specified in the parameter list, the function
returns TRUE. You can then safely install and execute your application.
Otherwise the function returns FALSE, indicating that your application will not
run properly, if at
all.
#include
<windows.h>
#include
<stdio.h>
#include
<tchar.h>
BOOL
bIsWindowsVersionOK( DWORD
dwWin9xMajor, DWORD dwWin9xMinor,
DWORD
dwWinNTMajor, DWORD dwWinNTMinor, WORD wWinNTSPMajor )
{
OSVERSIONINFO osvi;
// Initialize the OSVERSIONINFO structure.
memset( &osvi, sizeof( osvi ) );
osvi.dwOSVersionInfoSize = sizeof( osvi );
GetVersionEx( &osvi ); // Assume this function succeeds.
// Split code paths for NT and Win9x
if( osvi.dwPlatformId ==
VER_PLATFORM_WIN32_WINDOWS )
{
// Check the major version.
if( osvi.dwMajorVersion >
dwWin9xMajor )
return TRUE;
else if( osvi.dwMajorVersion ==
dwWin9xMajor )
{
// Check the minor version.
if( osvi.dwMinorVersion >=
dwWin9xMinor )
return TRUE;
}
}
else if( osvi.dwPlatformId ==
VER_PLATFORM_WIN32_NT )
{
// Check the major version.
if( osvi.dwMajorVersion >
dwWinNTMajor )
return TRUE;
else if( osvi.dwMajorVersion ==
dwWinNTMajor )
{
// Check the minor version.
if( osvi.dwMinorVersion > dwWinNTMinor
)
return TRUE;
else if( osvi.dwMinorVersion ==
dwWinNTMinor )
{
// Check the service pack.
DWORD dwServicePack = 0;
if( osvi.szCSDVersion )
{
_stscanf( osvi.szCSDVersion,
_T("Service Pack %d"),
&dwServicePack );
}
return ( dwServicePack >=
wWinNTSPMajor );
}
}
}
return FALSE;
}
If you are using the provided
sample project files, you do not need to verify these settings. They are
specified with the project files. For new applications, on the Project menu, click Configuration.
You should verify that the application is linked to the appropriate standard DirectX link libraries. They are: ddraw.lib, dxguid.lib, and ole32.lib at the bare minimum.
The Microsoft DirectX SDK installation program provides the option of installing either debug or retail builds of the DirectX dynamic-link libraries (DLLs).
When you develop software in C, it is best to install the debug versions of the DLLs. This option installs both debug and retail DLLs on your system. The retail option installs only the retail DLLs. The debug DLLs have additional code that validates internal data structures and output debug error messages, using the Microsoft Win32 API OutputDebugString function, while your program is executing. The strings that are passed to that primitive are shown by the lcc-win32 debugger in the events window, or in a message box. When an error occurs, the debug output gives you a more detailed description of the problem. The debug DLLs execute more slowly than the retail DLLs but are much more useful for debugging an application. Be sure to ship the retail version with your application.
You can use the DirectX Control Panel utility to switch between the debug and retail builds of DirectInput, Direct3D, and DirectMusic. To enable this feature, select the debug option when you install the SDK.
Returns the name associated with a DirectX error code.
TCHAR*
DXGetErrorString8( HRESULT hr);
Parameters
hr
HRESULT value returned from a DirectX method. This handles only error codes.
If successful, this function returns a pointer to a string that contains the name of the error code. If Unicode is set, DXGetErrorString8 will return a Unicode string. Otherwise, it will return an ANSI string.
Remarks
This function is designed to retrieve the text equivalent of a DirectX error message from a Direct3D, D3DX, DirectPlay, DirectInput, DirectMusic, or DirectSound method. For example, if you set hr to 0x88768686, DXGetErrorString8 will return D3DERR_DEVICELOST.
If an error code maps to more than one text string, DXGetErrorMsg will return a generic string. For example, there are several DirectX error codes, such as DIERR_OUTOFMEMORY, that indicate that you are out of memory, and all map to the same value. If you set hr to any of these codes, DXGetErrorString8 will return E_OUTOFMEMORY.
[1] If an object exposes an interface, it must support every method in the interface definition, i.e. the table of function pointer must be filled with valid pointers. In other words, you can call any method and be confident that it exists. However, the details of how a particular method is implemented may vary from object to object. For example, different objects may use different algorithms to arrive at the final result. There is also no guarantee that a method will be supported in a non-trivial way. Sometimes an object exposes a commonly used interface, but needs to support only a subset of the methods. You will still be able to call the remaining methods successfully, but they will return E_NOTIMPL. You should refer to the documentation to see how any particular object implements an interface.
[2] The list of HRESULT values that you find on a method's reference page is often only a subset of the possible values that may be returned. The list typically covers only those values that are specific to the method and those standard values that have some method-specific meaning. You should assume that a method may return a variety of standard HRESULT values, even if they are not explicitly documented.
[3] Properly handling reference counting is a crucial part
of COM programming. Failure to do so can easily create a memory leak. One of
the most common mistakes that COM programmers make is failing to release an
interface. When this happens, the reference count will never reach zero, and
the object will remain in memory indefinitely.
[4] The operating system information returned by GetVersionEx should not be used to test for the presence or version number of DirectX. In particular, the fact that a system is Windows NT-based does not necessarily mean that DirectX is absent. The Windows 2000 operating system, for example, includes DirectX 7.0 or later. Future versions of Windows NT-based operating systems can also be expected to include a version of DirectX. Use the procedures described in the previous section to determine whether DirectX is present and, if so, the version number.