And then SOM...
Volume Number: 14
Issue Number: 2
Column Tag: develop
by Éric Simenel
IBM System Object Model (SOM), which was introduced on Mac OS with OpenDoc, has
become even more important to developers with the introduction of the Contextual
Menus in Mac OS 8. It's not restricted to the development of Contextual Menus
plug-ins, though. SOM is also available to all developers and solves a lot of common
issues which plague real-life project development, management and maintenance. This
article will show how SOM can ease your product development and save your precious
time.
Since most developers develop in C++ or other object-oriented languages these days,
most of you know the obvious reasons why object-oriented programming is better than
the previous models. The main strengths (which are also its definitions) of OOP are
encapsulation, polymorphism, and inheritance, all of which are conducive to easy
reuse.
That's theory. In real life, we deal with different development environments which,
even when they're dealing with the same language, are not always compatible because
of implementation choices, and are usually even less compatible when dealing with
different languages. Even when staying with the same development environment, it
changes over time, and it's always trouble to reintegrate old code in new projects, and
sometimes, it's even trouble trying to reopen an old project for maintenance with a
current development environment.
So, a usual project these days involves many developers who have different tastes in
development environments and languages, old legacy source codes written in different
languages under different development environments, and sometimes old legacy binary
codes whose source codes are no longer available.
Although SOM is not the universal cure to these problems, it does bring a lot of relief,
and even if you're a single developer using only one language, SOM can help you manage
your project by breaking it into small easily reusable pieces.
Getting SOM
SOM provides developers with the advantages of both object-oriented programming and
shared libraries. The main advantage is that whether you're just using a SOM class
from a library or inheriting from it, if you later replace this library with a more
recent version, you don't have to recompile or rebuild all the client code. When you
stop and think about it, this feat is a real breakthrough from what we've been dealing
with until now. The second advantage is SOM's language independence, which allows
developers to use their favorite environment and still mesh with other people's code.
Due to its rather recent emergence in the Mac OS, the preferred (and only) language
used with SOM is C++, but it may change in the future.
This article will cover the basics of SOM encapsulation, polymorphism, and
inheritance, the use of a SOM library in an application, and in another SOM library,
multiple aspects of versioning, dealing with exceptions, etcetera. This article is aimed
at giving you a kick start, covering the basics aspects of SOM and how to use it for fun
and profit; it will explain in some places the internal works, in case you're interested,
but not everywhere. If you're interested in more knowledge about SOM and its
internals, then you should read the manuals provided where SOMObjects(tm) for Mac
OS is distributed (for example, on the Mac OS SDK CD). This knowledge is not an
absolute need, mostly you can just program by example (and a lot of examples are
provided in this article). An alternate title for this article could have been "SOM for
C++ developers...". When I refer in this article to the Users Guide, it means the Users
Guide found in the documentation folder of SOMObjects.
Throughout this article, I'll use the following example to illustrate the different
techniques:
Figure 1. SOM Classes of the example.
Where som_Taxes (encapsulation) is a SOM class used by som_Item (usage of a
SOM lib within a SOM lib). som_Solid (inheritance) and som_CarWash (Meta Classes) both inherit from som_Item. som_Car (exception handling) inherits
from som_Solid, som_Tires (multiple inheritance) multiple inherits from
som_Solid and som_Attr, and som_Tapes (versionning) inherits from som_Solid
but does interesting things when som_Item v1.1 is present.
Each class is there to illustrate one interesting point at a time (to prevent confusion of
the issues). To understand the example, you should note that I have assumed that we're
located in a country where products are submitted to sales tax but services are not.
I've been using the Direct-To-SOM capabilities of MetroWerks CodeWarrior MW
C/C++ (CodeWarrior Pro release). I verified that MPW MrCpp also has the same
capabilities, but I don't cover its usage in this article. I also verified that you can build
68K SOM libraries, but do not cover that in this article either as there are only minor
differences in the project settings.
Throughout this article, all the listings have been purged of irrelevant (for this
article) lines, such as debugging information, to hilite the more interesting parts.
Look at the real code provided with this article to see the complete sources.
Encapsulation
Let's say that you have old legacy source or binary code. Each time you want to use it in
a new project it may be a pain to integrate, because things change over time. If it's
binary code, it may be 68K code, for which you have to construct UniversalProcPtrs.
If it's source code, it may be in a different language than the one you're currently
using. Currently, if that code is callable from C/C++, then you can encapsulate it in 1
or more SOM classes distributed in 1 or more SOM shared libraries. In my example,
that would be the som_Taxes SOM class.
The main advantage of encapsulation in a SOM wrapper is to enable you to use this old
legacy code in all your new projects, with a nice interface. And, eventually, if you
decide to rewrite all or part of it, all the projects which have been using this code
won't have to be rebuilt to continue to work. The SOM wrapper really isolates the
interface from the implementation. Whereas, if you were to keep this code the way it
is, with maybe just a nice new set of headers to be able to use it easily in new
projects, and if you decide later to modify all or part of it, then you would have to
rebuild everything.
The costs involved are not only a development issue but also a sales issue, in the first
case, since we're dealing with shared libraries, you just have to ship your customers
the new version of the SOM library containing the old legacy code, now updated. In the
second case, after having rebuild everything, you have to ship everything.
Creating a SOM library is pretty straightforward. Without the Direct-To-SOM
capabilities of the most recent C++ compilers, we would have had to write first an .idl
file, then go through MPW somipc to generate the .xh, .xih and .cpp files, and then
modify the .cpp file according to what had to be done. With the Direct-To-SOM
capabilities, it's much quicker: we simply write a .hh header file (.hh is just a style
convention to differentiate them from simple .h header files, but there are no other
differences) and the corresponding .cpp file. As a reminder, let's take a look at the .idl
we would have written for straight SOM:
Listing 1. som_Taxes.idl
module CalcTaxes
interface som_Taxes : SOMObject
long CalcTheTax(in long value, in short kind);
#ifdef __SOMIDL__
implementation
{
majorversion = 1;
minorversion = 0;
functionprefix = som_Taxes__;
override: somInit;
releaseorder: CalcTheTax;
long tax;
#endif //__SOMIDL__
Compare this with the .hh we are writing for Direct-To-SOM compilation:
Listing 2. d2som_Taxes.hh
class CalcTaxes_som_Taxes : public virtual SOMObject {
public:
CalcTaxes_som_Taxes();
virtual ~CalcTaxes_som_Taxes();
virtual long CalcTheTax(INOUT Environment *ev, IN long value, IN
short kind);
private:
long fTax;
#if __SOM_ENABLED__
#pragma SOMReleaseOrder (CalcTheTax)
#pragma SOMClassVersion (CalcTaxes_som_Taxes, 1, 0)
#pragma SOMCallStyle IDL
#endif
Comparing both .idl and .hh, the meaning of the SOMReleaseOrder and SOMClassVersion
pragmas are pretty much obvious. The SOMCallStyle pragma can take 2 arguments,
IDL and OIDL. If we want to use the exceptions mechanism (more on that later in this
article), it is imperative we use the IDL argument, so it's a good idea to get into this
habit. This means that each method must have Environment *ev as its first parameter.
The first difference between a SOM class and a straight C++ class is that the
constructor (ie. CalcTaxes_som_Taxes() in this example) for a SOM class can't have
any arguments (we'll see later in this articles how to use an equivalent mechanism
with Meta Classes). We can also override somInit, which is a method defined in
SOMObject, but it can't take any arguments either. And anyway, you can't use an
efficient exceptions mechanism with either constructor or somInit, so we'll see in the
next example how you should use Initialize and Uninitialize methods to properly set up
and unset your objects. In the case of som_Taxes, since it doesn't do much, we simply
use a constructor which can't fail.
In this example, there's only one new method, CalcTheTax. CalcTaxes_som_Taxes is
inheriting from SOMObject which is the root for SOM classes, and has methods like
somGetClass and others which we'll see uses for later. The keyword IN found in the
parameter list of CalcTheTax means that the parameter is passed to the method and,
even if modified, not returned. As we'll see further below, the keywords OUT and
INOUT can also be used, OUT meaning that the parameter doesn't receive an initial
value and is returned by the method, INOUT meaning that the parameter is passed to
the method with an initial value, and that the method can return another value. In fact,
IN , OUT and INOUT are just for reading purposes, since these macros expand to
nothing, they're just here as a reminder for SOM's in, out and inout keywords. But the
same rules don't apply. A SOM out long value would be transformed in the .cpp as long
*value. Here we have to say OUT long *value. In addition, the used SOM classes won't
get an extra * (see the note on page 61 of develop 26).
SOM Objects and SOM Classes
SOM is a dynamic environment where classes themselves are instantiated as objects in
memory. Those special objects are called class objects to differentiate them from
simple objects, but there are objects nonetheless. Notwithstanding its name,
SOMObject is a SOM class. SOMClass is also a SOM Class inheriting from SOMObject. To
simplify complex matters, let's just say that when the SOM Runtime starts, it
instantiates a class object of the class SOMClassMgr, a class object of the class
SOMObject, and a class object of the class SOMClass. When you are instantiating an
object of the class CalcTaxes_som_Taxes for the first time, SOM actually instantiates a
class object for your class, and then, the object you requested. The class object of your
class is instantiated only once, whatever the number of objects of this class you are
instantiating. If you are interested in more details, please refer to chapter 2.1 of the
Users Guide. Throughout this article, I made an effort to distinguish a SOM object from
its SOM class, where it made sense, but mostly they're the same. When I'm writing
about a method, for example, I may refer to it as the "SOM object method" or "SOM
class method", but it doesn't matter much.
The next step is to write the .cpp file. It's in that .cpp file that you are going to either
include or link to your old legacy code:
Listing 3. d2som_Texes.cpp
CalcTaxes_som_Taxes::CalcTaxes_som_Taxes()
long CalcTaxes_som_Taxes::CalcTheTax(INOUT Environment *ev, IN long
value, IN short kind)
if (kind == 0) return value;
else return(value + ((value * fTax) / 100));
}
A big difference between C++ and SOM is that, by definition, all fields of a SOM class
are private to that object, and all methods declared in the releaseorder list must be
public and virtual. That means that if you want classes, inheriting from your class,
accessing your fields, you have to provide accessors for them.
The next step is simply to build the SOM shared library. Using MetroWerks
CodeWarrior, we'll need an extra file which contains the only symbol which has to be
exported, for CFM (Code Fragment Manager) and SOM to be happy. This symbol is the
complete name of your SOM class concatenated at the end with ClassData, ie.
CalcTaxes_som_TaxesClassData. The name of the file must end with .exp and may be
put in the project, ie. d2som_Taxes.exp. If there are more than one SOM class defined
in a particular library, then all the ClassData symbols should be listed in the .exp file.
Another way to achieve symbol export is to use the #pragma export directive in your
.hh file as in the following example (see the Solid project for more details):
#pragma export on
class MSolid_som_Solid;
#pragma export off
Since the #pragma direct_to_som directive is on, the development environment does
the right thing and adds the ClassData extension automatically.
You then create a new project based on the "ANSI C++ Console PPC (DLL)" (Pro 1) or
"Std C++ Console PPC (DLL)" (Pro 2) stationery. You add the .cpp source file, the
.exp file, and for convenience, the .hh file (duplicate the .h Target preference, and
change the extension to .hh) and other source or resource files if you need. You also add
the somlib shared library which contains the SOM code, and you modify the following
settings:
• PPC Target: you select Shared Library as project type, you type in the
name you chose, and you type cfmg as creator, unless you're providing your
own bundle.
• PPC Linker: you clear all Entry Points fields
• PPC PEF: you select either the "use the ".exp" file" or "use #pragma
item in the Export Symbols popup menu depending on the way you desire to
export symbols.
Depending on the code you either include or link to, you may or may not get rid of a lot
of unuseful libraries automatically put in the project stationery. In my case, I only
need to keep MSL ShLibRuntime.Lib and you build the library... (1421 bytes).
Figure 2. MetroWerks CodeWarrior SOM project for PowerPC.
In both MPW and MetroWerks environments, the Enums should be int (required for
Direct-To-SOM compilation), and it's a good idea to either generate MacsBug symbols
(for 68K) or tracebacks (for PowerPC) to ease your debugging (don't forget to turn
them off for your distribution release). You turn on the Direct-To-SOM compilation
in MPW MrCpp with the -som directive in the command line, and you can turn it on
(popup menu) in the C/C++ Language preference panel in MetroWerks CodeWarrior.
You can alternatively use the #pragma direct_to_som on, if you prefer.
Using the SOM Shared Library
It's very simple to use the SOM shared library in your application project (or another
SOM shared library project, in this case it's GeneralItem_som_Item), you just have
to add the TaxesSOMLib to your project window, add the #include "d2som_Taxes.hh
directive in your .cpp source file, allocate the SOM object with a new, deallocated it
with a delete, and use it as if it were a C++ object everywhere you need it.
But let's take a closer look at the d2som_Item project first. The .hh is:
Listing 4. d2som_Item.hh
class GeneralItem_som_Item : public virtual SOMObject {
public:
GeneralItem_som_Item();
virtual ~GeneralItem_som_Item();