June 96 - OpenDoc Parts and SOM Dynamic Inheritance
OpenDoc Parts and SOM Dynamic Inheritance
ÉRIC SIMENEL
OpenDoc, Apple's compound-document architecture, isn't just for
desktop publishing. The underlying IBM System Object Model (SOM)
can be used to implement dynamic inheritance within and between
applications, giving you the benefits of object-oriented
programming while avoiding code duplication and the need to
rebuild inherited parts when modifying the base part. The basic
mechanism is described and illustrated in this article, which also
serves as a starting point for developers who want to write
OpenDoc extensions and thus require knowledge of SOM.
The problem is as old as programming: you want to reuse code in your applications in
order to reduce development costs. The idea of linked libraries was a first step toward
a solution but introduced the issue of code duplication. The advent of object-oriented
programming reduced somewhat the complexity of the problem of reusing code but
didn't make it go away completely: a bug fix or other modification to a base class still
necessitated a rebuild of all projects using it. Dynamic shared libraries solve the issue
of code duplication, but they don't support object-oriented programming.
Now SOM, the object-oriented technology that underlies OpenDoc and enables part
editors to communicate with one another, offers a complete solution. With SOM, you
can have a dynamic shared library, which means that you don't have code duplication
and, in case of a bug fix or other modification, you don't need to rebuild projects that
use the library -- and you can also have inheritance, enabling you to take advantage of
the awesome strength of object-oriented programming.
SOMobjects(TM) for Mac OS is the Apple implementation for the
Macintosh of the IBM SOM technology. "SOM" is the name of the technology, and
"SOMobjects(TM) for Mac OS" is the name of the Macintosh extension file that
provides it.*
This article explains how to construct an OpenDoc part object that serves as a base
class to be inherited from in your own applications and by others, if you so desire. I
use the example of creating scrollable lists, something almost all developers have to
bother with at one time or another. My sample base class (or base part, as I prefer to
call it), named ListPart, doesn't do anything by itself but is inherited from by three
other parts (ListEx1Part, ListEx2Part, and ListEx3Part) that produce lists of
varying complexity and that we'll examine in some detail. Since the goal of this article
is to highlight the inheritance aspects, I won't describe much about how the list itself
is managed by the base part. If you're interested, see the source code on this issue's CD.
If you want to write OpenDoc extensions, you'll have to dive into SOM, so this article is
a good starting point for you, too.
OpenDoc developer releases are available at
http://www.opendoc.apple.com on the Web and on CD through a number of
different sources. These releases include the OpenDoc Programmer's Guide,
the IBM SOM manual, and the SOMobjects for Mac OS manual, the best
documentation available on SOM and dynamic inheritance.*
A LOOK AT THE BASE PART
We'll start with a look at the process of building an OpenDoc part, which is really a
SOM object. Since we currently don't have a direct-to-SOM compiler on the
Macintosh, the process consists of two steps:
• We first write the .idl source file, which is the SOM interface for our
object, describing fields and methods and how the new part inherits from
ODPart. Then, with the SOM compiler (currently distributed as an MPW
tool), we generate the .xh, .xih, and .cpp source files, which will be used as a
bridge between SOM and C++.
• We write in C++ the body of the methods described in the .idl source file.
We then have all the necessary files to build the whole project and get the OpenDoc
part. Because the first step is always the same for simple parts, most developers
never bother with it themselves, but instead use PartMaker to automatically generate
the files associated with this step (.idl, .xh, .xih, and .cpp) and then work mainly with
the constructed C++ object. Thus, they seldom open the subfolder containing the SOM
source files, and they modify these files even less often.
But if you want to inherit from a part other than ODPart, you've got to take things into
your own hands. What PartMaker would otherwise do for you, you've got to do for
yourself. It's easier than it sounds, as you'll see in the following pages. We'll look at
how to create the .idl, .xh, .xih, and .cpp source files, plus a .cpp source file that
manages the initializations for SOM and the Code Fragment Manager, and the .h and
.cpp source files containing the C++ class and its methods.
For the inheritance mechanism to be widely used by developers, it has to be simple. In
an ideal world, you would provide only the base part itself, its interface (the .idl
source file), and a little documentation describing the methods to be called or
overridden. But since we're in the real world, you may also want to provide a .xh
source file; this can be regenerated from the .idl file by the SOM compiler, but it's a
good idea to provide it to simplify the work of developers willing to inherit from your
part. I'll discuss these necessary files and then make some remarks about how the base
part works.
STARTING WITH THE .IDL SOURCE FILE
The complete class name for our sample base part is ACF_DevServ_som_ListPart. The
first step in creating this base part is generating the .idl source file. Listing 1 shows
only the differences from the .idl file generated by PartMaker.
SOM objects are passed into methods via pointers, so when generating the
C++ implementation function for a SOM method, the SOM compiler adds an
asterisk (*) to the type of each SOM object being passed to those methods.
When you use a SOM class name such as ODFacet and what you want is
ODFacet*, you only have to write ODFacet. If you write ODFacet* you'll get
ODFacet**. (In Listing 1, ODEventData isn't a class but a struct; thus the
asterisk on the end is correct.)*
Listing 1. Extract from the som_ListPart.idl source file
module ACF_DevServ
{
interface som_ListPart : ODPart
{
// To call
void ShowMe(in ODFacet facet, in short theLine);
short GetNbLines();
void SetNbLines(in short newNbLines);
short GetSel();
void SetSel(in ODFacet facet, in short theLine);
// To override
ODISOStr GetTheRealPartKind();
ODSLong OverrideBeginUsingLibraryResources();
void OverrideEndUsingLibraryResources(in ODSLong ref);
void SetUpGraphics(in void* theGWorld);
void FillCell(in short theLine, in Rect* theRect);
void FillHilCell(in short theLine, in Rect* theRect);
void ClickInActive(in ODFacet facet,
in ODEventData* event, in Rect* theRect);
void CloseOpenedCell(in ODFacet facet);
void IdleOpened(in ODFacet facet);
short KeyInActive(in ODFacet facet, in ODEventData* event);
short KeyShortCut(in char theChar);
void GotDoubleClick(in ODFacet facet, in short theLine);
void ExternalizeListData(in ODStorageUnit storageUnit);
void InternalizeListData(in ODStorageUnit storageUnit);
void SetUpListData(in ODStorageUnit storageUnit);
void InitializeListData(in short* pNbLines, in short*
pLineHeight, in short* pLineWidth,
in short* pLineDepth, in short* pKind,
in short* pAutoThumb, in short* pWantKey,
in short* pListIndex, in short* pSel,
in char** pMul);
#ifdef __SOMIDL__
implementation
{
...
override:
somInit, somUninit, ..., WriteActionState,
ReadActionState;
releaseorder:
ShowMe, GetNbLines, SetNbLines, GetSel, SetSel,
GetTheRealPartKind, OverrideBeginUsingLibraryResources,
OverrideEndUsingLibraryResources, SetUpGraphics,
FillCell, FillHilCell, ClickInActive, CloseOpenedCell,
IdleOpened, KeyInActive, KeyShortCut, GotDoubleClick,
ExternalizeListData, InternalizeListData, SetUpListData,
InitializeListData;
...
};
#endif
};
}; //# Module ACF_DevServ
Most field names in the .h and .idl source files are explicit enough -- fNbLines,
fLineHeight, fLineWidth, fLineDepth, fGWorld -- but these might need further
explanation:
• fListIndex is the number of the first line displayed.
• fAutoThumb tells whether we want live scrolling with the thumb.
• fKind specifies the kind of list we want, where
1 = no selection
2 = single selection (stored in fSel)
3 = live single selection (stored in fSel), where users can edit the line in
place
4 = multiple selection (stored in fMul)
• fWantKey tells whether we provide the user with keyboard shortcuts to
navigate in the list.
These methods are only to be called and not overridden:
• ShowMe, which scrolls the list to a desired position
• GetNbLines and SetNbLines
• GetSel and SetSel, which return and set the currently selected line
These methods are to be overridden if necessary:
• SetUpGraphics, which gives you a chance to further initialize the
offscreen buffer as you want (with default font and font size, for example)
• FillCell, which draws the content of one line
• FillHilCell, which draws the content of a selected line
• ClickInActive, CloseOpenedCell, IdleOpened, and KeyInActive, which deal
with the editing in place of a live selected line
• KeyShortCut, which scrolls the list according to the given character
• GotDoubleClick, which enables you to take appropriate actions in response
to a double click
• SetUpListData, ExternalizeListData, and InternalizeListData, which deal
with the storage unit
• InitializeListData, which asks for the initial values of the fields described
above
• GetTheRealPartKind, which returns the part kind usually defined in
xxxxPartDef.h and is necessary for the storage units to store the right owner
• OverrideBeginUsingLibraryResources and
OverrideEndUsingLibraryResources, which deal with resource management in
inherited parts
Using only GetTheRealPartKind, InitializeListData, and FillCell, we can get a complete
working list. This will be illustrated in ListEx1Part. Meanwhile, it's essential to keep
in mind that in dynamic inheritance we're dealing with SOM objects, not C++ objects.
The implications of this are described in "SOM Objects vs. C++ Objects.
______________________________
SOM OBJECTS VS. C++ OBJECTS
An OpenDoc part is really a SOM object (in our example,
ACF_DevServ_som_ListPart) and is known to OpenDoc as such. The C++
object generated by PartMaker (in our example, ListPart) is a wrapper that
serves to simplify the data management and the code writing in the absence of a
direct-to-SOM C++ compiler. In fact, the C++ object is just a field (in our
example, fPart) of the SOM object. We've written our SOM object's
implementation so that it simply delegates all messages to its C++ object.
For instance, a call to FillCell or FacetAdded in our base class object
(ACF_DevServ_som_ListPart) would go through fPart and thus to the C++
method FillCell or FacetAdded, as illustrated in Figure 1. The C++ field