Columns


The Learning C/C++urve

Bobby Schmidt

Into the Swamp

Our resident Microsoft refugee describes some of the local lingo stuff like COM and MAPI to be specific.


Copyright © 1997 Robert H. Schmidt

This month I resume my abstraction series with an overview of the Microsoft world we're invading.

Disclaimer

To reach the El Dorado of increased abstraction, I've chosen a route through the murky swamp of Microsoft's COM and Extended MAPI. I'm taking this route not because I'm especially enamored of these technologies, but rather because they offer (wittingly or otherwise) a wonderful demonstration of why we want to get to El Dorado in the first place.

Before stepping into the mire, I want to issue a disclaimer: This is not a treatise on Microsoft programming. I will explain just enough Microsoftese to get us through, but not so much that you become expert on the local terrain. I will purposely gloss over much of the Microsoft-specific detail, and generalize the API to suit my needs.

If you find that I've cut corners on some aspect of COM or MAPI programming, picked a route that's longer than it needs to be, or made some sweeping generalization that's not 100% true, please do not write me. I'm using Microsoft's system as a means to a larger end, and don't want to get bogged down in that means. I'm sure there are heaps of bored Microsoft Systems Journal readers lurking on comp.os.ms-windows.programmer.ole just dying to debate this [1] .

You C programmers may read what follows and wonder, "Hey, what about your promise to include C abstractions? This stuff is all C++!" Trust me, this is all relevant, if somewhat obliquely. Microsoft's program interface standard is designed to be implemented in and called by both C and C++. However, the underlying design of that interface is object-oriented, and in fact has been since the earliest days of the Windows SDK (when all we had was C and assembler). Since C++ is a more natural expressive medium for OOP discussion, I've chosen to focus on C++ below — but everything I show you is accessible by C, admittedly with more work [2] .

COM

COM == Component Object Model, Microsoft's standard for run-time objects and the foundation of their much-touted ActiveX. While you can write COM code with any compiler that supports the COM binary standard, C++ is typically the language of choice here. C++ compilers generate much of the code (especially v-tables and this pointers) that the COM binary standard requires. Also, C++ supports many of the object notions at compile time that COM supports at run time, letting you more easily model your intent in the code. In fact, I'm going to explain the essence of COM from the perspective of C++, since the latter is presumably a more familiar object paradigm. Purists be warned: I'm changing the real COM names, data types, and implementation options to streamline this overview.

Suppose you're writing a C++ project, and have designed all the public class interfaces. If you are truly a Diligent Reader, these interfaces have nothing but function members. For every class, separate the definition into two pieces, the public member function and everything else. The public members become the class interface, and the others become the class implementation.

As an example, if you have a circle class such as

class circle
    {
public:
    ~circle();
    circle();
    circle(circle const &);
    circle &operator=(circle const &);
    double get_area() const;
    void set_radius(double);
private:
    double radius_;
    };

separate it into

class circle_interface
    {
public:
    ~circle_interface();
    circle_interface();
    circle_interface(
        circle_interface const &);
    circle_interface &operator=
        (circle_interface const &);
    double get_area() const;
    void set_radius(double);
    };

class circle_implementation
    {
private:
    double radius_;
    };

Now make all the interface members pure virtual, moving the constructors, destructors, and assignment operator to the implementation class:

class circle_interface
    {
public:
    virtual double get_area() const
        = 0;
    virtual void set_radius(double)
        = 0;
    };

class circle_implementation
    {
public:
    ~circle_implementation();
    circle_implementation();
    circle_implementation(
       circle_implementation const &);
    circle_implementation &operator=
(circle_implementation const &);

private:
    double radius_;
    };

Why remove these members from interface classes? Several reasons:

A COMmon Base

Assume that every interface class has some magic compile-time number that uniquely identifies it. Next, have each interface class directly or indirectly derive from a common abstract base:

class common_interface
    {
public:
    virtual void add_reference() = 0;
    virtual void release() = 0;
    virtual common_interface *query_interface (unsigned) = 0;
    };

class circle_interface :
    public common_interface
    {
public:
    virtual double get_area() const
        = 0;
    virtual void set_radius(double)
        = 0;
    //
    //  Derived from
    //  'common_interface'.
    //
    virtual void add_reference() = 0;
    virtual void release() = 0;
    virtual common_interface
        *query_interface
            (unsigned) = 0;
    };

where

Since you can't directly create your first object of an interface class, add an implementation class member to create them for you:

class circle_implementation
    {
public:
    ~circle_implementation();
    circle_implementation();
    circle_implementation(
       circle_implementation const &);
    circle_implementation &operator=
       (circle_implementation const &);
    //
    //  New interface object
    //  creation member.
    //
    virtual common_interface
        *create_interface
            (unsigned);
private:
    double radius_;
    };

Once you obtain an interface from create_interface, you can call that interface's query_interface to get other interfaces. Because all interfaces provide query_interface, from one interface you can bootstrap your way to all interfaces the implementation supports (by calling query_interface for each interface of interest).

Putting It All Together

Quite often, the easiest way for an implementation object to return a corresponding interface object is if the implementation object is an interface object:

class circle_implementation :
     public circle_interface {
public:
    ~circle_implementation();
    circle_implementation();
    circle_implementation
        (circle_implementation const &);
    circle_implementation &operator=
            (circle_implementation const &);
    common_interface *create_interface(unsigned);
private:
    double radius_;
    unsigned reference_count_;
    //
    //  Derived from 'common_interface'.
    //
    virtual void add_reference();
    virtual void release();
    virtual common_interface *query_interface
            (unsigned);
    //
    //  Derived from 'circle_interface'.
    //
    virtual double get_area() const;
    virtual void set_radius(double);
    };

Such derivation allows the implementation object to simply return a pointer to itself in response to both query_interface and create_interface, and to manage one reference count for all supported interfaces. Further, because the base (interface) objects lack data members, implementations supporting multiple interfaces can use multiple inheritance without the hassles of virtual bases.

Listing 1 shows the full circle_implementation, while Listing 2 shows an example of how circle_interface and circle_implementation work together with client code.

Notice that when you copy one circle_implementation object to another (via copy constructor or copy assignment), you don't copy the reference_count_ data member. That member counts the number of interface object references, not implementation object references. Copying an implementation object doesn't change the number of references to the interfaces that object provides.

Also notice that you don't call interface members directly on the implementation object. Rather, you ask the implementation object to give you an interface object (via create_interface), and call the interface members on that interface object. Because they will never be called directly, I've declared those interface members private in the implementation object [3] .

Microsoft has several principle motivations for dissociating an interface from its implementing object, as I have done above — even given the resulting level of indirection:

As I mentioned, I've made some gross generalizations here, and changed the names to protect the guilty. As we work through this series, I'll point out relevant differences between my smoke-and-mirrors explanation and the actual Microsoft implementation.

Extended MAPI

Extended MAPI == Extended Messaging API, Microsoft's baroque support for mail clients and servers. This API is truly a swamp. I find it instructive that Microsoft has not enveloped MAPI in MFC. I wonder if that's because almost nobody is using MAPI (other than Microsoft itself), or because Microsoft has found impossible the task of adding order to the MAPI chaos.

I'm not interested in giving a full-blown discourse on the interrelationship among different MAPI components. At this stage, what you need to know about MAPI distills out to a few key generalities:

To be fair to Microsoft, I suspect they crafted MAPI at a time when they considered C compatibility paramount. However, it's taken so long for MAPI and COM to propagate that, had they the chance to do it over, they might well scrap the C compatibility and make the interface truly objectified from the start.

Next Steps

In coming months, I expect to follow this route:

What you won't see is a single large working program. Again, if you are after 8,000-line Windows apps, check out MSJ.

Erratica

Dugald A. Taylor was the first of several diligent readers who note that that my March '97 chemistry example

H2O2 --> H2 + O2

would create a fairly combustible mix in those brown peroxide bottles (think about the Hindenburg). Assuming there is no other agent (like to-be-bleached hair) present, the real net reaction is

2H2O2 --> 2H2O + O2

And to think, I had a concentration in Chemistry during college.o

Notes

[1] . Before you get your knickers in a knot, know that the technical and acquisitions editor of MSJ is a close friend. He would be gravely disappointed were I to pass up such an easy shot at his mag.

[2] Microsoft's COM headers include macros that let you write the same source for both languages. These macros expand to little or nothing in C++, but expand to magic "stuff" in C to emulate language features C++ supports directly. (Although it's still a chore to emulate C++'s virtual function tables in C.)

[3] . Remember last month, when I had you ponder a derived class decreasing a base class member's access? Here's a variation on that same notion.

[4] . The Microsoft magic performing this so-called marshaling is way beyond this column's scope. I'm not even sure Microsoft has it all working correctly yet.

Bobby Schmidt is a freelance writer, teacher, consultant, and programmer. He is also a member of the ANSI/ISO C standards committee, an alumnus of Microsoft, and an original "associate" of (Dan) Saks & Associates. In other career incarnations, Bobby has been a pool hall operator, radio DJ, private investigator, and astronomer. You may summon him at 14518 104th Ave NE Bothell WA 98011; by phone at +1-206-488-7696, or via Internet e-mail as rschmidt@netcom.com.