About Us
Education and Training
Consulting
Gifts
Contacting Us
Employment
Journal
Journal

Home

The Pantheon Systems Journal
Using the Component Object Model (COM)
Part I

By Niranjan Ramakrishnan
 
Few can doubt that the Component Object Model (COM), Microsoft's model for creating reusable components, is here to stay. It might change acronyms, its place in the architecture might shift somewhat, but its rationale is solid enough that it must remain in some guise. In this series of three articles, we shall explore the intricacies of COM. In this first article, we look at COM's rationale, and one of its key elements IUnknown, and illustrate its use with a code example.

Why COM?

COM is for C o m p o n e n t

The ruling idea of the age, to paraphrase Karl Marx, is the idea of components. We want to build things that others can use. And we want to use things that others build, if it can shorten our development cycle. For both, there must be some standardization both of how a service is provided and of how it is used.

Service, not Data

Did we just say service - and not component? That brings us to another ruling idea - that of interfaces. Everywhere you look these days, interfaces abound - in CORBA, in Java, in IDL and, as we shall see, in COM. The thinking here is that whatever component we are using, our interest in it is for its interface. Thus, our usage model of a component revolves around its interface - the functions that it performs for us. How it performs those functions are of no interest to the user.
COM is Microsoft's standard for constructing and using components. It relies rather heavily at the moment on the Windows/NT operating systems, based as it is on a binary storage model. However, this can - and almost certainly will - change as Microsoft adjusts itself to a multipolar world where CORBA and EJB are facts of life. At this point it is fair to say that COM allows us to build and use components, including distributed components, within the Windows/NT world.

The COM Architecture - The Key Ideas

The central idea in COM is that of an interface. A COM developer writes and publishes interfaces. This is all that a COM user sees. To see this via an example, imagine that you wanted to create something that housed an Automated Teller Machine (ATM). You don't care what kind of ATM it is (so long it supports your card). Let us examine a model where this might be supported.

First, let us define what operations one might want on an ATM. Keeping it simple, we get:

Abstract class ATMInterface
//===================================================
class ATMInterface {
//===================================================
   // Note that this is an abstract class 
   //   - with all methods pure virtual
   public:
      virtual void login(int acID, int PIN) = 0;
         
         // Methods below are based on a 
         // currently selected account
      virtual double    deposit(double amt) = 0;
         // Return balance after deposit
      virtual double    withdraw(double amt) = 0;
         // Return balance, or lack of success, 
         //   after withdrawal
      virtual double    balance() = 0;
         // Return the balance in the account
};
Abstract class ATMInterface
 

Now let's look at how we might use implement an actual ATM:

Internal class Account
//===================================================
class Account {
//===================================================
   public:
      double    deposit(double amt) {
         return (balance + amt);
      }
      double    withdraw(double amt) {
         return (balance - amt);
      }
      double    balance() {
         return balance;
      }
   private:
      double balance;
      int acID;
            //  etc.
};
Internal class Account
 
Concrete class ATMImplementation
//===================================================
class ATMImplementation : public ATMInterface {
//===================================================
   public:
      virtual void login(int acID, int PIN) {
         // Validate and set current
      }
      virtual double    deposit(double amt) {
         return current.deposit(amt);
      }
      virtual double    withdraw(double amt) {
         return current.withdraw(amt);
      }
      virtual double    balance() {
         return current.balance();
      }
   private:
      Account current;
};
Concrete class ATMImplementation

Finally, let's take a look at how we can use this. Any store which seeks to provide an ATM facility can do something like this:
Concrete class Store and its usage
//===================================================
class Store {
//===================================================
   public:
      operator ATMInterface&() {
         return atm;
      }
private:
   ATMImplementation atm;
};

void main() {
   // Assuming accounts have been added 
   //   to the ATMImplementation, etc.
   Store store;
   //...
   ATMInterface atmif = (ATMInterface) store;
   atmif.login(72313, 2352342);
   atmif.deposit(1000.00);
   atmif.login(52365, 2445342);
   atmif.withdraw(200.00);
   //...
}
Concrete class Store and its usage

The Class IUnknown

The COM architecture provides a top-level interface called IUnknown. It is useful because it provides standard ways of examining an object. It is rather like a front-desk of an office. Whenever a visitor wants to interact with anyone in the office, they go via the front-desk. So, anyone who knows how to inquire at the front-desk knows how to deal with the All interfaces are derived from IUnknown. IUnknown has three standard methods - one of which we shall use for our illustration. This method is called QueryInterface(). QueryInterface() allows us to inquire if an object supports a particular interface. For example, we might want to first find out if the store provides an ATM before going ahead with our use of it.
Using IUnknown
//===================================================
class Store : public IUnknown {
//===================================================
   public:
      HRESULT QueryInterface(const IID& iid, void** ppv) {
         // IID stands for interface id
         // ppv is for returning a pointer to the interface
      }
private:
   ATMImplementation atm;
};

void main() {
   // Assuming accounts have been added 
   //   to the ATMImplementation, etc.
   //....
   IUnknown maybeAStore;  // Somehow obtain this
   //...
   ATMInterface *pAtmi;
   HRESULT hr = maybeAStore.QueryInterface(IID_ATM, &pAtmi);
   if (SUCCEEDED(hr)) {
      pAtmi->login(52365, 2445342);
      pAtmi->deposit(200.00);
      pAtmi->withdraw(300.00);
      //...      
   }
}
Using IUnknown

What's happening here?

We obtain some pointer to an IUnknown. Having obtained it, we query it to see if it may be used as an ATM (the mechanics of how this might be achieved in practice are discussed in forthcoming articles). If so, we can obtain a pointer to an ATM interface from it, and use it as we might.

Unanswered questions

There are a number of things left unanswered in the above sequence. In practice, who creates the instance of the ATM? How do we know that we are using a specific instance? Can instances be shared? How can we destroy instances?

In the next article we shall answer these questions with a practical example of using COM with multiple interfaces working together. If you read the earlier set of articles in this column on CORBA, you might begin to see some similarities in the approach. Also, even though there is no direct indication of a client/server approach in what we have discussed, the notion of interfaces, so intrinsic to COM, is pregnant with exactly those possibilities. This is another area we shall talk about in forthcoming issues.


Top
Journal Archives | Send Feedback

Copyright© Pantheon Systems, Inc., All rights reserved.