The Metaclass Framework
Reprint Courtesy of International Business Machines Corporation, © International Business Machines Corporation
In SOM, classes are objects; metaclasses are classes and thus are objects, too. Also depicted are the three primitive class objects of the SOM run time: SOMClass, SOMobject, and SOMClassMgr.
Primitive objects of the SOM run time
The important point to be aware of here is that any class that is a subclass of SOMClass is a metaclass This chapter describes metaclasses that are available in SOMobjects Toolkit. There are two kinds of metaclasses:
Framework metaclasses-metaclasses for building new metaclasses and Utility metaclasses - metaclasses to help you write applications
Briefly, the SOMobjects Toolkit provides the following metaclasses of each category for use by programmers:
- Framework metaclasses
- SOMMBeforeAfter Used to create a metaclass that has "before" and "after" methods for all methods (inherited or introduced) invoked on instances of its classes.
- Utility metaclasses
- SOMMSingleInstance Used to create a class that may have at most one instance.
- SOMMTraced Provides tracing for every invocation of all methods on instances of its classes.
The following sections describe each metaclass more fully.
Class organization of the Metaclass Framework.
A note about metaclass programming
SOM metaclasses are carefully constructed so that they compose (see Section 8.1 below). If you need to create a metaclass, you can introduce new class methods, and new class variables, but you should not override any of the methods introduced by SOMClass. If you need more than this, request access to the experimental Cooperative metaclass used to implement the Metaclass Framework metaclasses described in this chapter.
Framework Metaclasses for "Before/After" Behavior
This section covers the following subjects:
- The 'SOMMBeforeAfter' metaclass
- Composition of before/after metaclasses
- Notes and advantages of 'before/after' usage
The 'SOMMBeforeAfter' metaclass
SOMMBeforeAfter is a metaclass that allows the user to create a class for which a particular method is invoked before each invocation of every method, nd for which a second method is invoked after each invocation. SOMMBeforeAfter defines two methods: sommBeforeMethod and sommAfterMethod. These two methods are intended to be overridden in the child of SOMMBeforeAfter to define the particular "before" and "after" methods needed for the client application.
For example, suppose a "Barking" metaclass (a subclass of SOMMBeforeAfter overrides the sommBeforeMethod and sommAfterMethod with a method that emits one bark when invoked. Thus, one can create the "BarkingDog" class, whose instances (such as "Lassie") bark twice when "disturbed" by a method invocation.
A hierarchy of metaclasses
The SOMMBeforeAfter metaclass is designed to be subclassed; a subclass (or child) of SOMMBeforeAfter is also a metaclass. The subclass overrides sommBeforeMethod or sommAfterMethod or both. These (redefned) methods are invoked before and after any method supported by instances of the subclass (these methods are called primary methods). That is, they are invoked before and after methods invoked on the ordinary objects that are instances of the class objects that are instances of the subclass of SOMMBeforeAfter.
The sommBeforeMethod returns a boolean value. This allows the "before" method to control whether the "after" method and the primary method get invoked. If sommBeforeMethod returns TRUE, normal processing occurs. If FALSE is returned, neither the primary method nor the corresponding sommAfterMethod is invoked. In addition, no more deeply nested before/after methods are invoked (see "Composition of before/after metaclasses" below). This facility can be used, for example, to allow a before/after metaclass to provide secure access to an object. The implication of this convention is that, if sommBeforeMethod is going to return FALSE, it must do any post-processing that might otherwise be done in the "after" method.
Caution: somInit and somFree are among the methods that get before/after behavior. This implies the following two obligations are imposed on the programmer of a SOMMBeforeAfter class. First, the implementation must guard against sommBeforeMethod being called before somInit has executed, and the object is not yet fully initialized. Second, the implementation must guard against sommAfterMethod being called after somFree, at which time the object no longer exists (see the example "C implementation for 'Barking' metaclass" below).
The following example shows the IDL needed to create a Barking metaclass. Just run the appropriate emitter to get an implementation binding, and then provide the appropriate "before" behavior and "after" behavior.
SOM IDL for 'Barking' metaclass
#ifndef Barking_idl #define Barking_idl #include <sombacls.idl> interface Barking : SOMMBeforeAfter { #ifdef __SOMIDL__ implementation { //# Class Modifiers filestem = barking; callstyle = idl; //# Method Modifiers sommBeforeMethod : override; sommAfterMethod : override; }; #endif /* __SOMIDL__ */ }; #endif /* Barking_idl */
The next example shows an implementation of the Barking metaclass in which no barking occurs when somFree is invoked.
C implementation for 'Barking' metaclass
#define Barking_Class_Source #include <barking.ih> static char *somMN_somFree = "somFree"; static somId somId_somFree = &somMN_somFree; SOM_Scope boolean SOMLINK sommBeforeMethod(Barking somSelf, Environment *ev, SOMObject object, somId methodId, va_list ap) { if ( !somCompareIds( methodId, somId_somFree ) printf( "WOOF" ); } SOM_Scope void SOMLINK sommAfterMethod(Barking somSelf, Environment *ev, SOMObject object, somId methodId, somId descriptor, somToken returnedvalue, va_list ap) { if ( !somCompareIds( methodId, somId_somFree ) printf( "WOOF" ); }
Composition of before/after metaclasses
When there are two before/after metaclasses -"Barking" (as before) and "Fierce", which has a sommBeforeMethod and sommAfterMethod that both growl (that is, both methods make a "grrrr" sound when executed). The preceding discussion demonstrated how to create a "FierceDog " or a "BarkingDog ", but has not yet addressed the question of how to compose these properties of fierce and barking. Composability means having the ability to easily create either a "FierceBarkingDog" that goes "grrr woof woof grrr " when it responds to a method call or a "BarkingFierceDog " that goes "woof grrr grrr woof" when it responds to a method call.
Example for composition of before or after metaclasses.
There are several ways to express such compositions. Figure 1 depicts SOM IDL fragments for three techniques in which composition can be indicated by a programmer. These are denoted as Technique 1, Technique 2, and Technique 3, each of which creates a "FierceBarkingDog" class, named "FB-1", "FB-2", and "FB-3", respectively, as follows:
- In Technique 1, a new metaclass ("FierceBarking") is created with both the "Fierce" and "Barking" metaclasses as parents. An instance of this new metaclass (that is, "FB-1") is a "FierceBarkingDog" (assuming "Dog " is a parent).
- In Technique 2, a new class is created which has parents that are instances of "Fierce" and "Barking" respectively. That is, "FB-2" is a "FierceBarkingDog" also (assuming "FierceDog" and "BarkingDog" do not further specialize "Dog").
- In Technique 3, "FB-3", which also is a "FierceBarkingDog", is created by declaring that its parent is a "BarkingDog" and that its explicit (syntactically declared) metaclass is "Fierce".
┌─────────────────────────┬─────────────────────────┬─────────────────────────┐ │TECHNIQUE 1 │TECHNIQUE 2 │TECHNIQUE 3 │ ├─────────────────────────┼─────────────────────────┼─────────────────────────┤ │interface │interface │interface │ ├─────────────────────────┼─────────────────────────┼─────────────────────────┤ │FB-1:Dog │FB-2:FierceDog, │FB-3:BarkingDog │ ├─────────────────────────┼─────────────────────────┼─────────────────────────┤ │{ │BarkingDog │{ │ ├─────────────────────────┼─────────────────────────┼─────────────────────────┤ │... │{ │... │ ├─────────────────────────┼─────────────────────────┼─────────────────────────┤ │implementation │... │implemetation │ ├─────────────────────────┼─────────────────────────┼─────────────────────────┤ │{ │Implementation │{ │ ├─────────────────────────┼─────────────────────────┼─────────────────────────┤ │metaclass=FierceBarking; │{ │metaclass=Fierce; │ ├─────────────────────────┼─────────────────────────┼─────────────────────────┤ │... │ ... │ ... │ ├─────────────────────────┼─────────────────────────┼─────────────────────────┤ │ }; │ }; │ }; │ ├─────────────────────────┼─────────────────────────┼─────────────────────────┤ │}; │}; │}; │ └─────────────────────────┴─────────────────────────┴─────────────────────────┘
Figure 1.Three techniques for composing before/after metaclasses.
Note that the explicit metaclass in the SOM IDL of "FB-1" is its derived class, "FierceBarking". The derived metaclass of "FB-2" is also "FierceBarking". Lastly, the derived metaclass of "FB-3" is not the metaclass explicitly specified in the SOM IDL; rather, it too is "FierceBarking."
Notes and advantages of 'before/after' usage
Notes on the dispatching of before/after methods:
- A before (after) method is invoked just once per primary method invocation.
- The dispatching of before/after methods is thread-safe.
- The dispatching of before/after methods is fast. The time overhead for dispatching a primary method is on the order of N times the time to invoke a before/after method as a procedure, where N is the total number of before/after methods to be applied.
In conclusion, consider an example that clearly demonstrates the power of the composition of before/after metaclasses. Suppose you are creating a class library that will have n classes. Further suppose there are p properties that must be included in all combinations for all classes. Potentially, the library must have n2p classes. Let us hypothesize that (fortunately) all these properties can be captured by before/after metaclasses. In this case, the size of the library is n+p.
The user of such a library need only produce those combinations necessary for a given application. In addition, note that there is none of the usual programming. Given the IDL for a combination of before/after metaclasses, the SOM compiler generates the implementation of the combination (in either C or C++) with no further manual intervention.
The 'SOMMSingleInstance' Metaclass
Sometimes it is necessary to define a class for which only one instance can be created. This is easily accomplished with the SOMMSingleInstance metaclass. Suppose the class "Collie" is an instance of SOMMSingleInstance. The first call to CollieNew creates the one possible instance of "Collie"; hence, subsequent calls to CollieNew return the first (and only) instance.
Any class whose metaclass is SOMMSingleInstance gets this requisite behavior; nothing further needs to be done. The first instance created is always returned by the <className>New maro.
Alternatively, the method sommGetSingleInstance does the same thing as the <className>New macro. This method invoked on a class object (for example, "Collie") is useful because the call site explicitly shows that something special is occurring and that a new object is not necessarily being created. For this reason, one might prefer the second form of creating a single-instance object to the first.
Instances of SOMMSingleInstance keep a count of the number of times somNew and sommGetSingleInstance are invoked. Each invocation of somFree decrements this count. An invocation of somFree does not actually free the single instance until the count reaches zero.
SOMMSingleInstance overrides somRenew, somRenewNoInit, somRenewNoInitNoZero, and somRenewNoZero so that a proxy is created in the space indicated in the somRenew* call. This proxy redispatches all methods to the single instance, which is always allocated in heap storage. Note that all of these methods (somRenew*) increment the reference count; therefore, somFree should be called on these objects, too. In this case, somFree decrements the reference and frees the single instance (and, of course, takes no action with respect to the storage indicated in the original somRenew* call).
If a class is an instance of SOMMSingleInstance, all of its subclasse are also instances of SOMMSingleInstance. Be aware that this also means that each subclass is allowed to have only a single instance. (This may seem obvious. However, it is a common mistake to create a framework class that must have a single instance, while at the same time expecting users of the framework to subclass the single instance class. The result is that two single-instance objects are created: one for the framework class and one for the subclass. One technique that can mitigate this scenario is based on the use of somSubstituteClass. In this case, the creator of the subclass must substitute the subclass for the framework class - before the instance of the framework class is created.)
The 'SOMMTraced' Metaclass
SOMMTraced is a metaclass that facilitates tracing of method invocations. If class "Collie" is an instance of SOMMTraced (if SOMMTraced is the metaclass of "Collie"), any method invoked on an instance of "Collie" is traced. That is, before the method begins execution, a message prints (to standard output) giving the actual parameters. Then, after the method completes execution, a second message prints giving the returned value. This behavior is attained merely by being an instance of the SOMMTraced metaclass.
If the class being traced is contained in the Interface Repository, actual parameters are printed as part of the trace. If the class is not contained in the Interface Repository, an ellipsis is printed.
To be more concrete, suppose that the class "Collie" is a child of "Dog" and is an instance of SOMMTraced. Because SOMMTraced is the metaclass of "Collie," any method invoked on "Lassie" (an instance of "Collie") is traced.
It is easy to use SOMMTraced: Just make a class an instance of SOMMTraced in order to get tracing.
There is one more step for using SOMMTraced: Nothing prints unless the environment variable SOMM_TRACED is set. If it is set to the empty string, all traced classes print. If SOMM_TRACED is not the empty string, it should be set to the list of names of classes that should be traced. For example, the following command turns on printing of the trace for "Collie", but not for any other traced class:
export SOMM_TRACED=Collie (on AIX) SET SOMM_TRACED=Collie (on OS/2 or Windows)
The example below shows the IDL needed to create a traced dog class: Just run the appropriate emitter to get an implementation binding.
SOM IDL for 'TracedDog' class #include "dog.idl" #include <somtrcls.idl> interface TracedDog : Dog { #ifdef __SOMIDL__ implementation { //# Class Modifiers filestem = trdog; metaclass = SOMMTraced; }; #endif /* __SOMIDL__ */ };
Error Codes
It is possible to receive the following messages from the Metaclass Framework while an application is running.
- 60001
- An attempt was made to construct a class with SOMMSingleInstance as a metaclass constraint. (This may occur indirectly because of the construction of a derived metaclass). The initialization of the class failed because somInitMIClass defined by SOMMSingleInstance is in conflict with another metaclass that has overridden somNew. That is, some other metaclass has already claimed the right to return the value for somNew.
- 60002
- An attempt was made to construct a class with SOMMSingleInstance as a metaclass constraint. (This may occur indirectly because of the construction of a derived metaclass). The initialization of the class failed because somInitMIClass defined by SOMMSingleInstance is in conflict with another metaclass that has overridden somFree. That is, some other metaclass has already claimed this right to override somFree.
- 60004
- An invocation of somrRepInit was made with a logging type other than 'o' or 'v'.
- 60005
- The sommBeforeMethod or the sommAfterMethod was invoke on a SOMRReplicbleObject whose logging type is other than 'o' or 'v'. This error cannot occur normally. The likely cause is that some method invoked on another object has overwritten this object's memory.
- 60006
- A Before/After Metaclass must override both sommBeforeMethod an sommAfterMethod. This message indicates an attempt to create a Before/After Metaclass where only one of the above methods is overridden.