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 a
component 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:
typedef struct {
unsigned char flags;
unsigned char paramSize;
short what;
long params[kSmallestArray];
} ComponentParameters;
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.
typedef struct {
unsigned long type; /* 4-byte code */
short id;
} ResourceSpec;
typedef struct {
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 */
} ComponentResource;
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 programming language are unavailable and that our application is in
desperate need of integer division and multiplication support.
We create a component called Math that performs integer division and multiplication.
THE FUNCTION PROTOTYPE DEFINITION
We need to define function prototypes for each of the calls in our component API -- namely,
DoDivide and DoMultiply. The function prototype for the DoDivide component call can be found
in MathComponent.h and is shown below. The declaration for the DoMultiply function is similar.
pascal ComponentResult DoDivide (MathComponent mathInstance,
short numerator, short denominator, short *result) =
ComponentCallNow (kDoDivideSelect, 0x08);
This resembles a normal C language function prototype with a relatively straightforward parameter
list. The mathInstance parameter is the component instance through which the application accesses
the component; we'll see how an application gets one of these instances in a moment. The numerator
and denominator parameters are self-explanatory and are passed in by the calling application as well.
The contents of the last parameter, result, are filled in by the DoDivide function upon completion.
Those of you who have a passing familiarity with C are probably more than a little curious about the
last portion of the declaration. ComponentCallNow is a macro defined by the Component Manager
(see "Inside the ComponentCallNow Macro" for the nuts and bolts of how the macro works). Its
main purpose is to identify a routine as a component function, as opposed to a normal C function.
When an application calls the DoDivide function, the macro is executed. This causes a trap to the
Component Manager to be executed, allowing the Component Manager to send a message to the
component responsible for handling the function.
The first parameter to the ComponentCallNow macro is an integer value representing the request
code for the integer division function. As noted earlier, your component's dispatcher uses this request
code to determine what function has been requested. Recall that you may only define request codes
that are positive.
The second parameter is an integer value that indicates the amount of stack space
(in bytes) that's required by the function for its parameters, not including the component instance
parameter. Be careful to note that Boolean and single-byte parameters may need to be passed as 16-
bit integer values (see the section "Eleven Common Mistakes" for details). For the Math component,
the space required for the DoDivide function is two 16-bit integers followed by a 32-bit pointer, for
a total of eight bytes.
THE MATH COMPONENT DISPATCHER
The dispatcher of the Math component is shown in its entirety below. Notice that the dispatcher
executes its component functions indirectly by calling one of two Component Manager utility
functions -- CallComponentFunction or CallComponentFunctionWithStorage. You use
CallComponentFunction when your component function needs only the fields in the
ComponentParameters structure, and CallComponentFunctionWithStorage when it also needs
access to the private storage that was allocated in your component's open function.
pascal ComponentResult main (ComponentParameters *params,
Handle storage)
{
// This routine is the main dispatcher for the Math component.
ComponentResult result = noErr;
// Did we get a Component Manager request code (< 0)?
if (params->what < 0) {
switch (params->what)
{
case kComponentOpenSelect: // Open request
result = CallComponentFunctionWithStorage
(storage, params,
(ComponentFunction) _MathOpen);
break;
case kComponentCloseSelect: // Close request
result = CallComponentFunctionWithStorage
(storage, params,
(ComponentFunction) _MathClose);
break;
case kComponentCanDoSelect: // Can do request
result = CallComponentFunction (params,
ComponentFunction) _MathCanDo);
break;
case kComponentVersionSelect: // Version request
result = CallComponentFunction (params,
(ComponentFunction) _MathVersion);
break;
case kComponentTargetSelect: // Target request
result = CallComponentFunctionWithStorage
(storage, params,
(ComponentFunction) _MathTarget);
break;
case kComponentRegisterSelect: // Register request not // supported
default: // Unknown request
result = paramErr;
break;
}
}
else { // One of our request codes?
switch (params->what)
{
case kDoDivideSelect: // Divide request
result = CallComponentFunction (params,
(ComponentFunction) _MathDoDivide);
break;
case kDoMultiplySelect: // Multiply request
result = CallComponentFunction (params,
(ComponentFunction) _MathDoMultiply);
break;
default: // Unknown request
result = paramErr;
break;
}
}
return (result);
}
A drawback of the dispatcher is the overhead incurred in having the Component Manager functions
mediate all your requests. To reduce your calling overhead and thus improve performance, you can
use a fast dispatch technique. While this technique is used in most of the QuickTime 1.0 components,
this is the first time that it's been publicly described. See "Fast Component Dispatch" for details.
THE MATH COMPONENT DODIVIDE CALL
For the Math component, the DoDivide function is declared as follows:
pascal ComponentResult _MathDoDivide (short numerator,
short denominator, short* quotient)
{
ComponentResult result = noErr;
if (denominator != 0) {
*quotient = numerator/denominator;
}
else {
*quotient = 0;
result = -1L; // Divide by zero not allowed
}
return (result);
}
The key thing to note here is that component functions must always return a result code. The return
value is 32 bits and is defined in the API for the component. In our case, a value of 0 (noErr)
indicates successful completion of the call and a negative value indicates that an abnormal completion
occurred. Note that for some components a negative result code could indicate that the returned
parameter values should be interpreted in a particular manner. For example, a video digitizer may
return a negative result code of notExactSize from the VDSetDestination call. This doesn't indicate
an error. It just means that the requested size wasn't available on the digitizer and that the next
closest size was given instead. Also, since this result code is 32 bits, you could actually return pointers
or handles as results, rather than error codes.
USING THE MATH COMPONENT
In this section, we look at how an application uses the Math component. First, the application has to
ask the Component Manager to locate the Math component. If the Math component is found, the
application can open it and make calls to it.
FINDING AND OPENING THE MATH COMPONENT
We tell the Component Manager which component we're looking for by sending it a
ComponentDescription record containing the type, subtype, and manufacturer codes for the desired
component. We then call the Component Manager routine FindNextComponent to locate a
registered component that fits the description. The code fragment below shows how this looks.
ComponentDescription mathDesc;
Component mathComponentID;
// Math component description
mathDesc.componentType = mathComponentType;
mathDesc.componentSubType = 0L; // Wild card
mathDesc.componentManufacturer = 'appl';
mathDesc.componentFlags = 0L; // Wild card
mathDesc.componentFlagsMask = 0L; // Wild card
// Find a Math component
mathComponentID = FindNextComponent (nil, &mathDesc);
The zeros in the componentSubType, componentFlags, and componentFlagsMask fields indicate
that they function as wild cards. If the Component Manager was unable to locate a component
matching the description, it returns zero.
Assuming the Component Manager returned a nonzero component ID, we now open the component
using the OpenComponent call, as follows:
mathInstance = OpenComponent (mathComponentID);
OpenComponent returns a unique connection reference -- a component instance -- to the Math
component. If the component instance is nonzero, we're ready to use the component. Figure 5
illustrates the process of finding a component.
Figure 5How Applications Find Components
MAKING CALLS TO THE MATH COMPONENT
The Math component performs only two functions, dividing and multiplying two integers. To ask it
to divide two numbers for us, we just call the component function DoDivide with the component
instance value we got by opening the Math component.
result = DoDivide (mathInstance, numerator, denominator, "ient);
When we're done with the component, we close the connection with the CloseComponent call, like
this:
result = CloseComponent (mathInstance);
That's all there is to it. You can see that making component function calls is much like making any
other kind of call.
EXTENDING EXISTING COMPONENTS
After defining the basic functionality for your component, you may find that you want to extend it
beyond what you originally specified in your component API. There are three ways to extend the
functionality of existing components:
- Use the subtype and/or manufacturer fields of the component description to
indicate to a client application that a specific component implementation provides
previously undefined functionality.
- Revise the component API to add calls that weren't specified in the original
interface.
- Modify the behavior of a particular component implementation by capturing it
and overriding a specific function.
The following sections examine these methods in detail.
ADDING NEW FUNCTIONALITY TO A SPECIFIC COMPONENT IMPLEMENTATION
Let's add some more functionality to the Math component. The MoMath component extends the
Math component by adding an addition function. A new function prototype is added for the new
function in MoMathComponent.h, along with a new request code, kDoAddSelect.
pascal ComponentResult DoAdd (MathComponent mathInstance,
short firstNum, short secondNum, short* result) =
ComponentCallNow (kDoAddSelect, 0x08);
Request codes for implementation-specific functions must have an ID of 256 or greater. This is
required to differentiate these functions from those that are generally defined in the API for the
component type. Implementation-specific functions usually provide capabilities beyond those
specified in the component API, and thus offer developers a way to differentiate their component
implementations from those of competing developers. The following code fragment from the
MoMath component dispatcher shows support for the DoAdd function:
case kDoAddSelect: // Add function
{
result = CallComponentFunction (params,
(ComponentFunction) _MoMathDoAdd);
break;
}
How does the calling application know that a superset of the Math component is around? To start
with, the caller needs to know that such a beast even exists. Remember, this is an extension of a
component implementation by a particular vendor, not of the component type in general. In this
case, the extended component is differentiated from its basic implementation by its manufacturer
code. Both Math and MoMath have the same component type ('math'), but their manufacturer codes
differ ('appl' for Math and 'gwck' for MoMath). Note that the subtype field can be used in a similar
manner, but it's typically used to distinguish algorithmic variations of a general component type. For
example, image compressor components ('imco') use the subtype field to differentiate various types of
compression algorithms ('rle ' for run length encoding, 'jpeg' for JPEG, and so on). The
manufacturer field is used to identify vendor-specific implementations of a particular compression
algorithm.
If the application is aware that this extended component exists, it can use the information stored in
the component's 'thng' resource to locate and open it. Once the component has been opened, the
application calls the extended function just as it would any other component function.
ADDING NEW FUNCTIONALITY TO A COMPONENT TYPE
In the preceding example, we used the manufacturer code to hook in new functionality to the Math
component; this allowed a specific implementation to extend the interface. In reality, we would be
better off extending the component by defining a change to the Math component API, so that all
components of this type would have an interface defined for the new addition function. Of course,
this is an option only when you're the owner of the component API. Changing component APIs that
are owned by others (for instance, by Apple) is a good way to break applications, and no one
appreciates that, least of all your users.
If you're going to take this route, be sure that the existing API is left unchanged, so that clients using
the old component's API can use your new component without having to be modified. In addition,
it's important to update the interface revision level of components that implement the new API, so
that clients can determine whether a particular component implementation supports the new API.
MODIFYING EXISTING FUNCTIONALITY
Modifying existing functionality is a little more complicated than adding functionality to a
component type. In the example component, the DoDivide function divides two 16-bit integers,
truncating the result. We would actually get a better answer if the result were rounded to the nearest
integer. We don't need to add a new call to do this, since what we really want to do is replace the
implementation of the existing call with a more accurate version. On the other hand, the Mathcomponent does an acceptable job of multiplying two integers, so we don't need to override that
function. Instead, we'll use the multiply function that's already implemented.
We can do this by writing a component that does the following:
- captures the original Math component
- overrides the original DoDivide function with a more accurate division function
- delegates the DoMultiply function to the original Math component
Let's start by writing a new component -- in the example code, it's called NuMathComponent --
that contains a dispatcher, as well as functions to handle the Component Manager request codes and
the new DoDivide routine. We use a register routine to check for the availability of a Math
component before we allow the NuMath component to be registered. If no Math component is
available, obviously we can't capture it, and we shouldn't register. We also set
cmpWantsRegisterMessage (bit 31) in the componentFlags field of the ComponentDescription
record in the NuMath component's 'thng' resource to let the Component Manager know that we
want a chance to check our environment before we're registered. With this flag set, the sequence of
requests that NuMath will get at registration time will be open, register, and close.
The NuMath component register routine is as follows:
pascal ComponentResult _NuMathRegister (void)
{
// See if a Math component is registered. If not, don't register
// this component, since it can't work without the Math component.
// We return 0 to register, 1 to not register.
ComponentDescription mathDesc;
mathDesc.componentType = mathComponentType;
mathDesc.componentSubType = 0L; // Wild card
mathDesc.componentManufacturer = 'appl';
mathDesc.componentFlags = 0L; // Wild card
mathDesc.componentFlagsMask = 0L; // Wild card
return ((FindNextComponent (nil, &mathDesc) != 0L) ? 0L : 1L);
}
The original Math component ID is now effectively removed from the Component Manager's
registration list. This means that the Math component is now hidden from all other clients, except
those that already had a connection open to it before it was captured.
We then open an instance of the Math component, and use the ComponentSetTarget utility (defined
in MathComponent.h) to inform Math that it's been captured by NuMath.
result = ComponentSetTarget (mathInstance, self);
Why does a component need to know that it's been captured? If a captured component makes use of
its own functions, it needs to call through the capturing component instead of through itself, becausethe capturing component may be overriding one of the calls that the captured component is using. A
captured component does this by keeping track of the component instance that the
ComponentSetTarget call passed to it and by using that instance to make calls to the capturing
component.
When the NuMath Comp;onent receives a divide request code, we dispatch to the new DoDivide
function, effectively overriding the DoDivide function that was implemented in the Math
component. However, when we receive a multiply request code, we delegate this to the captured
Math component, since we aren't overriding the multiply function. We do this by simply making a
DoMultiply call to the Math component, passing in the parameters that the NuMath component was
provided with.
result = DoMultiply (mathInstance, firstNum, secondNum,
multiplicationResult);
In the close routine of the NuMath component, we remember to close the instance of the Math
component we were using, and also to uncapture it so that we restore the system to its original state.
result = CloseComponent (mathInstance);
result = UncaptureComponent (mathComponentID);
THAT WASN'T SO BAD, WAS IT?
As you can see, adding new functionality is no big deal. As always, however, you should notify
developers who may use your component of any late-breaking interface changes. You want to be sure
that everyone's writing code that conforms to your most recent component specification.
ELEVEN COMMON MISTAKES
You may encounter some pitfalls during the development of your component. Here we discuss 11
common mistakes that we've either made personally or observed other developers make. We hope
that you'll learn from our own fumblings and save yourself time and frustration.
Allocating space at registration time. Generally, it's best if your component allocates its storage only
when it's about to be asked to do something -- that is, when it has received a
kOpenComponentSelect request code. This way, memory isn't tied up unnecessarily. Remember,
your component maynever be called during a given session, and if it's not, it shouldn't hang out
sucking up memory some other process might be able to use.
Allocating space in the system heap. The system heap shouldn't be your first choice as a place to put
your component globals. The system heap is generally reserved for system-wide resources (big
surprise), and most components fall into the category of application resources that needn't be
resident at all times. Consider carefully whether you need to scarf up system space. In addition, if
your component is registered in an application heap, you should never try to allocate space in the
system heap. The fact that you're registered in an application heap probably indicates that there isn't
any more space in the system heap for you to grab.
Not supporting the kComponentVersionSelect request code. This is a pretty nasty omission for several
reasons. First, this is theeasiest request code to implement; it takes only a single line of code! What
are you, lazy? (Don't answer that.) Second, clients may use the API version level to keep track of
extended functionality -- it may be that version 2 of a component interface contains additional calls
over version 1, and a client certainly has reason to want to know that. Third, clients may use the
component version to determine, for example, whether the component in question contains a recent
bug fix.
Incorrectly calculating the parameter size for your component function prototype. If you do this, you'll
probably notice it right after calling the offending component function, since your stack will be
messed up by however many bytes you failed to calculate correctly. A common instance of this error
occurs when calculating the space required by a function call that has char or Boolean parameters. Under certain circumstances, Boolean and char types are padded to two bytes when passed as
function parameters.
To illustrate, we'll look at two example declarations. How many bytes of stack space need to be
reserved for the parameters of the following function?
pascal ComponentResult I2CSendMessage (ComponentInstance ti,
unsigned char slaveAddr, unsigned char *dataBuf, short byteCount)
The correct answer is eight bytes. The slaveAddr parameter is promoted to two bytes, the dataBuf
pointer takes four bytes, and the byteCount takes two bytes. The rest of the declaration then takes
the following form:
= ComponentCallNow (kI2CSendMessageSelect, 0x08);
Let's look at the next example. How many bytes of stack space does this function require?
pascal ComponentResult MyFunction (ComponentInstance ti,
Boolean aBoolean, char aChar, short *aPointer)
The correct answer is six bytes. The aBoolean parameter takes one byte, the aChar parameter takes
one byte, and the aPointer parameter takes four bytes. What's that? Didn't we just say that Boolean
and char parameters got padded to two bytes? We certainly did, but these types get padded only
when an odd number of char or Boolean parameters occurs consecutively in the declaration. Because
we could add one byte for the Boolean to the one byte for the char following it, we didn't need to do
any padding -- the total number of bytes was even (two bytes), and that's what's important. In the
first example, this didn't work. We added one byte for the char to the four bytes for the pointer
following it, and got five bytes, and so we needed to
pad the char parameter by one byte. The rest of the declaration for the second example is
= ComponentCallNow (kMyFunctionSelect, 0x06);
Registering your component when its required hardware isn't available. If your component doesn't
depend on specific hardware functionality, don't worry about this. If it does (as, for example, video
digitizers do), make sure you check for your hardware before you register your component. The
Component Manager provides a flag, cmpWantsRegisterMessage, that you can set in the
componentFlags field of your component description record to inform the Component Manager that
your component wants to be called before it's registered. This gives your component an opportunity
to check for its associated hardware, and to decline registration if the hardware isn't available.
Creating multiple instances in response to OpenComponent calls when your component doesn't support
multiple instances. Only you can know whether your component can be opened multiple times. For
instance, the Math component is capable of being opened as many times as memory allows (although
our sample code restricts the number of open instances to three for the sake of illustration).
Normally, a component that controls a single hardware resource should be opened only once and
should fail on subsequent open requests. This will prevent clients from oversubscribing your
component.
Not performing requisite housekeeping in response to a CloseComponent call. Bad things will happen,
especially if you have hierarchies of components! As part of your close routine, remember to dispose
of your private global storage and to close any drivers, components, files, and so on that you no
longer need.
Allowing multiple instances from a single registration of a hardware component instead of allowing a single
instance from each of multiple registrations. While this isn't really a common mistake today, we want to
emphasize that there's a big difference between designing your component to allow multiple
instances versus registering the component multiple times and allowing each registered component to
open only once. In the case of a generic software library element (like Math), there's no problem with
multiple instances being opened. In the case of a hardware resource that's being controlled with acomponent, it's almost always preferable to register the component once for every resource that's
available (four widget cards would result in four different registrations rather than one registration
that can be opened four times).
Why does it matter? Consider an application whose sole purpose in life is to manage components
that control hardware resources. It may be selecting which resource to use, which one to configure,
or which one to pipe into another. It's much more natural to ask the Component Manager to provide
a list of all components of a certain type than it is to open each component that fits the criterian times (until it returns an open error) in order to determine how many are available.
To kill a dead horse, suppose we have three identical video digitizers, and we want to convey that
information to the user via a menu list. If all are registered separately, we can easily determine how
many video digitizers are available (without even opening them) by using the FindNextComponent
call. If only one were registered, the list
presented to the user would only be a partial list. Take the blind leap of faith: register duplicate
hardware resources!
As a final note, if you're registering a single component multiple times, be sure that the component
name is unique for each registration. This allows users to distinguish between available components
(as in the menu example in the previous paragraph), and it also helps you avoid the next gotcha.
Always counting on your component refCon being preserved. We know this may be upsetting to many of
you, but there exists a situation in which your component refCon may not be valid. A component
refCon (similar to a dialog, window, or control refCon) is a 4-byte value that a component or client
can use for any purpose. It's accessed through a pair of Component Manager calls,
GetComponentRefcon and SetComponentRefcon. Component refCons are frequently used to hold
useful information such as device IDs or other shared global data, and so can be quite critical to a
component. We can hear you now . . . "What ? You're going to nuke myglobal data reference?!" Well,
not exactly -- it's just not as immediately accessible as you would like it to be. Don't worry, it's
possible to detect when your component is in this situation and retrieve the refCon from it, as long as
you follow a few simple steps.
The situation in question arises when there's not enough room in the system heap to open a
registered component. This happens when you run an application (that uses your component) in a
partition space so large that all free memory is reserved by the application. This will prevent the
system heap from being able to grow. When the application calls OpenComponent, the component
may be unable to open in the system heap because there's no available space. In this case, the
Component Manager willclone the component. When a component is cloned, a new registration of
the component is created in the caller's heap, and the component ID of the cloned component is
returned to the caller,not the component ID of the original registration. The clone is very nearly a
perfect copy, but like the Dopplegänger Captain Kirk in theStar Trek episode "What Are Little
Girls Made Of?" it's missing something crucial.
That something is the component refCon. The refCon isn't preserved in the clone, so if your
component needs the refCon to perform properly, it must be recovered from the original
component. How you go about doing this is a bit tricky. We assume that you followed our advice and
made sure that your component registered itself with a unique name. (This technique isnot guaranteed to work properly unless this constraint is satisfied -- you'll see why shortly.)
The first problem is detecting whether your component has been cloned at open time. You can
determine this by examining your component's A5 world using the GetComponentInstanceA5
routine. If the A5 world is nonzero, you've been cloned. But wait, you say, what if I registered my
component locally? Won't it have a valid A5 value? Yep, it sure will, but if it was registered locally,
we won't have this nasty situation to begin with, since the component won't be in the system heap
anyway.
Now you know that you've been cloned, and that you can't depend on your refCon. How do you
retrieve it? Well, we know that there are two registrations of the same component in the Component
Manager registration list (the original and the clone). So all we have to do is to set up a componentdescription for our component, and then use FindNextComponent to iterate through all registrations
of it. We know what our current component description and ID are, so we can just examine the
component description and ID for each component returned. Once we find a component whose ID
is different from ours but whose description is identical,
we've found the original component registration. We can then make a call to GetComponentRefcon
to obtain the original refCon value, and then set the clone's refCon appropriately. Whew!
This technique won't work with a component that registers multiple times and doesn't register each
time with a unique name. If component X, capable of multiple registrations, always registers with the
name "X," then when we try to find the original component from the clone, there will be multiple
components named "X" in the registration list, and we'll be unable to determine which component is
the one we were cloned from.
Omitting the "pascal" keyword from declarations for your component dispatcher or for any functions that are
called by CallComponentFunction or CallComponentFunctionWithStorage. This bug will only antagonize
those developers who are working in C. As many of you know, the Macintosh system software was
originally written in Pascal, and functions that are called by Toolbox routines (in this case, by the
Component Manager) must conform to Pascal calling conventions. If you fail to include this keyword
where necessary, the parameters for your function will be interpreted in the reverse order from what
you intended, and your component may enter the Twilight Zone, perhaps never to return.
Trying to read resources from your component file when its resource fork isn't open. When one of your
component functions is called, the current resource file (as obtained from CurResFile) isnot the
component's resource file unless you explicitly make it so. If you need to access resources that are
stored in your component file, you must first call OpenComponentResFile to get an access path, and
then call UseResFile with that path. When you're done with the file, restore
the current resource file and call CloseComponentResFile to close your component file.
DEBUGGING TOOLS AND TECHNIQUES
Debugging components can be frustrating if all you have to work with is MacsBug. Fortunately,
there are a few tricks and tools that give you a little more power to terminate those pesky bugs. In
this section, we'll show you how to debug your component code with a symbolic debugger, and then
we'll examine three utilities that will help you test your component.
SYMBOLIC DEBUGGING
Let's suppose that we've got the Math component up and running, but something funny is happening
in our DoDivide routine. It would be nice to be able to step through the component code
symbolically and see what's happening. Fortunately, there's a simple trick that involves registering
our component in such a way that it can be symbolically debugged.
For the purposes of the example, we'll discuss how to do this with Symantec's THINK C
development system. The first step is to add the component source code to the application source
code project. Then we modify the application code so that instead of using the FindNextComponent
call to locate the Math component, we register it ourselves using the RegisterComponent call.
#define kRegisterLocally 0
mathComponentID = RegisterComponent (&mathDesc,
(ComponentRoutine) MathDispatcher, kRegisterLocally,
nil, nil, nil);
Note that when you register a component in an application heap as we're doing, you must register it
locally, or your system may die a horrible death after your application quits and its application heap
goes away.
The component description, mathDesc, is set up just as before. The second parameter is the main
entry point (the dispatcher) to the Math component. The Component Manager will call this routine
every time it receives a request code for an instance of the Math component. In the Math component code, we set up a debug compiler flag (DEBUG_IT, found in
DebugFlags.h) which, if defined, indicates whether we want to declare our component dispatcher as a
main entry point for a standalone code resource or as just another routine linked into our application
program.
#ifdef DEBUG_IT
// Use this declaration when we're running linked.
pascal ComponentResult MathDispatcher
(ComponentParameters *params, Handle storage)
#else
// Use this declaration when we're building a standalone
// component.
pascal ComponentResult main
(ComponentParameters *params, Handle storage)
#endif DEBUG_IT
The two declarations differ only in that one is declared as a main and one isn't. (Remember, with
both the source for the component and the application in the same project, we can't have two mains.)
Now, each time the Component Manager sends a request code to the Math component, it's calling a
component routine linked into the application (MathDispatcher) that we can trace with the
debugger. When we've finished debugging the component, we can undefine the debug flag and
rebuild the component as a standalone code resource. The test application will now use
FindNextComponent to access the standalone component.
THE THING MACSBUG DCMDThe thingdcmd is included on the QuickTime 1.0 Developer's CD. To use this dcmd, simply use
ResEdit to copy the 'thng' dcmd resource into a file named Debugger Prefs, and put this file into
your System Folder. Once in MacsBug, the dcmd is invoked by entering "thing". A samplethingdisplay is shown in Figure 6.
Displaying Registered Components
Cnt tRef# ThingName Type SubT Manu Flags EntryPnt FileName Prnt LocalA5 RefCon
#0 010005 Movie Grabber barg **** appl 40000000 00000000 QuickTi... 00000000 00000000
#0 010007 Preview Loader blob **** appl 00000000 00000000 QuickTi... 00000000 00000000
#0 01000c Apple Microse... clok micr appl 40000003 00000000 QuickTi... 00000000 00000000
#0 01000d Apple Tick Cl... clok tick appl 40000001 00000000 QuickTi... 00000000 00000000
#0 01000e Apple Alias D... dhlr alis appl 40000000 00000000 QuickTi... 00000000 00000000
#0 010018 Apple Photo -... imco jpeg appl 40600028 00000000 QuickTi... 00000000 00000000
#0 010014 Apple None imco raw appl 4060003f 00000000 QuickTi... 00000000 00000000
#0 01001c Apple Animati... imco rle appl 4060043f 00000000 QuickTi... 00000000 00000000
#0 010016 Apple Video imco rpza appl 40200438 00000000 QuickTi... 00000000 00000000
#0 01001a Apple Graphics imco smc appl 40600408 00000000 QuickTi... 00000000 00000000
#0 010012 imdc SIVQ appl 00000030 00000000 QuickTi... 00000000 00000000
#0 010017 Apple Photo -... imdc jpeg appl 40400028 00000000 QuickTi... 00000000 00000000
#0 010013 Apple None imdc raw appl 40400bff 00000000 QuickTi... 00000000 00000000
#0 01001b Apple Animati... imdc rle appl 40400c7f 00000000 QuickTi... 00000000 00000000
#0 010015 Apple Video imdc rpza appl 40000878 00000000 QuickTi... 00000000 00000000
#0 010019 Apple Graphics imdc smc appl 40400438 00000000 QuickTi... 00000000 00000000
#0 ..000b jimB jph leak 00000000 00000000 QuickTi... 00000000 00000000
#1 010002 NuMath Compon... math appl 80000000 001a9b80 NuMath ... 00000000 00000000
820000 0000 00000000 01263af8
#1 ..0000 Math Component math appl 00000000 001a9f80 Math Co... 00000000 00000000
840001 0000 00000000 01263b08
#0 010001 MoMath Compon... math gwck 00000000 00000000 MoMath ... 00000000 00000000
#0 010011 Apple Standar... mhlr mhlr appl 40000000 00000000 QuickTi... 00000000 00000000
#0 01000f Apple Sound M... mhlr soun appl 40000000 00000000 QuickTi... 00000000 00000000
#0 010010 Apple Video M... mhlr vide appl 40000000 00000000 QuickTi... 00000000 00000000
#0 010006 Movie Control... play **** appl 40000000 00000000 QuickTi... 00000000 00000000
#0 010009 Movie Preview... pmak MooV appl 00000000 00000000 QuickTi... 00000000 00000000
#0 010008 Pict Preview ... pmak PICT appl 00000000 00000000 QuickTi... 00000000 00000000
#0 01000a Picture Previ... pnot PICT appl 00000000 00000000 QuickTi... 00000000 00000000
#0 010003 Movie Grabber... sgch soun appl 40000000 00000000 QuickTi... 00000000 00000000
#0 010004 Movie Grabber... sgch vide appl 40000000 00000000 QuickTi... 00000000 00000000
#32 Thing Table entries, #29 in use. #32 Instance Table entries, #2 in use.
#5 File Table entries, #4 in use. Thing Modification Seed #33. Codec Manager 000dad3c
Figure 6Sample thing MacsBug Display
The Cnt field indicates the number of instances of a particular component.
The tRef# field shows the component ID that the Component Manager has assigned to a particular
component; this is the value that's returned to your application by the FindNextComponent call. If
there are instances of a component open, the component instances are listed below the component
ID in the tRef# field. Note that the tRef# for the Math component is ..0000. The two dots at the
beginning indicate that this component has been captured. (We know from the earlier discussion of
the NuMath component that it has captured the Math component.)
The ThingName field displays the name of a particular component. This is either the string that's
pointed to by the component's 'thng' resource or the name that it was registered with by a call to
RegisterComponent.
The Type, SubT, Manu, and Flags fields likewise correspond either to the information that's stored
in the component's 'thng' resource or to the codes and flags that were supplied to a call to
RegisterComponent.
The EntryPnt field is the main entry point of the component code.
The FileName field indicates what file the component's 'thng' resource resides in. This field is empty
for components registered without a component resource.
The Prnt field displays the parent of a cloned component. This information isn't available through
the Component Manager API.
The LocalA5 field shows the A5 world that the component is associated with; unless the component
is cloned or registered locally, this value is 0.
The RefCon field is the value of the component's refCon.
At the bottom of the display there's a decimal number indicating the number of component (thing)
entries allocated in the Component Manager registration list, along with the number of entries
actually in use. Similar information is given for the number of file table entries. Finally, the
Component Manager modification seed is listed.
THINGS! CONTROL PANEL
The Things! control panel, included on the QuickTime 1.0 Developer's CD, is similar to
thethingdcmd but provides several additional capabilities. These include displays of version levels, info and
name strings, and resource information, as well as controls to reorder the component search chain
and to unregister components.
Figure 7 Things! Control Panel Main Display
Figure 7 shows a sample display of the Things! control panel.
The list on the left in the top panel shows the types of components currently registered with the
Component Manager; the list on the right shows the components of the selected type that are
currently registered. The latest version of Things! doesn't display components that aren't registered
globally or that aren't registered in the same application heap as the control panel is operating in.
Things! also doesn't show components that aren't resource-based.
The middle panel shows the name of the currently selected component and a description of its type,
subtype, and manufacturer fields. The number of instances of the type of component selected (in the
example, the 'imco', or image compressor, component type) is displayed at the bottom of this panel.
Clicking this field will toggle it to display the number of instances of the selected component (in this
case, the Apple Video image compressor component).
The bottom panel shows an information string that usually describes what the component does. At
the upper left in this panel are two arrow buttons that can be used for paging the bottom panel (the
top and middle panels don't change).
Figure 8 shows a variation of the bottom panel's second page. The component version information is
displayed at the top. The "Set default" button allows you to assign a particular component as the first
component in the Component Manager's search chain for that component type.
Figure 8 Things! Page 2 Display
If the Option key is held down while paging to the second page, a Destroy button is displayed (as
shown in Figure 9). Clicking this button will unconditionally unregister the currently selected
component.
Figure 9 Things! Extended Page 2 Display
The third page shows the flags and mask fields of the component.
The fourth page displays a variety of information about the 'thng' resource associated with a
particular component, including the resource name and ID as well as its attributes.
Page 5 presents a summary of the system software configuration.
REINSTALLER
Reinstaller is a utility that lets you install resource-based components without restarting your
Macintosh. Launching the application presents a Standard File dialog asking you to choose the file
containing the component you want to register. Clicking the Open button will dismiss the dialog and
register the selected component with the Component Manager.
The same component file can be installed multiple times. Duplicate components aren't removed; the
most recently installed version of a component becomes the default component for that type. Note
that any components installed with Reinstaller are installed only until shutdown or reboot.
This utility is quite handy in conjunction with the Things! control panel's Destroy button. Between
the two of them, you can easily register and unregister your components without having to restart
your Macintosh.
GO DO YOUR OWN "THING"
Now you know how easy it is to write your own components. You've learned how to declare your
own component API and how to implement a component dispatcher for it. You've seen what
common pitfalls to avoid and how to symbolically debug your component to help you get around
new pitfalls we haven't thought of.
We're confident that once you start programming components, you'll become addicted! So what are
you hanging around here for? Get busy writing, and start amazing your users (and us, too) with some
way cool components. We're waiting . . .
COMPONENT TRIVIA #1
The original name for the Component Manager (as conceived of by Bruce "Of course the feature set is
frozen!" Leak) was the Thing Manager. Components were referred to as "things" (as were the QuickTime
project schedules, the significance of which engineers couldn't easily grasp). The use of this terminology led
to one of two conditions in most QuickTime engineers: in some, an irrepressible compulsion to make "thing"
puns, and in others, perhaps as a backlash against the former, an almost pathological aversion to the use of
the word "thing" in normal conversation.
COMPONENT TRIVIA #2
The original component type for the sequence grabber component was, logically enough, 'grab'. The
engineer primarily responsible for the sequence grabber, Peter Hoddie, requires massive infusions of Diet
Coke to function properly. During a particularly intense bout of engineering mania, the Diet Coke supply
was exhausted; unbeknownst to anyone, Peter became temporarily dyslexic and changed the sequence
grabber component type to 'barg'. The change was never noticed, and it caused no real harm, other than
the wasted time developers spent trying to figure out what 'barg' might be an acronym for (Boffo AudioReverb Gadget? Bodacious Analog Reference Gizmo?). Peter's brain has since returned to its (relatively)
normal state.
INSIDE THE COMPONENTCALLNOW MACRO
Some of you may be wondering exactly what the ComponentCallNow macro does. Let's expand this macro
for our DoDivide component call and examine it in detail.
= {0x2F3C, 0x08, kDoDivideSelect, 0x7000, 0xA82A};
The first element, 0x2F3C, is the Motorola 68000 opcode for a move instruction. Execution of this instruction
loads the contents of the next two elements onto the stack. The next element, 0x08, is the amount of stack
space that we calculated for the function parameters of the DoDivide call. The third element,
kDoDivideSelect, is the request code corresponding to the DoDivide call. The fourth element, 0x7000, is the
Motorola 68000 opcode for an instruction that sets the contents of register D0 to 0. The Component
Manager interprets this condition as a request to call your component rather than handling the request itself.
The last element, 0xA82A, is the opcode for an instruction that executes a trap to the Component Manager.
While you can use this inline code in your component function declarations directly, we recommend that you
use the ComponentCallNow macro to make your code more portable.
FAST COMPONENT DISPATCH BY MARK KRUEGER
If you're concerned about the time it takes to dispatch calls made to your component, try the fast dispatch
method. This method eliminates the need for your component to make the extra call to the Component
Manager functions CallComponentFunction and CallComponentFunctionWithStorage, and allows control to
pass directly back to the caller. It does this by calling your component entry point with the call's parameters,
the instance storage, and the caller's return address already on the stack. It passes the component request
code in register D0, and points register A0 at the stack location where the instance storage is kept.
To handle a fast dispatch, you must write your component entry point in assembly language. Use the request
code in D0 as an index into a table of function addresses, paying special attention to the negative request
codes used
for the standard Component Manager calls like OpenComponent and CloseComponent. If the functions are
defined correctly, the dispatcher can jump directly to the function address. Note that the function parameter
the caller uses to specify the component instance will instead be a handle to your component instance
storage. When the function completes, control will return to the calling application.
You need to tell the Component Manager that your component has a fast dispatch handler instead of a
normal dispatcher. To do this, set bit 30 ($40000000)
of the componentFlags field of your component resource, and the Component Manager will always call your
component using the fast dispatch method.
REQUIRED READING
- QuickTime Developer's Guide (part of the QuickTime Developer's Kit v. 1.0, ADPA #R0147LL/A).
Currently the essential reference for programming with the Component Manager. This documentation will
be replaced in the near future by three new Inside Macintosh volumes: QuickTime, QuickTime Components ,
and More Macintosh Toolbox. (The Component Manager will be documented in the latter volume.)
- "QuickTime 1.0: 'You Oughta Be in Pictures,'" Guillermo A. Ortiz, develop Issue 7. An overview of QuickTime, including the Component Manager.
GARY WOODCOCK AND CASEY KING have a long history of collaboration. They first met at a flight simulation company
in the early 80's where they worked together on designing a multimillion-dollar F-16 jet fighter simulator (and you thought
Falcon was cool!). They parted ways temporarily, but regrouped at Apple to join forces in what colleague Jim Batson has
termed the "QuickTime sleep deprivation experiment." They're both currently working on RISCy products, but from different
parts of the country (Gary in Cupertino, and Casey in the new PowerPC mecca of Austin, Texas). With his wife Lonna,Casey is the proud co-owner of his latest obsession -- a year-old baby boy named Brian -- but he still makes time for
mountain biking, hiking, and flying. Gary still spends much of his time diligently testing video capture cards for QuickTime
compatibility with Movie Recorder (translation: watching Star Trek: The Next Generation episodes on his Macintosh).
Occasionally he ventures out for a bit of mountain biking or flying. This article is their latest joint venture. *
HELPFUL TIP You can obtain the component ID corresponding to a component instance by calling GetComponentInfo with
the component instance (you'll need to cast it as a Component). The componentFlagsMask of the returned
ComponentDescription record will contain the component ID. *
In our sample code, ComponentSetTarget is defined in MathComponent.h because the QuickTime 1.0 Components.h
interface file doesn't declare it. The ComponentSetTarget declaration is included in newer QuickTime interface files, so if
you're using them, you should comment it out in MathComponent.h. *
THANKS TO OUR TECHNICAL REVIEWERS Neil Day, Mike Dodd, Mark Krueger, John Wang *