Jump to content

Implementing Classes in SOM: Difference between revisions

From EDM2
Line 1,413: Line 1,413:


==Customizing Multi-threading Services==
==Customizing Multi-threading Services==
Although the SOM kernel and the other SOMobjects frameworks allow applications to be multi-threaded, the kernel and frameworks generally do not require or exploit threads themselves. But there are some exceptions: for example, application servers in DSOM can be configured so that each incoming request is executed on a separate thread.
An application may choose to employ "native" multi-threading services provided by the underlying operating system (for example, OS/2). On other operating systems that do not support native multi-threading (such as, AIX 3.2), thread support may be provided as part of particular programming environments (like DCE) or libraries. SOM provides a mechanism that allows an application to define and customize the multi-threading services used by SOMobjects frameworks.
Four thread service functions are defined for use by SOMobjects frameworks. These functions may be called indirectly via the global pointer variables defined below. A somToken parameter (called "thrd" below) is used as a generic "handle" to refer to a thread # usually it is a pointer to a thread id or descriptor. The actual representation of the thread handle is hidden by the functions.
typedef void somTD_SOMThreadProc(void * data);
unsigned long (*SOMStartThread)(somToken *thrd,
                                somTD_SOMThreadProc proc,
                                void *data,
                                unsigned long datasz,
                                unsigned long stacksz);
:The referenced function starts a thread, and returns a thread handle in the somToken variable identified by "thrd". The thread executes the procedure whose address is specified by the proc parameter; the thread procedure takes a single void* argument and returns void. The data parameter passed to SOMStartThread is passed on to the thread procedure; the size of the data parameter, in bytes, is given by datasz. A stack of stacksz bytes will be allocated for the thread.
:Note: On OS/2, the thread procedure must be compiled with _Optlink linkage.)
:If the call succeeds, a 0 is returned. Otherwise, a non-zero error code is returned.
;unsigned long (*SOMEndThread)(void);
:The referenced function terminates the calling thread.
:If the call succeeds, a 0 is returned. Otherwise, a non-zero error code is returned.
;unsigned long (*SOMKillThread)(somToken thrd);
:The referenced function terminates the thread identified by the input parameter thrd.
:If the call succeeds, a 0 is returned. Otherwise, a non-zero error code is returned.
;unsigned long (*SOMGetThreadHandle)(somToken * thrd);
:The referenced function returns a handle that can be used to identify the calling thread. The handle is returned in the somToken variable pointed to by thrd.
:If the call succeeds, a 0 is returned. Otherwise, a non-zero error code is returned.
The actual mutex service function prototypes and global variable declarations are found in file "somthrd.h".
If the underlying operating system supports native multi-threading (currently, only OS/2), SOM provides appropriate default multi-threading service function implementations. On those operating systems that do not support native multi-threading, the default multi-threading service functions have null implementations.
An application may want to use threading services different from those provided by the underlying operating system (if any); for example, DCE applications will want to use DCE threads. In that case, the default multi-threading service functions can be replaced by application#defined functions.
An application program would use code similar to the following to install the replacement routines:
<pre>
#include <somthrd.h>/* Define a replacement routine: */
unsigned long myStartThread (somToken *thrd,
                            somTD_SOMThreadProc proc,
                            void *data,
                            unsigned long datasz,
                            unsigned long stacksz)
{
    (Customized code goes here)
}
...   
SOMStartThread =myStartThread;
</pre>
It is important to install custom multi-threading service functions before any SOM calls are made.

Revision as of 00:05, 4 October 2020

System Object Model Programming Guide
  1. About This Book
  2. Introduction to the SOMobjects Developer Toolkit
  3. Tutorial for Implementing SOM Classes
  4. Using SOM Classes in Client Programs
  5. SOM IDL and the SOM Compiler
  6. Implementing Classes in SOM
  7. Distributed SOM (DSOM)
  8. The SOM Interface Repository Framework
  9. The Metaclass Framework
  10. The Event Management Framework
  11. SOMobjects Error Codes
  12. SOM IDL Language Grammar
  13. Implementing Sockets Subclasses
  14. Glossary

Reprint Courtesy of International Business Machines Corporation, © International Business Machines Corporation

This chapter begins with a more in-depth discussion of SOM concepts and the SOM run-time environment than was appropriate in Tutorial for Implementing SOM Classes. Subsequent sections then provide information about completing an implementation template file, updating the template file, compiling and linking, packaging classes in libraries, and other useful topics for class implementors. During this process, you can refer to Chapter 4, "SOM IDL and the SOM Compiler," if you want to read the reference information or see the full syntax related to topics discussed in this chapter. The current chapter ends with topics describing how to customize SOMobjects execution in various ways.

The SOM Run-Time Environment

The SOMobjects Developer Toolkit provides:

  • The SOM Compiler, used when creating SOM class libraries, and
  • The SOM run-time library, for using SOM classes at execution time.

The SOM run-time library provides a set of functions used primarily for creating objects and invoking methods on them. The data structures and objects that are created, maintained, and used by the functions in the SOM run-time library constitute the SOM run-time environment.

A distinguishing characteristic of the SOM run-time environment is that SOM classes are represented by run-time objects; these objects are called class objects. By contrast, other object-oriented languages such as C++ treat classes strictly as compile-time structures that have no properties at run time. In SOM, however, each class has a corresponding run-time object. This has three advantages: First, application programs can access information about a class at run time, including its relationships with other classes, the methods it supports, the size of its instances, and so on. Second, because much of the information about a class is established at run time rather than at compile time, application programs needn't be recompiled when this information changes. Finally, because class objects can be instances of user-defined classes in SOM, users can adapt the techniques for subclassing and inheritance in order to build object-oriented solutions to problems that are otherwise not easily addressed within an OOP context.

Run-time environment initialization

When the SOM run-time environment is initialized, four primitive SOM objects are automatically created. Three of these are class objects (SOMObject, SOMClass, and SOMClassMgr), and one is an instance of SOMClassMgr, called the SOMClassMgrObject. Once loaded, application programs can invoke methods on these class objects to perform tasks such as creating other objects, printing the contents of an object, freeing objects, and the like. These four primitive objects are discussed below.

In addition to creating the four primitive SOM objects, initialization of the SOM run-time environment also involves initializing global variables to hold data structures that maintain the state of the environment. Other functions in the SOM run-time library rely on these global variables.

For application programs written in C or C++ that use the language-specific bindings provided by SOM, the SOM run-time environment is automatically initialized the first time any object is created. Programmers using other languages must initialize the run-time environment explicitly by calling the somEnvironmentNew function (provided by the SOM run-time library) before using any other SOM functions or methods.

SOMObject class object

SOMObject is the root class for all SOM classes. It defines the essential behavior common to all SOM objects. All user-defined SOM classes are derived, directly or indirectly, from this class. That is, every SOM class is a subclass of SOMObject or of some other class derived from SOMObject. SOMObject has no instance variables, thus objects that inherit from SOMObject incur no size increase. They do inherit a suite of methods that provide the behavior required of all SOM objects.

SOMClass class object

Because SOM classes are run-time objects, and since all run-time objects are instances of some class, it follows that a SOM class object must also be an instance of some class. The class of a class is called a metaclass. Hence, the instances of an ordinary class are individuals (nonclasses), while the instances of a metaclass are class objects.

In the same way that the class of an object defines the "instance methods" that the object can perform, the metaclass of a class defines the "class methods" that the class itself can perform. Class methods (sometimes called factory methods or constructors) are performed by class objects. Class methods perform tasks such as creating new instances of a class, maintaining a count of the number of instances of the class, and other operations of a "supervisory" nature. Also, class methods facilitate inheritance of instance methods from parent classes. For information on the distinction between parent classes and metaclasses, see the section "Parent Class vs. metaclass," later in this chapter.

SOMClass is the root class for all SOM metaclasses. That is, all SOM metaclasses must be subclasses of SOMClass or of some metaclass derived from SOMClass. SOMClass defines the essential behavior common to all SOM class objects. In particular, SOMClass provides:

  • Six class methods for creating new class instances: somNew, somNewNoInit, somRenew, somRenewNoInit, somRenewNoZero and somRenewNoInitNoZero.
  • A number of class methods that dynamically obtain or update information about a class and its methods at run time, including:
    • somInitMIClass, for implementing multiple inheritance from parent classes,
    • somOverrideSMethod, for overriding inherited methods, and
    • somAddStaticMethod and somAddDynamicMethod, for including new methods.

SOMClass is a subclass (or child) of SOMObject. Hence, SOM class objects can also perform the same set of basic instance methods common to all SOM objects. This is what allows SOM classes to be real objects in the SOM run-time environment. SOMClass also has the unique distinction of being its own metaclass (that is, SOMClass defines its own class methods). A user-defined class can designate as its metaclass either SOMClass or another user-written metaclass descended from SOMClass. If a metaclass is not explicitly specified, SOM determines one automatically.

SOMClassMgr class object and SOMClassMgrObject

The third primitive SOM class is SOMClassMgr. A single instance of the SOMClassMgr class is created automatically during SOM initialization. This instance is referred to as the SOMClassMgrObject, because it is pointed to by the global variable SOMClassMgrObject. The object SOMClassMgrObject has the responsibility to

  • Maintain a registry (a run-time directory) of all SOM classes that exist within the current process, and to
  • Assist in the dynamic loading and unloading of class libraries.

For C/C++ application programs using the SOM C/C++ language bindings, the SOMClassMgrObject automatically loads the appropriate library file and constructs a run-time object for the class the first time an instance of a class is created. For programmers using other languages, SOMClassMgr provides a method, somFindClass, for directing the SOMClassMgrObject to load the library file for a class and create its class object.

Again, the primitive classes supplied with SOM are SOMObject, SOMClass, and SOMClassMgr. During SOM initialization, the latter class generates an instance called SOMClassMgrObject. The SOMObject class is the parent class of SOMClass and SOMClassMgr. The SOMClass class is the metaclass of itself, of SOMObject, and of SOMClassMgr, which are all class objects at run time. SOMClassMgr is the class of SOMClassMgrObject.

Parent class vs. metaclass

There is a distinct difference between the notions of "parent" (or base) class and "metaclass." Both notions are related to the fact that a class defines the methods and variables of its instances, which are therefore called instance methods and instance variables.

A parent of a given class is a class from which the given class is derived by subclassing. (Thus, the given class is called a child or a subclass of the parent.) A parent class is a class from which instance methods and instance variables are inherited. For example, the parent of class "Dog" might be class "Animal". Hence, the instance methods and variables introduced by "Animal" (such as methods for breathing and eating, or a variable for storing an animal's weight) would also apply to instances of "Dog", because "Dog" inherits these from "Animal", its parent class. As a result, any given dog instance would be able to breath and eat, and would have a weight.

A metaclass is a class whose instances are class objects, and whose instance methods and instance variables (as described above) are therefore the methods and variables of class objects. For this reason, a metaclass is said to define class methods-the methods that a class object performs. For example, the metaclass of "Animal" might be "AnimalMClass", which defines the methods that can be invoked on class "Animal" (such as, to create Animal instances-objects that are not classes, like an individual pig or cat or elephant or dog).

Note: It is important to distinguish the methods of a class object (that is, the methods that can be invoked on the class object, which are defined by its metaclass) from the methods that the class defines for its instances.

To summarize: the parent of a class provides inherited methods that the class's instances can perform; the metaclass of a class provides class methods that the class itself can perform. These distinctions are further summarized below: The distinctions between parent class and metaclass are summarized in A class has both parent classes and a metaclass.

A class has both parent classes and a metaclass

Any class "C" has both a metaclass and one or more parent class(es).

  • The parent class(es) of "C" provide the inherited instance methods that individual instances (objects "O{i}") of class "C" can perform. Instance methods that an instance "O{i}" performs might include (a) initializing itself, (b) performing computations using its instance variables, (c) printing its instance variables, or (d) returning its size. New instance methods are defined by "C" itself, in addition to those inherited from C's parent classes.
  • The metaclass "M"defines the class methods that class "C" can perform. For example, class methods defined by metaclass "M" include those that allow "C" to (a) inherit its parents' instance methods and instance variables, (b) tell its own name, (c) create new instances, and (d) tell how many instance methods it supports. These methods are inherited from SOMClass. Additional methods supported by "M" might allow "C" to count how many instances it creates.
  • Each class "C" has one or more parent classes and exactly one metaclass. (The single exception is SOMObject, which has no parent class.) Parent class(es) must be explicitly identified in the IDL declaration of a class. (SOMObject is given as a parent if no subsequently-derived class applies.) If a metaclass is not explicitly listed, the SOM run time will determine an applicable metaclass.
  • An instance of a metaclass is always another class object. For example, class "C" is an instance of metaclass "M". SOMClass is the SOM-provided metaclass from which all subsequent metaclasses are derived.

A metaclass has its own inheritance hierarchy (through its parent classes) that is independent of its instances' inheritance hierarchies. For example, suppose a series of classes is defined (or derived), stemming from SOMObject. The child class (or subclass) at the end of this line ("C[2]") inherits instance methods from all of its ancestor classes (here, SOMObject and "C[1]"). An instance created by "C2" can perform any of these instance methods. In an analogous manner, a line of metaclasses can be defined, stemming from SOMClass. Just as a new class is derived from an existing class (such as SOMObject), a new metaclass is derived from an existing metaclass (such as SOMClass).

Parent classes and metaclasses each have their own independent inheritance hierrarchies

SOM-derived metaclasses

As previously discussed, a class object can perform any of the class methods that its metaclass defines. New metaclasses are typically created to modify existing class methods or introduce new class method(s). Chapter 8, "Metaclass Framework," discusses metaclass programming.

Three factors are essential for effective use of metaclasses in SOM:

  • First, every class in SOM is an object that is implemented by a metaclass.
  • Second, programmers can define and name new metaclasses, and can use these metaclasses when defining new SOM classes.
  • Finally, and most importantly, metaclasses cannot interfere with the fundamental guarantee required of every OOP system: specifically, any code that executes without method-resolution error on instances of a given class will also execute without method-resolution errors on instances of any subclass of this class.

Surprisingly, SOM is currently the only OOP system that can make this final guarantee while also allowing programmers to explicitly define and use named metaclasses. This is possible because SOM automatically determines an appropriate metaclass that supports this guarantee, automatically deriving new metaclasses by subclassing at run time when this is necessary. As an example, suppose class "A" is an instance of metaclass "AMeta".

Assume that "AMeta" supports a method "bar" and that "A" supports a method "foo" that uses the expression "_bar( _somGetClass(somSelf ) )." That is, method "foo" invokes "bar" on the class of the object on which "foo" is invoked. For example, when method "foo" is invoked on an instance of class "A" (say, object "O{1}"), this in turn invokes "bar" on class "A" itself.

Now consider what happens if class "A" were subclassed by "B," a class that has the explicit metaclass "BMeta" declared in its SOM IDL source file (and assuming "BMeta" is not derived from "AMeta"). Also assume that object "O{2}" is an instance of class "B."

Recall that "AMeta" supports method "bar" and that class "A" supports method "foo" (which incorporates "bar" in its definition). Given the hierarchy described above, an invocation of "foo" on "O {2}" would fail, because metaclass "BMeta" does not support the "bar" method.

Example of Metaclass Incompatibility

There is only one way that "BMeta" can support this specific method-by inheriting it from "AMeta" ("BMeta" could introduce another method named "bar", but this would be a different method from the one introduced by "AMeta"). Therefore, in this example, because "BMeta" is not a subclass of "AMeta", "BMeta" cannot be allowed to be the metaclass of "B". That is, "BMeta" is not compatible with the requirements placed on "B" by the fundamental principle of OOP referred to above. This situation is referred to as metaclass incompatibility.

SOM does not allow hierarchies with metaclass incompatibilities. Instead, SOM automatically builds derived metaclasses when this is necessary. For example, SOM would create a "DerivedMeta" metaclass that has both "AMeta" and "BMeta" as parents. This ensures that the invocation of method "foo" on instances of class "B" will not fail, and also ensures that the desired class methods provided by "BMeta" will be available on class "B".

Example of a Derived Metaclass

There are three important aspects of SOM's approach to derived metaclasses:

  • First, the creation of SOM-derived metaclasses is integrated with programmer-specified metaclasses. If a programmer-specified metaclass already supports all the class methods and variables needed by a new class, then the programmer-specified metaclass will be used as is.
  • Second, if SOM must derive a different metaclass than the one explicitly indicated by the programmer (in order to support all the necessary class methods and variables), then the SOM-derived metaclass inherits from the explicitly indicated metaclass first. As a result, the method procedures defined by the specified metaclass take precedence over other possibilities (see the following section on inheritance and the discussion of resolution of ambiguity in the case of multiple inheritance).
  • Finally, the class methods defined by the derived metaclass invoke the appropriate initialization methods of its parents to ensure that the class variables of its instances are correctly initialized.

As further explanation for the automatic derivation of metaclasses, consider the following multiple-inheritance example. Class "C" (derived from classes "A" and "B") does not have an explicit metaclass declaration in its SOM IDL, yet its parents "A" and "B" do. As a result, class "C" requires a derived metaclass. (If you still have trouble following the reasoning behind derived metaclasses, ask yourself the following question: What class should "C" be an instance of? After a bit of reflection, you will conclude that, if SOM did not build the derived metaclass, you would have to do so yourself.)

Multiple Inheritance requires Derived Metaclasses

In summary, SOM allows and encourages the definition and explicit use of named metaclasses. With named metaclasses, programmers can not only affect the behavior of class instances by choosing the parents of classes, but they can also affect the behavior of the classes themselves by choosing their metaclasses. Because the behavior of classes in SOM includes the implementation of inheritance itself, metaclasses in SOM provide an extremely flexible and powerful capability allowing classes to package solutions to problems that are otherwise very difficult to address within an OOP context.

At the same time, SOM is unique in that it relieves programmers of the responsibility for avoiding metaclass incompatibility when defining a new class. At first glance, this might seem to be merely a useful (though very important) convenience. But, in fact, it is absolutely essential, because SOM is predicated on binary compatibility with respect to changes in class implementations.

A programmer might, at one point in time, know the metaclasses of all ancestor classes of a new subclass, and, as a result, be able to explicitly derive an appropriate metaclass for the new class. Nevertheless, SOM must guarantee that this new class will still execute and perform correctly when any of its ancestor class's implementations are changed (which could even include specifying different metaclasses). Derived metaclasses allow SOM to make this guarantee. A SOM programmer need never worry about the problem of metaclass incompatibility; SOM does this for the programmer. Instead, explicit metaclasses can simply be used to "add in" whatever behavior is desired for a new class. SOM automatically handles anything else that is needed. Chapter 10 provides useful examples of such metaclasses. A SOM programmer should find numerous applications for the techniques that are illustrated there.

Inheritance

One of the defining aspects of an object model is its characterization of inheritance. This section describes SOM's model for inheritance.

A class in SOM defines an implementation for objects that support a specific interface:

  • The interface defines the methods supported by objects of the class, and is specified using SOM IDL.
  • The implementation defines what instance variables implement an object's state and what procedures implement its methods.

New classes are derived (by subclassing) from previously existing classes through inheritance, specialization, and addition. Subclasses inherit interface from their parent classes: any method available on instances of a class is also available on instances of any class derived from it (either directly or indirectly). Subclasses also inherit implementation (the procedures that implement the methods) from their parent classes unless the methods are overridden (redefined or specialized). In addition, a subclass may introduce new instance methods and instance variables that will be inherited by other classes derived from it.

SOM also supports multiple inheritance. That is, a class may be derived from (and may inherit interface and implementation from) multiple parent classes. Note: Multiple inheritance is available only to SOM classes whose interfaces are specified in IDL, and not to SOM classes whose interfaces are specified in SOM's earlier interface definition language, OIDL. See Appendix B for information on how to automatically convert existing OIDL files to IDL.

It is possible under multiple inheritance to encounter potential conflicts or ambiguities with respect to inheritance. All multiple inheritance models must face these issues, and resolve the ambiguities in some way. For example, when multiple inheritance is allowed, it is possible that a class will inherit the same method or instance variable from different parents (because each of these parents has some common ancestor that introduces the method or instance variable). In this situation, a SOM subclass inherits only one implementation of the method or instance variable. (The implementation of an instance variable within an object is just the location where it is stored. The implementation of a method is a procedure pointer, stored within a method table.) The following illustration addresses the question of which method implementation would be inherited.

Consider this situation: Class "W" defines a method "foo", implemented by procedure "proc1". Class "W" has two subclasses, "X" and "Y". Subclass "Y" overrides the implementation of "foo" with procedure "proc2". Subclass "X" does not override "foo". In addition, classes "X" and "Y" share a common subclass, "Z". That is, the IDL interface statement for class "Z" lists its parents as "X" and "Y" in that order. (These relationships form a diamond shape, with class "W" at the top.)

The question is thus: which implementation of method "foo" does class "Z" inherit-procedure "proc1" defined by class "W", or procedure "proc2" defined by class "Y"? The procedure for performing inheritance that is defined by SOMClass resolves this ambiguity by using the left path precedence rule: when the same method is inherited from multiple ancestors, the procedure used to support the method is the one used by the leftmost ancestor from which the method is inherited. (The ordering of parent classes is determined by the order in which the class implementor lists the parents in the IDL specification for the class.)

Class "Z" inherits the implementation of method "foo" defined by class "W" (procedure "proc1"), rather than the implementation defined by class "Y" (procedure "proc2"), because "X" is the leftmost ancestor of "Z" from which the method "foo" is inherited. This rule may be interpreted as giving priority to classes whose instance interfaces are mentioned first in IDL interface definitions.

If a class implementor decides that the default inherited implementation is not appropriate (for example, procedure "proc2" is desired), then SOM IDL allows the class designer to select the parent whose implementation is desired. For more information concerning this approach, see the Select modifier, which is documented in the topic "Modifier statements" in Chapter 4, "SOM IDL and the SOM Compiler."

Note
Alternatively, an explicit metaclass for "Z" could be introduced to change the way methods are inherited. However, this would be a fairly serious step to take-it would also affect the semantics of inheritance for all of Z's descendant classes.

Another conflict that may arise with the use of multiple inheritance is when two ancestors of a class define different methods (in general, with different signatures) of the same name. For example, suppose Class "X" defines a method "bar" with type T1, and class "Y" defines a method "bar" with type T2. Class "Z" is derived from both "X" and "Y", and "Z" does not override method "bar".

This example illustrates a method name that is "overloaded"-that is, used to name two entirely different methods (note that overloading is completely unrelated to overriding). This is not necessarily a difficult problem to handle. Indeed, the run-time SOM API allows the construction of a class that supports the two different "bar" methods. (They are implemented using two different method-table entries, each of which is associated with its introducing class.)

However, the interface to instances of such classes can not be defined using IDL. IDL specifically forbids the definition of interfaces in which method names are overloaded. Furthermore, within SOM itself, the use of such classes can lead to anomalous behavior unless care is taken to avoid the use of name-lookup method resolution (discussed in the following section), since, in this case, a method name alone does not identify a unique method. For this reason, (statically declared) multiple-inheritance classes in SOM are currently restricted to those whose instance interfaces can be defined using IDL. Thus, the above example cannot be constructed with the aid of the SOM Compiler.

Method Resolution

Method resolution is the step of determining which procedure to execute in response to a method invocation. For example, consider this scenario:

  • Class "Dog" introduces a method "bark", and
  • A subclass of "Dog", called "BigDog", overrides "bark", and
  • A client program creates an instance of either "Dog" or "BigDog" (depending on some run-time criteria) and invokes method "bark" on that instance.

Method resolution is the process of determining, at run time, which method procedure to execute in response to the method invocation (either the method procedure for "bark" defined by "Dog", or the method procedure for "bark" defined by "BigDog"). This determination depends on whether the receiver of the method (the object on which it is invoked) is an instance of "Dog" or "BigDog" (or perhaps depending on some other criteria).

SOM allows class implementors and client programs considerable flexibility in deciding how SOM performs method resolution. In particular, SOM supports three mechanisms for method resolution, described in order of increased flexibility and increased computational cost: offset resolution, name-lookup resolution, and dispatch-function resolution.

Offset resolution

When using SOM's C and C++ language bindings, offset resolution is the default way of resolving methods, because it is the fastest. For those familiar with C++, it is roughly equivalent to the C++ "virtual function" concept.

Although offset resolution is the fastest technique for method resolution, it is also the most constrained. Specifically, using offset resolution requires these constraints:

  • The name of the method to be invoked must be known at compile time,
  • The name of the class that introduces the method must be known at compile time (although not necessarily by the programmer), and
  • The method to be invoked must be part of the introducing class's static (IDL) interface definition.

To perform offset method resolution, SOM first obtains a method token from a global data structure associated with the class that introduced the method. This data structure is called the ClassData structure. It includes a method token for each method the class introduces. The method token is then used as an "index" into the receiver's method table, to access the appropriate method procedure. Because it is known at compile time which class introduces the method and where in that class's ClassData structure the method's token is stored, offset resolution is quite efficient. The cost of offset method resolution is currently about twice the cost of calling a C function using a pointer loaded with the function address.

An object's method table is a table of pointers to the procedures that implement the methods that the object supports. This table is constructed by the object's class and is shared among the class instances. The method table built by class (for its instances) is referred to as the class's instance method table. This is useful terminology, since, in SOM, a class is itself an object with a method table (created by its metaclass) used to support method calls on the class.

Usually, offset method resolution is sufficient; however, in some cases, the more flexible name-lookup resolution is required.

Name-lookup resolution

Name-lookup resolution is similar to the method resolution techniques employed by Objective-C and Smalltalk. It is currently about five times slower than offset resolution. It is more flexible, however. In particular, name-lookup resolution, unlike offset resolution, can be used when:

  • The name of the method to be invoked isn't known until run time, or
  • The method is added to the class interface at run time, or
  • The name of the class introducing the method isn't known until run time.

For example, a client program may use two classes that define two different methods of the same name, and it might not be known until run time which of the two methods should be invoked (because, for example, it will not be known until run time which class's instance the method will be applied to).

Name-lookup resolution is always performed by a class, so it requires a method call. (Offset resolution, by contrast, requires no method calls.) To perform name-lookup method resolution, the class of the intended receiver object obtains a method procedure pointer for the desired method that is appropriate for its instances. In general, this will require a name-based search through various data structures maintained by ancestor classes.

Offset and name-lookup resolution achieve the same net effect (that is, they select the same method procedure); they just achieve it differently (via different mechanisms for locating the method's method token). Offset resolution is faster, because it does not require searching for the method token, but name-lookup resolution is more flexible.

When defining (in SOM IDL) the interface to a class of objects, the class implementor can decide, for each method, whether the SOM Compiler will generate usage bindings that support name-lookup resolution for invoking the method. Regardless of whether this is done, however, application programs using the class can have SOM use either technique, on a per-method-call basis. Chapter 3, "Using SOM Classes in Client Programs," describes how client programs invoke methods.

Dispatch-function resolution

Dispatch-function resolution is the slowest, but most flexible, of the three method-resolution techniques. Dispatch functions permit method resolution to be based on arbitrary rules associated with the class of which the receiving object is an instance. Thus, a class implementor has complete freedom in determining how methods invoked on its instances are resolved.

With both offset and name-lookup resolution, the net effect is the same-the method procedure that is ultimately selected is the one supported by the class of which the receiver is an instance. For example, if the receiver is an instance of class "Dog", then Dog's method procedure will be selected; but if the receiver is an instance of class "BigDog", then BigDog's method procedure will be selected.

By contrast, dispatch-function resolution allows a class of instances to be defined such that the method procedure is selected using some other criteria. For example, the method procedure could be selected on the basis of the arguments to the method call, rather than on the receiver. For more information on dispatch-function resolution, see the description and examples for the somDispatch and somOverrideMTab methods in the SOMobjects Developer Toolkit: Programmers Reference Manual.

Customizing Method Resolution

Customizing method resolution requires the use of metaclasses that override SOMClass methods. This is not recommended without use of the Cooperative Metaclass that guarantees correct operation of SOMobjects in conjunction with such metaclasses. SOMobjects users who require this functionality should request access to the experimental Cooperative Metaclass used to implement the SOMobjects Metaclass Framework. Metaclasses implemented using the Cooperative Metaclass may have to be reprogrammed in the future when SOMobjects introduces an oficially supported Cooperative Metaclass.

The four kinds of SOM methods

SOM supports four different kinds of methods: static methods, nonstatic methods, dynamic methods, and direct-call procedures. The following paragraphs explain these four method categories and the kinds of method resolution available for each.

Static methods

These are similar in concept to C++ virtual functions. Static methods are normally invoked using offset resolution via a method table, as described above, but all three kinds of method resolution are applicable to static methods. Each different static method available on an object is given a different slot in the object's method table. When SOMobjects Toolkit language bindings are used to implement a class, the SOM IDL method modifier can be specified to indicate that a given method is static; however, this modifier is rarely used since it is the default for SOM methods.

Static methods introduced by a class can be overridden (redefined) by any descendant classes of the class. When SOMobjects language bindings are used to implement a class, the SOM IDL override modifier is specified to indicate that a class overrides a given inherited method. When a static method is resolved using offset resolution, it is not important which interface is accessing the method - the actual class of the object determines the method procedure that is selected.

Note
All SOM IDL modifiers are described in the topic "Modifier statements" in Chapter 4, "SOM IDL and the SOM Compiler."

Nonstatic methods

These methods are similar in concept to C++ nonstatic member functions (that is, C++ functions that are not virtual member functions and are not static member functions). Nonstatic methods are normally invoked using offset resolution, but all three kinds of method resolution are applicable to nonstatic methods. When the SOMobjects language bindings are used to implement a class, the SOM IDL nonstatic modifier is used to indicate that a given method is nonstatic.

Like static methods, nonstatic methods are given individual positions in method tables. However, nonstatic methods cannot be overridden. Instead, descendants of a class that introduces a nonstatic method can use the SOM IDL reintroduce modifier to "hide" the original nonstatic method with another (nonstatic or static) method of the same name. When a nonstatic method is resolved, selection of the specific method procedure is determined by the interface that is used to access the method.

Dynamic methods

These methods are not declared when specifying an object interface using IDL. Instead, they are registered with a class object at run time using the method somAddDynamicMethod. Because there is no way for SOM to know about dynamic methods before run time, offset resolution is not available for dynamic methods. Only name-lookup or dispatch-function resolution can be used to invoke dynamic methods. Dynamic methods cannot be overridden.

Direct-call procedures

These are similar in concept to C++ static member functions. Direct-call procedures are not given positions in SOM method tables, but are accessed directly from a class's ClassData structure. Strictly speaking, none of the previous method-resolution approaches apply for invoking a direct-call procedure, although SOMobjects language bindings provide the same invocation syntax for direct-call procedures as for static or nonstatic methods. Direct-call procedures cannot be overridden, but they can be reintroduced. When SOMobjects language bindings are used to implement a class, the SOM IDL procedure modifier is used to indicate that a given method is a direct-call procedure. Note: Methods having the procedure modifier cannot be invoked remotely using DSOM.

Implementing SOM Classes

The interface to a class of objects contains the information that a client must know to use an object - namely, the signatures of its methods and the names of its attributes. The interface is described in a formal language independent of the programming language used to implement the object's methods. In SOM, the formal language used to define object interfaces is the Interface Definition Language (described in Chapter 4, "SOM IDL and the SOM Compiler").

The implementation of a class of objects (that is, the procedures that implement the methods and the instance variables that store an object's state) is written in the implementor's preferred programming language. This language can be object-oriented (for instance, C++) or procedural (for instance, C).

A completely implemented class definition, then, consists of two main files:

  • An IDL specification of the interface to instances of the class - the interface definition file (or .idl file) and
  • Method procedures written in the implementor's language of choice - the implementation file.

The SOM Compiler provides the link between those two files: To assist users in implementing classes, the SOM Compiler produces a template implementation file - a type-correct guide for how the implementation of a class should look. Then, the class implementor modifies this template file to fully implement the class's methods. That process is the subject of the remainder of this chapter.

The SOM Compiler can also update the implementation file to reflect changes subsequently made to a class's interface definition file (the .idl file). These incremental updates include adding new stub procedures, adding comments, and changing method prototypes to reflect changes made to the method definitions in the IDL specification. These updates to the implementation file, however, do not disturb existing code in the method procedures. These updates are discussed further in "Running incremental updates of the implementation template file" later in this section.

For C programmers, the SOM Compiler generates a <filestem>.c file. For C++ programmers, the SOM Compiler generates a <filestem>.C file (for AIX) or a <filestem>.cpp file (for OS/2). To specify whether the SOM Compiler should generate a C or C++ implementation template, set the value of the SMEMIT environment variable, or use the -s option when running the SOM Compiler. (See "The SOM Compiler" in Chapter 4, "SOM IDL and the SOM Compiler.")

Note
As this chapter describes, a SOM class can be implemented by using C++ to define the instance variables introduced by the class and to define the procedures that implement the overridden and introduced methods of the class. Be aware, however, that the C++ class defined by the C++ usage bindings for a SOM class (described in Chapter 3) cannot be subclassed in C++ to create new C++ or SOM classes. [The reason why the C++ implementation of a SOM class involves the definition of C++ procedures (not C++ methods) to support SOM methods is that there is no language-neutral way to call a C++ method. Only C++ code can call C++ methods, and this calling code must be generated by the same compiler that generates the method code. In contrast, the method procedures that implement SOM methods must be callable from any language, without knowledge on the part of the object client as to which language is used to implement the resolved method procedure]

The implementation template

Consider the following IDL description of the "Hello" class:

#include <somobj.idl>

interface Hello : SOMObject
{
    void sayHello();
    // This method outputs the string "Hello, World!".
};

From this IDL description, the SOM Compiler generates the following C implementation template, hello.c (a C++ implementation template, hello.C or hello.cpp, is identical except that the #included file is <hello.xih> rather than <hello.ih>):

#define Hello_Class_Source
#include <hello.ih>

/*
 *  This method outputs the string "Hello, World!".
 */

SOM_Scope void   SOMLINK sayHello(Hello somSelf, Environment *ev)
{
    /* HelloData *somThis = HelloGetData(somSelf); */
    HelloMethodDebug("Hello","sayHello");
}

The first line defines the "Hello_Class_Source" symbol, which is used in the SOM-generated implementation header files for C to determine when to define various functions, such as "HelloNewClass." For interfaces defined within a module, the directive "#define <className>_Class_Source" is replaced by the directive "#define SOM_Module_<moduleName>_Source".

The second line (#include <hello.ih> for C, or #include <hello.xih> for C++) includes the SOM-generated implementation header file. This file defines a struct holding the class's instance variables, macros for accessing instance variables, macros for invoking parent methods, and so forth.

Stub procedures for methods

For each method introduced or overridden by the class, the implementation template includes a stub procedure-a procedure that is empty except for an initialization statement, a debugging statement, and possibly a return statement. The stub procedure for a method is preceded by any comments that follow the method's declaration in the IDL specification.

For method "sayHello" above, the SOM Compiler generates the following prototype of the stub procedure:

SOM_Scope void SOMLINK sayHello(Hello somSelf, Environment *ev)

The "SOM_Scope" symbol is defined in the implementation header file as either "extern" or "static," as appropriate. The term "void" signifies the return type of method "sayHello". The "SOMLINK" symbol is defined by SOM; it represents the keyword needed to link to the C or C++ compiler, and its value is system-specific. Using the "SOMLINK" symbol allows the code to work with a variety of compilers without modification.

Following the "SOMLINK" symbol is the name of the procedure that implements the method. If no functionprefix modifier has been specified for the class, then the procedure name is the same as the method name. If a functionprefix modifier is in effect, then the procedure name is generated by prepending the specified prefix to the method name. For example, if the class definition contained the following statement:

  functionprefix = xx_;

then the prototype of the stub procedure for method "sayHello" would be:

  SOM_Scope void SOMLINK xx_sayHello(Hello somSelf, Environment *ev)

The functionprefix can not be

  <classname>_

since this is used in method invocation macros defined by the C usage bindings.

Following the procedure name is the formal parameter list for the method procedure. Because each SOM method always receives at least one argument (a pointer to the SOM object that responds to the method), the first parameter name in the prototype of each stub procedure is called somSelf. (The macros defined in the implementation header file rely on this convention.) The somSelf parameter is a pointer to an object that is an instance of the class being implemented (here, class "Hello") or an instance of a class derived from it.

Unless the IDL specification of the class included the callstyle=oidl modifier, then the formal parameter list will include one or two additional parameters before the parameters declared in the IDL specification: an (Environment *ev) input/output parameter, which permits the return of exception information, and, if the IDL specification of the method includes a context specification, a (Context *ctx) input parameter. These parameters are prescribed by the CORBA standard. For more information on using the Environment and Context parameters, see the section entitled "Exceptions and error handling" in Chapter 3, "Using SOM Classes in Client Programs," and the book The Common Object Request Broker: Architecture and Specification, published by Object Management Group and X/Open.

The first statement in the stub procedure for method "sayHello" is the statement:

  /* HelloData *somThis = HelloGetData(somSelf); */

This statement is enclosed in comments only when the class does not introduce any instance variables. The purpose of this statement, for classes that do introduce instance variables, is to initialize a local variable (somThis) that points to a structure representing the instance variables introduced by the class. The somThis pointer is used by the macros defined in the "Hello" implementation header file to access those instance variables. (These macros are described below.) In this example, the "Hello" class introduces no instance variables, so the statement is commented out. If instance variables are later added to a class that initially had none, then the comment characters can be removed if access to the variable is required.

The "HelloData" type and the "HelloGetData" macro used to initialize the somThis pointer are defined in the implementation header file. Within a method procedure, class implementers can use the somThis pointer to access instance data, or they can use the convenience macros defined for accessing each instance variable, as described below.

To implement a method so that it can modify a local copy of an object's instance data without affecting the object's real instance data, declare a variable of type <className>Data (for example, "HelloData") and assign to it the structure that somThis points to; then make the somThis pointer point to the copy. For example:

HelloData myCopy = *somThis;
somThis = &myCopy;

Next in the stub procedure for method "sayHello" is the statement:

HelloMethodDebug("Hello", "sayHello");

This statement facilitates debugging. The "HelloMethodDebug" macro is defined in the implementation header file. It takes two arguments, a class name and a method name. If debugging is turned on (that is, if global variable SOM_TraceLevel is set to one in the calling program), the macro produces a message each time the method procedure is entered. (See the next Chapter 3, "Using SOM Classes in Client Programs," for information on debugging with SOM.)

Debugging can be permanently disabled (regardless of the setting of the SOM_TraceLevel setting in the calling program) by redefining the <className>MethodDebug macro to be SOM_NoTrace(c,m) following the #include directive for the implementation header file. (This can yield a slight performance improvement.) For example, to permanently disable debugging for the "Hello" class, insert the following lines in the hello.c implementation file following the line "#include hello.ih" (or "#include hello.xih," for classes implemented in C++):

#undef HelloMethodDebug
#define HelloMethodDebug(c,m) SOM_NoTrace(c,m)

The way in which the stub procedure ends is determined by whether the method is a new or an overriding method.

  • For non-overriding (new) methods, the stub procedure ends with a return statement (unless the return type of the method is void). The class implementer should customize this return statement.
  • For overriding methods, the stub procedure ends by making a "parent method call" for each of the class's parent classes. If the method has a return type that is not void, the last of these parent method calls is returned as the result of the method procedure. The class implementer can customize this return statement if needed (for example, if some other value is to be returned, or if the parent method calls should be made before the method procedure's own processing). See the next section for a discussion of parent method calls.

If a classinit modifier was specified to designate a user-defined procedure that will initialize the "Hello" class object, as in the statement:

classinit = HInit;

then the implementation template file would include the following stub procedure for "HInit", in addition to the stub procedures for Hello's methods:

void  SOMLINK HInit(SOMClass *cls)
{

}

This stub procedure is then filled in by the class implementer. If the class definition specifies a functionprefix modifier, the classinit procedure name is generated by prepending the specified prefix to the specified classinit name, as with other stub procedures.

Extending the implementation template

To implement a method, add code to the body of the stub procedure. In addition to standard C or C++ code, class implementers can also use any of the functions, methods, and macros SOM provides for manipulating classes and objects. Chapter 3, "Using SOM Classes in Client Programs," discusses these functions, methods, and macros.

In addition to the functions, methods, and macros SOM provides for both class clients and class implementers, SOM provides two facilities especially for class implementers. They are for (1) accessing instance variables of the object responding to the method and (2) making parent method calls, as follows.

Accessing internal instance variables

To access internal instance variables, class implementers can use either of the following forms:

_variableName
somThis->variableName

To access internal instance variables "a", "b", and "c", for example, the class implementer could use either _a, _b, and _c, or somThis->a, somThis->b, and somThis->c. These expressions can appear on either side of an assignment statement. The somThis pointer must be properly initialized in advance using the <className>GetData procedure, as shown above.

Instance variables can be accessed only within the implementation file of the class that introduces the instance variable, and not within the implementation of subclasses or within client programs. (To allow access to instance data from a subclass or from client programs, use an attribute rather than an instance variable to represent the instance data.) For C++ programmers, the _variableName form is available only if the macro VARIABLE_MACROS is defined (that is, #define VARIABLE_MACROS) in the implementation file prior to including the .xih file for the class.

Making parent method calls

In addition to macros for accessing instance variables, the implementation header file that the SOM Compiler generates also contains definitions of macros for making "parent method calls." When a class overrides a method defined by one or more of its parent classes, often the new implementation simply needs to augment the functionality of the existing implementation(s). Rather than completely re-implementing the method, the overriding method procedure can conveniently invoke the procedure that one or more of the parent classes uses to implement that method, then perform additional computation (redefinition) as needed. The parent method call can occur anywhere within the overriding method. (See Example 3 of the SOM IDL tutorial.)

The SOM-generated implementation header file defines the following macros for making parent-method calls from within an overriding method:

<className>_parent_<parentClassName>_<methodName>

(for each parent class of the class overriding the method), and

<className>_parents_<methodName>.

For example, given class "Hello" with parents "File" and "Printer" and overriding method somInit (the SOM method that initializes each object), the SOM Compiler defines the following macros in the implementation header file for "Hello":

  Hello_parent_Printer_somInit
  Hello_parent_File_somInit
  Hello_parents_somInit

Each macro takes the same number and type of arguments as <methodName>. The <className>_parent_<parentClassName>_<methodName> macro invokes the implementation of <methodName> inherited from <parentClassName>. Hence, using the macro "Hello_parent_File_somInit" invokes the File's implementation of somInit.

The <className>_parents_< methodName> macro invokes the parent method for each parent of the child class that supports <methodName>. That is, "Hello_parents_somInit" would invoke File's implementation of somInit, followed by Printer's implementation of somInit. The <className>_parents_<methodName> macro is redefined in the binding file each time the class interface is modified, so that if a parent class is added or removed from the class definition, or if <methodName> is added to one of the existing parents, the macro <className>_parents_<methodName > will be redefined appropriately.

Converting C++ classes to SOM classes

For C++ programmers implementing SOM classes, SOM provides a macro that simplifies the process of converting C++ classes to SOM classes. This macro allows the implementation of one method of a class to invoke another new or overriding method of the same class on the same receiving object by using the following shorthand syntax:

  _methodName(arg1, arg2, ...)

For example, if class X introduces or overrides methods m1 and m2, then the C++ implementation of method m1 can invoke method m2 on its somSelf argument using _m2(arg, arg2, ...), rather than somSelf->m2(arg1, arg2, ...), as would otherwise be required. (The longer form is also available.) Before the shorthand form in the implementation file is used, the macro METHOD_MACROS must be defined (that is, use #define METHOD_MACROS) prior to including the xih file for the class.

Running incremental updates of the implementation template file

Refining the .idl file for a class is typically an iterative process. For example, after running the IDL source file through the SOM Compiler and writing some code in the implementation template file, the class implementer realizes that the IDL class interface needs another method or attribute, a method needs a different parameter, or any such changes.

As mentioned earlier, the SOM Compiler (when run using the c or xc emitter) assists in this development by reprocessing the .idl file and making incremental updates to the current implementation file. This modify-and-update process may in fact be repeated several times before the class declaration becomes final. Importantly, these updates do not disturb existing code for the method procedures. Included in the incremental update are these changes:

  • Stub procedures are inserted into the implementation file for any new methods added to the .idl file.
  • New comments in the .idl file are transferred to the implementation file, reformatted appropriately.
  • If the interface to a method has changed, a new method procedure prototype is placed in the implementation file. As a precaution, however, the old prototype is also preserved within comments. The body of the method procedure is left untouched (as are the method procedures for all methods).
  • Similarly left intact are preprocessor directives, data declarations, constant declarations, non-method functions, and additional comments-in essence, everything else in the implementation file.

Some changes to the .idl file are not reflected automatically in the implementation file after an incremental update. The class implementer must manually edit the implementation file after changes such as these in the .idl file:

  • Changing the name of a class or a method.
  • Changing the parents of a class (see also "If you change the parents of a class..." later in this topic).
  • Changing a functionprefix class modifier statement.
  • Changing the content of a passthru statement directed to the implementation (.c, .C, or cpp) file. As previously emphasized, however, passthru statements are primarily recommended only for placing #include statements in a binding file (.ih, xih, .h, or .xh file) used as a header file in the implementation file or in a client program.
  • If the class implementer has placed "forward declarations" of the method procedures in the implementation file, those are not updated. Updates occur only for method prototypes that are part of the method procedures themselves.

Considerations to ensure that updates work

To ensure that the SOM Compiler can properly update method procedure prototypes in the implementation file, class implementers should avoid editing changes such as the following:

  • A method procedure name should not be enclosed in parentheses in the prototype.
  • A method procedure name must appear in the first line of the prototype, excluding comments and white space. Thus, a new line must not be inserted before the procedure name.

Error messages occur while updating an existing implementation file if it contains syntax that is not ANSI C. For example, "old style" method definitions such as the example on the left generate errors:

Invalid "old" syntax Required ANSI C void foo(x) void foo(short x) { short x; ... { } ... }

Similarly, error messages occur if anything in the .idl file would produce an implementation file that is not syntactically valid for C/C++ (such as nested comments). If update errors occur, either the .idl file or the implementation file may be at fault. One way to track down the problem is to run the implementation file through the C/C++ compiler. Or, move the existing implementation file to another directory, generate a completely new one from the .idl file, and then run it through the C/C++ compiler. One of these steps should pinpoint the error, if the compiler is strict ANSI.

Conditional compilation (using #if and #ifdef directives) in the implementation file can be another source of errors, because the SOM Compiler does not invoke the preprocessor (it simply recognizes and ignores those directives). The programmer should be careful when using conditional compilation, to avoid a situation such as shown below; here, with apparently two open braces and only one closing brace, the c or xc emitter would report an unexpected end-of-file:

Invalid syntax Required matching braces

  1. ifdef FOOBAR #ifdef FOOBAR

{ { ... ...

  1. else }

{ #else ... {

  1. endif ...

} }

 #endif 

If you change the parents of a class...

Because the implementation-file emitters never change any existing code within a previously generated implementation file, changing the parents of a class requires extremely careful attention by the programmer. For example, for overridden methods, changing a class's parents may invalidate previous parent-method calls provided by the template, and require the addition of new parent-method calls. Neither of these issues is addressed by the incremental update of previously generated method-procedure templates.

The greatest danger from changing the parents of a class, however, concerns the ancestor-initializer calls provided in the stub procedures for initializer methods. (For further information on ancestor initializer calls, see "Initializing and Uninitializing Objects" later in this chapter.) Unlike parent-method calls, ancestor-initializer calls are not optional - they must be made t all classes specified in a directinitclasses modifier, and these calls should always include the parents of the class (the default when no directinitclasses modifier is given). When the parents of a class are changed, however, the ancestor-initializer calls (which must be made in a specific order) are not updated.

The easiest way to deal with this problem is to change the method name of the previously generated initializer stub procedure in the implementation template file. Then, the SOM Compiler can correctly generate a completely new initializer stub procedure (while ignoring the renamed procedure). Once this is done, your customization code from the renamed initializer procedure can be "merged" into the newly generated one, after which the renamed initializer procedure can be deleted.

Compiling and linking

After you fill in the method stub procedures, the implementation template file can be compiled and linked with a client program as follows. (In these examples, the environment variable SOMBASE represents the directory in which SOM has been installed.)

Note
If you are building an application that uses a combination of C and C++ compiled object modules, then the C++ linker must be used to link them.

For AIX: When the client program (main.c) and the implementation file (hello.c) are written in C:

  > xlc -I. -I$SOMBASE/include -o hello main.c hello.c -L$SOMBASE/lib -lsomtk

When the client program and the implementation file are written in C++:

  > xlC -I. -I$SOMBASE/include -o hello main.C hello.C -L$SOMBASE/lib -lsomtk

For OS/2: When the client program (main.c) and the implementation file (hello.c) are in C:

  > set LIB=%SOMBASE%\lib;%LIB%
  > icc -I. -I%SOMBASE%\include -Fe hello main.c hello.c somtk.lib

When the client program and the implementation file are written in C++:

  > set LIB=%SOMBASE%\lib;%LIB%
  > icc -I. -I%SOMBASE%\include -Fe hello main.cpp hello.cpp somtk.lib

If the class definition (in the .idl file) changes, run the SOM Compiler again. This will generate new header files, and it will update the implementation file to include any:

  • New comments,
  • Stub procedures for any new methods, and
  • Revised method procedure prototypes for methods whose signatures have been changed in the .idl file.

After rerunning the SOM Compiler, add to the implementation file the code for any newly added method procedures, and recompile the implementation file with the client program.

Initializing and Uninitializing Objects

This section discusses the initialization and uninitialization of SOM objects. Subsequent topics introduce the methods and capabilities that the SOMobjects Developer Toolkit provides to facilitate this.

Object creation is the act that enables the execution of methods on an object. In SOM, this means storing a pointer to a method table into a word of memory. This single act converts raw memory into an (uninitialized) SOM object that starts at the location of the method table pointer.

Object initialization, on the other hand, is a separate activity from object creation in SOM. Initialization is a capability supported by certain methods available on an object. An object's class determines the implementation of the methods available on the object, and thus determines its initialization behavior.

The instance variables encapsulated by a newly created object must be brought into a consistent state before the object can be used. This is the purpose of initialization methods. Because, in general, every ancestor of an object's class contributes instance data to an object, it is appropriate that each of these ancestors contribute to the initialization of the object.

SOM thus recognizes initializers as a special kind of method. One advantage of this approach is that special metaclasses are not required for defining constructors (class methods) that take arguments. Furthermore, a class can define multiple initializer methods, thus enabling its different objects to be initialized supporting different characteristics or capabilities. This results in simpler designs and more efficient programs.

The SOMobjects Toolkit provides an overall framework that class designers can easily exploit in order to implement default or customized initialization of SOM objects. This framework is fully supported by the SOM Toolkit emitters that produce the implementation template file. The following sections describe the declaration, implementation, and use of initializer (and uninitializer) methods.

Important
All code written prior to SOMobjects Release 2.1 using documented guidelines for the earlier initialization approach based on the somInit method (as well as all existing class binaries) continues to be fully supported and useful.

Initializer methods

As noted above, in the SOMobjects Toolkit each ancestor of an object contributes to the initialization of that object. Initialization of an object involves a chain of ancestor-method calls that, by default, are automatically determined by the SOM Compiler emitters. The SOMobjects framework for initialization of objects is based on the following approach:

  1. SOMobjects recognizes initializers as a special kind of method, and supports a special mechanism for ordering the execution of ancestor-initializer method procedures. The SOMObject class introduces an initializer method, somDefaultInit that uses this execution mechanism.
  2. The SOM Compiler's emitters provide special support for methods that are declared as initializers in the .idl file. To supplement the somDefaultInit method, SOM class designers can also declare additional initializers in their own classes.

Two SOM IDL modifiers are provided for declaring initializer methods and controlling their execution, init and directinitclasses:

  • The init modifier is required in order to designate a given method is a initializer; that is, to indicate that the method both uses and supports the object-initialization protocol described here.
  • The directinitclasses modifier can be used to control the order of execution of initializer method procedures provided by the different ancestors of the class of an object.
  • For full definitions of init and directinitclasses, see the topic "Modifier statements" in Chapter 4,"SOM IDL and the SOM Compiler."

Every SOM class has a list that defines (in sequential order) the ancestor classes whose initializer method procedures the class should invoke. If a class's IDL does not specify an explicit directinitclasses modifier, the default for this list is simply the class's parents - in left-to-right order.

Using the directinitclasses list and the actual run-time class hierarchy above itself, each class inherits from SOMClass the ability to create a data structure of type somInitCtrl. This structure is used to control the execution of initializers. Moreover, it represents a particular visit-ordering that reaches each class in the transitive closure of directinitclasses exactly once. To initialize a given object, this visit-ordering occurs as follows: While recursively visiting each ancestor class whose initializer method procedure should be run, SOMobjects first runs the initializer method procedures of all of that class's directinitclasses if they have not already been run by another class's initializers, with ancestor classes always taken in left-to-right order.

The code that deals with the somInitCtrl data structure is generated automatically within the implementation bindings for a class, and need not concern a class implementor.

When an instance of a given class (or some descendant class) is initialized, only one of the given class's initializers will be executed, and this will happen exactly once (under control of the ordering determined by the class of the object being initialized).

The somInitCtrl structure solves a problem originally created by the addition of multiple inheritance to SOMobjects 2.0. With multiple inheritance, any class can appear at the top of a multiple inheritance diamond. Previously, whenever this happened, the class could easily receive multiple initialization calls. In the current version of the SOMobjects Toolkit, however, the somInitCtrl structure prevents this from happening.

Declaring new initializers in SOM IDL

When defining SOMobjects classes, programmers can easily declare and implement new initializers. Classes can have as many initializers as desired, and subclassers can invoke whichever of these they want. When introducing new initializers, developers must adhere to the following rules:

  • All initializer methods take a somInitCtrl data structure as an initial inout parameter (its type is defined in the SOMobjects header file somapi.h), and
  • All initializers return void.

Accordingly, the somDefaultInit initializer introduced by SOMObject takes a somInitCtrl structure as its (only) argument, and returns void. Here is the IDL syntax for this method, as declared in somobj.idl:

void  somDefaultInit (inout somInitCtrl ctrl);

When introducing a new initializer, it is also necessary to specify the init modifier in the implementation section. The init modifier is what tells emitters that the new method is actually an initializer, so the method can be properly supported from the language bindings. As described below, this support includes the generation of special initializer stub procedures in the implementation template file, as well as bindings containing ancestor-initialization macros and object constructors that invoke the class implementor's new initializers.

It is a good idea to begin the names of initializer methods with the name of the class (or some other string that can be unique for the class). This is important because all initializers available on a class must be newly introduced by that class (that is, you cannot override initializers - except for somDefaultInit). Using a class-unique name means that subclasses will not be unnecessarily constrained in their choice of initializer names.

Here are two classes that introduce new initializers:

interface Example1 : SOMObject
{
   void Example1_withName (inout somInitCtrl ctrl,in string name);
   void Example1_withSize (inout somInitCtrl ctrl,in long size);
   void Example1_withNandS(inout somInitCtrl ctrl,in string name,
                                                   in long size);
        implementation {
                releaseorder: Example1_withName,
                              Example1_withSize,
                              Example1_withNandS; 
                somDefaultInit: override, init;
                somDestruct: override;
                Example1_withName: init;
                Example1_withSize: init;
                Example1_withNandS: init;
        };
};

interface Example2 : Example1
{
   void Example2_withName(inout somInitCtrl ctrl, in string name);
   void Example2_withSize(inout somInitCtrl ctrl, in long size);
        implementation {
                releaseorder: Example2_withName,
                              Example2_withSize; 
                somDefaultInit: override, init;
                somDestruct: override;
                Example2_withName: init;
                Example2_withSize: init;
        };
};

Here, interface "Example1" declares three new initializers. Notice the use of inout somInitCtrl as the first argument of each initializer, and also note that the init modifier is used in the implementation section. These two things are required to declare initializers. Any number of initializers can be declared by a class. "Example2" declares two initializers.

"Example1" and "Example2" both override the somDefaultInit initializerThis initializer method is introduced by SOMObject and is special for two resons: First, somDefaultInit is the only initializer that can be overridden And, second, SOMobjects arranges that this initializer will always be available on any class (as further explained below).

Historically in the SOMobjects Toolkit, object#initialization methods by default have invoked the somInit method, which class implementors could override to customize initialization as appropriate. SOMobjects continues to support this approach, so that existing code (and class binaries) will execute correctly. However, the somDefaultInit method is now the preferred form of initialization because it offers greatly improved efficiency.

Even if no specialized initialization is needed for a class, you should still override the somDefaultInit method in the interest of efficiency. If you do not override somDefaultInit, then a generic (and therefore less efficient) somDefaultInit method procedure will be used for your class. This generic method procedure first invokes somDefaultInit on the appropriate ancestor classes. Then (for consistency with earlier versions of SOMobjects), it checks to determine if the class overrides somInit and, if so, calls any customized somInit code provided by the class.

When you override somDefaultInit, the emitter's implementation template file will include a stub procedure similar to those used for other initializers, and you can fill it in as appropriate (or simply leave it as is). Default initialization for your class will then run much faster than with the generic method procedure. Examples of initializer stub procedures (and customizations) are given below.

In summary, the initializers available for any class of objects are somDefaultInit (which you should always override) plus any new initializers explicitly declared by the class designer. Thus, "Example1" objects may be initialized using any of four different initializers (the three that are explicitly declared, plus somDefaultInit). Likewise, there are three initializers for the "Example2" objects. Some examples of using initializers are provided below.

Considerations re: 'somInit' initialization from earlier SOM releases

To re-emphasize: All code written prior to SOMobjects Release 2.1 using documented guidelines for the earlier initialization approach based on the somInit method (as well as all existing class binaries) continues to be fully supported and useful.

Prior to SOMobjects 2.1, initialization was done with initializer methods that would simply "chain" parent-method calls upward, thereby allowing the execution of initializer method procedures contributed by all ancestors of an object's class. This chaining of initializer calls was not supported in any special way by the SOM API. Parent-method calls are simply one of the possible idioms available to users of OOP in SOM, easily available to a SOM class designer as a result of the support provided by the SOMobjects Toolkit emitters for parent-method calls.

So, SOM did not constrain initialization to be done in any particular way or require the use of any particular ordering of the method procedures of ancestor classes. But, SOM did provide an overall framework that class designers could easily utilize in order to implement default initialization of SOM objects. This framework is provided by the somInit object-initializatio method introduced by the SOMobject class and supported by the SOM Toolkit emitters. The emitters create an implementation template file with stub procedures for overridden methods that automatically chain parent-method calls upward through parent classes. Many of the class methods that perform object creation called somInit automatically.

Note
These will now call somDefaultInit, which in turn calls somInit for legacy code, as described in the previous topic.

Because it takes no arguments, somInit best served the purpose of a default initializer. SOM programmers also had the option of introducing additional "non-default" initialization methods that took arguments. In addition, by using metaclasses, they could introduce new class methods as object constructors that first create an object (generally using somNewNoInit.) and then invoke some non-default initializer on the new object.

For a number of reasons, the somInit framework has been augmented by recognizing initializers special kind of method in SOMobjects. One advantage of this approach is that special metaclasses are no longer required for defining constructors that take arguments. Instead, because the init modifier identifies initializers, usage-binding emitters can now provide these constructors. This results in simpler designs and more efficient programs.

Although somDefaultInit replaces somInit as the no-argument initializer used for SOM objects, all previous use of somInit is still supported by the SOMobjects Developers Toolkit on AIX, OS/2 and 16-bit Windows. You may continue to use somInit on these systems if you like, although this is somewhat less efficient than using somDefaultInit.

However, you cannot use both methods. In particular, if a class overrides both somDefaultInit and somInit, its somInit code will never be executed. It is recommended that you always override somDefaultInit for object initialization. For one thing, it is likely that when SOMobjects is ported to new systems, somInit (and somUninit) may not be supported on those systems. Thus, code written using these (obsolete) methods will be less portable.

Implementing initializers

When new initializers are introduced by a class, as in the preceding examples, the implementation template file generated by the SOM Toolkit C and C++ emitters automatically contains an appropriate stub procedure for each initializer method, for the class implementor's use. The body of an initializer stub procedure consists of two main sections:

  • The first section performs calls to ancestors of the class to invoke their initializers.
  • The second section is used by the programmer to perform any "local" initializations appropriate to the instance data of the class being defined.

In the first section, by default, the parents of the new class are the ancestors whose initializers are called. When something else is desired, the IDL directinitclasses modifier can be used to explicitly designate the ancestors whose initializer methods should be invoked by a new class's initializers.

Important: Under no circumstances can the number or the ordering of ancestor initializer calls in the first section of an initializer stub procedure be changed. The control masks used by initializers are based on these orderings. (If you want to change the number or ordering of ancestor initializer calls, you must use the directinitclasses modifier.) The ancestor initializer calls themselves can be modified as described below.

Each call to an ancestor initializer is made using a special macro (much like a parent call) that is defined for this purpose within the implementation bindings. These macros are defined for all possible ancestor initialization calls. Initially, an initializer stub procedure invokes the default ancestor initializers provided by somDefaultInit. However, a class implementor can replace any of these calls with a different initializer call, as long as it calls the same ancestor (see the example in the next topic). Non-default initializer calls generally take other arguments in addition to the control argument.

In the second section of an initializer stub procedure, the programmer provides any class-specific code that may be needed for initialization. For example, the "Example2_withName" stub procedure is shown below. As with all stub procedures produced by the SOMobjects implementation-template emitters, this code requires no modification to run correctly.

SOM_Scope void SOMLINK Example2_withName(Example2 *somSelf,
                                         Environment *ev,
                                         somInitCtrl* ctrl,
                                         string name)
{
    Example2Data *somThis; /* set by BeginInitializer */
    somInitCtrl globalCtrl;
    somBooleanVector myMask;
    Example2MethodDebug("Example2","withName");

    /*
     * first section -- calls to ancestor initializers
     */
     Example2_BeginInitializer_Example2_withName;
     Example2_Init_Example1_somDefaultInit(somSelf, ctrl);

    /*
     * second section -- local Example2 initialization code
     */
}

In this example, notice that the "Example2_withName" initializer is an IDL callstyle method, so it receives an Environment argument. In contrast, somDefaultInit is introduced by the SOMObject class (so it has an OIDL callstyle initializer, without an environment).

Important: If a class is defined where multiple initializers have exactly the same signature, then the C++ usage bindings will not be able to differentiate among them. That is, if there are multiple initializers defined with environment and long arguments, for example, then C++ clients would not be able to make a call using only the class name and arguments, such as:

new Example2(env, 123);

Rather, C++ users would be forced to first invoke the somNewNoInit method on the class to create an uninitialized object, and then separately invoke the desired initializer method on the object. This call would pass a zero for the control argument, in addition to passing values for the other arguments. For further discussion of client usage, see "Using initializers when creating new objects" later in this chapter.

Selecting non-default ancestor initializer calls

Often, it will be appropriate (in the first section of an initializer stub procedure) to change the invocation of an ancestor's somDefaultInit initializer to some other initializer available on the same class. The rule for making this change is simple: Replace somDefaultInit with the name of the desired ancestor initializer, and add any new arguments that are required by the replacement initializer. Important: Under no circumstances can you change anything else in the first section.

This example shows how to change an ancestor-initializer call correctly. Since there is a known "Example1_withName" initializer, the following default ancestor-initializer call (produced within the stub procedure for "Example2_withName") can be changed from

Example2_Init_Example1_somDefaultInit(somSelf, ctrl);

to

Example2_Init_Example1_Example1_withName(somSelf, ev, ctrl, name)

Notice that the revised ancestor-initializer call includes arguments for an Environment and a name, as defined by the "Example1_withname" initializer.

Using initializers when creating new objects

There are several ways that client programs can take advantage of the somDefaultInit object initialization. If desired, clients can use the SOM API directly (rather than taking advantage of the usage bindings). Also, the general object constructor, somNew, can always be invoked on a class to create and initialize objects. This call creates a new object and then invokes somDefaultInit on it.

To use the SOM API directly, the client code should first invoke the somNewNoInit method on the desired class object to create a new, uninitialized object. Then, the desired initializer is invoked on the new object, passing a null (that is, 0) control argument in addition to whatever other arguments may be required by the initializer. For example:

/* first make sure the Example2 class object exists */
Example2NewClass(Example2_MajorVersion, Example2_MinorVersion);

/* then create a new, uninitialized Example2 object */
myObject = _somNewNoInit(_Example2);

(null)
/* then initialize it with the desired initializer */
Example2_withName(myObject, env, 0, "MyName");

Usage bindings hide the details associated with initializer use in various ways and make calls more convenient for the client. For example, the C usage bindings for any given class already provide a convenience macro, <className>New, that first assures existence of the class object, and then calls somNew on it to create and initialize a new object. As explained above, somNew will use somDefaultInit to initialize the new object.

Also, the C usage bindings provide object-construction macros that use somNewNoInit and then invoke non-default initializers. These macros are named using the form <className>New_<initializerName>. For example, the C usage bindings for "Example2" allow using the following expression to create, initialize, and return a new "Example2" object:

Example2New_Example2_withName(env, "AnyName");

In the C++ bindings, initializers are represented as overloaded C++ constructors. As a result, there is no need to specify the name of the initializer method. For example, using the C++ bindings, the following expressions could be used to create a new "Example2" object:

new Example2;                   // will use somDefaultInit
new Example2();                 // will use somDefaultInit
new Example2(env,"A.B.Normal"); // will use Example2_withName
new Example2(env,123);          // will use Example2_withSize

Observe that if multiple initializers in a class have exactly the same signatures, the C++ usage bindings would be unable to differentiate among the calls, if made using the forms illustrated above. In this case, a client could use somNewNoInit first, and then invoke the specific initializer, as described in the preceding paragraphs.

Uninitialization

An object should always be uninitialized before its storage is freed. This is important because it also allows releasing resources and freeing storage not contained within the body of the object. SOMobjects handles uninitialization in much the same way as for initializers: An uninitializer takes a control argument and is supported with stub procedures in the implementation template file in a manner similar to initializers.

Only a single uninitialization method is needed, so SOMObject introduces the method that provides this function: somDestruct. As with the default initializer method, a class designer who requires nothing special in the way of uninitialization need not be concerned about modifying the default somDestruct method procedure. However, your code will execute faster if the .idl file overrides somDestruct so that a non-generic stub-procedure code can be provided for the class. Note that somDestruct was overridden by "Example1" and "Example2" above No specific IDL modifiers other than override are required for this.

Like an initializer template, the stub procedure for somDestruct consists of two sections: The first section is used by the programmer for performing any "local" uninitialization that may be required. The second section (which consists of a single EndDestructor macro invocation) invokes somDestruct on ancestors The second section must not be modified or removed by the programmer. It must be the final statement executed in the destructor.

Using 'somDestruct'

It is rarely necessary to invoke the somDestruct method explicitly This is because object uninitialization is normally done just before freeing an object's storage, and the mechanisms provided by SOMobjects for this purpose will automatically invoke somDestruct. For example, if an object were created using somNew or somNewNoInit, or by using a convenience macro provided by the C languag bindings, then the somFree method can be invoked on the object to delete the object. This automatically calls somDestruct before freeing storage.

C++ users can simply use the delete operator provided by the C++ bindings. This destructor calls somDestruct before the C++ delete operator frees the object's storage.

On the other hand, if an object is initially created by allocating memory in some special way and subsequently some somRenew methods are used, somFree (or C++ delete) is probably not appropriate. Thus, the somDestruct method should be explicitly called to uninitialize the object before freeing memory.

A complete example

The following example illustrates the implementation and use of initializers and destructors from the C++ bindings. The first part shows the IDL for three classes with initializers. For variety, some of the classes use callstyle OIDL and others use callstyle IDL.

#include <somobj.idl>

interface A : SOMObject {
        readonly attribute long a;
        implementation {
                releaseorder: _get_a;
                functionprefix = A;
                somDefaultInit: override, init;
                somDestruct: override;
                somPrintSelf: override;
        };
};


(null)
interface B : SOMObject {
        readonly attribute long b;
        void BwithInitialValue(inout somInitCtrl ctrl,
                               in long initialValue);
        implementation {
                callstyle = oidl;
                releaseorder: _get_b, BwithInitialValue;
                functionprefix = B;
                BwithInitialValue: init;
                somDefaultInit: override, init;
                somDestruct: override;
                somPrintSelf: override;
        };
};


(null)
interface C : A, B      {
        readonly attribute long c;
        void CwithInitialValue(inout somInitCtrl ctrl,
                               in long initialValue);
        void CwithInitialString(inout somInitCtrl ctrl,
                                in string initialString);
        implementation {
                releaseorder: _get_c, CwithInitialString,
                              CwithInitialValue;
                functionprefix = C;
                CwithInitialString: init;
                CwithInitialValue: init;
                somDefaultInit: override;
                somDestruct: override;
                somPrintSelf: override;
        };
};

Implementation code

Based on the foregoing class definitions, the next example illustrates several important aspects of initializers. The following code is a completed implementation template and an example client program for the preceding classes. Code added to the original template is given in bold.

/*
 *  This file generated by the SOM Compiler and Emitter Framework.
 *  Generated using:
 *      SOM Emitter emitxtm.dll: 2.22
 */

#define SOM_Module_ctorfullexample_Source
#define VARIABLE_MACROS
#define METHOD_MACROS
#include <ctorFullExample.xih>
#include <stdio.h>

SOM_Scope void SOMLINK AsomDefaultInit(A *somSelf,
                                       somInitCtrl* ctrl)
{
    AData *somThis; /* set by BeginInitializer */
    somInitCtrl globalCtrl;
    somBooleanVector myMask;
    AMethodDebug("A","somDefaultInit");

    A_BeginInitializer_somDefaultInit;
    A_Init_SOMObject_somDefaultInit(somSelf, ctrl);
    /*
     * local A initialization code added by programmer
     */
_a = 1;
}

SOM_Scope void SOMLINK AsomDestruct(A *somSelf, octet doFree,
                                    somDestructCtrl* ctrl)
{
    AData *somThis; /* set by BeginDestructor */
    somDestructCtrl globalCtrl;
    somBooleanVector myMask;
    AMethodDebug("A","somDestruct");
    A_BeginDestructor;

    /*
     * local A deinitialization code added by programmer
     */
    A_EndDestructor;
}

SOM_Scope SOMObject*  SOMLINK AsomPrintSelf(A *somSelf)
{
    AData *somThis = AGetData(somSelf);
    AMethodDebug("A","somPrintSelf");
    somPrintf("{an instance of %s at location %X with (a=%d)}\n",
             _somGetClassName(),somSelf,__get_a((Environment*)0));
    return (SOMObject*)((void*)somSelf);
}

SOM_Scope void SOMLINK BBwithInitialValue(B *somSelf,
                                          somInitCtrl* ctrl,
                                          long initialValue)
{
    BData *somThis; /* set by BeginInitializer */
    somInitCtrl globalCtrl;
    somBooleanVector myMask;
    BMethodDebug("B","BwithInitialValue");

    B_BeginInitializer_withInitialValue;
    B_Init_SOMObject_somDefaultInit(somSelf, ctrl);

    /*
     * local B initialization code added by programmer
     */
 _b = initialValue;
}



SOM_Scope void SOMLINK BsomDefaultInit(B *somSelf,
                                       somInitCtrl* ctrl)
{
    BData *somThis; /* set by BeginInitializer */
    somInitCtrl globalCtrl;
    somBooleanVector myMask;
    BMethodDebug("B","somDefaultInit");

    B_BeginInitializer_somDefaultInit;
    B_Init_SOMObject_somDefaultInit(somSelf, ctrl);

    /*
     * local B initialization code added by programmer
     */
 _b = 2;
}



SOM_Scope void SOMLINK BsomDestruct(B *somSelf, octet doFree,
                                    somDestructCtrl* ctrl)
{
    BData *somThis; /* set by BeginDestructor */
    somDestructCtrl globalCtrl;
    somBooleanVector myMask;
    BMethodDebug("B","somDestruct");
    B_BeginDestructor;

    /*
     * local B deinitialization code added by programmer
     */

    B_EndDestructor;
}



SOM_Scope SOMObject*  SOMLINK BsomPrintSelf(B *somSelf)
{
    BData *somThis = BGetData(somSelf);
    BMethodDebug("B","somPrintSelf");
    printf("{an instance of %s at location %X with (b=%d)}\n",
           _somGetClassName(),somSelf,__get_b());
    return (SOMObject*)((void*)somSelf);
}

Note: The following initializer for a C object accepts a string as an argument, converts this to an integer, and uses this for ancestor initialization of "B." This illustrates how a default ancestor initializer call is replaced with a non-default ancestor initializer call.

SOM_Scope void SOMLINK CCwithInitialString(C *somSelf,
                                           Environment *ev,
                                           somInitCtrl* ctrl,
                                           string initialString)
{
    CData *somThis; /* set by BeginInitializer */
    somInitCtrl globalCtrl;
    somBooleanVector myMask;
    CMethodDebug("C","CwithInitialString");

    C_BeginInitializer_withInitialString;
    C_Init_A_somDefaultInit(somSelf, ctrl);
    C_Init_B_BwithInitialValue(somSelf, ctrl,
                               atoi(initialString)-11);

    /*
     * local C initialization code added by programmer
     */
    _c = atoi(initialString);
}

SOM_Scope void SOMLINK CsomDefaultInit(C *somSelf,
                                       somInitCtrl* ctrl)
{
    CData *somThis; /* set by BeginInitializer */
    somInitCtrl globalCtrl;
    somBooleanVector myMask;
    CMethodDebug("C","somDefaultInit");

    C_BeginInitializer_somDefaultInit;
    C_Init_A_somDefaultInit(somSelf, ctrl);
    C_Init_B_somDefaultInit(somSelf, ctrl);

    /*
     * local C initialization code added by programmer
     */
_c = 3;
}

SOM_Scope void SOMLINK CsomDestruct(C *somSelf, octet doFree,
                                    somDestructCtrl* ctrl)
{
    CData *somThis; /* set by BeginDestructor */
    somDestructCtrl globalCtrl;
    somBooleanVector myMask;
    CMethodDebug("C","somDestruct");
    C_BeginDestructor;

    /*
     * local C deinitialization code added by programmer
     */

    C_EndDestructor;
}

SOM_Scope SOMObject*  SOMLINK CsomPrintSelf(C *somSelf)
{
    CData *somThis = CGetData(somSelf);
    CMethodDebug("C","somPrintSelf");
    printf("{an instance of %s at location %X with"
           " (a=%d, b=%d, c=%d)}\n",
        _somGetClassName(),somSelf,
        __get_a((Environment*)0),
        __get_b(),
        __get_c((Environment*)0));
    return (SOMObject*)((void*)somSelf);
}

SOM_Scope void SOMLINK CCwithInitialValue(  C *somSelf,
                                        Environment *ev,
                                        somInitCtrl* ctrl,
                                        long initialValue)
{
    CData *somThis; /* set by BeginInitializer */
    somInitCtrl globalCtrl;
    somBooleanVector myMask;
    CMethodDebug("C","CwithInitialValue");

    C_BeginInitializer_withInitialValue;
    C_Init_A_somDefaultInit(somSelf, ctrl);
    C_Init_B_BwithInitialValue(somSelf, ctrl, initialValue-11);

    /*
     * local C initialization code added by programmer
     */
    _c = initialValue;
}

Here is a C++ program that creates instances of "A", "B", and "C" using the initializers defined above.

main()
{
    SOM_TraceLevel = 1;

    A *a = new A;
    a->somPrintSelf();
    delete a;
    printf("\n");

    B *b = new B();
    b->somPrintSelf();
    delete b;
    printf("\n");

    b = new B(22);
    b->somPrintSelf();
    delete b;
    printf("\n");

    C *c = new C;
    c->somPrintSelf();
    delete c;
    printf("\n");

    c = new C((Environment*)0, 44);
    c->somPrintSelf();
    delete c;
    printf("\n");

    c = new C((Environment*)0, "66");
    c->somPrintSelf();
    delete c;
}

The output from the preceding program is as follows:

"ctorFullExample.C": 18:        In A:somDefaultInit
"ctorFullExample.C": 48:        In A:somPrintSelf
"./ctorFullExample.xih": 292:     In A:A_get_a
{an instance of A at location 20063C38 with (a=1)}
"ctorFullExample.C": 35:        In A:somDestruct

"ctorFullExample.C": 79:        In B:somDefaultInit
"ctorFullExample.C": 110: In B:somPrintSelf
"./ctorFullExample.xih": 655:     In B:B_get_b
{an instance of B at location 20064578 with (b=2)}
"ctorFullExample.C": 97:        In B:somDestruct

"ctorFullExample.C": 62:        In B:BwithInitialValue
"ctorFullExample.C": 110: In B:somPrintSelf
"./ctorFullExample.xih": 655:     In B:B_get_b
{an instance of B at location 20064578 with (b=22)}
"ctorFullExample.C": 97:        In B:somDestruct

"ctorFullExample.C": 150: In C:somDefaultInit
"ctorFullExample.C": 18:        In A:somDefaultInit
"ctorFullExample.C": 79:        In B:somDefaultInit
"ctorFullExample.C": 182: In C:somPrintSelf
"./ctorFullExample.xih": 292:     In A:A_get_a
"./ctorFullExample.xih": 655:     In B:B_get_b
"./ctorFullExample.xih": 1104:    In C:C_get_c
{an instance of C at location 20065448 with (a=1, b=2, c=3)}
"ctorFullExample.C": 169: In C:somDestruct
"ctorFullExample.C": 35:        In A:somDestruct
"ctorFullExample.C": 97:        In B:somDestruct

"ctorFullExample.C": 196: In C:CwithInitialValue
"ctorFullExample.C": 18:        In A:somDefaultInit
"ctorFullExample.C": 62:        In B:BwithInitialValue
"ctorFullExample.C": 182: In C:somPrintSelf
"./ctorFullExample.xih": 292:     In A:A_get_a
"./ctorFullExample.xih": 655:     In B:B_get_b
"./ctorFullExample.xih": 1104:      In C:C_get_c
{an instance of C at location 20065448 with (a=1, b=33, c=44)}
"ctorFullExample.C": 169: In C:somDestruct
"ctorFullExample.C": 35:        In A:somDestruct
"ctorFullExample.C": 97:        In B:somDestruct



"ctorFullExample.C": 132: In C:CwithInitialString
"ctorFullExample.C": 18:        In A:somDefaultInit
"ctorFullExample.C": 62:        In B:BwithInitialValue
"ctorFullExample.C": 182: In C:somPrintSelf
"./ctorFullExample.xih": 292:     In A:A_get_a
"./ctorFullExample.xih": 655:     In B:B_get_b
"./ctorFullExample.xih": 1104:      In C:C_get_c
{an instance of C at location 20065448 with (a=1, b=55, c=66)}
"ctorFullExample.C": 169: In C:somDestruct
"ctorFullExample.C": 35:        In A:somDestruct
"ctorFullExample.C": 97:        In B:somDestruct

Customizing the initialization of class objects

As described previously, the somDefaultInit method can be overridden to customize the initialization of objects. Because classes are objects, somDefaultInit is also invoked on classes when they are first created (generally by invoking the somNew method on a metaclass). For a class object, however, somDefaultInit normally just sets the name of the class to "unknown," after which the somInitMIClass method must be used for the major portion of class initialization. Of course, metaclasses can override somDefaultInit to initialize introduced class variables that require no arguments for their initialization.

Note: Because somNew does not call somInitMIClass, class objects returned from invocations of somNew on a metaclass are not yet useful class objects.

The somInitMIClass method (introduced by SOMClass) is invoked on a new class object using arguments to indicate the class name and the parent classes from which inheritance is desired (among other arguments). This invocation is made by whatever routine is used to initialize the class. (For SOM classes using the C or C++ implementation bindings, this is handled by the somBuildClass procedure, which is called by the implementation bindings automatically.) The somInitMIClass method is often overridden by a metaclass to influence initialization of new classes in some way. Typically, the overriding procedure begins by making parent method calls, and then performs additional actions thereafter.

However, without use of the Cooperative Metaclass to guarantee correct operation of SOMobjects in general, none of the methods introduced by SOMClass should be overridden. As a result, customizing the initialization of class objects (other than through overriding somDefaultInit for initialization of class variables) is not recommended in SOMobjects 2.1. Users whose applications require this should request access to the experimental Cooperative Metaclass used to implement the SOMobjects Metaclass Framework. But, metaclasses implemented using the experimental Cooperative Metaclass may require reprogramming when SOMobjects introduces an officially supported Cooperative Metaclass.

Creating a SOM Class Library

One of the principal advantages of SOM is that it makes "black box" (or binary) reusability possible. Consequently, SOM classes are frequently packaged and distributed as class libraries. A class library holds the actual implementation of one or more classes and can be dynamically loaded and unloaded as needed by applications. Importantly, class libraries can also be replaced independently of the applications that use them and, provided that the class implementor observes simple SOM guidelines for preserving binary compatibility, can evolve and expand over time.

General guidelines for class library designers

One of the most important features of SOM is that it allows you to build and distribute class libraries in binary form. Because there is no "fragile base class" problem in SOM, client programs that use your libraries (by subclassing your classes or by invoking the methods in your classes) will not need to be recompiled if you later produce a subsequent version of the library, provided you adhere to some simple restrictions.

  1. You should always maintain the syntax and the semantics of your existing interfaces. This means that you cannot take away any exposed capabilities, nor add or remove arguments for any of your public methods.
  2. Always maintain the releaseorder list, so that it never changes except for additions to the end. The releaseorder should contain all of your public methods, the one or two methods that correspond to each public attribute, and a placeholder for each private method (or private attribute method).
  3. Assign a higher minorversion number for each subsequent release of a class, so that client programmers can determine whether a new feature is present or not. Change the majorversion number only when you deliberately wish to break binary compatibility. (See the topic "Modifier statements" in Chapter 4, "SOM IDL and the SOM Compiler" for explanations of the releaseorder, minorversion and majorversion modifiers.)
  4. Under Windows, you should avoid the use of methods or attributes that return structures. In the DOS/Windows environment, there is no universally agreed upon calling convention for returning structures that is observed by all popular language compilers. Instead, define attributes that return pointers to structures, or define methods that take an out parameter for passing a structure back to the caller.

Note that you can always avoid this problem in classes of your own design. However, some of the attributes and methods in the frameworks that come with the SOMobjects Toolkit do return structures. Many of these are dictated by the OMG CORBA standard, and could not be avoided. For each of these methods two common calling conventions have been implemented:

  • Microsoft convention, where the address of the structure is returned in AX:DX, and
  • Borland convention, where the caller provides a hidden first argument to receive a copy of the returned structure.

No action is needed on your part to use the Microsoft convention. To use the Borland convention, you should include the file BCCSTRUC.H following any other "includes" of SOM headers.

5. With each new release of your class library, you have significant degrees of freedom to change much of the implementation detail. You can add to or reorganize your instance variables, add new public or private methods, inject new base classes into your class hierarchies, change metaclasses to more derived ones, and relocate the implementation of methods upward in your class hierarchies. Provided you always retain the same capabilities and semantics that were present in your first release, none of these changes will break the client programs that use your libraries.

Types of class libraries

Since class libraries are not programs, users cannot execute them directly. To enable users to make direct use of your classes, you must also provide one or more programs that create the classes and objects that the user will need. This section describes how to package your classes in a SOM class library and what you must do to make the contents of the library accessible to other programs.

On AIX, class libraries are actually produced as AIX shared libraries, whereas on OS/2 or Windows they appear as dynamically-linked libraries (or DLLs). The term "DLL" is sometimes used to refer to either an AIX, an OS/2, or a Windows class library, and (by convention only) the file suffix ".dll" is used for SOM class libraries on all platforms.

A program can use a class library containing a given class or classes in one of two ways:

  1. If the programmer employs the SOM bindings to instantiate the class and invoke its methods, the resulting client program contains static references to the class. The operating system will automatically resolve those references when the program is loaded, by also loading the appropriate class library.
  2. If the programmer uses only the dynamic SOM mechanisms for finding the class and invoking its methods (for example, by invoking somFindClass, somFindMethod, somLookupMethod, somDispatch, somResolveByName,and so forth), the resulting client program does not contain any static references to the class library. Thus, SOM will load the class library dynamically during execution of the program. Note: For SOM to be able to load the class library, the dllname modifier must be set in the .idl file. (See the topic "Modifier statements" in Chapter 4, "SOM IDL and the SOM Compiler.")

Because the provider of a class library cannot predict which of these ways a class will be used, SOM class libraries must be built such that either usage is possible. The first case above requires the class library to export the entry points needed by the SOM bindings, whereas the second case requires the library to provide an initialization function to create the classes it contains. The following topics discuss each case.

Building export files

The SOM Compiler provides an "exp" emitter for AIX and a "def" emitter for OS/2 to produce the necessary exported symbols for each class. For example, to generate the necessary exports for a class "A", issue the sc or somc command with one of the following -s options. (For a discussion of the sc or somc command and options, see "Running the SOM Compiler" in Chapter 4, "SOM IDL and the SOM Compiler.")

For AIX, this command generates an "a.exp" file:

  sc -sexp a.idl

For OS/2, this command generates an "a.def" file:

  sc -sdef a.idl

Typically, a class library contains multiple classes. To produce an appropriate export file for each class that the library will contain, you can create a new export file for the library itself by combining the exports from each "exp" or "def" file into a single file. Following are examples of a combined export "exp" file for AIX and a combined "def" file for OS/2. Each example illustrates a class library composed of three classes, "A", "B", and "C".

AIX "exp" file:

  #! abc.dll
  ACClassData
  AClassData
  ANewClass
  BCClassData
  BClassData
  BNewClass
  CCClassData
  CClassData
  CNewClass

OS/2 "def" file:

  LIBRARY abc INITINSTANCE
  DESCRIPTION 'abc example class library'
  PROTMODE
  DATA MULTIPLE NONSHARED LOADONCALL
  EXPORTS
    ACClassData
    AClassData
    ANewClass
    BCClassData
    BClassData
    BNewClass
    CCClassData
    CClassData
    CNewClass

Other symbols in addition to those generated by the "def" or "exp" emitter can be included if needed, but this is not required by SOM. One feature of SOM is that a class library needs no more than three exports per class (by contrast, many OOP systems require externals for every method as well). One required export is the name of a procedure to create the class (<className>NewClass), and the others are two external data structures that are referenced by the SOM bindings.

Specifying the initialization function

An initialization function for the class library must be provided to support dynamic loading of the library by the SOM Class Manager. The SOM Class Manager expects that, whenever it loads a class library, the initialization function will create and register class objects for all of the classes contained in the library.

These classes are then managed as a group (called an affinity group). One class in the affinity group has a privileged position-namely, the class that was specifically requested when the library was loaded. If that class (that is, the class that caused loading to occur) is subsequently unregistered, the SOM Class Manager will automatically unregister all of the other classes in the affinity group as well, and will unload the class library. Similarly, if the SOM Class Manager is explicitly asked to unload the class library, it will also automatically unregister and free all of the classes in the affinity group.

It is the responsibility of the class-library creator to supply the initialization function. The interface to the initialization function is given by the following C/C++ prototype:

#ifdef __IBMC__
  #pragma linkage (SOMInitModule, system)
#endif

SOMEXTERN void  SOMLINK SOMInitModule ( long majorVersion,
                                        long minorVersion,
                                        string className);

The parameters provided to this function are the className and the major/minor version numbers of the class that was requested when the library was loaded (that is, the class that caused loading). The initialization function is free to use or to disregard this information; nevertheless, if it fails to create a class object with the required name, the SOM Class Manager considers the load to have failed. As a rule of thumb, however, if the initialization function invokes a <className>NewClass procedure for each class in the class library, this condition will always be met. Consequently, the parameters supplied to the initialization function are not needed in most cases.

Here is a typical class-library initialization function, written in C, for a library with three classes ("A", "B", and "C"):

   #include "a.h"
   #include "b.h"
   #include "c.h"
   #ifdef __IBMC__
     #pragma linkage (SOMInitModule, system)
   #endif

   SOMEXTERN void  SOMLINK SOMInitModule (long majorVersion,
                             long minorVersion, string className)
   {
       SOM_IgnoreWarning (majorVersion);  /* This function makes */
       SOM_IgnoreWarning (minorVersion);  /* no use of the passed */
       SOM_IgnoreWarning (className);     /* arguments.   */
       ANewClass (A_MajorVersion, A_MinorVersion);
       BNewClass (B_MajorVersion, B_MinorVersion);
       CNewClass (C_MajorVersion, C_MinorVersion);
   }

The source code for the initialization function can be added to one of the implementation files for the classes in the library, or you can put it in a separate file and compile it independently.

Using Windows class libraries

Some additional considerations apply for Windows class libraries: Each class library must also supply a Windows LibMain function. The LibMain function is invoked automatically whenever a Windows DLL is loaded, and is responsible for identifying the library and its SOMInitModule function to the SOM Kernel. Here is an example of a typical Windows LibMain function for a SOM class library as it would appear in a C or C++ program:

    #include <som.h>
    SOMEXTERN void SOMLINK SOMInitModule (long majorVersion,
                                          long minorVersion,
                                          string className);

    #include <windows.h>
    int CALLBACK LibMain (HINSTANCE inst,
                          WORD ds,
                          WORD Heapsize,
                          LPSTR cmdLine)
    {
        SOM_IgnoreWarning (inst);
        SOM_ignoreWarning (ds);
        SOM_IgnoreWarning (heapSize);
        SOM_IgnoreWarning (cmdLine);

        SOM_ClassLibrary ("xyz.dll");
        return 1;  /* Indicate success to loader */
    }

The only operative statement in the LibMain function is the macro SOM_ClassLibrary, which identifies the actual file name of the library as it would appear in a Windows LoadLibrary call, and internally generates a reference to the library's SOMInitModule function. This information is passed to the SOM Kernel, which in turn registers the library and schedules the execution of the SOMInitModule function.

Typically, the SOM Kernel invokes the SOMInitModule function of each statically loaded class library during execution of the SOM_MainProgram macro within the using application; otherwise, SOMInitModule is invoked immediately upon completio of the dynamic loading of a class library by an already executing application. Regardless of the loading mechanism, the SOM Kernel guarantees that the SOMInitModule function executes exactly once for each class library.

Creating the import library

Finally, for each of your class libraries, you should create an import library that can be used by client programs (or by other class libraries that use your classes) to resolve the references to your classes.

Here is an example illustrating all of the steps required to create a class library ("abc.dll") that contains the three classes "A", "B", and "C".

1.Compile all of the implementation files for the classes that will be included in the library. Include the initialization function also.

For AIX written in C:

 xlc -I. -I$SOMBASE/include -c a.c
 xlc -I. -I$SOMBASE/include -c b.c
 xlc -I. -I$SOMBASE/include -c c.c
 xlc -I. -I$SOMBASE/include -c initfunc.c

For AIX written in C++:

 xlC -I. -I$SOMBASE/include -c a.C
 xlC -I. -I$SOMBASE/include -c b.C
 xlC -I. -I$SOMBASE/include -c c.C
 xlC -I. -I$SOMBASE/include -c initfunc.C

For OS/2 written in C:

  icc -I. -I%SOMBASE%\include -Ge- -c a.c
  icc -I. -I%SOMBASE%\include -Ge- -c b.c
  icc -I. -I%SOMBASE%\include -Ge- -c c.c
  icc -I. -I%SOMBASE%\include -Ge- -c initfunc.c
Note
The "-GE" option is used only with the IBM compiler. It indicates that the object files will go into a DLL.

For OS/2 written in C++:

  icc -I. -I%SOMBASE%\include -Ge- -c a.cpp
  icc -I. -I%SOMBASE%\include -Ge- -c b.cpp
  icc -I. -I%SOMBASE%\include -Ge- -c c.cpp
  icc -I. -I%SOMBASE%\include -Ge- -c initfunc.cpp
Note
The "-Ge" option is used only with the IBM compiler. It indicates that the object files will go into a DLL.

2. Produce an export file for each class.

For AIX:

  sc -sexp a.idl b.idl c.idl

For OS/2:

  sc -sdef a.idl b.idl c.idl

3. Manually combine the exported symbols into a single file.

For AIX, create a file "abc.exp" from "a.exp", "b.exp", and "c.exp". Do not include the initialization function (SOMInitModule) in the export list.

For OS/2, create a file "abc.def" from "a.def", "b.def", and c.def". Include the initialization function (SOMInitModule) in the export list, so that all classes will be initialized automatically, unless your initialization function does not need arguments and you explicitly invoke it yourself from an OS/2 DLL initialization routine.

4. Using the object files and the export file, produce a binary class library.

For AIX:

  ld -o abc.dll -bE:abc.exp -e SOMInitModule -H512 -T512 \
     a.o b.o c.o initfunc.o -lc -L$SOMBASE/lib -lsomtk

The -o option assigns a name to the class library ("abc.dll"). The -bE: option designates the file with the appropriate export list. The -e option designates SOMInitModule as the initialization function. The -H and -T options must be supplied as shown; they specify the necessary alignment information for the text and data portions of your code. The -l options name the specific libraries needed by your classes. If your classes make use of classes in other class libraries, include a -l option for each of these also. The ld command looks for a library named "lib<x>.a", where <x> is the name provided with each -l option. The -L option specifies the directory where the "somtk" library resides.

For OS/2:

  set LIB=%SOMBASE%\lib;%LIB%
  link386 /noi /packd /packc /align:16 /exepack \
      a.obj b.obj c.obj initfunc.obj, abc.dll,,os2386 somtk, \
      abc.def

If your classes make use of classes in other class libraries, include the names of their import libraries immediately after "somtk" (before the next comma).

5. Create an import library that corresponds to the class library, so that programs and other class libraries can use (import) your classes.

For AIX:

  ar ruv libabc.a abc.exp   �Note the use of the  ".exp" file, not a ".o" file

The first filename ("libabc.a") specifies the name to give to the import library. It should be of the form "lib<x>.a", where <x> represents your class library. The second filename ("abc.exp") specifies the exported symbols to include in the import library.

Caution: Although AIX shared libraries can be placed directly into an archive file ("lib<x>.a"), this is not recommended! A SOM class library should have a corresponding import library constructed directly from the combined export file.

For OS/2:

  implib /noi abc.lib abc.def

The first filename ("abc.lib") specifies the name for the import library and should always have a suffix of ".lib". The second filename ("abc.def") specifies the exported symbols to include in the import library.

Note
SOMInitModule should be included in the <x>.dll but not in <x>.lib. If you are using an export file that contains the symbol SOMInitModule, delete it first; SOMInitModule should not appear in your import library because it need not be exported. SOMInitModule should be included when creating your file <x>.dll because all classes in the <x>.dll will be initialized.

Customizing Memory Management

SOM is designed to be policy free and highly adaptable. Most of the SOM behavior can be customized by subclassing the built-in classes and overriding their methods, or by replacing selected functions in the SOM run-time library with application code. This chapter contains more advanced topics describing how to customize the following aspects of SOM behavior: memory management, dynamic class loading and unloading, character output, error handling, and method resolution. Information on customizing Distributed SOM is provided in Chapter 6.

The memory management functions used by the SOM run-time environment are a subset of those supplied in the ANSI C standard library. They have the same calling interface and return the equivalent types of results as their ANSI C counterparts, but include some supplemental error checking. Errors detected in these functions result in the invocation of the error-handling function to which SOMError points.

The correspondence between the SOM memory-management function variables and their ANSI standard library equivalents is given in the table below.


Memory-Management Functions

┌───────────────┬───────────────┬────────────┬──────────────────┐
│SOM FUNCTION   │ANSI STANDARD C│RETURN TYPE │ARGUMENT TYPES    │
│VARIABLE       │LIBRARY        │            │                  │
│               │FUNCTION       │            │                  │
├───────────────┼───────────────┼────────────┼──────────────────┤
│SOMCalloc      │calloc( )      │somToken    │size_t, size_t    │
├───────────────┼───────────────┼────────────┼──────────────────┤
│SOMFree        │free( )        │void        │somToken          │
├───────────────┼───────────────┼────────────┼──────────────────┤
│SOMMalloc      │malloc( )      │somToken    │size_t            │
├───────────────┼───────────────┼────────────┼──────────────────┤
│SOMRealloc     │realloc( )     │somToken    │somToken, size_t  │
└───────────────┴───────────────┴────────────┴──────────────────┘

An application program can replace SOM's memory management functions with its own memory management functions to change the way SOM allocates memory (for example, to perform all memory allocations as suballocations in a shared memory heap). This replacement is possible because SOMCalloc, SOMMalloc, SOMRealloc, and SOMFree are actually global variables that point to SOM's default memory management functions, rather than being the names of the functions themselves. Thus, an application program can replace SOM's default memory management functions by assigning the entry-point address of the user-defined memory management function to the appropriate global variable. For example, to replace the default free procedure with the user-defined function MyFree (which must have the same signature as the ANSI C free function), an application program would require the following code:

#include <som.h>
/* Define a replacement routine: */

#ifdef __OS2__                  /* not for SOM 3.0 */
#pragma linkage(myFree, system) /* not for SOM 3.0 */
#endif                          /* not for SOM 3.0 */

void SOMLINK myFree (somToken memPtr)
{
     (Customized code goes here)
}
...
SOMFree = myFree;
Note
In general, all of these routines should be replaced as a group. For instance, if an application supplies a customized version of SOMMalloc, it should also supply correponding SOMCalloc, SOMFree, and SOMRealloc functions that conform to this same style of memory management.

Customizing Class Loading and Unloading

SOM uses three routines that manage the loading and unloading of class libraries (referred to here as DLLs). These routines are called by the SOMClassMgrObject as it dynamically loads and registers classes. If appropriate, the rules that govern the loading and unloading of DLLs can be modified, by replacing these functions with alternative implementations.

Customizing class initialization

The SOMClassInitFuncName function has the following signature:

string (*SOMClassInitFuncName) ( );

This function returns the name of the function that will initialize (create class objects for) all of the classes that are packaged together in a single class library. (This function name applies to all class libraries loaded by the SOMClassMgrObject.) The SOM-supplied version of SOMClassInitFuncName returns the string " SOMInitModule". The interface to the library initialization function is described under the topic "Creating a SOM Class Library" earlier in this chapter.

Customizing DLL loading

To dynamically load a SOM class, the SOMClassMgrObject calls the function pointed to by the global variable SOMLoadModule to load the DLL containing the class. The reason for making public the SOMLoadModule function (and the following SOMDeleteModule function) is to reveal the boundary where SOM touches the operating system. Explicit invocation of these functions is never required. However, they are provided to allow class implementors to insert their own code between the operating system and SOM, if desired. The SOMLoadModule function has the following signature:

long (*SOMLoadModule) ( string className,
                        string fileName,
                        string functionName,
                        long majorVersion,
                        long minorVersion,
                        somToken *modHandle);

This function is responsible for loading the DLL containing the SOM class className and returning either the value zero (for success) or a nonzero system-specific error code. The output argument modHandle is used to return a token that can subsequently be used by the DLL-un loading routine (described below) to unload the DLL. The default DLL-loading routine returns the DLL's module handle in this argument. The remaining arguments are used as follows:

   Argument     Usage 

fileName

The file name of the DLL to be loaded, which can be either a simple name or a full path name.

functionName

The name of the routine to be called after the DLL is successfully loaded by the SOMClassMgrObject. This routine is responsible for creating the class objects for the class(es) contained in the DLL. Typically, this argument has the value "SOMInitModule", which is obtained from the function SOMClassInitFuncName described above. If no SOMInitModule entry exists in the DLL, the default DLL-loading routine looks in the DLL for a procedure with the name <className>NewClass instead. If neither entry point can be found, the default DLL-loading routine fails.

majorVersion

The major version number to be passed to the class initialization function in the DLL (specified by the functionName argument).

minorVersion

The minor version number to be passed to the class initialization function in the DLL (specified by the FunctionName argument).

An application program can replace the default DLL-loading routine by assigning the entry point address of the new DLL-loading function (such as MyLoadModule) to the global variable SOMLoadModule, as follows:

   #include <som.h>/* Define a replacement routine: */
   long myLoadModule (string className, string fileName,
                      string functionName, long majorVersion,
                      long minorVersion, somToken *modHandle)
   {
        (Customized code goes here)
   }
   ...
   SOMLoadModule = MyLoadModule;

Customizing DLL unloading

To unload a SOM class, the SOMClassMgrObject calls the function pointed to by the global variable SOMDeleteModule. The SOMDeleteModule function has the following signature:

long (*SOMDeleteModule) (in somToken modHandle);

This function is responsible for unloading the DLL designated by the modHandle parameter and returning either zero (for success) or a nonzero system-specific error code. The parameter modHandle contains the value returned by the DLL loading routine (described above) when the DLL was loaded.

An application program can replace the default DLL-unloading routine by assigning the entry point address of the new DLL-unloading function (such as, MyDeleteModule) to the global variable SOMDeleteModule, as follows:

#include <som.h>
/* Define a replacement routine: */
long myDeleteModule (somToken modHandle)
{
     (Customized code goes here)
}
...
SOMDeleteModule = MyDeleteModule;

Customizing Character Output

The SOM character-output function is invoked by all of the SOM error-handling and debugging macros whenever a character must be generated (see "Debugging" and "Exceptions and error handling" in Using SOM Classes in Client Programs, "Using SOM Classes in Client Programs"). The default character-output routine, pointed to by the global variable SOMOutCharRoutine, simply writes the character to "stdout", then returns 1 if successful, or 0 otherwise.

For convenience, SOMOutCharRoutine is supplemented by the somSetOutChar function. The somSetOutChar function enables each task to have a customized character output routine, thus it is often preferred for changing the output routine called by somPrintf (because SOMOutChrRoutine would remain in effect for subsequent tasks). On Windows, the somSetOutChar function is required (rather than SOMOutCharRoutine); it is optional on other operating systems.

An application programmer might wish to supply a customized replacement routine to:

  • Direct the output to stderr,
  • Record the output in a log file,
  • Collect characters and handle them in larger chunks,
  • Send the output to a window to display it,
  • Place the output in a clipboard, or
  • Some combination of these.

With SOMOutCharRoutine, an application program would use code similar to the following to install the replacement routine:

#include <som.h>#pragma linkage(myCharacterOutputRoutine, system)
/* Define a replacement routine: */
int SOMLINK myCharacterOutputRoutine (char c)
{
     (Customized code goes here)
}

/* After the next stmt all output */
/* will be sent to the new routine   */
SOMOutCharRoutine = myCharacterOutputRoutine;

With somSetOutChar, an application program would use code similar to the following to install the replacement routine:

#include <som.h>
static int irOutChar(char c);

static int irOutChar(char c)
{
    (Customized code goes here)
}

main (...)
{
    ...
somSetOutChar((somTD_SOMOutCharRoutine *) irOutChar);
}

Customizing Error Handling

When an error occurs within any of the SOM-supplied methods or functions, an error-handling procedure is invoked. The default error-handling procedure supplied by SOM, pointed to by the global variable SOMError, has the following signature:

void (*SOMError) (int errorCode , string fileName, int lineNum );

The default error-handling procedure inspects the errorCode argument and takes appropriate action, depending on the last decimal digit of errorCode (see "Exceptions and error handling" in Using SOM Classes in Client Programs for a discussion of error classifications). In the default error handler, fatal errors terminate the current process. The remaining two arguments (fileName and lineNum), which indicate the name of the file and the line number within the file where the error occurred, are used to produce an error message.

An application programmer might wish to replace the default error handler with a customized error-handling routine to:

  • Record errors in a way appropriate to the particular application,
  • Inform the user through the application's user interface,
  • Attempt application-level recovery by restarting at a known point, or
  • Shut down the application.

An application program would use code similar to the following to install the replacement routine:

#include <som.h>/* Define a replacement routine: */
void myErrorHandler (int errorCode, string fileName, int lineNum)
{
     (Customized code goes here)
}
...
/* After the next stmt all errors */
/* will be handled by the new routine   */
SOMError = myErrorHandler;

When any error condition originates within the classes supplied with SOM, SOM is left in an internally consistent state. If appropriate, an application program can trap errors with a customized error-handling procedure and then resume with other processing. Application programmers should be aware, however, that all methods within the SOM run-time library behave atomically. That is, they either succeed or fail; but if they fail, partial effects are undone wherever possible. This is done so that all SOM methods remain usable and can be re-executed following an error.

Customizing Mutual Exclusion Services (Thread Safety)

The SOM kernel and the other SOMobjects frameworks (DSOM, Persistence, Replication, and so on), have been made thread safe with respect to multi-threaded processes. As used here, "thread safe" means that the SOMobjects run time has been implemented using mutual exclusion semaphores to protect sections of the code which must only be executed by a single thread in a multi-threaded application process at one time.

Some operating systems provide native multi-threading (for example, OS/2.) On other operating systems that do not support native multi-threading (such as, AIX 3.2), thread support may be provided as part of particular programming environments (like DCE) or libraries.

It is vital that SOM employ the mutex services that are provided by the thread package used by the application. Consequently, SOM provides a mechanism for defining and customizing mutex services.

Five mutex service functions are used to implement mutual exclusion in SOM. These functions are called indirectly via the global pointer variables defined below. A somToken parameter (called "sem" below) is used as a generic "handle to refer to a mutex semaphore - usually it is a pointer to a mutex semaphore variable or data structure. The actual representation of the mutex semaphore is hidden by the functions.

unsigned long (*SOMCreateMutexSem)(somToken *sem);
The referenced function creates a mutex semaphore, whose handle is returned as an output parameter in the somToken variable identified by "sem".
If the call succeeds, a 0 is returned. Otherwise, a non-zero error code is returned.
unsigned long (*SOMRequestMutexSem)(somToken sem);
The referenced function requests ownership of the mutex semaphore identified by the parameter, sem. If the semaphore is not currently owned by another thread, ownership is assigned to the calling thread. Otherwise, the calling thread is blocked until the semaphore is released by the current owner.
Important: If the same thread calls SOMRequestMutexSem multiple times, a reference count must be kept, so that the semaphore is released only after the same number of calls to SOMReleaseMutexSem. Some, but not all, thread packages provide reference counting automatically, via "counting semaphores."
If the call succeeds, a 0 is returned. Otherwise, a non-zero error code is returned.
unsigned long(*SOMReleaseMutexSem)(somToken sem);
The referenced function releases ownership of the mutex semaphore identified by the parameter, sem.
Important: If the same thread calls SOMRequestMutexSem multiple times, a reference count must be kept, so that the semaphore is released only after the same number of calls to SOMReleaseMutexSem. Some, but not all, thread packages provide reference counting automatically, via "counting semaphores."
If the call succeeds, a 0 is returned. Otherwise, a non-zero error code is returned.
unsigned long (*SOMDestroyMutexSem)(somToken sem);
The referenced function destroys the a mutex semaphore identified by the parameter, sem.
If the call succeeds, a 0 is returned. Otherwise, a non-zero error code is returned.
unsigned long (*SOMGetThreadId)();
The referenced function returns a small integer identifier for the calling thread. The ID cannot be associated with any other thread in the process. The ID is used as an index for table lookups.
If threads are not supported, the function must return 0.

The actual mutex service function prototypes and global variable declarations are found in file "somapi.h".

If the underlying operating system supports native multi-threading (currently, only OS/2), SOM provides appropriate default mutex service function implementations. On those operating systems that do not support native multi-threading, the default mutex service functions have null implementations.

An application may want to use threading services different from those provided by the underlying operating system (if any); for example, DCE applications will want to use DCE threads. In that case, the default mutex service functions can be replaced by application-defined functions.

An application program would use code similar to the following to install the replacement routines:

#include <som.h>/* Define a replacement routine: */
unsigned long myCreateMutexSem (somToken *sem)
{
    (Customized code goes here)
}
...      
SOMCreateMutexSem= myCreateMutexSem;

It is important to install custom mutex service functions before any SOM calls are made.

Customizing Multi-threading Services

Although the SOM kernel and the other SOMobjects frameworks allow applications to be multi-threaded, the kernel and frameworks generally do not require or exploit threads themselves. But there are some exceptions: for example, application servers in DSOM can be configured so that each incoming request is executed on a separate thread.

An application may choose to employ "native" multi-threading services provided by the underlying operating system (for example, OS/2). On other operating systems that do not support native multi-threading (such as, AIX 3.2), thread support may be provided as part of particular programming environments (like DCE) or libraries. SOM provides a mechanism that allows an application to define and customize the multi-threading services used by SOMobjects frameworks.

Four thread service functions are defined for use by SOMobjects frameworks. These functions may be called indirectly via the global pointer variables defined below. A somToken parameter (called "thrd" below) is used as a generic "handle" to refer to a thread # usually it is a pointer to a thread id or descriptor. The actual representation of the thread handle is hidden by the functions.

typedef void somTD_SOMThreadProc(void * data);

unsigned long (*SOMStartThread)(somToken *thrd,
                                somTD_SOMThreadProc proc,
                                void *data,
                                unsigned long datasz,
                                unsigned long stacksz);
The referenced function starts a thread, and returns a thread handle in the somToken variable identified by "thrd". The thread executes the procedure whose address is specified by the proc parameter; the thread procedure takes a single void* argument and returns void. The data parameter passed to SOMStartThread is passed on to the thread procedure; the size of the data parameter, in bytes, is given by datasz. A stack of stacksz bytes will be allocated for the thread.
Note: On OS/2, the thread procedure must be compiled with _Optlink linkage.)
If the call succeeds, a 0 is returned. Otherwise, a non-zero error code is returned.
unsigned long (*SOMEndThread)(void);
The referenced function terminates the calling thread.
If the call succeeds, a 0 is returned. Otherwise, a non-zero error code is returned.
unsigned long (*SOMKillThread)(somToken thrd);
The referenced function terminates the thread identified by the input parameter thrd.
If the call succeeds, a 0 is returned. Otherwise, a non-zero error code is returned.
unsigned long (*SOMGetThreadHandle)(somToken * thrd);
The referenced function returns a handle that can be used to identify the calling thread. The handle is returned in the somToken variable pointed to by thrd.
If the call succeeds, a 0 is returned. Otherwise, a non-zero error code is returned.

The actual mutex service function prototypes and global variable declarations are found in file "somthrd.h".

If the underlying operating system supports native multi-threading (currently, only OS/2), SOM provides appropriate default multi-threading service function implementations. On those operating systems that do not support native multi-threading, the default multi-threading service functions have null implementations.

An application may want to use threading services different from those provided by the underlying operating system (if any); for example, DCE applications will want to use DCE threads. In that case, the default multi-threading service functions can be replaced by application#defined functions.

An application program would use code similar to the following to install the replacement routines:

#include <somthrd.h>/* Define a replacement routine: */
unsigned long myStartThread (somToken *thrd,
                             somTD_SOMThreadProc proc,
                             void *data,
                             unsigned long datasz,
                             unsigned long stacksz)
{
    (Customized code goes here)
}
...    
SOMStartThread =myStartThread;

It is important to install custom multi-threading service functions before any SOM calls are made.