December 92 - TECHNIQUES FOR WRITING AND DEBUGGING COMPONENTS
TECHNIQUES FOR WRITING AND DEBUGGING COMPONENTS
GARY WOODCOCK AND CASEY KING
Programmers first saw the Component Manager as part of the QuickTime 1.0 system
extension. Now that the Component Manager is part of System 7.1, components aren't
just for QuickTime programmers any more. This article shows you how to take
advantage of the power and flexibility of components as a way to give extended
functionality to any Macintosh application.
Software developers are continually searching for ways to avoid reinventing the
proverbial wheel every time they need new capabilities for their programs. A new
approach is available with components. Components are modules of functionality that
applications can share at run time. They enable applications to extend the services of
the core Macintosh system software with minimal risk of introducing
incompatibilities (unlike, for example, trap patching).
As Figure 1 suggests, components also encourage a building-block approach to solving
complex problems. Higher-level components can call lower-level components to build
sophisticated functionality, while at the same time making the application program
interface (API) much simpler. What's more, because components are separate from
an application that uses them, you can modify and extend components without affecting
the application.
Components are maintained by the Component Manager, which is responsible for
keeping track of the components available at any given time and of the particular
services they provide. The Component Manager provides a standard interface through
which applications establish connections to the components they need.
Almost anything you can dream up can be a component -- video digitizer drivers,
dialogs, graphics primitives, statistical functions, and more. QuickTime 1.0 itself
contains a number of useful components, including the movie controller, the sequence
grabber, and a variety of image compressors and decompressors (codecs ), all of
which are available to any client application.
Figure 1 Using Components as Software Building Blocks
To demonstrate the all-around usefulness of components, we'll examine the
development and implementation of a component that does some rather trivial
mathematical calculations. This example will help us focus on concepts rather than
getting lost in the details of solving a complex problem. We'll build a fairly generic
component template that you can use in your own designs. We'll also discuss some
advanced component features, such as extending component functionality, capturing
components, and delegating component functions. Finally, we'll show you some
techniques and tools for debugging your components. The accompanyingDeveloper CD
Series disc contains our example component's source code, a simple application to test
our component, and the debugging tools.
Note that this article doesn't spend a great deal of time explaining how applications can
find and use components. We assume that you've invested some effort in reading
theQuickTime Developer's Guide (part of the QuickTime Developer's Kit). If you
haven't, we strongly urge you to do so, since theDeveloper's Guide contains the
definitive description of the Component Manager.
SHOULD YOU WRITE A COMPONENT?
OK, components sound interesting, but should you write one? Why write a component
when you can just code the functionality you need directly into your application or
write a device driver? Here are a few reasons to choose components over the
alternatives:
• Components are easier for applications to use. Client applications don't
have to know what they're looking for before opening a service. This is
different from device drivers, where open calls must provide either a driver
name or a refNum. An application can simply tell the Component Manager,
"I'm looking for somebody to do this for me. Is anybody available?" In addition,
clients don't need to set up parameter blocks or make control/status calls to
use components. Armed with the API of the component type, the caller simply
makes normal function calls to the component, and the Component Manager
does the work.
• Components are more flexible. You can modify the behavior of a
component by overriding its capabilities without adversely affecting the
application. The Component Manager enables the component to communicate its
capabilities to clients dynamically.
• Components allow you to design more flexible applications. They can be
used to divide the functional aspects of an application into parts. For example,
a word processing application might use a spelling checker component, a
thesaurus component, and a grammar checker component. If the thesaurus
component is updated, the application code doesn't have to change at all. A user
can simply replace the old thesaurus component with the new one.
• Components are easier to implement than device drivers. There are no
declaration structures, driver headers, assembly code glue, installation INITs,
or any of the peculiarities that come with device drivers.
• Components are easier to debug than device drivers. No longer will you be
walking the unit table to find your driver so that you can set a breakpoint at
your control call dispatcher. You can easily and effectively debug your code
using a source-level debugger such as Symantec's THINK C Debugger.
Now that you know the advantages of components, you have to decide whether the
functionality you need is a good candidate for a component. To do this, ask yourself the
following:
• Do I anticipate reusing this functionality in other applications?
Components are ideal for providing services that many applications can use.
• Do I anticipate having to modify certain aspects of this functionality in the
future? Functionality encapsulated in a component can be extended or
modified without disturbing the original interface specification.
• Is there a benefit to users in establishing a common API for this
functionality, so that other developers can use or extend it? You might want to
be able to allow third parties to extend your application without having to
expose detailed information about your application's internal data structures.
For example, many of the "plug-in" modules for today's popular graphics
applications could easily be implemented as components.
A "yes" to more than one of these questions means that components are probably a good
approach for your next product. But you still have one last question to answer: has
someone else already written a component that solves your problem? To find out, you
need to contact Apple's Component Registry group (AppleLink REGISTRY) and ask them.
These folks maintain a database of all registered component types, subtypes, and
manufacturers, as well as the corresponding APIs (if they're publicly available). A
check with the Registry is mandatory for anyone who's contemplating writing a
component.
If after all this you find that you're still about to embark into uncharted territory,
read on, and we'll endeavor to illuminate your passage.
COMPONENT BASICS 101
Client applications use the Component Manager to access components. As shown in
Figure 2, the Component Manager acts as a mediator between an application's requests
for component services and a component's execution of those requests. The Component
Manager uses acomponent instance to determine which component is needed to satisfy
an application's request for services. An instance can be thought of as an application's
connection to a component. We'll have more to say about component instances later on.
Figure 2 How Applications Work With Components
Conceptually, components consist of two parts: a collection of functions as defined in
the component's API, and a dispatcher that takes care of routing application requests to
the proper function. These requests are represented by request codes that the
Component Manager maps to the component functions. Let's take a look at both the
component functions and the component dispatcher in detail.
COMPONENT FUNCTIONS
There are two groups of functions that are implemented in a component. One group does
the custom work that's unique to the component. The nature of these functions depends
on the capabilities that the component is intended to provide to clients. For example,
the movie controller component, which plays QuickTime movies, has a number of
functions in this category that control the position, playback rate, size, and other
movie characteristics. Each function defined in your component API must have a
corresponding request code, and you must assign these request codes positive values (0
or greater).
The second group of functions comprises the standard calls defined by the Component
Manager for use by a component. Currently, four of these standard callsmust be
implemented by every component: open, close, can do, and version. Two more request
codes, register and target, are defined, but supporting these is optional. The standard
calls are represented by negative request codes and are definedonly by Apple.
Here's a quick look at each of the six standard calls.
The open function. The open function gives a component the opportunity to
initialize itself before handling client requests, and in particular to allocate any
private storage it may need. Private storage is useful if your component has
hardware-dependent settings, local environment settings, cached data structures, IDs
of component instances that may provide services to your component, or anything else
you might want to keep around.
The close function. The close function provides for an orderly shutdown of a
component. For simple components, closing mainly involves disposing of the private
storage created in the open function. For more complex components, it may be
necessary to close supporting components and to reset hardware.
The can do function. The can do function tells an application which functions in the
component's API are supported. Clients that need to query a component about its
capabilities can use the ComponentFunctionImplemented routine to send the component
a can do request.
The version function. The version function provides two important pieces of
information: the component specification level and the implementation level. A change
in the specification levelnormally indicates a change in the basic API for a particular
component class, while implementation- level changes indicate, for example, a bug fix
or the use of a new algorithm.
The register function. The register function allows a component to determine
whether it can function properly with the current system configuration. Video
digitizer components, for example, typically use register requests to check for the
presence of their corresponding digitizing hardware before accepting registration with
the Component Manager. A component receives a register request code only if it
explicitly asks for it. We'll see how this is done when we walk through our sample
component.
The target function. The target function informs your component it has
beencaptured by another component. Capturing a component is similar to subclassing
an object, in that the captured component is superseded by the capturing component.
The captured component is replaced by the capturing component in the component
registration list and is no longer available to clients. We'll discuss the notion of
capturing components in more detail later.
THE COMPONENT DISPATCHER
All components must have a main entry point consisting of a dispatcher that routes the
requests the client application sends via the Component Manager. When an application
calls a component function, the Component Manager passes two parameters to the
component dispatcher -- a ComponentParameters structure and a handle to any
private storage that was set up in the component's open function. The
ComponentParameters structure looks like this:
unsigned char flags;
unsigned char paramSize;
short what;
long params[kSmallestArray];
The first two fields are used internally by the Component Manager and aren't of much
interest here. The what field contains the request code corresponding to the
component function call made by the application. The params field contains the
parameters that accompany the call.
Figure 3 shows a detailed view of how a component function call from an application is
processed. The component dispatcher examines the what field of the
ComponentParameters record to determine the request code, and then transfers control
to the appropriate component function.
REGISTERING A COMPONENT
Before a component can be used by an application, it must be registered with the
Component Manager. This way the Component Manager knows which components are
available when it's asked to open a particular type of component.
Figure 3Processing an Application's Request for Component Services
Autoregistration versus application registration. There are two ways that
you can register a component. By far the easiest way is to build a standalone
component file of type 'thng'. At system startup, the Component Manager will
automatically register any component that it finds in files of type 'thng' in the System
Folder and in the Extensions folder (in System 7) and its subfolders. The 'thng'
component file must contain both your component and the corresponding component
('thng') resource. The definition of this resource can be found in the Components.h
header file and is shown below.
unsigned long type; /* 4-byte code */
short id;
ComponentDescription td; /* Registration parameters */
ResourceSpec component;
/* Resource where code is found */
ResourceSpec componentName; /* Name string resource */
ResourceSpec componentInfo; /* Info string resource */
ResourceSpec componentIcon; /* Icon resource */
Figure 4 shows the contents of the component resource that we'll use for the example
component.
Figure 4 Math Component Resource
An application can also register a component itself using the Component Manager call
RegisterComponent or RegisterComponentResource. As we'll see, this registration
method facilitates symbolic debugging of components.
Global versus local registration. Components can be registered locally or
globally. A component that's registered locally is visible only within the A5 world in
which it's registered, whereas a globally registered component is available to all
potential client applications. Typically, you register a component locally only if you
want to restrict its use to a particular application.
A SIMPLE MATH COMPONENT
To help you understand how to write a component, we're going to go through the whole
process with an example -- in this case, a simple math component. We start by
contacting the Apple Component Registry group, and to our astonishment (and their
bemusement), we find that there are no registered components that do simple math!
We assume for the moment that the arithmetic operators in our high-level