COM And Directx

Created by Jijith Nadumuri at 28 Apr 2010 12:02 and updated at 28 Apr 2010 12:43

COM Programming and DirectX

The Component Object Model (COM) is an object-oriented programming model used by numerous applications. The bulk of the Microsoft DirectX run-time is in the form of COM-compliant objects.

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. Applications interact with COM objects in somewhat the same way they do with C++ objects. However, there are some distinct differences.

COM Objects and COM Interfaces

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 object may expose any number of interfaces. All objects must expose the IUnknown interface.

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.

Microsoft DirectX typically labels successive generations of interfaces with the DirectX version number.

GUIDS

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:

  • To uniquely identify a particular COM object (Class ID or CLSID)
  • To uniquely identify a particular COM interface (Interface ID or IID)

For example the string form of the IID for the IDirect3D8 interface is:
{1DD9E8DA-1C77-4D40-B0CF-98FEFDFF9512}. It is later redifined for simplicity as IID_IDirect3D8.

HRESULT Values

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:

  • Whether the method succeeded or failed.
  • More detailed information about the outcome of the operation supported by the method.

While HRESULT values are often used to return error information, you should not think of them as error 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. But it is not safe to use them like if(hr== S_OK) /*Success*/; if(hr== E_FAIL) /*failure*/; Because there are many success codes and failure codes.

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 Winerror.h.

  • The SUCCEEDED macro returns TRUE for a success code and FALSE for a failure code.
  • The FAILED macro returns TRUE for a failure code and FALSE for a success code.

if(FAILED(hr)) /*Handle the failure*/;
else
{
/*Handle the success*/;
}

Use of pointers to pointers

While a normal pointer is quite familiar to any C/C++ developer, COM often uses an additional level of indirection, ie double-star:- **. Unlike C++, you do not access a COM object's methods directly. Instead, you must obtain a pointer to an interface that exposes the method. The need for a second level of indirection comes from the fact that you do not create interface pointers directly.

EG:- IDirect3DDevice8** ppReturnedDeviceInterface;

Creating a COM Object

There are several ways to create COM objects. The two most common ones used in Microsoft DirectX programming are:

  • Directly, by passing the object's CLSID to the CoCreateInstance function. The function will create an instance of the object, and it will return a pointer to an interface that you specify.
  • Indirectly, by calling a DirectX method or function that creates the object for you. The method creates the object and returns an interface on the object. When you create an object this way, you usually cannot specify which interface should be returned.

Using Class ID

If you need to create an object with CoCreateInstance, you must call CoInitialize to initialize COM, explicitly. When you are finished, COM must be uninitialized by calling CoUninitialize. If the CLSID is not publicly available, you cannot create the object directly.

Using an object creation method

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.

Using a COM Object

After creation, the interface pointer is used to access any of the interface's methods.

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. All COM interfaces inherit from or extend the IUnknown interface.

Because all interfaces are children of IUnknown you can use any of the interface pointers you already have for the object to call QueryInterface.

Managing a COM Object's Lifetime

With C++ objects, you can control the object's lifetime directly with the new and delete operators. 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.

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.

Incrementing reference count

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.

Decrementing reference count

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.

Using C to Access COM Objects

Although C++ is the most commonly used language for COM programming, you can also access COM objects using C.

  • All methods have an additional parameter added to the beginning of the parameter list. This parameter must be set to the interface pointer.
  • You must explicitly reference the object's vtable.

Every COM object has a vtable that contains a list of pointers to methods that the object exposes. An interface pointer points to the appropriate location in the vtable, which in turn contains a pointer to the particular method you are calling. In C++, the vtable is essentially invisible. However, if you wish to call COM methods with C, you must include an additional level of indirection that explicitly references the vtable.

C++:- g_pDP->Initialize( NULL, DirectPlayMessageHandler, 0 );
C:- g_pDP->lpVtbl->Initialize(g_pDP,NULL, DirectPlayMessageHandler, 0);

Using Macros to Call DirectX COM Methods

Many of the Microsoft DirectX interfaces have macros defined for each method that make using the methods in your applications simpler. The macros are designed to be used by both C and C++ applications. To use the C++ macros, you must define _cplusplus. Otherwise, the C macros will be used.

Share:- Facebook

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License