Beginning
Components in ASP
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.