Interfaces and Implementation
So far, we've talked only in general terms about how we can
pass some relevant values to a component and ask it to perform a task for us.
We haven't yet talked about how we tell
the component what we want it do, or how we pass in the required values. That's
what this section is about.
One of the key characteristics of all COM components is that
they are able to perform tasks for us without telling us how the task is going to be performed. To achieve this, the
component must give us clearly defined information saying what the component
can do, what type of information it expects us to pass, and what it will return
when the task is complete. In other words, we need to know what methods the
component exposes, the parameters that each method expects, and the return
value from each method. In order to facilitate this, COM distinguishes the
description of a component's functionality from its internal workings.
q
This description of the component's functionality is
defined by the component's interface. A component can have many
interfaces, but related methods are generally grouped together
within the same interface. A lot of people in
the COM world (including the authors of this book!) agree that interface-based
programming is the single most important and powerful aspect of
COM.
q
The 'internal workings' of the methods and
properties�that is, the code that allows them to perform their tasks�is generally referred to as the component's
implementation.
An interface is really nothing more than a list of methods, properties and events (you'll meet the second and
third of these terms in a moment). When we want to use the component, the
interface tells us how to do it. The interface doesn't give any
details of the component's implementation, but its existence implies a�promise that the functionality it describes will always be available.
The distinction between interface and implementation is an
important one. It's the interface that provides the link between our
applications and the component itself. We can replace an old component with a
new one that has a different implementation, provided that
the new component provides the same interface as the old
one. Otherwise, applications designed to use the original interface may break.
Understanding Interfaces
To help us understand interfaces, let's think about cars.
We're going to model a car in terms of a component. If you want to be really
imaginative, you can pretend that you're an ASP page (rather than a human
being) driving the car. The car component provides functionality that can be
defined by a number of interfaces. For example, the component has an interface
that defines how you drive the car�we'll call it IDrive (by convention, interface
names often begin
with I).
Methods
The IDrive
interface has these methods:
Method
|
Description
|
Accelerate()
|
Push the accelerator down by
the specified amount
|
Brake()
|
Stop the car
|
BrakeHard()
|
Stop the car fast!
|
SteerLeft()
|
Turn the steering wheel left
by the specified amount
|
SteerRight()
|
Turn the steering wheel right
by the specified amount
|
The methods are the object's way of allowing us to use it to
perform a task. The methods above allow us to accelerate, brake, and steer the
car. By using the object's methods to perform the tasks, we don't need to worry
about what is going on inside the object. If we want to turn the car to the
left, we don't need to get under the car to find out how the steering mechanism
works�we just sit in the driver's seat and call its SteerLeft() method (equivalent to turning the
steering wheel).
Some methods need additional information, and will adjust
their behavior accordingly. For example, the SteerLeft method would need to be told just how far
to turn the steering wheel to the left. To this end, such methods are capable
of receiving parameters�values
required to execute the method.
Properties
Properties are the settings or stored values that are
contained within an object, some of which are exposed to the user. These values tell us
about the appearance or behavior of the object�and we can change some
properties too. The IDrive
interface might include properties that tell us the temperature of the engine,
and the amount of gas in the tank.
There are three types of properties: read-only properties,
write-only properties, and read�write properties. For example, the car probably
has a read-only property that tells us what mileage it has done. You can affect
the values of some read-only properties indirectly�driving the car will
increase the mileage�but you're not permitted to set the mileage directly. The
tripometer (which tells you how far you have gone) would be a read-write
property�you can write to it by pressing the button that resets it to 0, and
you can read it by looking at the display that tells you how far you traveled
since you last pressed the button.
Events
We should also briefly mention events.
If methods are our way of telling an object what to do, events are the object's
way of telling us that something has happened. For
example, many modern cars have a device that is capable of monitoring the
amount of gas in the tank. If the gas level falls below a certain level, the
device fires an event, which informs the object that the gas level is low. This
allows the object to react to the event�in this case by displaying a bright-red
warning sign on the dashboard. Reacting to an
event in this way is called event handling.
Using the Interface
The nice thing about cars is that generally, once you know
how to drive one, you know how to drive them all. All cars use a steering wheel
to steer; you make them go faster by pressing the accelerator pedal, and slower
by pressing the brake pedal. The same is true for trucks and juggernauts. How
does this relate to components? Well, we can have lots of different types of
component that all expose the same interface. The car component exposes the IDrive interface, but so do the
truck component and the juggernaut component. Once we know the methods and
properties exposed by the IDrive
interface, we know roughly how to use that interface on any component that exposes it.
You will often see methods written with parentheses after
them, like this:
��
BrakeHard()
Within the parentheses, you might need to include one or
more other items�these are the parameters we mentioned earlier. The BrakeHard() method doesn't have
any parameters (when we ask the car to brake hard, it will stop quickly�the car
doesn't need any more information than that). By contrast, when we accelerate
we'll need to tell the car by how much we want to accelerate�and to do that we
use a parameter like this:
��
Accelerate(3)
This will work provided that the object knows what we mean
by an acceleration unit of 3. This should be defined in the documentation for
the component: "Insert an integer as the first parameter and I'll convert
that into miles per hour and increase
the speed by that amount". Again, as a driver we don't mind how the car component achieves this
acceleration�we just have the promise that it will.
Lollipop Diagrams
To represent the interfaces that a COM component supports,
we use a simple, pictorial technique called lollipop
diagrams. A lollipop
diagram represents the component in
the form of a box; the interfaces extrude from the left side of the
component. A name that appears within the box is the name of the component. The single line
sprouting from the top of box represents an interface called IUnknown�this is a rather
special interface in COM, so it gets special attention in lollipop diagrams.
Every
component implements IUnknown, but programming languages like Visual Basic
shield us from IUnknown and other COM-specific workings on the
supposition that they're difficult to understand. We could explain IUnknown here, but there's no gain, because we'll be
using Visual Basic until Chapter 10. We'll come back to talk about IUnknown in some detail in Chapter 10, when we start
using C++.
The following figure shows the lollipop diagrams for the car
and the truck in our example. Both components provide all the basic
functionality needed to drive such a vehicle.

It's not a great diagram, but it clearly shows what we can
do with our vehicles. Let's look at a more realistic lollipop diagram for our
car example. Interfaces generally group together related functionality, so in
the following we have three interfaces (or four, if you include IUnknown extruding from the
top):

One interface provides methods for driving the car, another
for controlling the locks and the alarm, and yet another for controlling the
in-car music system.
Identifying the Component
IDrive
is quite a nice name for our driving interface. The trouble is, it's such an
obvious name that it wouldn't be at all surprising if someone else designed an
interface of their own that did something similar, and they might call it IDrive as well. That's going to
cause severe problems if a component that exposes my IDrive gets installed on the same machine as a
component that exposes this other developer's IDrive interface. An application could easily end up
talking to the 'wrong' interface�which will almost inevitably cause it to
crash.
COM resolves such problems by ensuring that each COM
interface (and, for that matter, each component) has a 'real' name that is
guaranteed to be unique. An interface's unique name is called an interface
identifier (IID), and a component's unique name is called a class identifier (CLSID). IIDs
and CLSID are both types of globally unique identifier (GUID); a GUID is a 128-bit number
that can be generated with special a
utility supplied by Microsoft.
For example, the IID for my IDrive might be
67741683-547D-11D0-8236-00A0C908DB96. Inspection alone should tell you that
it's more than a little unlikely anyone else will come up with that name by
chance.
The utility that
generates GUIDs does so partly at random, and partly by scrambling information
like the address of the Ethernet card in the machine on which it's running and
the current time (to 100 nanosecond intervals). The algorithm used has been
carefully designed to guarantee that identical GUIDs will not be accidentally
generated for at least several thousand years. This should guarantee that there
won't be any confusion between interfaces!
Where Are Components Stored
Normally, when you run a program, you're actually running an
executable file. The executable file might call up some other files called dynamic-link libraries (or DLLs) to perform some tasks. A DLL
is like an executable file, in that it contains instructions that the computer
can run directly. But a DLL differs from an executable file because a DLL cannot be run independently. A DLL
really is like a library that can be called up by any executable that's already running.
Executables can also
call upon the functionality contained in other files, such as OLE control extensions (or OCXs). An OCX is
essentially a DLL that implements a visual interface. We won't
deal with OCXs in this book.
COM is designed to allow any application or component to
call up any other component, no matter where the other component is. This means
that COM components can be stored within executable files or as DLLs. When you
create a component in Visual Basic or by using the Visual C++ ATL Wizards, you
get to choose what type of file you'd like to host the component�a .dll or a .exe file.
There are several factors to consider when choosing which
type you want, but broadly speaking an executable offers greater security,
while a DLL can give greater performance. This is because an executable will
run in a separate process, which means that in order to use the component
within the calling application, COM has to spend time passing data back and
forth between the application's process and the component's process. A
DLL-hosted component doesn't run in a separate process, so for components
hosted in DLLs, this overhead is not normally an issue.
Because of this, components hosted in DLLs are referred to as in-process components, while those
located
in executables
are referred to as out-of-process
components.
COM Servers
One of the slightly confusing aspects of COM is that there
are several different names for some of the concepts involved. For example,
what we have been referring to as a component, a Visual Basic programmer might call
an "externally creatable class module". To confuse matters even
further, C++ programmers may refer to the same thing as a "COM
server", or a "COM object". We'll generally stick to the term component here.
Strictly speaking, a COM
server is a file (such as a DLL or an EXE) that contains all the
executable code for one or more COM components, like this. One
corollary of this is that related components can be hosted within the same COM
server if necessary.
