Object-Oriented Programming Using SOM and DSOM/Understanding the Basics

We covered the development and use of a SOM object in the last chapter. In this chapter, we will look at the SOM kernel. We will discuss the concepts and the terminologies that make up the SOM run-time environment. We will examine the SOM inheritance model and the options it provides for method resolution. A good understanding of these fundamental concepts will help you when you write your SOM programs.

SOMObject, The Root Of All SOM Classes
In SOM, an object is a run-time entity with a specific set of instance methods and instance variables. The methods can be called by a client program, and the variables store data that is specific to the object. This model is similar to most object-oriented languages.

SOMObject is the root class for all SOM classes. All SOM classes must be subclasses of SOMObject or of some other class derived from SOMObject. SOMObject introduces a number of methods that define the behavior common to all SOM objects. SOMObject has no instance variables. Therefore, inheriting from SOMObject does not increase the size of the objects.

SOMObject provides the somInit and the somUninit methods. The somInit method is called on every object as it is created in order to initialize the object. The somUninit method performs the inverse of somInit. These methods are typically overridden by subclasses that have instance variables, so they can be initialized and uninitialized.

SOMObject also provides a number of methods for obtaining information about an object at run-time. For example, you can use these methods to find the class name of an object, the size of an object or determine whether an object is an instance of a specific class. We will see a usage example in Section 4.4 on page 85.

SOMObject also provides methods for performing dispatch-function resolution, a topic that will be discussed later in this chapter.

SOMClass, The Root of all SOM Metaclasses
A SOM class defines the implementation of a SOM object. Every SOM object is an instance of a SOM class. In contrast to object-oriented languages such as C++, SOM classes are also represented as objects at run-time. Classes can have their own variables and methods. These objects are called class objects. Their variables are called class variables (to distinguish them from instance variables), and their methods are called class methods (to distinguish them from instance methods).

Just like an object is an instance of a class, a class object is an instance of another class, called a metaclass.

A class defines instance methods to which its objects respond. A metaclass defines class methods to which a class object responds. Class methods perform class-related operations 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.

SOMClass is the root class for all SOM metaclasses. All SOM metaclasses must be subclasses of SOMClass or of some other class derived from SOMClass. SOMClass introduces a number of methods that define the behavior common to all SOM class objects. The default metaclass for every class is SOMClass.

SOMClass provides the somNew method for creating a new instance of a class. When you invoke New in C or new  in C++ to create an instance of , the bindings invoke somNew on the specified class. The somNew method allocates space for the new class object. It then calls somInit to initialize the newly created object and returns a pointer to it.

SOMClass also provides a number of methods for obtaining information about a class at run-time. For example, you can use these methods to find the methods the class supports, its relationships with other classes, or its version number. As we will see later in this book, SOM's implementation of classes as objects gives us a lot of flexibility at run-time.

SOM Kernel Classes
The SOM kernel is made up of three classes: SOMObject, SOMClass, and SOMClassMgr. The relationship of these classes are shown in Figure 3.1.

SOMClassMgr is a special class, one instance of this class is created automatically during SOM initialization. This instance is pointed to by the global variable SOMClassMgrObject.



It maintains a run-time registry for all existing classes within the current process and assists in the dynamic loading and unloading of class libraries.

You can package multiple classes into a class library. On OS/2, these libraries are known as Dynamic Linked Libraries (DLLs). SOMClassMgr automatically loads the DLLs when you instantiate an object of a class using the language bindings. SOMClassMgr also provides methods that let you dynamically load a class if you do not know at compile time which class you are going to use. This topic is discussed in Section 4.3 on page 82.

SOMClassMgr, however, does not automatically unload a DLL when all the instances of a class are freed. It does provide methods that allow you to unload a class and its class library. However, you have to determine when it will be safe to unload a class. A technique you can use is a metaclass to keep track of all the instances that are created for a class and unload the class when they are all freed. Usage examples for a metaclass are provided later on in this chapter.

SOM Run-time Environment Initialization
During the initialization of the SOM run-time environment, four primitive SOM objects are automatically created. They are the class objects for SOMObject, SOMClass, and SOMClassMgr, plus one instance of SOMClassMgr pointed to by the global variable SOMClassMgrObject. In addition to creating these four primitive SOM objects, the initialization also sets up global variables to hold data structures that maintain the state of the environment.

So when does the initialization of the SOM run-time take place? If you are using the C or C++ bindings, the SOM run-time environment is automatically initialized the first time you create an object.

If you are using other languages, or no bindings, you need to call the som.EnvironmentNew function to initialize the SOM run-time environment.

Attributes vs. Instance Variables
In Section 2.4.3 on page 17, we specified that an attribute is an instance variable where the get and set methods are automatically generated. In SOM IDL, there is another way of defining an instance variable. One can define an instance variable in the implementation section. The syntax is identical to the ANSI C syntax for variable declarations. For example, the following declares two instance variables, x and y: implementation {   short x;    double y; }; Instance variables differ from attributes in the following ways. Within the class that defines the instance variable, the way to access it is the same as an attribute. For example, to access the instance variable x, one can use the following form: somThis->x;        //C++ Bindings If you want the instance data to be accessed by client programs or subclasses, use an attribute declaration instead. How to access a parent's attribute is discussed later in this chapter.
 * 1) ifdef _SOMIDL_
 * 1) end if
 * 1) An instance variable cannot be accessed by client programs.
 * 2) An instance variable cannot be accessed by subclasses' methods.
 * 3) An instance variable can only be accessed by the methods in the defining class.

Public vs. Private
By definition, everything specified in a CORBA IDL file is public. That is, an IDL describes the services that an object provides and can be invoked by a client.

What if you want to designate some methods or data as private? In SOM IDL, you can use the #ifdef _PRNATE_ pre-processor macro. For example, the following IDL (Figure 3.2) shows how you can specify a private method. Figure 3.2 Declaring a private method in IDL

When you run the SOM compiler to generate the binding files and the implementation template, you need to specify the -p option if you want the compiler to generate bindings for both the private and public attributes and methods. For example, the following command will generate a C++ implementation template that provides a stub procedure for every method in the Foo interface. sc -p -sxc foo.idl How do you prevent a client from knowing about the private methods? You can either generate a usage binding :file that does not contain private information by not specifying the -p option in the SOM compile command, or you can strip away the private information from the IDL file by running a utility (called pdl) that is provided with the SOM Toolkit. You then give the version that only contains public definitions to the client.

Private methods also need to be listed in the releaseorder in order to maintain binary compatibility. To prevent client from knowing the name of a private method, you can use a dummy name as a place holder in the releaseorder as il1ustrated in the above example.

Inheritance
Inheritance is the mechanism that allows you to create new classes from existing classes. The original class is called the parent class or the base class. The derived class is called a child class or a subclass. The inheritance model in SOM is summarized below and is illustrated with usage examples.
 * 1) A subclass inherits interfaces from its parent classes: attributes (which are equivalent to a get and a set method) and methods that are available in a parent class are also available in any class that is derived either directly or indirectly from it.
 * 2) A subclass inherits implementations from its parent classes. The implementations are the procedures that implement the methods and can be written in any supported programming language.
 * 3) A subclass can introduce its own attributes and methods.
 * 4) A subclass can override (redefine) the methods from their parent classes using the override modifier.
 * 5) A class can disallow a method from being overridden by subclasses by using the nooverride modifier.
 * 6) When you redefine (override) an inherited method, you cannot change the signature of the method.
 * 7) A class cannot inherit from multiple parents that provide different methods of the same name.
 * 8) A class can inherit from multiple parents that have a common ancestor. In this case, the class only inherits one copy of the same method or attribute. An ambiguity situation may arise when one of its parents overrides a method in the common ancestor class. SOM uses the left path precedence rule to resolve this kind of ambiguity. This topic is discussed in detail in Section 3.7.6 on page 38.

The override and nooverride Modifiers
Let's look at the following Animal IDL. The Animal class overrides the method somInit from its parent class SOMObject. It specifies that the sleep method cannot be overridden by subclasses, since all animals must sleep.

How to Invoke a Parent's Method
A subclass invokes a method introduced in a parent class using the same notation, as if the method is introduced by the subclass. That is, a subclass can use the form: somSelf-> (args) to invoke a parent's method where args are the arguments to the method. To access a parent's attribute, the subclass would substitute  with the _get and the _set methods for the attribute.

When you run the SOM compiler to generate the implementation template for the class, a stub procedure is generated for each inherited method. A default implementation is also provided for each inherited method. The default implementation invokes the parent's method.

SOM generates convenience macros in the implementation header file so that a subclass can invoke the parent's method that it is overridding. Typically, a subclass calls the parent's method and then adds additional functions to the method.

The macro has the following syntax: _parent__ The following shows the stub procedure that is generated for the somInit method. The Animal_parent_SOMObject_somInit macro invokes the SOMObject implementation of somInit. If a class has multiple parents, the SOM compiler will generate a macro for each of them.

Trying to Override a Method that Has nooverride Modifier
If a subclass attempts to override a method that has a rwoverride modifier, the SOM compiler produces an error. Let's look at the following Cat IDL: When you compile the Cat IDL, the compiler produces the following error: > "cat.idl", line 15: error: "sleep", cannot override "nooverride" methods.

Overriding an Attribute
Let's modify the Cat IDL to fix the sleep problem. We also want to override the name attribute. The new error-free Cat IDL is shown below: When you override an attribute, you are actually redefining the pair of accessor methods on the attribute. It is illegal IDL syntax to specify name: override. The compiler will produce an error because it assumes you are trying to re-define a method called name, which is not present in the base Animal class. (Recall how an attribute is short-hand for a _get and a _set method.)

Ambiguity with Multiple Inheritance
It is illega1 IDL to inherit from two interfaces with the same method name. Let's look at the following example. We add a new class Pet. The Pet IDL is shown below. It introduces two methods habitat and owner. We modified our Cat IDL so that it inherits from both Animal and Pet. The new Cat IDL is shown below: When we compile the Cat IDL, the compiler produces the following error: > "cat.idl", line 5: error: "habitat" ambiguous: introduced by "Animal" and "Pet".

Left Path Precedence Rule
If a class inherits from multiple parents that have a common ancestor, the class only inherits one copy of the same method. However, an ambiguous situation can arise when one of its parents overrides a method in the common ancestor class. Which implementation should this class inherit? Should it inherit the implementation from the common ancestor class or should it inherit the implementation from the class that overrides the method? Consider the inheritance diagram in Figure 3.3.



Figure 3.3 Common parents in multiple inheritance

The LivingThings class defines a method breath, implemented by procedure proc1. The Animal and the Pet class are derived from the LivingThings class. The Pet class overrides the implementation of the breath method withproc4. The class Cat is derived from both the Animal and the Pet class. Which implementation of the breath method does the Cat class inherit?

SOM resolves this ambiguity by using the left path precedence rule: the procedure that is used for the method is the one that is on the leftmost path. The ordering is determined by the position of the parent classes in the IDL specification. In our example, assuming that the IDL for the Cat class lists the parent Animal before Pet, Cat inherits the implementation of the breath method defined by the class LivingThings instead of the class Pet.

A conceptual diagram that shows the method table for Cat is shown in Figure 3.4. A method table is a table of pointers to the procedures that implement the methods that an object supports. This table is built by the class of the object. The breath method in the Cat class is implemented using proc1 instead of proc4, since the Animal class is the left most ancestor of Cat, from which the method breath is inherited.



Figure 3.4 Method table for cat

Parent Class vs. Metaclass
The notion of a parent class and a metaclass and their respective inheritance hierarchy can be confusing. Any given class in SOM has one or more parent classes and one metaclass. A metaclass has its own inheritance hierarchy that is independent of the class inheritance hierarchy. Consider the example in Figure 3.5.



Figure 3.5 Parent class vs. metaclass

Here the parent class of Animal is SOMObject. This means that Animal inherits all the instance methods and the instance variables from SOMObject. An instance of Animal can invoke any of the instance methods defined in SOMObject or Animal class.

The metaclass of Animal is MetaAnimal. MetaAnimal defines the class methods for the Animal class object to perform. The class object Animal is an instance of the metaclass MetaAnimal.

Recall that SOMObject is the root of all SOM classes and SOMClass is the root of all SOM metaclasses. Thus, two independent hierarchies are formed as denoted by the solid line. The relationships between a class and its parent classes, or metaclass, are defined in the IDL. If you do not specify a parent class, the default is SOMObject. If you do not specify a metaclass, the default is SOMClass.

Metaclass Example
Let's look at a typical use of an explicitly defined metaclass. Recall that we use the new operator (or the NewO macro in C) to create an instance of a class. Unfortunately, the language bindings for both do not allow you to pass parameters. For an object that requires particular initialization values, it would be nice if one could pass these values as each object is created. Native C++ provides you with this capability. One can specify arguments in a class constructor to initialize the data members of the class.

Luckily, in SOM, one can explicitly define metaclass to handle this situation. If a class requires particular initialization values for its instance variables, it can implement a class method that will take input values, create an instance of the class, and initialize the instance variables to the specified input values. Instead of using new, a client program will call this class method to create an instance of the class.

The Animal class, whose IDL is given in Figure 3.6, illustrates this. We define the metaclass MetaAnimal for Animal. The metaclass provides the class method createAnimal that creates an instance of the Animal class and initializes the name of the animal.

The interface for MetaAnimal is defined in the same IDL source file. The meta· class modifier in the implementation section specifies the metaclass for Animal. Figure 3.6 The animal.idl file contains MetaAnimal and Animal

The implementation of createAnimal is given in Figure 3.7. The som.New method is called to create an instance of the Animal class. The _set_name method is then invoked to set the name of the animal to the specified input value. Figure 3.7 The createAnimal implementation

Client Usage Example
Instead of using the new operator, a client program will use the createAnimal class method to create an instance of Animal. Let's look at the code fragment (Figure 3.8) for creating two Animal objects with names Spot and Lassie.

We first invoke the AnimalNewClass procedure to create a new Animal class object. For each class, SOM generates a NewClass procedure in the usage binding file (.xh or .h file). This procedure creates the class object for the given class, as well as the class objects for its parent classes and metaclass. The NewClass procedure takes two arguments: the major version number and the minor version number of the class. The Animal_MajorVersion and AnimaCMinorVersion arguments are defined in the usage binding file and refer to the version numbers of the Animal class at the time the usage binding file is generated.

You can specify the major and minor version number of a class by specifying the majorversion and minorversion modifiers in the implementation section of the IDL. Their defaults are zero if you do not specify them. The values for Animal_MajorVersion and Animal Minor Version are therefore zero, since we did not specify the modifiers in the IDL. Figure 3.8 A client that invokes the createAnimal method

If the version numbers are non-zero, then the numbers you pass to  NewClass are checked against the version numbers in the class implementation to detemine if the class is compatible with the client's expectation. If it is not compatible, an error will occur.

After the class object is created, the createAnimal method can be called to create an instance of Animal with the specified name.

Notice that when we use the new operator (or the New macro when using C), we do not have to invoke NewClass explicitly. This is because the new operator calls NewCiass implicitly to create the class object.

The SOMSinglelnstance Metaclass
Sometimes it is necessary to define a class for which only one instance can be created. You may want such a class to keep track of some global information. SOM provides a SOMSinglelnstance metaclass that allows you to do this: When you specify SOMSinglelnstance as the metaclass, SOM will guarantee that there is only one instance of the class, no matter how many times you invoke new to create an instance of that class.

Derived Metaclass
A special situation can occur with metaclasses when using multiple inheritance. Consider the following interface definitions: The Robin class inherits from the Animal class and the FlyingObject class. Both the Animal class and the FlyingObject class have an explicitly defined metaclass. However, the Robin class does not define an explicit metaclass in its IDL. What metaclass should Robin have?

SOM automatically builds a Derived Metaclass for the Robin class that inherits from MetaAnimal and MetaFlyingObject. This guarantees that an instance of Robin class can always invoke a method on Animal class or FlyingObject class. If a Derived Metaclass is not built, then an error situation can occur when a Robin instance invokes a method from the Animal class that invokes a method in the MetaAnimal class. This hierarchy is shown in Figure 3.9.

To summarize, SOM encourages the explicit definition of named metaclasses. At the same time, SOM relieves programmers from any metaclass incompatibility problems when defining a new class. If you do not explicitly define your metaclass, SOM will automatically derive the right one for you.

Method Resolution
Method Resolution is the process of determining, at run-time, which method procedure to execute in response to a method invocation. SOM provides three mechanisms for method resolution: offset resolution, name-lookup resolution, and dispatch-function resolution.

As a client, your choice of method resolution depends on how much information you have when you are writing your program. At one end of the spectrum, if you know which method you want to invoke and the name of the class at compile time, you will use offset resolution because it is the fastest. At the other end, if you do not know anything about the class or the method until run-time, you will use the dispatch-function resolution which is the slowest but also the most flexible.



Figure 3.9 Multiple inheritance and metaclasses

In the following sections, we will look at the three method resolution techniques and compare them. We will also show client usage in all tlu·ee cases. We will use the Car class whose IDL is given in Figure 3.10. The Car class has an instance method printCarSpec. It also has an explicitly defined metaclass MetaCar. The MetaCar class has a class method createCar. The examples will also help reinforce the differences between an object and a class object.

Offset Resolution
Offset resolution is the fastest and easiest to use. It is the default way of invoking methods when using the C and C++ language bindings. When offset resolution is used, SOM locates the address of a method procedure from a method table. Exactly how this process is performed is explained in Section 4.1.2 on page 75.

The following constraints must be satisfied to use offset resolution:
 * The name of the class must be known at compile time.
 * The name of the method, and the required arguments, must be known at compile time.
 * The method must be a static method, instead of a dynamic method. A static method is a method that is declared in the IDL specification of a class. A dynamic method is a method that is not declared in the IDL specification of a class but is added to the class at run-time, using the somAdd.Dyna.micMethod* in SOMClass. To invoke a dynamic method, you must use either name-lookup or dispatch-function resolution. A static method can be invoked using any of the method resolution techniques.

Offset resolution is nearly as fast as an ordinary procedure call. It is similar in speed to the C++ virtual function, as both of them involve looking up the address of a function from a method table. #include  #include <Somcls.idl> interface Car; interface MetaCar : SOMClass {   // This method creates an instance of Car and // sets the make, model and price of the car Car createCar(in string make, in string model, in long price); #ifdef _ SOMIDL_ implementation {         releaseorder : createCar; };   #end if  }; interface Car : SOMObject {   attribute string make; attribute string model; attribute long price;

// This method prints the car specification void printCarSpec; #ifdef _SOMIDL_ implementation {       releaseorder : _set_make, _get_make, _set_ model, _get_model, _set_price, _get_price, printCarSpec; metaclass = MetaCar; };   #end if }; Figure 3.10 The car IDL

Client Usage
Figure 3.11 shows how a C++ client will invoke the createCar and the printCarSpec methods using offset resolution. MetaCar *mcar; Car *car; // Create a class object using the CarNewCiass procedure mcar = CarNewCiass(O,O); // Invoke the createCar method on the class object to create an object car= mcar->createCar(ev, "Honda", "Prelude", 20000); //Invoke the printCarSpec method on the object car->printCarSpec(ev); Figure 3.11 A client program that uses offset resolution


 * The SOMAddDynamicMethod is not documented in lhe Reference Manual. However, its description can be found in somcls.idl. It is used by interpreted system su.ch as Object Rexx or Object Basic, where classes are constructed dynamically.

Name-Lookup Resolution
At first glance, name-lookup resolution looks strange. Name-lookup resolution requires the client to first acquire a pointer to the method procedure and then use this pointer to invoke the method.

SOM provides many ways to obtain a pointer to a method procedure. For example, any of the somLookupMethod, somFind.Method, somFindMethodOK, or somResolveByName can be used. These methods are invoked on a class object and take as an argument the somld for the desired method or the name for the method. The somld of a method can be obtained by calling the somld.FromString procedure and passing the method's name. In addition to returning a pointer to the method procedure, some of these methods return additional information such as whether or not the class supports the specified method.

The procedure pointer that is obtained from any of the above methods has to be typecasted so that the compiler can create the correct procedure call. This type name follows the notation somTD_<className>_<methodName>.

To use name-lookup resolution, you must know the method arguments at compile time. The method name can be unknown at compile time since it can be supplied as a parameter to any of the look-up methods. The class name can also be unknown at compile time, as long as you can obtain a pointer to the class object at run-time, so that you can invoke any of the look-up methods.

Name-lookup resolution is slower than offset resolution, roughly two to three times the cost of an ordinary procedure call.

Client Usage
Figure 3.12 shows the equivalent code for invoking the createCar and theprintCarSpec methods using name-lookup resolution.

The somLookupMethod is used to find the address of the procedure that implements the method on the receiver class. Notice that we invoke the somLookupMethod on the _MetaCar and the _Car class objects. Where do these class objects come from?

If you look at the usage binding file for the Car IDL, you will see the following definitions: _MetaCar points to the MetaCar class object and _Car points to the Car class object. But where do these data structures come from, and how do they get initialized? We will examine this topic in Chapter 4.
 * 1) define _MetaCar   MetaCarClassData.classObject
 * 2) define _Car      CarClassData.classObject

Comparing the syntax for offset resolution versus name-lookup resolution, it might not be clear why anyone would want to use name-lookup resolution. One obvious use of name-lookup resolution is when you do not know which method to invoke until run-time. However, the usefulness of name-lookup resolution goes beyond that. MetaCar                    *mcar; Car                        *car; somTD_MetaCar_createCar    metaCarMethodPtr; somTD_Car_printCarSpec     carMethodPtr; // Create a class object using the CarNewClass procedure mcar = CarNewCiass(O,O); // To invoke class method createCar, first get a pointer to the method procedure. metaCarMethodPtr = (somTD_MetaCar_createCar) _MetaCar->somlookupMethod( somldFromString("createCar")); // Invoke the createCar method by using the pointer and passing the right arguments. car= metaCarMethodPtr(mcar, ev, "Honda", "Prelude", 20000); // To invoke Instance method printCarSpec, first get the pointer to the method procedure. carMethodPtr = (somTO_Car_printCarSpec) _Car->somlookupMethod( somldFromString("printCarSpec")); // Invoke the printCarSpec method using the pointer and passing the right arguments. carMethodPtr(car,ev); Figure 3.12 A client program that uses name-lookup resolution

Name-lookup resolution allows clients to write generic code, when abstract base classes are used in a class hierarchy. An abstract base class defines a set of base methods where subclasses can subsequently redefine. Since the name and the signature of the methods are the same, it is sufficient for the client to only include the usage bindings for the base class, and use name-lookup resolution to invoke the methods in the subclass. The following example illustrates this.

Assuming we have two classes, AmericanCar and BritishCar that are derived from Car. Both of them redefine the methods in the Car class. A client program can write a generic procedure to invoke the printCarSpec method as shown in Figure 3.13.

Based on the type of the targetObj, the corresponding printCarSpec method will be invoked. Note that the client does not have to include the usage binding files for the AmericanCar and the BritishCar class. The typedef from the base class is sufficient. In addition, if new subclasses are added in the future, the same piece of generic code can still be used to invoke the printCarSpec methods from the new subclasses. generic( Car *targetObj) { somTO_Car_printCarSpec carMethodPtr; //******************************************************* // Get a pointer to the method produce that implements the // printCarSpec method on the "targetObj". //******************************************************* carMethodPtr = {somTD_Car_printCarSpec) somResolveByName( targetObj, *printCarSpec"); //******************************************************* // Invoke the printCarSpec method by using the pointer and passing the right arguments.  //******************************************************* carMethodPtr(targetObj,ev); } Figure 3.13 Writing generic code with name-lookup resolution
 * 1) include "car.xh"

As an added convenience, the SOM compiler generates macros for namelookup resolution, if you specify that a method is to be invoked using namelookup resolution, by using the namelookup modifier in the IDL. The macro has the following syntax: lookup_<methodName> (receiver, args) The receiver is a pointer to the object and args are arguments to the method.

For example, suppose you add the following to the implementation section of the Car IDL: implementation {    printCarSpec: namelookup; } Then, a client can write a much simpler procedure to invoke the printCarSpec method, as illustrated in Figure 3.14. generic2(Car *targetObj) { //******************************************************* // Convenience macro if namelookup modifier is defined in IDL //*******************************************************   lookup_PrintCarSpec(targetObj.ev); } Figure 3.14 Writing generic code with convenience macros
 * 1) ifdef _SOMIDL_

Dispatch-Function Resolution
Dispatch-function resolution provides you with the maximum flexibility to determine, at run-time, what method you want to invoke. The class name, method name, and the list of arguments can all be determined at run-time.

The SOMObject class provides the som.Dispatch method that can be used to perform dispatch-function resolution. The arguments for the method are given below: boolean somDispatch (retValue,            //Return value                      somld,                //method ld                      arglist);             //variable argument list The parameter retValue is a pointer to the result of the method that is being invoked. The parameter somld identifies the method to be invoked. Recall that the somld of a method can be obtained by calling the somld.FromString procedure and passing the method's name. The parameter argList is a variable argument list that contains the arguments to be passed to the method. The argument list must contain all arguments for the method. That is, the first entry must include a pointer to the target object, and the second entry must include a pointer to the Environment structure unless the OIDL style is used. The return value is a boolean that indicates whether the method was successfully dispatched.

The variable argument list parameter requires special discussion. SOM allows you to declare a method that takes a variable number of arguments through a parameter of type va_list. Note the following when using this type.


 * 1) The va_list parameter must be a final parameter, preceded by at least one other parameter.
 * 2) You must use the parameter name ap when you declare a parameter of type va_list. Note the following example: <tt>void myMethod(in short count, in va_list ap);</tt>
 * 3) The va_list type is not a CORBA data type. It is not supported by DSOM. If a method requires a variable argument list, but the object also has to be accessed remotely, use a sequence instead. The sequence type is discussed in Section 3.13.2 on page 55.

Client Usage
Figure 3.15 shows the equivalent code for invoking the createCar and the printCarSpec methods using the dispatch-function resolution. It shows how you can use the ua_arg macro to initialize the variable argument list.

Regardless of which method resolution technique you use, you must clearly differentiate between an object and a class object. In our examples, we first use the CarNewClass procedure to create a Car class object. We then invoke the class method, createCar, on the Car class object to create a Car object. We then invoke the instance method, printCarSpec, on the Car object.

We also want to recap the differences between a class method and an instance method. The createCar method is a class method that is defined in the metaclass. The printCarSpec method is an instance method that is defined in the class. To invoke a class method, we need a class object handle. To invoke an instance method, we need an instance object handle. Failing to do this will result in a runtime error when the wrong method is dispatched on a handle.

In using the CarNewClass procedure to create a class object, we require the usage binding file to be included in the client program. In Section 4.3 on page 82, we will show you a more dynamic way to create class objects that do not require the usage binding file. MetaCar  *mcar; Car      *car; va_list  startArg, arg, arg2; long     total; // Create a class object using the CarNewCiass procedure. mcar * CarNewCiass(O,O); // ******************************************* // Allocate space for createCar argumets. // ******************************************* total = strlen("Honda")+ 1 + strlen("Prelude")+ 1 + sizeof(long) + sizeof(MetaCar') + sizeof(Environment"); arg = (char") SOMMalloc(total); startArg = arg;            // remember the beginning of the argument list. // ******************************************* // Push the arguments into the va_list. // ******************************************* va_arg(arg, MetaCar•) - mcar; va_arg(arg, Environment") = ev; va_arg(arg, string) - "Honda"; va_arg(arg, string) = "Prelude"; va_arg(arg, long) = 20000; // Invoke the createCar method on the class object mcar->SOMObject_somCispatch((somToken*)&car,                            somldFromString("createCar"),                             startArg); // ******************************************* // Allocate space for prlntCarSpec arguments.  // ******************************************* arg2 = (char•) SOMMalloc(8); startArg = arg2:               // remember the beginning of the argument list // ******************************************* // Push the arguments into the va_list.  // ******************************************* va_arg(arg2, Car') = car; va_arg(arg2, Environment*) = ev;. // Invoke the printCarSpec method on the instance object. car->SOMObject_somDispatch((somToken')0,                            som ldFromString("printCarSpec"), startArg); Figure 3.15 A client program that uses dispatch-function resolution

SOM Data Types
SOM supports ali the type declarations in the CORBA IDL. It also includes some types that are specific to SOM. Table 3.1 provides a summary of the list of data types. The more complicated types are described in the following sections.

String Type
The default implementation of the set method for an attribute does a shallow copy of the value that is passed. This means that the attribute value will disappear when the client program frees its memory. For an attribute of type string, this might not be appropriate since the object might want to retain a copy of the string value after the set method is invoked. Should the object desire such a behavior, the class implementor can specify the noset modifier and implement the set method manually. The Employee class, whose IDL is given in Figure 3.16, illustrates this situation. {|class="wikitable" !IDL/SOM Type||Description||IDL Usage Examples attribute short x; attribute long x; attribute unsigned short x; attribute unsigned long x; attribute float x; attribute double x; attribute char x; attribute boolean x; attribute octet x; attribute any x; struct Point { short x;   shorty: }; attribute Point aPoint; union swType switch (long) { case 1: long x;  case 2: float y;   default: char z; }; attribute swType too; enum Fruit {apple,orange}; attribute Fruit aFruit; sequence<type, size> where type is any valid IDL type, and size is an optional number that specifies the maximum size of the sequence. attribute sequence shortseq; attribute sequence Figure 3.16 The Employee IDL
 * +Table 3.1 Summary of IDUSOM Type
 * short||range: -2~15 .. 2~15 -1 ||
 * short||range: -2~15 .. 2~15 -1 ||
 * long||range: -~1 .. 2~1 -1 ||
 * long||range: -~1 .. 2~1 -1 ||
 * unsigned short||range: 0 .. 216-1 -- ||
 * unsigned short||range: 0 .. 216-1 -- ||
 * unsigned long|| range: 0 .. iJ<::-1 ||
 * unsigned long|| range: 0 .. iJ<::-1 ||
 * float||single-precision floating point||
 * float||single-precision floating point||
 * double||double-precision floating point||
 * double||double-precision floating point||
 * char||an 8-bit quantity||
 * char||an 8-bit quantity||
 * boolean||values: TRUE or FALSE||
 * boolean||values: TRUE or FALSE||
 * octet||an 8-bit quantity||
 * octet||an 8-bit quantity||
 * any||Use to specify any IDL type.||
 * any||Use to specify any IDL type.||
 * struct||The same as a C struct.||
 * struct||The same as a C struct.||
 * union||Discriminated union, a cross between C union and switch.||
 * union||Discriminated union, a cross between C union and switch.||
 * anum||An ordered list of identifiers.||
 * anum||An ordered list of identifiers.||
 * sequence||Defines a sequence of types. The syntax is:
 * sequence||Defines a sequence of types. The syntax is:

The Employee class specifies the noset modifier for the name attribute. This causes the SOM compiler to generate a _set_name stub procedure for the attribute.

The _set_name method allocates memory for the name using SOMMalloc and then performs a string copy to store the name. The somlnit method is redefined to set the name attribute to zero. Since the _set_name method allocates memory for the name attribute, the somUninit method is redefined to free that memory.

The implementation for the Employee class is shown in Figure 3.17.

Sequence Type
The C and C++ bindings for SOM map sequence onto struct with the following members: unsigned long   _maximum; unsigned long   _length; type           *_buffer; The type is substituted by the specifed type of the sequence. The _maximum member stores the total size that is allocated for the sequence. The _length member stores the number of values actually stored in the _buffer member. The bindings also provide the macros sequenceLength, sequenceMaximum, and sequenceElement so that you can use them to access the members of the struct.

To refer to the sequence type in your Cor C++ client programs, use the following notation: _IDL_SEQUENCE_type where type is the type of the sequence. The following example illustrates how to use sequence type in the class implementation, as well as from a client's perspective. /* * Method from the IOL attribute statement: * "attribute string name" */ SOM_Scope void SOMLINK _set_name{Employee *somSelf, Environment *ev, string name) {    EmployeeData *somThis = EmployeeGetData(somSelf);     EmployeeMethodDebug("Employee","_set_name");     If (somThis->name)     {          SOMFree(somThis->name);     }    somThis->name = {string) SOMMalioc(strlen(name)+ 1); strcpy(somThis->name, name); } SOM_Scope void SOMLINK somInit(Employee *somSelf) {    EmployeeData *somThis = EmployeeGetData(somSelf); EmployeeMethodDebug("Employee","somInit"); Empioyee_parent_SOMObject_somInit(somSelf); somThis->name =NULL; } SOM_Scope void SOMLINK somUninit(Employee *somSelf) {   EmployeeData *somThis = EmployeeGetData(somSelf); EmployeeMethodDebug("Employee", "somUninit"); if (somThis->name) {      SOMFree(somThis->name); }   Employee parent_ SOMObject_somUninit(somSelf); } Figure 3.17 The Employee class implementation
 * 1) define Employee_Ciass_Source
 * 2) include <employee.xih>

The Company class IDL is given in Figure 3.18. It contains the attribute empList which is a sequence type. The empList is used to maintain the list of Employee. The addEmployee method adds an Employee object to the list.

The somlnit method is redefined so that we can allocate storage for the empList. Similarly, the somUninit method is redefined, so that we can free that storage. The implementation of the methods are given in Figure 3.19.

A client program that uses the Employee and Company class is given in Figure 3.20. The notation _IDL_SEQUENCE_Employee refers to the sequence empList.

Any Type
The C and C++ bindings for SOM map any onto the following struct: typedef struct any { TypeCode _type; void    *_value; } any;

interface Employee; interface Company : SOMObject {        const unsigned long MAXNUM=10; attribute sequence<Employee,MAXNUM> emplist; long addEmployee(in Employee entry); #ifdef _ SOMIDL_ implementation {             releaseorder : _get_emplist, _set_emplist. add Employee; somInit: override; somUninit: override; };       #end if }; Figure 3.18 The Company IDL
 * 1) include <Somobj.idb>

SOM_Scope long SOMLINK addEmployee(Company 'somSelf, Environment 'ev, Employee* entry) {       CompanyData *somThis = CompanyGetData(somSeH); long rc; CompanyMethodDebug("Company","addEmployee"); if (sequencelength(somThis->empList) < sequenceMaximum(somThis->emplist)) {            sequenceEiement(somThis->emplist, sequencelength(somThis->emplist)) = entry; sequencelength(somThls->emplist)++; rc = OL; }       else {            // reach maximum sequence boundary rc=-1L; }       return rc; } SOM_Scope void SOMLINK somInit(Company *somSelf) {     CompanyData ·somThis - CompanyGetData(somSelf); CompanyMethodDebug("Company","somInit"); Company parent_ SOMObject_som lnit(somSelt); sequenceMaximum(somThis->emplist) = MAXNUM; sequencelength(somThis·>emplist) = 0; somThis->emplist._bufler = (Employee '') SOMMalloc(sizeof (Employee *) *MAXIMUN); } SOM_Scope void SOMLINK somUninit(Company 'somSelf) {     CompanyData 'somThis = CompanyGetData(somSelf); CompanyMethodDebug("Company","somUninit"); if (somThis->empUst_buffer) {      SOMFree(somThis->empUst._bufler); }     Company_parent_SOMObject_somUninlt(somSelf); } Figure 3.19 The Company class implementation

main {   Environment *ev = somGetGiobalEnvironment; Employee *emp1, ·emp2; Company *comp; _IDL_SEQUENCE_Employee list; short i;   // Create Employee Mary emp1 =new Employee; emp1 -> set_name(ev, "Mary"); // Create Employee John emp2 = new Employee; emp2-> set_name(ev, "John"); // Add Mary and John to Company comp = new Company; comp->addEmployee(ev, emp1); comp->addEmployee(ev, emp2); // Print out all the Employee name list= comp-> get_emplist(ev); for (i=O; i < sequencelength(list); i++) {   Understanding the Basics 59 cout << sequenceElement(list,i)-> _get_name(ev) << "\n"; } } Figure 3.20 A client program that uses Employee and Company classes
 * 1) include "employee.xh"
 * 2) include "company.xh"
 * 3) include <iostream.h>

The _type member is a TypeCode that represents the type of the value. The _value member is a pointer to the actual value. A TypeCode provides the complete information about an IDL type. TypeCode is described in more detail in Chapter 8, and a complete list of TypeCodes is given in Table 8.1.

The following shows how you can assign a float to an any type: any myany; float val = 1.2; myany._type = TC_float; myany._value =&val; printf("%f\n", *((float*) myany._value));

SOM Exceptions
An exception is an indication that a method request was not performed successfully. In IDL, exceptions are implemented by passing back error information after a method call, as opposed to the "catch/throw" model in C++.

Exception Declaration
Exceptions are declared in IDL. An exception declaration begins with the keyword exception. It contains a list of members whose values can be accessed when the exception is raised. The syntax is similar to a struct definition. Indeed the C and C++ language bindings map an exception declaration to a structure construct.

To associate specific exception information for a method, a raises expression must be included in the method declaration in the IDL. The raises expression specifies which exceptions may be raised as a result of an invocation of the method. The exceptions that are specified must have been previously declared.

The ErrorExample class, whose IDL is given in Figure 3.21, illustrates how to declare exceptions in IDL. The BadCall exception may be raised by the execute method. The BadCall exception contains two fields for supplying additional information on the exception.

CORBA also defines a list of standard exceptions that can be raised by any method call. These standard exceptions may not be listed in the raises statement. Therefore, if a method declaration does not include any raises statement, it means that there are no specific exceptions. However, it is still possible to receive one of the standard exceptions from the method.

Setting Exception Value
When a method detects an exception, it must call somSetException to set the exception value in the Environment structure that is passed to the method.

The somSetException procedure requires the following arguments: void somSetException(env,               // pointer to Environment structure                      exception_type,     // Exception Type (system/user/none)                      exception_name,     // Exception Name                      parms);             // Exception Structure

interface ErrorExample : SOMObject {        enum reasonCode {OK, FATAL, INCOMPLETE}; exception BadCall {        long errorCode; char reason[BO);        };         void execute raises(BadCall);         #ifdef _SOMIDL_         implementation         {            releaseorder : execute;         };         #end if }; Figure 3.21 The ErrorExample IDL
 * 1) include <somobj.idl>

The parameter exception_type can be either NO_EXCEPTION, USER_EXCEPTION or SYSTEM_EXCEPTION. The parameter exception_name is the string name of the exception.

The SOM compiler generates a string name for each expression. For example, the string name for the BadCall exception is ex_ErrorExample_BadCall. The parameter parms is a pointer to the exception structure which must be allocated using SOMMalloc.

The implementation for the execute method as shown in Figure 3.22 demonstrates how to set an exception value.

Getting Exception Value
We mentioned that the Environment structure is used for passing exception information between the caller and the called method. We have also seen how to use the somGetGlobalEnvironment function to obtain the global Environment structure. You can use a local Environment structure to pass exception information so that the exception raised in this call is not known to all the other objects that use the global environment structure. The SOM_CreateLocal Environment or the SOM_InitEnvironment macros can be used to create a local environment. SOM_Scope void SOMLINK execute(ErrorExample *somSelf, Environment *ev) {   /* ErrorExampleData •somThis = ErrorExampleGetData(somSelf); */ BadCall *badRc; ErrorExampleMethodDebug ("ErrorExample", "execute"); // Method logic ... /*********************************************************************   // Demonstrate Exception Setting /*********************************************************************   badRc = (BadCall •) SOMMalloc(sizeof(BadCall)); // allocate exception structure badRc->errorCode = ErrorExample_INCOMPLETE; // set exception detail strcpy(badRc->reason, "Unexpected error"); somSetException( ev,                    USER_EXCEPTION,                     ex_ErrorExample_BadCall,                     (void*) badRc); } Figure 3.22 ErrorExample::execute method implementation
 * 1) define ErrorExample_Ciass_Source
 * 2) include <error.xih>

Environment or the SOM_InitEnvironment macros can be used to create a local environment.

After a method returns, a client program can look at the Environment structure to see if there was an exception. The Environment structure contains a _major field. If _major is not equal to NO_EXCEPTION, then an exception is raised by the call.

Th retrieve the exception name and the exception value, the somExceptionld and the somExceptionValue functions must be called. Both of these functions take an Environment structure pointer as parameter. The somExceptionld function returns the exception name as a string. The somException Value function returns a pointer to the exception structure. Based on the exception name that you obtained using somExceptionld, you can cast the pointer to the appropriate exception structure and retrieve the values.

If the exception is a standard exception, a generic exception structure, StExcep, is used to return the values. This generic exception structure has two fields: one to return a subcategory error code, and the other to return a completion status code.

Figure 3.23 shows how a client program invokes the execute method from the ErrorExample class and then checks for exception information. If there is an exception, the client obtains the exception values and then calls somExceptionFree to free the memory that is allocated for the exception structure.

SOM Context
CORBA defines a context object that contains a list of properties. Each property consists of a name and a string value associated with that name. A client can use a context object to store information about its environment, and the information can be propagated to a server's environment. By convention, context properties represent information about circumstances of a request that are inconvenient to pass as parameters.

SOM implements a Context class that can be used to create a context object. It also supports the passing of a Context parameter in a method call, when a context expression is present in a method declaration. Both of these specifications are defined in CORBA. The following sections provide some usage examples.

Context Declaration
Th associate context information for a method, a context expression must be included in the method declaration in the IDL. The context expression specifies a list of property names that the method can use.

When a context expression is present in the method declaration, the method invocation requires an additional Context argument after the Environment argument. This additional argument is used to pass the context object. If the properties that are defined in the context expression are included in the context object, their values will be passed to the method implementation.

The ContextExample class, whose IDL is given in Figure 3.24, illustrates how to declare a context expression in IDL. A context expression begins with the keyword context. The context expression that is associated with the startup method contains two properties: userid and password.

Setting Context
To set the Context parameter, a client must create a Context object and then set those properties to be made available to the method call. The Context class provides the methods set_one_ value and set_ values for setting one or more property values in the Context object.

Figure 3.25 shows how a client program would create a Context object and then invoke the set_one_value to set the property userid to the value chris. The Context object is then passed as a parameter when the startup method is invoked. Figure 3.23 A client program that uses the Error Example class Figure 3.24 The ContextExample IDL Figure 3.25 A client program that uses the ContextExample class

Getting Context
To get the Context value in a method implementation, the Context class provides the method get_values that can be used to retrieve the context property value(s). The properties are returned in a list object known as a NVList. The NVList interface is also defined by CORBA. It is used for constructing parameter lists and supports operations to add and get elements from the list. NVList is used in the dynamic invocation interface, which is discussed in Section 5.9 on page 107.

Figure 3.26 shows the implementation of the startup method. The startup method invokes the get_values method on the Context parameter to retrieve the property value for the userid property. The result is returned in the NVList object val. The get_item method is then invoked on the NVList object to retrieve the first item. The get_item method returns the name of the item, the data type of the item, the value of the item, the length of the value, and a bitmask that contains flag values.

Using the client program shown in the last section, the item name will contain the string userid, and the item value will contain the string chris.

Module Statement
In SOMobjects Developer Toolkit 2.0, if you want to define multiple interfaces in a single .idl file, then you need to use a module statement. If you do not use a module statement, and the interfaces are not a class-metaclass pair, then the SOM compiler will only generate the bindings for the last interface in the file.

However, if you install the SOMobjects Developer Toolkit CSD202 or higher, you can define as many interfaces as you want in a single .idl file without using a module statement. The SOM compiler will generate the bindings for all the interfaces. The module statement is simply a scoping mechanism.

Figure 3.27 shows an example of a module statement. The common.idl file contains two interfaces: Rectangle and Circle.

The usage binding that is generated by the SOM compiler forms the class name by concatenating the module name, an underscore, and the interface name. Figure 3.28 shows how the two interfaces can be accessed by a C++ client. Figure 3.26 ContextExample::startup method implementation Figure 3.27 The CommonShape module Figure 3.28 A client program that uses the CommonShape module