Implementing Classes in SOM: Difference between revisions
Line 504: | Line 504: | ||
===Building export files=== | ===Building export files=== | ||
===Specifying the initialization function=== | ===Specifying the initialization function=== | ||
====Using Windows class libraries==== | |||
===Creating the import library=== | ===Creating the import library=== | ||
==Customizing Memory Management== | ==Customizing Memory Management== | ||
==Customizing Class Loading and Unloading== | ==Customizing Class Loading and Unloading== |
Revision as of 03:58, 28 August 2020
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.
TheSOM 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
- ifdef FOOBAR #ifdef FOOBAR
{ { ... ...
- else }
{ #else ... {
- 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
Initializer methods Declaring new initializers in SOM IDL Considerations re: 'somInit' initialization from earlier SOM releases Implementing initializers Selecting non-default ancestor initializer calls Using initializers when creating new objects Uninitialization Using 'somDestruct' A complete example Implementation code Customizing the initialization of class objects