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.
|