Emily Vander Veer
OpenDoc is a cross-platform, component-based architecture designed to let developers create
software components that interact consistently through a set of standard APIs. In an OpenDoc
document or application, this interaction provides the glue that binds component building blocks
into complete software solutions. Think of a stereo system: as with stereo receivers,
turntables, and speakers from different manufacturers, OpenDoc components produced by various
developers can be interconnected to build functional systems because their interfaces are based
on open standards.
One of OpenDoc's most compelling features is its extensibility. OpenDoc is essentially a
framework for persistent, visible, interactive components; it doesn't mandate specific
implementation details. It does, however, provide a technique for developers to define,
implement, and enforce such details. Every OpenDoc object--specifically, every object that
ultimately derives from ODObject--can be extended by a developer. These objects include
components, or "parts," as well as OpenDoc service objects such as the clipboard, dispatcher,
and session. By implementing extensions to OpenDoc service objects in conjunction with a
run-time initialization entry point called a "shell plug-in," developers can actually extend the
OpenDoc architecture itself. Extensions can be implemented privately, by developers producing a
suite of interoperable parts; or publicly, to expose function for use by others. One of the
benefits of implementing OpenDoc extensions is that now developers have a way to make their
function easily accessible to other developers and system integrators.
Let's explore the concept of OpenDoc extensions and shell plug-ins: what they are, when to use
them, and how to design them. Sample code is included to demonstrate the programming effort
required for each. We'll begin by examining how to implement, query, and access an interface
extension. Then we'll take a look at how shell plug-ins can be used to time the creation of
extensions and how this affects the availability of extensible objects.
Some OpenDoc Definitions
Interface extensions
What we've been
referring to as "extensions" are more accurately called "interface extensions." Their purpose is
to extend an object's interface; that is, they allow objects to expose custom interfaces and
communicate through them to other objects in a well-defined way. While interfaces can be
exposed without using extensions, extensions allow them to be organized in a meaningful way
and added to objects in groups. These sets of behaviors are implemented as objects themselves;
that is, they can be queried, reused, inherited from, and so on. Therefore, once a set of
behaviors is developed and tested, it can then be used to extend any other object.
An example of the type of function an extension can be used to implement is the ability to
respond to speech input. Once the problem is solved for one part, the solution can be applied to
any other. The resulting extension object actually becomes a mini-software developer's kit that
makes it easy for other part developers to incorporate speech capability into their applications.
Other examples include the semantic interface extension, which supports scripting, and the
menu extension, which allows parts to collaborate on part-spanning menus. These objects,
along with the settings and view extensions and sample parts that exploit them, are part of the
OpenDoc for OS/2 toolkit and are useful as guides for custom implementations. (For more
detailed information, see the class descriptions of ODSemanticInterface, ODMenuExtension,
ODSettingsExtension, and ODViewExtension in the OpenDoc for OS/2 Programmer's
Reference.)
As noted above, implementing the semantic interface extension and responding to semantic
events defines an object as "scriptable." This extension offers a ready-made means of
implementing part interaction and is often recommended for this purpose. While the extension
can play a very important role in the development of OpenDoc solutions, however, it does not
provide a one-size-fits-all approach. The difference between modifying semantic interface
extensions and "rolling your own" is one of performance and utility.
The primary benefit of subclassing and reusing the semantic interface is that it can be used to
make a part scriptable; that is, to respond to events that have been specified, typically by a
power user or system integrator, in a scripting language such as Object REXX. Because the
design of this extension focuses on scriptability in favor of efficiency, its benefit comes at
the price of nontrivial performance overhead. If high bandwidth, fast interaction, or specialized
collaboration between objects are anticipated, creating and using an extension derived from
ODExtension is better than subclassing the semantic interface extension.
There are three objects to be considered when implementing and using extensions: the extension
itself, the base object (the object to be extended), and the calling object (the object that will
be accessing the extension).
Extension. The extension must inherit from ODExtension, which is an abstract base
class capable of maintaining associations to a base object and releasing resources, both its
own and those associated to the base object. All other functionality must be implemented in
the derived class. The methods ODExtension defines include BaseRemoved, CheckValid,
GetBase, InitExtension, and IsValid.
Keeping in mind that one of the primary benefits of
implementing extensions is the ability to reuse them, it's important to design them as groups
of meaningfully associated behaviors. Ideally, each of the methods in an extension should relate
to a single activity. It might help to make sure that a proposed extension can be described by
the following sentence: "This extensions allows to ." For
example, "This extension allows parts to be scriptable"; or "This extension allows parts to
accept speech input." Listing 1 shows the IDL file for the implementation of the
ODBaseSemanticInterface extension. In this example we see that semantic interfaces can be
extensions of any class derived from ODPart or ODSession. (Remember that extensions in
general can be attached to any object that inherits from ODObject.)
Base object. Whether the base object you choose derives from ODPart, ODDispatcher,
or some other object, it is responsible for instantiating, managing, destroying, and providing
access to any attached extensions. To that end, the object to be extended must create the
extension and implement the HasExtension, AcquireExtension, and ReleaseExtension APIs
defined in ODObject.
HasExtension, AcquireExtension, and ReleaseExtension all require an extension name as an
argument. HasExtension returns a value of true if an extension with that name exists for the
base object, and false if it does not. AcquireExtension returns the requested extension if it
exists; if it does not exist, an exception is raised. ReleaseExtension does not return a
value, but instead releases the specified extension if it exists or raises an error if it does not.
An extension is typically instantiated during the initialization routine (InitPart and
InitPartFromStorage methods) of its base object. After both the base and the extension have
been instantiated, the extension is available to other objects. The base object also takes care
of destroying its extension and managing associated resources as appropriate.
Besides representing a logically cohesive set of behaviors, an extension also should be tied
conceptually to its base object. In the case of semantic interfaces, if the goal of this extension
is to make parts scriptable, it should be attached to a base descended from ODPart. In some
cases, though, it will not be appropriate for a part to be an extension's base. For example, if
a developer wants to log event activity for all parts, no one part should be extended. In this
case, the dispatcher (an instantiation of ODDispatcher) should act as the base. As we will see
shortly in our discussion of shell plug-ins, extensions to objects other than parts must be
implemented in a slightly different manner.
Listing 2 shows an implementation of a base--a part called KeyPadPart--which supports the
extension defined in Listing 1. KPInitSemanticInterface is called from KeyPad's initialization
methods (InitPart and InitPartFromStorage). KeyPadPart derives indirectly from ODPart.
Calling object. One of the benefits of using extensions is that it provides other
developers with a way to query an object to see if it offers a specific integrated set of
behaviors, rather than forcing them to ask the object if it supports each individual method call
necessary to perform a task. To query the existence of an extension, the calling object must
invoke the extended object's HasExtension method, passing the name of the extension it intends
to use. If the HasExtension call returns true, the calling object next invokes the extended
object's AcquireExtension method to get a reference to the extension. The caller is then able to
make calls directly to this newly acquired extension object, which in turn forwards the calls to
its base object.
When it no longer needs access to the called object's extension, the calling object should
invoke the extension object's ReleaseExtension method. The extension calls the
ReleaseExtension of its base object, which deletes the extension and frees up resources as
appropriate.
Shell plug-ins
A
shell plug-in is a mechanism that allows developers to implement extensions (or other
functions) early in the document instantiation process, before any parts are instantiated. To
understand the significance of this statement, let's look at the OpenDoc run-time process.
OpenDoc's document-centered approach is substantially different from the classic
application-centered model (some prefer to view the approach as application-centered, where
the document represents a specialized form of an application). The classic model focuses on an
application--such as a word-processing application--which provides the address space for its
own execution and manages memory for every document it creates. An OpenDoc document, on
the other hand, may be composed of multiple parts--specifically, part handlers--each of
different types (like word processing, chart, spreadsheet, data feed, and so on).
OpenDoc part handlers can be compared to mini-applications in the classic model: applications
because they define and implement behavior for a specific type of content, and "mini-"
because the standard tasks common to all parts contained in a document are managed not by
any of the individual part handlers, but by a shared OpenDoc library called the document shell.
Because the document shell (or "docshell") handles file management, the event loop, printing,
and interacting with system menus and dialog boxes, part handlers are free to concentrate on
their value-add function. The centralization of these tasks by the OpenDoc architecture permits
parts to be much more compact.
As owner of the process, the docshell instantiates and initializes itself before it instantiates
any parts, even a root part. Shell plug-ins provide the ability to insert routines into this
process. Extensions implemented as shell plug-ins thus have access to run-time objects that
are unavailable by the time parts are instantiated. And because they are instantiated prior to
any part, shell plug-ins can even be used to monitor and log activities related to the part
creation process itself. A developer can choose to implement any function desired;
implementing an extension is just one option.
You might think of shell plug-ins as allowing interface extensions to operate on a higher level
than otherwise possible. Extensions implemented as shell plug-ins can extend the utility of the
OpenDoc run time itself and operate on a document-wide basis. Because they are invoked prior
to the instantiation of any part, they can also provide services that aren't logically associated
with any particular part. Examples of the type of function a shell plug-in extension might
provide are the creation of dispatch modules to intercept and log user events before dispatching
them, or the creation of focus modules for new types of events, such as speech input, so parts
can identify and accept these events.
A basic rule of thumb is that an extension should be implemented as a shell plug-in if it's not
tied conceptually to any specific part, but instead offers part-neutral utility (like a spell
checker); or if it must exist before any part is instantiated.
The implementation of a shell plug-in is very straightforward. As shown in Listing 3, all a
developer must do is define and export an entry point called ODShellPlugInInstallProc that
contains the custom functionality. This method will be invoked via DosLoadModule by the
docshell after it has initialized itself and its session, but prior to the instantiation of any
parts. To be available to the docshell for invocation, a DLL containing a shell plug-in must
reside in the OpenDoc Shell Plug-In folder (WPS object ID OD_SHELL PLUGINS>) at run time.
Although multiple shell plug-ins may exist, each must reside in its own DLL. Currently no
method exists to specify ordering for the invocation of multiple shell plug-ins.
It's a wrap
OpenDoc extensions represent sets of behavior that can be added to an OpenDoc object and then
modified, reused, queried, and accessed as necessary. Any object that inherits directly or
indirectly from ODObject can be extended; this includes, but is not limited to, OpenDoc parts.
Shell plug-ins provide a mechanism for hooking into the OpenDoc run time early in the
document start-up process. Extensions implemented as shell plug-ins thus have the power to
extend OpenDoc run-time objects; these extended objects then participate in the instantiation
and management of OpenDoc parts, documents, and applications.
Understanding and using OpenDoc extensions are essential for the development of
production-level, component-based OpenDoc for OS/2 applications. There is a tremendous
amount of value in the ability to extend a part's interface in a reusable manner, or similarly to
extend the functionality of the OpenDoc architecture itself. Developers, system integrators, and
value-added resellers will be able to exploit such extensions in combination with other OpenDoc
objects to create tightly integrated, cohesive applications with consistent user interfaces
across parts.
References
OpenDoc for OS/2 is available on IBM Developer Connection 9 Special Edition CD or
download from IBM Software's Application Development Download Zone.
OpenDoc for OS/2 Programming Guide (included in the OpenDoc developer toolkit).
OpenDoc for OS/2 Programming Reference (included in the OpenDoc developer toolkit).
OpenDoc: An Introduction to Part Development (Redbook, IBM order #SG24-4673-00, pp. 79-80).
Emily A. Vander Veer has participated in the design, implementation, and management of
object-oriented software development projects for nearly five years.