Chapter 3. COM Internals

At this point, those of you developing in Visual Basic and VJ++ have enough basic understanding to move forward into COM+ services and begin the in-depth exploration of these topics. Discussion of these services begins in Chapter 6, " The COM+ Catalog." This section is intended for C++ developers and for VB/VJ++ developers who are interested in seeing COM in its truest form, mainly its C++ implementation. Understanding COM at this level will give you some insight into how COM+ evolved into its present form and why it has the strengths and limitations that it does. Arguably, this chapter deals specifically with the internals of COM and not COM+, though I feel that making this distinction is rather like saying that a study of algebra is not relevant to a study of calculus. One builds on the other. This chapter can be skipped, but if you are really interested in how COM, and therefore COM+, ticks, you might give this section a try. Note that everything contained in this chapter is true for writing both COM and COM+ objects.

Virtual Function Tables (vtables), Abstract Base Classes, and Polymorphism

In this section, I'm not going to walk you through the C++ language. However, I am going to illustrate a few key features of the language that can give you a good sense of why COM is designed the way it is.

No matter what language you work in these days, most have the concept of a class. However, C++ offers the developer a great deal of latitude in terms of how a class is constructed and how it behaves. This often draws criticism from anti-C++ factions. This makes C++ too complicated, they argue, and therefore too difficult to learn and maintain. I always argue that increased latitude comes with the cost of increased complexity. At any rate, there are three aspects of C++ that are key to understanding if you want to really know COM:

  • Abstract base classes

  • Polymorphism

  • Vtables

Interfaces as Abstract Base Classes

In the C++ implementation of COM, interfaces are declared as abstract base classes. Basically, an abstract base class' entire purpose is to force a certain structure on classes that will derive from them. In C++, ICalc appears as the following:

struct ICalc: public IUnknown 
{ 

virtual HRESULT Add(int x,int y,int *result)=0; 
virtual HRESULT Subtract(int x,int y,int *result)=0;

}; 

//NOTE: struct in C++ is really a class where all the methods are public. 
//don't confuse this with a C structure.

The =0 at the of the method declarations indicates that these functions have no body. They exist only so that any class derived from ICalc must implement them. The virtual keyword indicates that polymorphism is in use.

Polymorphism in Practice

I don't want to get into a quasi-religious object-oriented discussion about the merits of polymorphism; rather, I'll just demonstrate what it does. C++ gurus can skip this paragraph, but if you need a refresher, look at Listing 3.1 and tell me what the output will be.

Example 3.1. Code Demonstrating the Trickle-Down Effect of Virtual Functions

class A
{
public:
    virtual void Func1() { cout<<"This is Func 1 in A n";} 
    virtual void Func2() { cout<<"This is Func 2 in A n";} 


};

class B: public A
{
public:
    void Func2() { cout<<"This is an override of Func 2 in B n";} 
    void Func3() { cout<<"This is Func 3 in B n";} 

};

void main()
{ 
A * pA;
B * pB;


pB=new B;
pA=pB;

pA->Func2();


}

If you believe that the output is "This is Func 2 in A", you are mistaken. The output is "This is an override of Func 2 in B." Why? Because of the virtual keyword. In a C++ class hierarchy, although a pointer of the base class type can hold an instance of any derived class, the virtual keyword ensures that the correct version of the function is called. In other words, although a pointer of type A is used to hold an object instance of B, when pA is used to call Func2(), the call trickles down from A to B, and B's Func2() is called. If you take away the virtual keyword from the Func2() declaration in class A, the wrong version of Func2() is called; the one from class A is called, even though the pointer is really holding an instance of class B.

The "trickle-down" behavior provided by virtual functions is very useful, particularly when you want to create some kind of collection class that can hold many different types of classes. As long as all these classes share a common ancestor, you can create a collection class that stores pointers to that ancestor (base) class, and it will be more than happy to store pointers to any derived class no matter how far down the line. Using virtual functions, you can iterate through your collection, call some virtual method, and be sure that the correct version of the function is invoked.

Writing COM Objects in C++

Another place virtual functions are useful is when you are writing COM objects in C++. As I said earlier, abstract base classes are used to represent interfaces in C++. You write implementation classes that inherit from one or many interfaces. Keeping in mind that all interfaces are abstract base classes that inherit from IUnknown, for our Calc object, we have what's shown in Listing 3.2.

Example 3.2. Code Demonstrating the Pseudo-Implementation of a COM Class

//NOTE:  the following interfaces have had their arguments and return values
//removed to keep things as simple as possible.

//IUnknown is declared in the Windows header files, you would not ordinarily
//declare it in your source files.  This is done just for example.
struct IUnknown
{
    virtual void QueryInterface(void)=0;
    virtual void AddRef(void)=0;
    virtual void Release(void)=0;

};

struct ICalc: public IUnknown
{ 
    virtual void a(void)=0;
    virtual void b(void)=0;
    virtual void c(void)=0;

};

struct IFinancial: public IUnknown
{ 
    virtual void d(void)=0;
    virtual void e(void)=0;
    virtual void f(void)=0;
};

//Implementation class
//we are required to actually implement the
//functions here.
//Note that multiple inheritance is most commonly used in COM/C++.
struct COMCalc: ICalc, IFinancial
{

    void QueryInterface(void){ cout<<"QueryInterface"<<endl;} 
    void AddRef(void){ coutx`"AddRef"<<endl;} 
    void Release(void){ cout<<"Release"<<endl;} 
    void a(void){ cout<<"a"<<endl;} 
    void b(void){ cout<<"b"<<endl;} 
    void c(void){ cout<<"c"<<endl;} 
    void d(void){ cout<<"d"<<endl;} 
    void e(void){ cout<<"e"<<endl;} 
    void f(void){ cout<<"f"<<endl;} 


} ;

Notice that COMCalc is responsible for actually implementing all the methods declared by all the interfaces. If you do not implement one of these pure virtual methods—for example, a()—Visual C++ would have this to say:

D: demo.cpp(68) : error C2259: 'COMCalc': cannot instantiate abstract class due to following members:
       D: demo.cpp(44) : see declaration of 'COMCalc'
D: demo.cpp(68) : warning C4259: 'void __thiscall ICalc::a(void)': pure virtual function was not defined
       D: demo.cpp(29) : see declaration of 'a'

In other words, at no point is the function a() ever implemented. Because interfaces are abstract base classes that have pure virtual functions, you cannotimplement the functions in the abstract base class definition. Rather, it is the derived class'responsibility to implement all functions on all the interfaces (abstract base classes) that it inherits from. And because every interface inherits from IUnknown, the derived object also must implement IUnknown's methods.

Virtual and Pure Virtual Functions

Any method of a class can be virtual, simply put the virtual keyword in front of the method prototype. You don't need to modify any implementation code for that method. From then on, any classes derived from the class containing a virtual function are capable of polymorphism.

In Listing 3.2, you might have noticed that function bodies are not provided for ICalc and IFinancial. All member functions of these interfaces with an =0. This syntax indicates that the methods are pure virtual—that is, they have no implementation.The existence of one or more pure virtual functions turns a class into an abstract class.

Look at it this way: An interface is nothing more than, well, an interface. It doesn'tdo anything other than guarantee what methods the underlying object supports. All methods called on an interface trickle down the object that supports the interface. In C++, the implementing COM object is usually a class that multiply inherits from all the interfaces it intends to support.

You might be wondering why I am spending this much time focusing on C++ implementation. After all, virtual functions, polymorphism, inheritance. . .theseare all things that are specific to C++ and don't have much bearing in otherlanguages, so why look at them so deeply? The simple answer is these concepts are central to COM no matter what language you are developing components in. As you'll see, the mechanism by which virtual functions work (vtables) comes into play in Java and VB as well, so if you truly understand the C++ structure, you will truly understand COM.

Internal Structure of a Vtable

Referring to Figure 3.1, let's look at how this class is put together behind the scenes. You wouldn't know it to look at it, but an instance of this class is 8 bytes in size. Specifically, this class is nothing more than two 4-byte pointers to vtables. A vtable is an array of pointers to virtual functions. Graphically, the class looks like what is shown in Figure 3.1.

The ICalc and IFinancial Vtables and Their Relation to COMCalc

Each interface is nothing more than an array of function pointers. Notice also that both ICalc and IFinancial begin with the three methods of IUnknown. As you'll see, this is critical to the functioning of COM. All interfaces must begin with these three methods so that they are available to be called by any client who has a reference to any interface.

Virtual Inheritance

C++ virtual inheritancecan be used in multiple inheritance scenarios to suppress the repetition of an abstract base class that is inherited from more than once. The following line changes the resulting vtable in COMCalc so that the methods of IUnknown do not repeat:

COMCalc: virtual ICalc, virtual IFinancial

However, this is a bad idea. The three methods of IUnknown must be at the top of every interface and so must repeat. Access violations are certain to occur if virtual inheritance is used.

Graphical description of the vtable layout for COMCalc.

Figure 3.1. Graphical description of the vtable layout for COMCalc.

If your COM object is implemented in C++ and your client is implemented in C++, an interface is really just a pointer to an abstract base class. Review Listing 3.3:

Example 3.3.  Client Code Demonstrating Casting to ICalc and IFinancial

ICalc * pICalc;
IFinancial * pIFin;
COMCalc CalcObj;

pICalc=static_castI<Calc*>&CalcObj;

pICalc->a();
pICalc->AddRef();

pIFin=static_cast<IFinancial*>&CalcObj;
pIFin->d();

Note that you cannotcreate an instance of an ICalc class. ICalc is an abstract base class; it has no implementation. However, an implementation class that derives from ICalc(COMCalc, in this case) and implements its methods can be cast to an ICalc. Of course, any method calls made to COMCalc cast as an ICalc ultimately trickle back down to COMCalc anyway (the methods are all virtual). If a C++ client application has only the abstract class definitions of the interfaces (ICalc and IFinancial) that an object supports (COMCalc), the client does not need to have any information about the implementation object(COMCalc). Put simply, if a C++ application could somehow get a pointer to a live instance of COMCalc, it could cast this pointer to either ICalc or IFinancial and be able to call the methods on either. The client would not need to have a prototype for COMCalc because it only loves COMCalc for its interfaces, not its true self.

C++ Clients

What exactly do I mean by C++ client? If I have a pointer to a COMCalc object instance, all my client needs is the prototype class definitions for ICalc and IFinancial; by casting the pointer to either, I can call methods on COMCalc. Why is this a big deal? COMCalc is right there in my source file, so why bother casting at all? You need to remember that interfaces are the only connection between a COM client and a COM object. The only thing a COM client knows about the server is what interfaces it might or might not support. The only thing a COM client can have is a declaration of these interfaces; it may never have a declaration of the underlying COM object.

In-Process COM Servers and COM+ Library Applications

In traditional COM, if a client application requests an object in a COM DLL that resides on the same machine, COM'sService Control Manager (SCM) seeks out this DLL and loads it into the client's process. This is typically the fastest way for clients and objects to talk because the client and server share the same address space. In COM+, the COM DLL is hosted by a surrogate process, and the client application is in a separate process unless the object resides in a library application.In this latter case, the COM DLL is brought into the process of the calling client.

If a COM client and a COM DLL share the same process, it is possible that the client application could, through a pre-defined COM mechanism, get a pointer to a COMCalc object living in the COM DLL. But the actual structure of the object, COMCalc in this case, would be unknown to the client who only knows about the interfaces. But then again, this is enough, right? Remember: If the client has the pure abstract base class definitions of ICalc and IFinancial listing nothing but their pure virtual function prototypes, andthe client is given a pointer of a class instance that inherits from ICalc and IFinancial, thenthat class can be cast to either ICalc or IFinancial. Where did the C++ COM client get the abstract base classes for ICalc and IFinancial? Remember from Chapter 1, "COM+ An Evolution", that comcalc.h is a file generated by MIDL. The client application simply needs to include this file, and it will have class prototypes for ICalc and IFinancial. Or, if using Visual C++, the #import directive can be used to generate a header file from any type library. For more information on #import, see the following sidebar, "The #import Directive."

The #import Directive

What #include is for a header file, #import is for a type library: both directives import C++ class/function prototypes, constants, and other constructs into a .CPP file. The difference is that the #include brings the contents of a header file which already contains C++ syntax, whereas #import reads a type library (type libraries are tokenized Interface Definition Language [IDL] files and do not contain C++syntax) and converts the contents of the type library into C++ accessible syntax. It does this by creating two hidden header files containing the C++ translated type library ending with the extensions .TLH and .TLI. Thus, a simple client program containing a #import, such as

//client.cpp
#include "windows.h"
//Note that the DLL below contains a type library
#import "d: Projects Calc Debug calcsdk.dll"  

main()
{

will cause the creation of two hidden header files,calcsdk.tlh and calcsdk.tli to be created in the debug directory. These two files contain C++ prototypes for, among other things, interfaces. Thus, if the type library inside calcsdk.dll came from an IDL that included the following:

[
          object,
          uuid(638094E5-758F-11d1-8366-0000E83B6EF3),
          dual,
          helpstring("ICalc Interface"),
          pointer_default(unique)
]
interface ICalc : IUnknown
{
          [id(1), helpstring("method Add")] HRESULT Add([in] int x, [in] int y, [out,retval] int * r );
          [id(2), helpstring("method Divide")] HRESULT Divide([in] int x, [in] int y,
                    [out,retval] int * r);
} ;

the client application, upon compilation, will include an automatically generated C++ prototype for ICalc. This is because #import implicitly includes the files it creates: calcsdk.tlh and calcsdk.tli. A look at the contents of calcsdk.tlh will reveal a C++ prototype for ICalc:

struct __declspec(uuid("638094e5-758f-11d1-8366-0000e83b6ef3"))
ICalc : IUnknown
{
    //
    // Wrapper methods for error-handling
    //

    int Add (
        int x,
        int y );
    int Divide (
        int x,
        int y );

    //
    // Raw methods provided by interface
    //

    virtual HRESULT __stdcall raw_Add (
        int x,
        int y,
        int * r ) = 0;
    virtual HRESULT __stdcall raw_Divide (
        int x,
        int y,
        int * r ) = 0;
} ;

Thus, a client application that uses #import to create C++ prototypes for COM entities containing calcsdk.dll will now be able to declare pointers of type ICalc, because the #import created an appropriate abstract base class based on ICalc's IDL description.

Note that #import recognizes command-line style arguments. Two that I often use are: "named_guids" and "no_namespace." For example:

#import '93d: Projects Calc Debug calcsdk.dll'94 named_guids no_namespace

named_guids puts friendly-named GUID declarations for all interfaces and coclasses in the .tlh file. I would always recommthis option. Theno_namespace argument prevents the generated prototypes from existing in a C++ namespace. There are other arguments, but these can be found and easily understood in VC++ documentation.

Assuming that the client gets the prototypes for ICalc and IFinancial from the MIDL-generated header file, all that remains is for the C++ client to get a pointer to a live instance of COMCalc. There is, of course, a mechanism for this. One of the exported functions of all COM DLLs, GetClassObject(), is used to get a class factory that can be used to create an instance of the object. We will talk about class factories more in the upcoming section " COMCalc C++ Example."

Your C++ client could be given a pointer to an instance of COMCalc created by COM and given to your client. This pointer has to be of type void, and the client has to cast this pointer to ICalc or IFinancial pointers before calling any methods on the object. But, after that's done, you have the power to call methods on an object without knowing anything other than the interfaces the object supports. You don't know if COMCalc has any member variables, how big it is, if it has any functions in addition to the ones in its interfaces, and so on. You have, in essence, supported another fundamental tenet of COM: Implementation must be separate from interface.

Problems with the Fundamental Tenet of COM

The basic premise of the tenet above is okay, but we have a little problem with implementation. Casting is largely a C++ concept that doesn't even exist in some languages—VB, for example. So, even though having a client cast an object pointer given to it by COM to the appropriate interface works in the C++ case, a Visual Basic client using the same object cannot perform this. VB does not know how to cast. Plus, there is another, huge problem: COM objects can choose not to support an interface that the client requests.

In other words, although COMCalc happens to support both ICalc and IFinancial, how does the client know that? In COM, a client is supposed to ask if an object supports a given interface; it cannot demand that it does. This asking happens through the IUnknown function QueryInterface() (discussed in Chapter 2, "COM Fundamentals" ), which has every right to say upon returning, "No, I don't support that interface. Pick another." In fact, the only interface a client can be sure an object supports is IUnknown.

We must, therefore, put the casting responsibility on the object itself. In other words, the client must be able to ask the object to transform itself into ICalc or IFinancial and return a pointer to the appropriate interface. But this is exactly what QueryInterface() is intended to do—produce the vtable of the requested interface and return it to the client. In a C++ object implementation, this is usually done by casting the object's this pointer to one of the interfaces the object multiply inherits from (see Listing 3.4).

REFIID and QueryInterface

REFIID is simply the GUID of the requested interface. QueryInterface() puts the pointer to the appropriate interface (vtable) into the ppv variable and returns it to the client. An HRESULT is long andis used to report errors.

Example 3.4.  A Typical Implementation of QueryInterface()

//If COMCalc is defined:
struct COMCalc: public ICalc, IFinancial
{ 
...
//Then its QueryInterface method would likely be defined:
HRESULT COMCalc::QueryInterface(REFIID riid, void **ppv)
{

    if (riid == IID_ICalc)
        *ppv = static_cast<ICalc*>(this);
    else if (riid == IID_IFinancial)
        *ppv = static_cast<IFinancial*>(this);
    else if (riid == IID_IUnknown)
        *ppv = static_cast<ICalc*>(this);
    else 
    { 
        *ppv = 0;
        return E_NOINTERFACE;
    } 

    AddRef();

    return S_OK;
}

You can think of QueryInterface() as returning the vtable to the interface you request. If you request an interface that the object does not support, QueryInterface() simply returns an error code (E_NOINTERFACE) indicating this.

Although it is true that vtables are commonly associated with C++, other languages like Java and Visual Basic can also use vtables. So, although the implementation of QueryInterface() looks very different for these languages, the basic principle is the same—the QueryInterface() function returns the vtable of the interface the client requests if the object supports that interface. Calls into functions of this interface then trickle down to the implementation object, which implements and executes the function called. At that point, however, you don't care how the object is put together. Think of objects as a black hole and interfaces the event horizon—what lies beyond is unknowable and unreachable.

Putting It All Together (RPC, DLL, Type Libraries, vtables)

In Chapter 1, we talked about Remote Procedure Call (RPC) and type libraries. In Chapter 2 and this chapter, we've discussed vtables, interfaces, DLLs, and IUnknown. You have now been exposed, at least superficially, to all these things. The real trick to knowing COM is to understand how these seemingly disjoint technologies coalesce into the COM gestalt.Here is a logical way to connect the parts to make the whole:

  • RPC and type libraries are closely linked. After all, type libraries often come from IDL files, and traditional RPC uses IDL files too. So, a type library is a binary IDL file that COM's RPC can use as a guide when calling interface methods on objects across network boundaries. Marshaling is perhaps the type library's most critical purpose, but the information in the .TLB file can also be used to provide for programmer-friendly features like Visual Basic's IntelliSense (discussed in Chapter 1).

  • Objects support interfaces. On the object side, an interface is nothing more than a vtable. On the client side, this is also true; interfaces are nothing more than a vtable. A vtable is just an array of function pointers. The difference is, on the client side, this array of function pointers point toproxy functions—that is, plain-Jane RPC ANSI C-style functions that are remoted to the object where they trip their corresponding entry in the object's vtable. Then, the method call falls into the event horizon of the black hole (object) and mysteriously gets executed. Outside of the interfaces it exposes, the object itself is a black box.

  • COM interfaces are often defined in type libraries, but a type library is just a binary IDL file. Just as methods described in an ordinary RPC IDL file allow RPC to remote method calls between clients and server, the same is true for methods of COM interfaces described in a type library.

Now that we've taken a look at type libraries, I want to focus again on the C++ implementation of a COM class. For the remainder of this chapter, we'll step through a complete COM server/client implementation in C++.

COMCalc C++ Example

The following example demonstrates the major facets of a COM client and a COM object, both of which exist in the same .CPP file. Although this example is using the actual COM header files and libraries, we are doing all of your work in a single file so that the COM Service Control Manager is not involved. This gives you an isolated example of how COM clients and servers interact. First, examine Listing 3.5. A detailed description of all significant sections follows after the listing.

Example 3.5.  Implementation of a COM Class and Associated Client All in One CPP File

#include "windows.h" 
#include "iostream.h"
const IID IID_ICalc = 
    { 0x638094E5,0x758F,0x11d1,{ 0x83,0x66,0x00,0x00,0xE8,0x3B,0x6E,0xF3} } ;
const IID IID_IFinancial =
    { 0x638094E6,0x758F,0x11d1,{ 0x83,0x66,0x00,0x00,0xE8,0x3B,0x6E,0xF3} } ;
const IID CLSID_COMCalc = 
    {  0x61305038, 0x396f, 0x11d2, {  0x80, 0x10, 0x0, 0xe0, 0x81, 0x10, 0x8, 0xed }  } ;
//Abstract base class for the ICalc interface.
//Like all interfaces, it must inherit from IUnknown
//and it does not supply implementation for its methods.

struct ICalc: public IUnknown
{
//=0 means "pure virtual"
 STDMETHOD(Add)(int x,int y,int *r)=0;
 STDMETHOD(Subtract)(int x,int y,int *r)=0;

};


struct IFinancial: public IUnknown
{

    STDMETHOD(Mortgage)(int,int,int,int *r)=0;
};

//The COM class, COMCalc, multiply inherits from
//both ICalc and IFinancial.  It gets IUnknown
//in the bargain since both interfaces inherit
//from IUnknown.
struct COMCalc: public ICalc, IFinancial
{ 
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
    STDMETHODIMP_(ULONG) AddRef(void);
    STDMETHODIMP_(ULONG) Release(void);


    STDMETHOD(Add)(int x,int y, int *r);
    STDMETHOD(Subtract)(int x,int y,int *r);
    STDMETHOD(Mortgage)(int,int,int,int *r);


};

STDMETHODIMP COMCalc::Add(int x,int y, int *r)
{
        cout<<"add n";
        *r =x + y;
        return S_OK;
} 
STDMETHODIMP COMCalc::QueryInterface(REFIID riid, void **ppv)
{ 
//Because we are using multiple inheritance,
//the class can cast itself to any of its 
//interfaces.  It CANNOT cast itself to
//IUnknown.  The reason and solution for and
//to this problem is discussed in the code
//walk-through which follows.

    if (riid == IID_ICalc)
        *ppv = static_cast<ICalc*>(this);
    else if (riid == IID_IFinancial)
        *ppv = static_cast<IFinancial*>(this);
    else if (riid == IID_IUnknown)
        *ppv = static_cast<ICalc*>(this);
    else 
    { 
        *ppv = 0;
        return E_NOINTERFACE;
    } 

    AddRef();

    return S_OK;
}

STDMETHODIMP_(ULONG) COMCalc::AddRef(void)
{
//Since this is a pseudo-implementation,
//AddRef() and Release() don't do anything.

    cout<<"COMCALC add ref n";
    return 0;
}


STDMETHODIMP_(ULONG) COMCalc::Release(void)
{ 
    
    cout<<"COMCALC release n";
    return 0;
}
STDMETHODIMP COMCalc::Subtract(int x,int y,int *r)
{
    cout<<"subtract n";
    *r= x - y;
    return S_OK;
}
STDMETHODIMP COMCalc::Mortgage(int amount, int percent, int c, int *r)
{
    cout<<"mortage n";
    cout<<percent<<endl;
    *r= -22; //just a bogus value
    return S_OK;
} 

//Class factories are used to create
//instantiate COM objects. We'll discuss 
//in the following walk-through.
struct ClassFactory: public IClassFactory
{ 
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
    STDMETHODIMP_(ULONG) AddRef(void){ return 1;} 
    STDMETHODIMP_(ULONG) Release(void){ return 1;} 


// *** IClassFactory methods ***
    STDMETHODIMP CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppv);
    STDMETHODIMP LockServer(int fLock) { return S_OK;} 

} ;

//Class factories are COM objects, so they must
//support QueryInterface.
STDMETHODIMP ClassFactory::QueryInterface (REFIID riid, void * * ppvObj) 
{ 

    if (riid == IID_IClassFactory)
        *ppvObj = static_cast<IClassFactory*>(this);
    else if (riid == IID_IUnknown)
        *ppvObj = static_cast<IUnknown*>(this);
    else 
    {     
        *ppvObj = 0;
        return E_NOINTERFACE;
    } 
    
    
    AddRef();
    
    return S_OK;

} 

//This method is called by COM or the client application directly
//to create a COM object.
STDMETHODIMP ClassFactory::CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppvObj)
} 
    COMCalc * pC;
    HRESULT hr;
    pC=new COMCalc;            
    hr=pC->QueryInterface(riid, ppvObj);
    return hr;

} 

ClassFactory g_CF;

namespace PHONYCOM         //so that my bogus override
                           //doesn't name clash  with the
                           //real CoGetClassObject()
{     
STDAPI CoGetClassObject(    REFCLSID rclsid,   //CLSID associated with 
                                               //the  class object
                            DWORD dwClsContext,//demand in-proc or out-of proc or both
                            COSERVERINFO * pServerInfo,  //Pointer to machine on which the 
                                                         //object is to be instantiated 
                            REFIID riid,    //Reference to the identifier of the interface
                            LPVOID * ppv    //Address of output variable that receives the // interface pointer requested in riid
)
{ 

    if(rclsid==CLSID_COMCalc)
    { 
        g_CF.QueryInterface(riid, ppv);

    } 


    return S_OK;

} 

} //end namespace
main()
{ 
int result;
IClassFactory * pCF;
IFinancial *pIf;
ICalc * pIc;
//begin psudocom

CoInitialize(0);  //must call this or CoInitializeEx() for EVERY thread that
                  //uses COM.

PHONYCOM::CoGetClassObject(CLSID_COMCalc, CLSCTX_ALL, NULL,IID_IClassFactory,
                          (void**)&pCF);

pCF->CreateInstance(NULL,IID_IFinancial, (void**)&pIf);
pCF->Release();

pIf->Mortgage(0,0,0,&result);
cout<<"Mortgage is "<<result<<endl;
pIf->QueryInterface(IID_ICalc, (void**)&pIc);

pIf->Release();

pIc->Add(1,2,&result);

pIc->Release();
CoUninitialize();

}

Specifying GUIDs in Code

const IID IID_ICalc = {0x638094E5,0x758F,0x11d1,{ 0x83,0x66,0x00,0x00,0xE8,0x3B,0x6E,0xF3}};
const IID IID_IFinancial = { 0x638094E6,0x758F,0x11d1,{0x83,0x66,0x00,0x00,0xE8,0x3B,0x6E,0xF3}};
const IID CLSID_COMCalc = {0x61305038, 0x396f, 0x11d2, { 0x80, 0x10, 0x0, 0xe0, 0x81, 0x10, 0x8,

In COM, every interface is uniquely identified with what is called a Globally Unique Identifier (GUID). As mentioned in Chapter 1 a GUID is a 128-bit statistically unique number generated by GUIDGEN.EXE.

In COM, the following entities need individual GUIDs:

  • Interfaces

  • coclasses

  • Type libraries

In the preceding declarations, we have run GUIDGEN.EXE and created GUIDs for the coclass COMCalc and the interfaces ICalc and IFinancial. (Remember, in COM, you always ask for an interface or a coclass by GUID, not by name.)

Inheriting from IUnknown

struct ICalc: public IUnknown
{
…

Note that every interface must inherit from IUnknown. IUnknown provides the reference counting functions AddRef() and Release(), as well as QueryInterface(), which is the only way to get the interface pointer to another interface that the underlying object may also support.

STDMETHOD, STDMETHODIMP, and Method Implementation

STDMETHOD(Add)(int x,int y,int *r)=0;
// STDMETHOD expands to virtual HRESULT _stdcall.

The virtual keyword is critical in this code so that polymorphism is in effect. This allows calls made to the methods of ICalc or IFinancial to trickle down to the base client. In COM, you never implement functions in your interface declarations. Rather, interface declarations should be abstract base classes. The implementation, then, should occur in the class that inherits from your interface. You might notice that implemented functions in the derived class are declared as STDMETHODIMP (the IMP meaning implementation).

All COM methods must return an HRESULT, which is simply a long data type.HRESULT indicates the success or failure of a method call. Because COM methodscannot return application specific data, they must modify variables passed in by reference. As you'll soon see in Chapter 5, "Method Invocation and Marshaling," there are ways to fool the client into believing that the methods return values, even though in reality, they only return HRESULTs.

Multiple Inheritance

struct COMCalc: public ICalc, IFinancial
{
…

Multiple Inheritance (MI) is extremely common in COM. It provides the most efficient and elegant method of writing COM classes. The alternative to MI isnested classes. The term is used to describe a class that is declared in another class. A class instance that is held in a member variable of another class can also be thought of as a nested class. Although MI is thought by many to add too much complexity and ambiguity, when used with pure abstract base classes (interfaces), it makes the implementation of QueryInterface(), AddRef(), and Release() very simple.

Implementation of QueryInterface()

//note that STDMETHODIMP simply expands to HRESULT __stdcall
STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
...
//You are now in the derived class. This is where you will actually implement
// all the pure virtual functions of your interfaces.
STDMETHODIMP COMCalc::QueryInterface(REFIID riid, void **ppv)
{ 
    if (riid == IID_ICalc)
        *ppv = static_castI<Calc*>(this);
    else if (riid == IID_IFinancial)
        *ppv = static_cast<IFinancial*>(this);
    else if (riid == IID_IUnknown)
        *ppv = static_cast<ICalc*>(this);
    else 
    { 
        *ppv = 0;
        return E_NOINTERFACE;
    } 

    AddRef();
    
    return S_OK;
}

This is the actual implementation of the QueryInterface() function. Because the COMCalc class inherits from ICalc and IFinancial, it can cast itself to either of these classes or to IUnknown, because both ICalc and IFinancial inherit from it.

Because ICalc and IFinancial are abstract base classes with pure virtual functions, when COMCalc casts itself to either of these types, it is really assigning *ppv to the vtable of either ICalc or IFinancial. This is all an interface pointer really is, a pointer to a vtable.

One cast that might seem puzzling, however, is this one:

else if (riid == IID_IUnknown) 
        *ppv = static_cast<ICalc*>(this);

You might be wondering, if you want IUnknown, why don't you simply cast to IUnknown? Because you can't. Because both ICalc and IFinancial inherit from IUnknown, IUnknown actually exists in COMCalc twice.In other words, the vtable of IUnknown repeats and sits on the top of both ICalc and IFinancial's vtables. If you try to cast COMCalc to IUnknown, the compiler is unsure which IUnknown to cast it to—remember, there are two. The compiler then issues an error, claiming an ambiguous cast.

The solution is simple and a little odd. Because all interfaces inherit from IUnknown, all interfaces have IUnknown's three methods at the top of their vtables. It is, therefore, permissible to simply cast COMCalc (or any COM class) to any of its interfaces when QI (QI is a shortcut term for QueryInterface) is calling for IUnknown. Although technically, the QI client gets a pointer to a vtable larger than it asked for, it doesn't matter because the client asked for IUnknown, the first three methods of any interface are IUnknown's, and the client never calls deeper than the third method.

Class Factories

struct ClassFactory: public IClassFactory
{ 
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
    STDMETHODIMP_(ULONG) AddRef(void){ return 1;} 
    STDMETHODIMP_(ULONG) Release(void){ return 1;} 


    // *** IClassFactory methods ***
    STDMETHODIMP CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppv);
    STDMETHODIMP LockServer(int fLock) { return S_OK;} 

} ;

This is COMCalc's class factory. A class factory is the only way to instantiate a COM object. It must, at the very least, inherit from an interface called IClassFactory.The derived class factory must then implement the CreateInstance and LockServer functions of this interface.

There is a one-to-one relationship between a COM coclass and its class factory. Every COM coclass must have a class factory (which is itself a COM object) that knows how to create objects of that type. The client application must first get the IClassFactory pointer for the specific class factory of a specific coclass, then call CreateInstance to create an instance of that coclass, and return the requested interface pointer to the client application. Functions like CoCreateInstance() undergo this process behind the scenes.

If you want your client to contact the class factory of an object directly, you cando what CoCreateInstance() does and call a Win32 method, CoGetClassObject().We won't discuss CoGetClassObject() in detail, but if you look it up in Microsoft Developer Network (MSDN), you'll find it is simply a way to get an interface from the class factory of any object.

Don't use SWITCH…CASE

We use IF. . .ELSE here because SWITCH. . .CASE doesn't work with GUIDs since they are structures.

While it is usually unnecessary to interact with the class factory directly to create an object, it is extremely useful when trying todebug components that will not instantiate. For example, when VB client's return runtime error 429, "ActiveX component can't create object," it is impossible to know what went wrong. All you know is that creation failed, but you don't know where in the chain the failure occurred. If, however, you are able to contact the class factory, but the class factory fails to create the object, then you know that the component is at least partially registered in the OS and was found by COM+, but the problem probably lies in the object's code. If, on the other hand, you fail to contact the class factory, then the component is either not registered, or not registered correctly in the system. I have used this technique many times to resolve creation problems.

Class Factories, IClassFactory, and Other Interfaces

STDMETHODIMP ClassFactory::QueryInterface (REFIID riid, void * * ppvObj) 
{
    if (riid == IID_IClassFactory)
        *ppvObj = static_cast<IClassFactory*>(this);
    else if (riid == IID_IUnknown)
        *ppvObj = static_cast<IUnknown*>(this);
    else 
    {     
        *ppvObj = 0;
        return E_NOINTERFACE;
    } 
    
    
    AddRef();
    
    return S_OK;
}

Class factories often inherit from interfaces in addition to IClassFactory. Following are some examples:

  • IClassFactory2. Provides object creation with licensing verification.

  • IOleItemContainer. Provides moniker support (monikers, an advanced COM concept, are covered in the "Creating a Queued Object using a Moniker" section in Chapter 10, "Queued Components").

  • <your own interface>. Perhaps you want to provide a special interface that has methods providing special object creation and initialization functionality. Languages like Visual Basic cannot use it, and C++ clients using CoCreateInstance(), by default, request IClassFactory only. However, a C++ client can ask for this customized creation interface using CoGetClassObject().

IClassFactory's CreateInstance() Method

STDMETHODIMP 
ClassFactory::CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppvObj)
{ 
    COMCalc * pC;

    pC=new COMCalc;            
    pC->QueryInterface(riid, ppvObj);

It might be surprising and even counter-intuitive, but creating a new instance of COMCalc and then QueryInterfaceing for the requested interface via the second parameter (riid) is pretty much all CreateInstance() does. Note that every call to CreateInstance() creates a new COM object. Although it is discouraged in COM, your customized class factory could return an interface pointer to a COM object it previously created, instead of creating a new one, thereby turning the object into a singleton. The reasons why singletons are a bad idea will become apparent when we talk more about object lifetimes in Chapter 8, "Transactions."

Global Scope for Class Factories

ClassFactory g_CF;
…

Unlike other COM objects, class factories are usually declared globally and are, therefore, automatically instantiated and available to the COM sub-system when the DLL containing the COM class is loaded. Also, class factories do not destroy themselves. Interestingly, the IClassFactory interface has one method, LockServer, that actually forces a DLL (or EXE, although this type of server is not relevant to a COM+ discussion) to stay loaded even if there is no client with any outstanding references to any objects of the COM DLL. This is to prevent the DLL from being unloaded while a client is talking to a class factory.

The Win32 CoGetClassObject() Method

namespace PHONYCOM        //So that my bogus override
                          //doesn't interfere with the
                          //real CoGetClassObject().
{ 
  STDAPI CoGetClassObject(REFCLSID rclsid,  //CLSID associated with the class
                                              //object.
                          DWORD dwClsContext, //demand in-proc or out-of proc
                                              //or both.
                          COSERVERINFO * pServerInfo,//Pointer to machine on
                                                     // which the object is to 
                                              //be instantiated (can be NULL).
                          REFIID riid, //Reference to the identifier of the interface.
                             LPVOID * ppv //Address of output variable that receives the 
                                       //interface pointer requested in riid.
)
…

The client application calls CoGetClassObject() to get an interface pointer (usually IClassFactory) from the class factory of a specific coclass. This method is a Win32 system function, but I have overridden and greatly simplified it here for the purpose of demonstration. In the real CoGetClassObject(), COM does all the work in terms of finding the appropriate EXE or DLL (called a COM server) where the coclass resides, running it, and handling all networking issues in the Out-of-Process case.

To simulate real COM in a single .CPP file, I've written a grossly oversimplified CoGetClassObject() function so the client code seems real. I declare it in the PHONYCOM namespace so that it doesn't conflict with the real CoGetClassObject():

main()
{ 
int result;
IClassFactory * pCF;
IFinancial *pIf;
ICalc * pIc;

//begin psudocom

CoInitialize(0);  //must call this or CoInitializeEx() for EVERY thread that
                  //uses COM.

PHONYCOM::CoGetClassObject(CLSID_COMCalc, CLSCTX_ALL, NULL,IID_IClassFactory,
                                                         (void**)&pCF);

The main() is pretty much identical to the main() you might find in a COM client application. The difference here is you are calling PHONYCOM::CoGetClassObject, instead of the real thing, which would do the following:

  • Find the appropriate class factory on the client's machine (or another machine altogether)

  • Run the COM server

  • Return the IClassFactory pointer to the client

You are actually initializing the COM libraries by calling CoInitialize(0), even though you are not taking advantage of their services.

Summary

In this chapter, we looked at the implementation of COM classes and interfaces in C++. Interfaces, you learned, are always pure abstract base classes—that is, classes containing only pure virtual functions. Pure virtual functions, by definition, have no implementation. It is always the responsibility of the derived class to provide the implementation for the methods of all interfaces it inherits from. Any calls made by clients to the methods of an interface ultimately trickle down to the base class' implementation.

MI is the simplest mechanism by which a derived class can support multiple interfaces. MI makes it possible for a COM class to simply cast itself to any of the multiple interfaces that it inherits from. This makes the implementation of QueryInterface() trivial.

A class factory is used to create an instance of a specific COM class (each class has its own). A client application can call CoGetClassObject() as opposed to CoCreateInstance() if it wants to work with a specific class'class factory directly. Otherwise, CoCreateInstance() does this behind the scenes.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.217.29.234