Revisiting COM

The most important concept to understand about COM programming is that it is interface-based. As you saw in Chapter 24, you don't need real COM or even Microsoft runtime support to use interface-based programming. All you need is some discipline.

Think back to the spaceship example in Chapter 24. You started out with a single class named CSpaceship that implemented several functions. Seasoned C++ developers usually sit down at the computer and start typing a class like this:

class CSpaceship {     void Fly();     int& GetPosition(); };

However, the procedure is a little different with interface-based development. Instead of writing the class directly, interface-based programming involves spelling out an interface before implementing it. In Chapter 24, the Fly and GetPosition functions were moved into an abstract base class named IMotion.

struct IMotion {     virtual void Fly() = 0;     virtual int& GetPosition() = 0; };

Then we inherited the CSpaceship class from the IMotion interface like this:

class CSpaceship : IMotion {     void Fly();     int& GetPosition(); };

Notice that at this point the motion interface has been separated from its implementation. When practicing interface development, the interface comes first. You can work on the interface as you develop it, making sure it's complete while at the same time not over-bloated. But once the interface has been published (that is, once a lot of other developers have started coding to it), the interface is frozen and can never change.

This subtle distinction between class-based programming and interface-based programming seems to introduce some programming overhead. However, it turns out to be one of the key points to understanding COM. By collecting the Fly and the GetPosition functions in an interface, you've developed a binary signature. That is, by defining the interface ahead of time and talking to the class through the interface, client code has a potentially language-neutral way of talking to the class.

Gathering functions together into interfaces is itself quite powerful. Imagine you want to describe something other than a spaceship—an airplane, for example. It's certainly conceivable that an airplane would also have Fly and GetPosition functions. Interface programming provides a more advanced form of polymorphism—polymorphism at the interface level, not only at the single-function level.

Separating interface from implementation is the basis of interface-based development. The Component Object Model is centered on interface programming. COM enforces the distinction between interface and implementation. In COM, the only way client code can talk to an object is through an interface. However, gathering functions together into interfaces isn't quite enough. There's one more ingredient needed—a mechanism for discovering functionality at runtime.

The Core Interface: IUnknown

The key element that makes COM different from ordinary interface programming is this rule: the first three functions of every COM interface are the same. The core interface in COM, IUnknown, looks like this:

struct IUnknown {     virtual HRESULT QueryInterface(REFIID riid, void** ppv) = 0;     virtual ULONG AddRef() = 0;     virtual ULONG Release() = 0; };

Every COM interface derives from this interface (meaning the first three functions of every COM interface you ever see will be QueryInterface, AddRef, and Release). To turn IMotion into a COM interface, derive it from IUnknown like this:

struct IMotion : IUnknown {     void Fly();     int& GetPosition(); };

If you wanted these interfaces to work out-of-process, you'd have to make each function return an HRESULT. You'll see this when we cover Interface Definition Language (IDL) later in this chapter.

AddRef and Release deserve some mention because they are part of IUnknown. AddRef and Release allow an object to control its own lifetime if it chooses to. As a rule, clients are supposed to treat interface pointers like resources: clients acquire interfaces, use them, and release them when they are done using them. Objects learn about new references to themselves via AddRef. Objects learn they have been unreferenced through the Release function. Objects often use this information to control their lifetimes. For example, many objects self-destruct when their reference count reaches zero.

Here's how some client code might use the spaceship:

void UseSpaceship() {     IMotion* pMotion = NULL;     pMotion = GetASpaceship(); // This is a member of the                                //  hypothetical Spaceship                                 //  API. It's presumably an                                //  entry point into some DLL.                                //  Returns an IMotion* and                                //  causes an implicit AddRef.     If(pMotion) {         pMotion->Fly();         int i = pMotion->GetPosition();         pMotion->Release(); // done with this instance of CSpaceship     } }

The other (and more important) function within IUnknown is the first one: QueryInterface. QueryInterface is the COM mechanism for discovering functionality at runtime. If someone gives you a COM interface pointer to an object and you don't want to use that pointer, you can use the pointer to ask the object for a different interface to the same object. This mechanism, along with the fact that interfaces remain constant once published, are the key ingredients that allow COM-based software to evolve safely over time. The result is that you can add functionality to your COM software without breaking older versions of the clients running that software. In addition, clients have a widely recognized means of acquiring that new functionality once they know about it. For example, you add functionality to the implementation of CSpaceship by adding a new interface named IVisual. Adding this interface makes sense because you can have objects in three-dimensional space that move in and out of view. You might also have an invisible object in three-dimensional space (a black hole, for example). Here's the IVisual interface:

struct IVisual : IUnknown {     virtual void Display() = 0; };

A client might use the IVisual interface like this:

void UseSpaceship() {     IMotion* pMotion = NULL;     pMotion = GetASpaceship(); // Implicit AddRef     if(pMotion) {         pMotion->Fly();         int i = pMotion->GetPosition();         IVisual* pVisual = NULL;         PMotion->QueryInterface(IID_IVisual, (void**) &pVisual);         // Implicit AddRef within QueryInterface         if(pVisible) {             pVisual->Display(); // uncloaking now             pVisual->Release(); // done with this interface         }     }     pMotion->Release(); // done with this instance of IMotion }

Notice that the preceding code uses interface pointers very carefully: it uses them only if the interface was acquired properly, and then it releases the interface pointers when it is done using them. This is raw COM programming at the lowest level—you acquire an interface pointer, you use the interface pointer, and you release it when you're done with it.



Programming Microsoft Visual C++
Programming Microsoft Visual C++
ISBN: 1572318570
EAN: 2147483647
Year: 1997
Pages: 332

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