What You Need to Know to Use a DirectX API

[Previous] [Next]

To use a DirectX API effectively, you need to understand several key concepts: using Component Object Model (COM) objects, using the AddRef and Release methods, knowing the difference between calling DirectX methods with C and C++, and handling the DirectX return codes. We'll cover these topics in the remainder of this chapter.

What Is a COM Object?

A COM interface is similar to an abstract class in C++. Just as a C++ abstract code class has no code associated with it, a COM interface describes a set of signatures and semantics but not the implementation. Both COM interfaces and pure virtual functions use a device known as a vtable, which holds the addresses of the functions (also known as methods) used to implement the interface.

To have an object use the methods of a COM interface, you call the QueryInterface method to make sure an interface exists for the object and to acquire a pointer to the interface. You can call the interface methods the object implements by using the pointer to the vtable received from the call to the QueryInterface method.

Programmers access the DirectX features through several COM objects. Each interface to an object that represents a device, such as IDirectDraw7, is derived directly from the IUnknown COM interface. These objects are created through the use of functions in the dynamic-link library (DLL) for each object. In some cases, the interface to the base-level object is acquired through a function such as DirectDrawCreateEx. In other cases, the standard COM CoCreateInstance function is used to initialize the object and return a pointer to its interface.

The DirectX object model supplies one main object for each device, and other support objects are created from this object. For DirectDraw, the main object represents the display adapter. You use this object to find out what capabilities, such as color depth and supported screen sizes, exist on the hardware device. You also use it to create other objects and acquire their interfaces, such as an IDirectDrawPalette object, which represents a hardware palette, and an IDirectDrawSurface7 object, which represents display memory.

The capabilities of COM objects are updated by creating new interfaces that provide new features rather than by changing the methods within the existing interfaces. COM was designed to make sure that all software and objects written for a COM-based application would be completely compatible with any new version of that software. To access the features a new DirectX interface provides, you might need to call the object's QueryInterface method, using the globally unique identifier (GUID) of the interface you want to acquire (for example, IID_IDirectDraw7). If your application doesn't need the new features provided in newer versions of the interface, it doesn't need to retrieve these newer interfaces.

Since its inception, DirectX has used the COM style of interface design. As an example, DirectDraw provides five interfaces for accessing a DirectDrawSurface object: IDirectDrawSurface, IDirectDrawSurface2, IDirectDrawSurface3, IDirectDrawSurface4, and IDirectDrawSurface7. Even if DirectX eventually offers IDirectDrawSurface29, you can be certain that your applications' calls to IDirectDrawSurface7 will still work.

What Are the AddRef and Release Methods For?

In addition to the QueryInterface method, all COM interfaces supply two other methods: AddRef and Release. These methods maintain an object's reference count. Reference counting is a technique by which multiple pieces of code can make use of a resource while handling deallocation properly. The AddRef method increases an object's reference count by one, and Release decreases the object's reference count by one. Whenever a function returns a pointer to an interface for an object (for example, when you create an instance of the object) that function must call AddRef through that pointer to increment the reference count. You must match each call to AddRef with a call to Release through the same pointer. You must call Release through the pointer before the pointer can be destroyed. When the object's reference count hits 0, the object is destroyed and all interfaces to it are no longer valid.

So, when QueryInterface returns a pointer to an interface, it calls AddRef to increment the reference count. Because of this, you must make sure that you call Release to decrement the reference count before destroying the pointer to the interface. The same rule applies when you call any DirectX API that gives back a pointer to an interface, such as IDirectDrawSurface7::GetAttachedSurface.

How C and C++ Calls to DirectX Methods Differ

DirectX works with C-based, C++-based, or Microsoft Visual Basic-based applications. In this book, the focus is on using DirectX with C++. When you're deciding whether to develop your code in C or C++, you need to consider a few issues.

For a DirectX method, the first parameter is the pointer to the interface or class (the object invoking the method), which is basically the same as the this argument in C++. COM objects and C++ objects are binary compatible, so compilers handle COM interfaces and C++ abstract classes in the same way.

In C, you call COM interface methods by passing the this pointer as the first parameter of the method and reference the interface's method by using a pointer to the interface object's vtable. As an example, we create a surface for our 3D application using the following line of C code.

hr = lpdd->lpVtbl->CreateSurface(lpdd, &ddsd, &lpdds, NULL);

The first argument (lpdd) is a pointer to the DirectDraw object that controls the display. The second argument (ddsd) is a DDSURFACEDESC2 structure that contains information about the surface we want to create. The third argument (lpdds) is an address to be filled with a pointer to the IDirectDrawSurface7 object that is created. The final argument is NULL because it is a variable used for future compatibility with COM aggregation features; if you set it to anything other than NULL, an error will occur.

In C++, the lpVtbl pointer is implicitly dereferenced and the this parameter is implicitly passed. Thus, the call would look like this instead:

hr = lpdd->CreateSurface(&ddsd, &lpdds, NULL);

Other than these issues, coding in either language should be fairly similar. I prefer C++ because I use its object-oriented capabilities in my applications. I find that the ability to define things ranging from textures to characters as objects is the most logical approach, especially when I write code to simulate real-world objects, which most first-person-perspective games require.

Return Codes

Any code you produce with DirectX should be written to handle the return codes received when you make a call to a DirectX method. A return code is the value returned when the method completes, indicating the success or failure of the call. All return codes from DirectX methods are of the type HRESULT. An HRESULT is a 32-bit number that indicates two things: (1) whether the call succeeded or failed, and (2) why the call succeeded or failed. The SUCCEEDED() and FAILED() macros take an HRESULT as an argument and return whether it indicates a success code or a failure code, respectively. (SUCCEEDED() is the same as !FAILED(), so use whichever macro makes your code clearer.) Sometimes programmers try to check for failure by comparing the HRESULT against S_OK. This is a bad idea because some functions return success codes other than S_OK (for example, a function might return S_FALSE to indicate that it did nothing because there was no need to do it). On the other hand, it's okay to compare an HRESULT against a specific error code and respond accordingly, as long as you don't ignore other types of failures.

DirectX functions can return failure codes for a variety of reasons. A common reason for failure when writing new code is when you've passed invalid input to a function or when you call a function on an object that you haven't fully initialized yet. When you get this sort of failure, the best way to have the program react (assuming you haven't released the program yet) is to halt the program and report the file, line, and error code so that you can fix the bug. In other cases, DirectX returns a failure code to indicate that it can't perform an operation, but you might be able to do something different instead. For example, if you try to create a surface in video memory and DirectDraw returns DDERR_OUTOFVIDEOMEMORY, you might want to try to create the surface in system memory instead. Or a failure can indicate that a resource is busy, in which case you should try the operation again a bit later. Most of the time, however, a DirectX failure means that something unexpected has happened (such as running out of memory) or an operation that you need to take place just can't happen (such as when you really need a feature that isn't available). In these cases, you need to have your program interpret the error code as well as possible, report the situation to the user in plain English, and exit.

Because every DirectX function returns an HRESULT, you need to make a significant effort to structure your code to check for and handle all these potential errors. This work is frustrating at first, but it soon becomes second nature. The result of this work is that your program will be much more resistant to crashing unexpectedly.



Inside Direct3D
Inside Direct3D (Dv-Mps Inside)
ISBN: 0735606137
EAN: 2147483647
Year: 1999
Pages: 131

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net