The Component Object Model

COM is an "industry-standard" software architecture supported by Microsoft, Digital Equipment Corporation, and many other companies. It's by no means the only standard. Indeed, it competes directly against other standards, such as Corba from the Open Software Foundation (OSF). Some people are working to establish interoperability between COM and other architectures, but my guess is that COM will become the leading standard.

The Problem That COM Solves

The "problem" is that there's no standard way for Windows program modules to communicate with one another. "But," you say "what about the DLL with its exported functions, Dynamic Data Exchange (DDE), the Windows Clipboard, and the Windows API itself, not to mention legacy standards such as VBX and OLE 1? Aren't they good enough?" Well, no. You can't build an object-oriented operating system for the future out of these ad hoc, unrelated standards. With the Component Object Model, however, you can, and that's precisely what Microsoft is doing.

The Essence of COM

What's wrong with the old standards? A lot. The Windows API has too large a programming "surface area"—more than 350 separate functions. VBXs don't work in the 32-bit world. DDE comes with a complicated system of applications, topics, and items. How you call a DLL is totally application-specific. COM provides a unified, expandable, object-oriented communications protocol for Windows that already supports the following features:

  • A standard, language-independent way for a Win32 client EXE to load and call a Win32 DLL

  • A general-purpose way for one EXE to control another EXE on the same computer (the DDE replacement)

  • A replacement for the VBX control, called an ActiveX control

  • A powerful new way for application programs to interact with the operating system

  • Expansion to accommodate new protocols such as Microsoft's OLE DB database interface

  • The distributed COM (DCOM) that allows one EXE to communicate with another EXE residing on a different computer, even if the computers use different microprocessor-chip families

So what is COM? That's an easier question to ask than to answer. At DevelopMentor (a training facility for software developers), the party line is that "COM is love." That is, COM is a powerful integrating technology that allows you to mix all sorts of disparate software parts together at runtime. COM allows developers to write software that runs together regardless of issues such as thread-awareness and language choice.

COM is a protocol that connects one software module with another and then drops out of the picture. After the connection is made, the two modules can communicate through a mechanism called an interface. Interfaces require no statically or dynamically linked entry points or hard-coded addresses other than the few general-purpose COM functions that start the communication process. An interface (more precisely, a COM interface) is a term that you'll be seeing a lot of.

What Is a COM Interface?

Before digging into the topic of interfaces, let's re-examine the nature of inheritance and polymorphism in normal C++. We'll use a planetary-motion simulation (suitable for NASA or Nintendo) to illustrate C++ inheritance and polymorphism. Imagine a spaceship that travels through our solar system under the influence of the sun's gravity. In ordinary C++, you could declare a CSpaceship class and write a constructor that sets the spaceship's initial position and acceleration. Then you could write a nonvirtual member function named Fly that implemented Kepler's laws to model the movement of the spaceship from one position to the next—say, over a period of 0.1 second. You could also write a Display function that painted an image of the spaceship in a window. The most interesting feature of the CSpaceship class is that the interface of the C++ class (the way the client talks to the class) and the implementation are tightly bound. One of the main goals of COM is to separate a class's interface from its implementation.

If we think of this example within the context of COM, the spaceship code could exist as a separate EXE or DLL (the component), which is a COM module. In COM the simulation manager (the client program) can't call Fly or any CSpaceship constructor directly: COM provides only a standard global function to gain access to the spaceship object, and then the client and the object use interfaces to talk to one another. Before we tackle real COM, let's build a COM simulation in which both the component and the client code are statically linked in the same EXE file. For our standard global function, we'll invent a function named GetClassObject.

If you want to map this process back to the way MFC works, you can look at CRuntimeClass, which serves as a class object for CObject-based classes. A class object is a meta-class (either in concept or in form).

In this COM simulation, clients will use this global single abstract function (GetClassObject) for objects of a particular class. In real COM, clients would get a class object first and then ask the class object to manufacture the real object in much the same way MFC does dynamic creation. GetClassObject has the following three parameters:

 BOOL GetClassObject(int nClsid, int nIid, void** ppvObj); 

The first GetClassObject parameter, nClsid, is a 32-bit integer that uniquely identifies the CSpaceship class. The second parameter, nIid, is the unique identifier of the interface that we want. The third parameter is a pointer to an interface to the object. Remember that we're going to be dealing with interfaces now, (which are different from classes). As it turns out, a class can have several interfaces, so the last two parameters exist to manage interface selection. The function returns TRUE if the call is successful.

Now let's back up to the design of CSpaceship. We haven't really explained spaceship interfaces yet. A COM interface is a C++ base class (actually, a C++ struct) that declares a group of pure virtual functions. These functions completely control some aspect of derived class behavior. For CSpaceship, let's write an interface named IMotion, which controls the spaceship object's position. For simplicity's sake, we'll declare just two functions, Fly and GetPosition, and we'll keep things uncomplicated by making the position value an integer. The Fly function calculates the position of the spaceship, and the GetPosition function returns a reference to the current position. Here are the declarations:

 struct IMotion {     virtual void Fly() = 0;     virtual int& GetPosition() = 0; }; class CSpaceship : public IMotion { protected:     int m_nPosition; public:     CSpaceship() { m_nPosition = 0; }     void Fly();     int& GetPosition() { return m_nPosition; } }; 

The actual code for the spaceship-related functions—including GetClassObject—is located in the component part of the program. The client part calls the GetClassObject function to construct the spaceship and to obtain an IMotion pointer. Both parts have access to the IMotion declaration at compile time. Here's how the client calls GetClassObject:

 IMotion* pMot; GetClassObject(CLSID_CSpaceship, IID_IMotion, (void**) &pMot); 

Assume for the moment that COM can use the unique integer identifiers CLSID_CSpaceship and IID_IMotion to construct a spaceship object instead of some other kind of object. If the call is successful, pMot points to a CSpaceship object that GetClassObject somehow constructs. As you can see, the CSpaceship class implements the Fly and GetPosition functions, and our main program can call them for the one particular spaceship object, as shown here:

 int nPos = 50; pMot->GetPosition() = nPos; pMot->Fly(); nPos = pMot->GetPosition(); TRACE("new position = %d\n", nPos); 

Now the spaceship is off and flying. We're controlling it entirely through the pMot pointer. Notice that pMot is technically not a pointer to a CSpaceship object. However, in this case, a CSpaceship pointer and an IMotion pointer are the same because CSpaceship is derived from IMotion. You can see how the virtual functions work here: it's classic C++ polymorphism.

Let's make things a little more complex by adding a second interface, IVisual, which handles the spaceship's visual representation. One function is enough—Display. Here's the whole base class:

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

Are you getting the idea that COM wants you to associate functions in groups? You're not imagining it. But why? Well, in your space simulation, you probably want to include other kinds of objects in addition to spaceships. Imagine that the IMotion and IVisual interfaces are being used for other classes. Perhaps a CSun class has an implementation of IVisual but does not have an implementation of IMotion, and perhaps a CSpaceStation class has other interfaces as well. If you "published" your IMotion and IVisual interfaces, perhaps other space simulation software companies would adopt them.

Think of an interface as a contract between two software modules. The idea is that interface declarations never change. If you want to upgrade your spaceship code, you don't change the IMotion or the IVisual interface; rather, you add a new interface, such as ICrew. The existing spaceship clients can continue to run with the old interfaces, and new client programs can use the new ICrew interface as well. These client programs can find out at runtime which interfaces a particular spaceship software version supports.

Consider the GetClassObject function as a more powerful alternative to the C++ constructor. With the ordinary constructor, you obtain one object with one batch of member functions. With the GetClassObject function, you obtain the object plus your choice of interfaces. As you'll see later, you start with one interface and then use that interface to get other interfaces to the same object.

So how do you program two interfaces for CSpaceship? You could use C++ multiple inheritance, but that wouldn't work if two interfaces had the same member function name. The MFC library uses nested classes instead, so that's what we'll use to illustrate multiple interfaces on the CSpaceship class. Not all C++ programmers are familiar with nested classes, so I'll offer a little help. Here's a first cut at nesting interfaces within the CSpaceship class:

 class CSpaceship { protected:     int m_nPosition;     int m_nAcceleration;     int m_nColor; public:     CSpaceship()         { m_nPosition = m_nAcceleration = m_nColor = 0; }     class XMotion : public IMotion     {     public:         XMotion() { }         virtual void Fly();         virtual int& GetPosition();     } m_xMotion;     class XVisual : public IVisual     {     public:         XVisual() { }         virtual void Display();     } m_xVisual;     friend class XVisual;     friend class XMotion; }; 

It might make sense to make m_nAcceleration a data member of XMotion and m_nColor a data member of XVisual. We'll make them data members of CSpaceship because that strategy is more compatible with the MFC macros, as you'll see later.

Notice that the implementations of IMotion and IVisual are contained within the "parent" CSpaceship class. In COM, this parent class is known as the class with object identity. Be aware that m_xMotion and m_xVisual are actually embedded data members of CSpaceship. Indeed, you could have implemented CSpaceship strictly with embedding. Nesting, however, brings to the party two advantages : 1) nested class member functions can access parent class data members without the need for CSpaceship pointer data members, and 2) the nested classes are neatly packaged along with the parent while remaining invisible outside the parent. Look at the code below for the GetPosition member function.

 int& CSpaceship::XMotion::GetPosition() {     METHOD_PROLOGUE(CSpaceship, Motion) // makes pThis     return pThis->m_nPosition; } 

Notice also the double scope resolution operators, which are necessary for nested class member functions. METHOD_PROLOGUE is a one-line MFC macro that uses the C offsetof operator to retrieve the offset used in generating a this pointer to the parent class, pThis. The compiler always knows the offset from the beginning of parent class data to the beginning of nested class data. GetPosition can thus access the CSpaceship data member m_nPosition.

Now suppose you have two interface pointers, pMot and pVis, for a particular CSpaceship object. (Don't worry yet about how you got these pointers.) You can call interface member functions in the following manner:

 pMot->Fly(); pVis->Display(); 

What's happening under the hood? In C++, each class (at least, each class that has virtual functions and is not an abstract base class) has a virtual function table, which is otherwise known as a vtable. In this example, that means there are vtables for CSpaceship::XMotion and CSpaceship::XVisual. For each object, there's a pointer to the object's data, the first element of which is a pointer to the class's vtable. The pointer relationships are shown here.

click to view at full size.

Theoretically, it's possible to program COM in C. If you look at the Windows header files, you'll see code such as this:

 #ifdef __cplusplus         // C++-specific headers #else         /* C-specific headers */ #endif 

In C++, interfaces are declared as C++ structs, often with inheritance; in C, they're declared as C typedef structs with no inheritance. In C++, the compiler generates vtables for your derived classes; in C, you must "roll your own" vtables, and that gets tedious. It's important to realize, however, that in neither language do the interface declarations have data members, constructors, or destructors. Therefore, you can't rely on the interface having a virtual destructor—but that's not a problem because you never invoke a destructor for an interface.

The IUnknown Interface and the QueryInterface Member Function

Let's get back to the problem of how to obtain your interface pointers in the first place. COM declares a special interface named IUnknown for this purpose. As a matter of fact, all interfaces are derived from IUnknown, which has a pure virtual member function, QueryInterface, that returns an interface pointer based on the interface ID you feed it.

Once the interface mechanisms are hooked up, the client needs to get an IUnknown interface pointer (at the very least) or a pointer to one of the derived interfaces. Here is the new interface hierarchy, with IUnknown at the top:

 struct IUnknown {     virtual BOOL QueryInterface(int nIid, void** ppvObj) = 0; }; struct IMotion : public IUnknown {     virtual void Fly() = 0;     virtual int& GetPosition() = 0; }; struct IVisual : public IUnknown {     virtual void Display() = 0; }; 

To satisfy the compiler, we must now add QueryInterface implementations in both CSpaceship::XMotion and CSpaceship::XVisual. What do the vtables look like after this is done? For each derived class, the compiler builds a vtable with the base class function pointers on top, as shown here.

click to view at full size.

GetClassObject can get the interface pointer for a given CSpaceship object by getting the address of the corresponding embedded object. Here's the code for the QueryInterface function in XMotion:

 BOOL CSpaceship::XMotion::QueryInterface(int nIid,                                          void** ppvObj) {     METHOD_PROLOGUE(CSpaceship, Motion)     switch (nIid) {     case IID_IUnknown:     case IID_IMotion:         *ppvObj = &pThis->m_xMotion;         break;     case IID_IVisual:         *ppvObj = &pThis->m_xVisual;         break;     default:         *ppvObj = NULL;         return FALSE;     }     return TRUE; } 

Because IMotion is derived from IUnknown, an IMotion pointer is a valid pointer if the caller asks for an IUnknown pointer.

The COM standard demands that QueryInterface return exactly the same IUnknown pointer value for IID_IUnknown, no matter which interface pointer you start with. Thus, if two IUnknown pointers match, you can assume that they refer to the same object. IUnknown is sometimes known as the "void*" of COM because it represents the object's identity.

Below is a GetClassObject function that uses the address of m_xMotion to obtain the first interface pointer for the newly constructed CSpaceship object:

BOOL GetClassObject(int& nClsid, int& nIid,                     void** ppvObj) {     ASSERT(nClsid == CLSID_CSpaceship);      CSpaceship* pObj = new CSpaceship();     IUnknown* pUnk = &pObj->m_xMotion;     return pUnk->QueryInterface(nIid, ppvObj); } 

Now your client program can call QueryInterface to obtain an IVisual pointer, as shown here:

 IMotion* pMot; IVisual* pVis; GetClassObject(CLSID_CSpaceship, IID_IMotion, (void**) &pMot); pMot->Fly(); pMot->QueryInterface(IID_IVisual, (void**) &pVis); pVis->Display(); 

Notice that the client uses a CSpaceship object, but it never has an actual CSpaceship pointer. Thus, the client cannot directly access CSpaceship data members even if they're public. Notice also that we haven't tried to delete the spaceship object yet—that will come shortly.

There's a special graphical representation for interfaces and COM classes. Interfaces are shown as small circles (or jacks) with lines attached to their class. The IUnknown interface, which every COM class supports, is at the top, and the others are on the left. The CSpaceship class can be represented like this.

Reference Counting: The AddRef and Release Functions

COM interfaces don't have virtual destructors, so it isn't cool to write code like the following:

 delete pMot;  // pMot is an IMotion pointer; don't do this 

COM has a strict protocol for deleting objects; the two other IUnknown virtual functions, AddRef and Release, are the key. Each COM class has a data member—m_dwRef, in the MFC library—that keeps track of how many "users" an object has. Each time the component program returns a new interface pointer (as in QueryInterface), the program calls AddRef, which increments m_dwRef. When the client program is finished with the pointer, it calls Release. When m_dwRef goes to 0, the object destroys itself. Here's an example of a Release function for the CSpaceship::XMotion class:

 DWORD CSpaceship::XMotion::Release() {     METHOD_PROLOGUE(CSpaceship, Motion) // makes pThis     if (pThis->m_dwRef == 0)         return 0;     if (--pThis->m_dwRef == 0) {         delete pThis; // the spaceship object         return 0;     }     return pThis->m_dwRef; } 

In MFC COM-based programs, the object's constructor sets m_dwRef to 1. This means that it isn't necessary to call AddRef after the object is first constructed. A client program should call AddRef, however, if it makes a copy of an interface pointer.

Class Factories

Object-oriented terminology can get a little fuzzy sometimes. Smalltalk programmers, for example, talk about "objects" the way C++ programmers talk about "classes." The COM literature often uses the term "component object" to refer to the object plus the code associated with it. COM carries with it the notion of a "class object," which is sometimes referred to as a "class factory." To be more accurate, it should probably be called an "object factory." A COM class object represents the global static area of a specific COM class. Its analog in MFC is the CRuntimeClass. A class object is sometimes called a class factory because it often implements a special COM interface named IClassFactory. This interface, like all interfaces, is derived from IUnknown. IClassFactory's principal member function is CreateInstance, which in our COM simulation is declared like this:

 virtual BOOL CreateInstance(int& nIid, void** ppvObj) = 0; 

Why use a class factory? We've already seen that we can't call the target class constructor directly; we have to let the component module decide how to construct objects. The component provides the class factory for this purpose and thus encapsulates the creation step, as it should. Locating and launching component modules—and thus establishing the class factory—is expensive, but constructing objects with CreateInstance is cheap. We can therefore allow a single class factory to create multiple objects.

What does all this mean? It means that we screwed up when we let GetClassObject construct the CSpaceship object directly. We were supposed to construct a class factory object first and then call CreateInstance to cause the class factory (object factory) to construct the actual spaceship object.

Let's properly construct the spaceship simulation. First we declare a new class, CSpaceshipFactory. To avoid complication, we'll derive the class from IClassFactory so that we don't have to deal with nested classes. In addition, we'll add the code that tracks references:

 struct IClassFactory : public IUnknown {     virtual BOOL CreateInstance(int& nIid, void** ppvObj) = 0; }; class CSpaceshipFactory : public IClassFactory { private:     DWORD m_dwRef; public:     CSpaceshipFactory() { m_dwRef = 1; }     // IUnknown functions     virtual BOOL QueryInterface(int& nIid,                                 void** ppvObj);     virtual DWORD AddRef();     virtual DWORD Release();     // IClassFactory function     virtual BOOL CreateInstance(int& nIid,                                 void** ppvObj); }; 

Next we'll write the CreateInstance member function:

 BOOL CSpaceshipFactory::CreateInstance(int& nIid, void** ppvObj) {     CSpaceship* pObj = new CSpaceship();     IUnknown* pUnk = &pObj->m_xMotion;     return pUnk->QueryInterface(nIid, ppvObj); } 

Finally, here is the new GetClassObject function, which constructs a class factory object and returns an IClassFactory interface pointer.

 BOOL GetClassObject(int& nClsid, int& nIid,                     void** ppvObj) {     ASSERT(nClsid == CLSID_CSpaceship);     ASSERT((nIid == IID_IUnknown) || (nIid == IID_IClassFactory));     CSpaceshipFactory* pObj = new CSpaceshipFactory();     *ppObj = pObj; // IUnknown* = IClassFactory* = CSpaceship* } 

The CSpaceship and CSpaceshipFactory classes work together and share the same class ID. Now the client code looks like this (without error-checking logic):

 IMotion* pMot; IVisual* pVis; IClassFactory* pFac; GetClassObject(CLSID_CSpaceship, IID_IClassFactory, (void**) &pFac); pFac->CreateInstance(IID_IMotion, &pMot); pMot->QueryInterface(IID_IVisual, (void**) &pVis); pMot->Fly(); pVis->Display(); 

Notice that the CSpaceshipFactory class implements the AddRef and Release functions. It must do this because AddRef and Release are pure virtual functions in the IUnknown base class. We'll start using these functions in the next iteration of the program.

The CCmdTarget Class

We're still a long way from real MFC COM-based code, but we can take one more step in the COM simulation before we switch to the real thing. As you might guess, some code and data can be "factored out" of our spaceship COM classes into a base class. That's exactly what the MFC library does. The base class is CCmdTarget, the standard base class for document and window classes. CCmdTarget, in turn, is derived from CObject. We'll use CSimulatedCmdTarget instead, and we won't put too much in it—only the reference-counting logic and the m_dwRef data member. The CSimulatedCmdTarget functions ExternalAddRef and ExternalRelease can be called in derived COM classes. Because we're using CSimulatedCmdTarget, we'll bring CSpaceshipFactory in line with CSpaceship, and we'll use a nested class for the IClassFactory interface.

We can also do some factoring out inside our CSpaceship class. The QueryInterface function can be "delegated" from the nested classes to the outer class helper function ExternalQueryInterface, which calls ExternalAddRef. Thus, each QueryInterface function increments the reference count, but CreateInstance calls ExternalQueryInterface, followed by a call to ExternalRelease. When the first interface pointer is returned by CreateInstance, the spaceship object has a reference count of 1. A subsequent QueryInterface call increments the count to 2, and in this case, the client must call Release twice to destroy the spaceship object.

One last thing—we'll make the class factory object a global object. That way we won't have to call its constructor. When the client calls Release, there isn't a problem because the class factory's reference count is 2 by the time the client receives it. (The CSpaceshipFactory constructor sets the reference count to 1, and ExternalQueryInterface, called by GetClassObject, sets the count to 2.)

The EX24A Example—A Simulated COM

Figures 24-1, 24-2, 24-3, and 24-4 show code for a working "simulated COM" program, EX24A. This is a Win32 Console Application (without the MFC library) that uses a class factory to construct an object of class CSpaceship, calls its interface functions, and then releases the spaceship. The Interface.h header file, shown in Figure 24-1, contains the CSimulatedCmdTarget base class and the interface declarations that are used by both the client and component programs. The Spaceship.h header file shown in Figure 24-2 contains the spaceship-specific class declarations that are used in the component program. Spaceship.cpp, shown in Figure 24-3, is the component that implements GetClassObject; Client.cpp, shown in Figure 24-4, is the client that calls GetClassObject. What's phony here is that both client and component code are linked within the same ex24a.exe program. Thus, our simulated COM is not required to make the connection at runtime. (You'll see how that's done later in this chapter.)

INTERFACE.H

 // definitions that make our code look like MFC code #define BOOL   int #define DWORD  unsigned int #define TRUE   1 #define FALSE  0 #define TRACE  printf #define ASSERT assert //----------definitions and macros----------------------------------- #define CLSID_CSpaceship    10 #define IID_IUnknown        0 #define IID_IClassFactory   1 #define IID_IMotion         2 #define IID_IVisual         3 // this macro for 16-bit Windows only #define METHOD_PROLOGUE(theClass, localClass) \     theClass* pThis = ((theClass*)((char*)(this) - \     offsetof(theClass, m_x##localClass))); \ BOOL GetClassObject(int nClsid, int nIid, void** ppvObj); //----------interface declarations----------------------------------- struct IUnknown {     IUnknown() { TRACE("Entering IUnknown ctor %p\n", this); }     virtual BOOL QueryInterface(int nIid, void** ppvObj) = 0;     virtual DWORD Release() = 0;     virtual DWORD AddRef() = 0; }; struct IClassFactory : public IUnknown {     IClassFactory()         { TRACE("Entering IClassFactory ctor %p\n", this); }     virtual BOOL CreateInstance(int nIid, void** ppvObj) = 0; }; struct IMotion : public IUnknown {     IMotion() { TRACE("Entering IMotion ctor %p\n", this); }     virtual void Fly() = 0; // pure     virtual int& GetPosition() = 0; }; struct IVisual : public IUnknown {     IVisual() { TRACE("Entering IVisual ctor %p\n", this); }     virtual void Display() = 0; }; class CSimulatedCmdTarget // `simulated' CSimulatedCmdTarget { public:     DWORD m_dwRef; protected:     CSimulatedCmdTarget() {         TRACE("Entering CSimulatedCmdTarget ctor %p\n", this);         m_dwRef = 1; // implied first AddRef     }     virtual ~CSimulatedCmdTarget()         { TRACE("Entering CSimulatedCmdTarget dtor %p\n", this); }     DWORD ExternalRelease() {     TRACE("Entering CSimulatedCmdTarget::ExternalRelease--RefCount = \           %ld\n", m_dwRef);         if (m_dwRef == 0)             return 0;         if(--m_dwRef == 0L) {             TRACE("deleting\n");             delete this;             return 0;         }         return m_dwRef;     }     DWORD ExternalAddRef() { return ++m_dwRef; } }; 

Figure 24-1. The Interface.h file.

SPACESHIP.H

 class CSpaceship; //----------class declarations----------------------------------------- class CSpaceshipFactory : public CSimulatedCmdTarget { public:     CSpaceshipFactory()         { TRACE("Entering CSpaceshipFactory ctor %p\n", this); }     ~CSpaceshipFactory()         { TRACE("Entering CSpaceshipFactory dtor %p\n", this); }     BOOL ExternalQueryInterface(int lRid, void** ppvObj);     class XClassFactory : public IClassFactory     {     public:         XClassFactory()             { TRACE("Entering XClassFactory ctor %p\n", this); }         virtual BOOL QueryInterface(int lRid, void** ppvObj);         virtual DWORD Release();         virtual DWORD AddRef();         virtual BOOL CreateInstance(int lRid, void** ppvObj);     } m_xClassFactory;     friend class XClassFactory; }; class CSpaceship : public CSimulatedCmdTarget { private:     int m_nPosition; // We can access these from                      //  all the interfaces     int m_nAcceleration;     int m_nColor; public:     CSpaceship() {         TRACE("Entering CSpaceship ctor %p\n", this);         m_nPosition = 100;         m_nAcceleration = 101;         m_nColor = 102;     }     ~CSpaceship()         { TRACE("Entering CSpaceship dtor %p\n", this); }     BOOL ExternalQueryInterface(int lRid, void** ppvObj);     class XMotion : public IMotion     {     public:         XMotion()             { TRACE("Entering XMotion ctor %p\n", this); }         virtual BOOL QueryInterface(int lRid, void** ppvObj);         virtual DWORD Release();         virtual DWORD AddRef();         virtual void Fly();         virtual int& GetPosition();     } m_xMotion;      class XVisual : public IVisual     {     public:         XVisual() { TRACE("Entering XVisual ctor\n"); }         virtual BOOL QueryInterface(int lRid, void** ppvObj);         virtual DWORD Release();         virtual DWORD AddRef();         virtual void Display();     } m_xVisual;     friend class XVisual;  // These must be at the bottom!     friend class XMotion;     friend class CSpaceshipFactory::XClassFactory; }; 

Figure 24-2. The Spaceship.h file.

SPACESHIP.CPP

 #include <stdio.h> #include <stddef.h> // for offsetof in METHOD_PROLOGUE #include <ASSERT.h> #include "Interface.h" #include "Spaceship.h" CSpaceshipFactory g_factory; //----------member functions----------------------------------------- BOOL CSpaceshipFactory::ExternalQueryInterface(int nIid,                                                void** ppvObj) {     TRACE("Entering CSpaceshipFactory::ExternalQueryInterface--nIid =  \           %d\n", nIid);     switch (nIid) {     case IID_IUnknown:     case IID_IClassFactory:         *ppvObj = &m_xClassFactory;         break;     default:         *ppvObj = NULL;         return FALSE;     }     ExternalAddRef();     return TRUE; }                                BOOL CSpaceshipFactory::XClassFactory::QueryInterface(int nIid,                                                       void** ppvObj) {     TRACE("Entering CSpaceshipFactory::XClassFactory::\            QueryInterface--nIid = %d\n", nIid);     METHOD_PROLOGUE(CSpaceshipFactory, ClassFactory) // makes pThis     return pThis->     ExternalQueryInterface(nIid, ppvObj); // delegate to                                           //  CSpaceshipFactory }                                BOOL CSpaceshipFactory::XClassFactory::CreateInstance(int nIid,                                                       void** ppvObj) {     TRACE("Entering CSpaceshipFactory::XClassFactory::CreateInstance\n");     METHOD_PROLOGUE(CSpaceshipFactory, ClassFactory) // makes pThis     CSpaceship* pObj = new CSpaceship();     if (pObj->ExternalQueryInterface(nIid, ppvObj)) {         pObj->ExternalRelease(); // balance reference count         return TRUE;     }     return FALSE; } DWORD CSpaceshipFactory::XClassFactory::Release() {     TRACE("Entering CSpaceshipFactory::XClassFactory::Release\n");     METHOD_PROLOGUE(CSpaceshipFactory, ClassFactory) // makes pThis     return pThis->ExternalRelease(); // delegate to CSimulatedCmdTarget } DWORD CSpaceshipFactory::XClassFactory::AddRef() {     TRACE("Entering CSpaceshipFactory::XClassFactory::AddRef\n");     METHOD_PROLOGUE(CSpaceshipFactory, ClassFactory) // makes pThis     return pThis->ExternalAddRef(); // delegate to CSimulatedCmdTarget } BOOL CSpaceship::ExternalQueryInterface(int nIid, void** ppvObj) {     TRACE("Entering CSpaceship::ExternalQueryInterface--nIid =             %d\n", nIid);     switch (nIid) {     case IID_IUnknown:     case IID_IMotion:         *ppvObj = &m_xMotion; // Both IMotion and IVisual are derived         break;                //  from IUnknown, so either pointer will do     case IID_IVisual:         *ppvObj = &m_xVisual;         break;     default:         *ppvObj = NULL;         return FALSE;     }     ExternalAddRef();     return TRUE; } BOOL CSpaceship::XMotion::QueryInterface(int nIid, void** ppvObj) {     TRACE("Entering CSpaceship::XMotion::QueryInterface--nIid =  \             %d\n", nIid);     METHOD_PROLOGUE(CSpaceship, Motion) // makes pThis     return pThis->ExternalQueryInterface(nIid, ppvObj); // delegate to                                                         //  CSpaceship }  DWORD CSpaceship::XMotion::Release() {     TRACE("Entering CSpaceship::XMotion::Release\n");     METHOD_PROLOGUE(CSpaceship, Motion) // makes pThis     return pThis->ExternalRelease(); // delegate to CSimulatedCmdTarget } DWORD CSpaceship::XMotion::AddRef() {     TRACE("Entering CSpaceship::XMotion::AddRef\n");     METHOD_PROLOGUE(CSpaceship, Motion) // makes pThis     return pThis->ExternalAddRef(); // delegate to CSimulatedCmdTarget } void CSpaceship::XMotion::Fly() {     TRACE("Entering CSpaceship::XMotion::Fly\n");     METHOD_PROLOGUE(CSpaceship, Motion) // makes pThis     TRACE("this = %p, pThis = %p\n", this, pThis);     TRACE("m_nPosition = %d\n", pThis->m_nPosition);     TRACE("m_nAcceleration = %d\n", pThis->m_nAcceleration); } int& CSpaceship::XMotion::GetPosition() {     TRACE("Entering CSpaceship::XMotion::GetPosition\n");     METHOD_PROLOGUE(CSpaceship, Motion) // makes pThis     TRACE("this = %p, pThis = %p\n", this, pThis);     TRACE("m_nPosition = %d\n", pThis->m_nPosition);     TRACE("m_nAcceleration = %d\n", pThis->m_nAcceleration);     return pThis->m_nPosition; } BOOL CSpaceship::XVisual::QueryInterface(int nIid, void** ppvObj) {     TRACE("Entering CSpaceship::XVisual::QueryInterface--nIid =  \            %d\n", nIid);     METHOD_PROLOGUE(CSpaceship, Visual) // makes pThis     return pThis->ExternalQueryInterface(nIid, ppvObj); // delegate to                                                         //  CSpaceship } DWORD CSpaceship::XVisual::Release() {     TRACE("Entering CSpaceship::XVisual::Release\n");     METHOD_PROLOGUE(CSpaceship, Visual) // makes pThis     return pThis->ExternalRelease(); // delegate to CSimulatedCmdTarget } DWORD CSpaceship::XVisual::AddRef() {     TRACE("Entering CSpaceship::XVisual::AddRef\n");     METHOD_PROLOGUE(CSpaceship, Visual) // makes pThis     return pThis->ExternalAddRef(); // delegate to CSimulatedCmdTarget } void CSpaceship::XVisual::Display() {     TRACE("Entering CSpaceship::XVisual::Display\n");     METHOD_PROLOGUE(CSpaceship, Visual) // makes pThis     TRACE("this = %p, pThis = %p\n", this, pThis);     TRACE("m_nPosition = %d\n", pThis->m_nPosition);     TRACE("m_nColor = %d\n", pThis->m_nColor); } //----------simulates COM component ----------------------------------- // In real COM, this would be DllGetClassObject, which would be called //  whenever a client called CoGetClassObject  BOOL GetClassObject(int nClsid, int nIid, void** ppvObj) {     ASSERT(nClsid == CLSID_CSpaceship);     ASSERT((nIid == IID_IUnknown) || (nIid == IID_IClassFactory));     return g_factory.ExternalQueryInterface(nIid, ppvObj);     // Refcount is 2, which prevents accidental deletion } 

Figure 24-3. The Spaceship.cpp file.

CLIENT.CPP

 #include <stdio.h> #include <stddef.h> // for offsetof in METHOD_PROLOGUE #include <assert.h> #include "interface.h" //----------main program----------------------------------------------- int main() // simulates OLE client program {     TRACE("Entering client main\n");     IUnknown* pUnk; // If you declare these void*, you lose type-safety     IMotion* pMot;     IVisual* pVis;     IClassFactory* pClf;           GetClassObject(CLSID_CSpaceship, IID_IClassFactory,                   (void**) &pClf);      pClf->CreateInstance(IID_IUnknown, (void**) &pUnk);     pUnk->QueryInterface(IID_IMotion, (void**) &pMot); // All three     pMot->QueryInterface(IID_IVisual, (void**) &pVis); //  pointers                                                        //  should work     TRACE("main: pUnk = %p, pMot = %p, pDis = %p\n", pUnk, pMot, pVis);          // Test all the interface virtual functions     pMot->Fly();     int nPos = pMot->GetPosition();     TRACE("nPos = %d\n", nPos);     pVis->Display();     pClf->Release();     pUnk->Release();     pMot->Release();     pVis->Release();     return 0; } 

Figure 24-4. The Client.cpp file.



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