Using SOM for Tool Integration
Abstract
IBM's System Object Model (SOM) is an object-oriented programming interface for building binary class libraries. When changes are made to a SOM class, client programs that use the SOM class will not need to be recompiled. The SOM Toolkit provides a set of frameworks for building object oriented applications. These include Distributed SOM, the Persistence Framework, the Replication Framework, and the Emitter Framework. In this paper, we discuss our experience using SOM to handle the control aspects of tool integration.
Introduction
It is generally believed that software developed using object-oriented techniques is easier to use, maintain, and extend. SOM complements existing programming languages by providing a language-neutral model for developing object-oriented applications[1]. To use SOM, a developer defines the class interfaces in a language called Interface Definition Language (IDL). The developer then implements the methods in any of the languages that are supported by the SOM compiler. Finally, the classes are compiled into a dynamic link library (DLL) to be distributed to clients. Clients can use these classes directly or can override existing methods to develop their own applications . As long as the class interfaces remain unchanged, a new DLL can be redistributed to clients without requiring them to compile their applications. This full backward binary compatibility of SOM is best demonstrated in the building of the OS/2 Workplace Shell[4] .
As the computer industry is moving toward distributed client-server systems, tools and applications have to interoperate in a heterogeneous environment. Existing integra -tion techniques such as Dynamic Data Exchange[5] do not extend well into such distributed environments . The Object Request Broker (ORB), as defined by the Object Management Group (OMG), provides mechanisms by which objects transparently make requests and receive responses. The ORB thus provides interoperability between applications on different machines[2] .
Distributed SOM (DSOM), which is built on top of SOM, was developed to comply with the OMG's specification of the ORB[1]. DSOM makes the location of an object transparent to clients . This means that a client program will always use the same invocation to access an object regardless of whether it is local or remote . Therefore a tool can use DSOM to interact with another tool without knowing where the other tool is located .
In this paper, we will look at how to use the infrastructure in SOM to build and integrate tools. We will then look at how these SOM -based tools can be easily used to compose new tools . Tool Composition refers to the ability to build new tools by using existing tools . Finally, we will look at the notification aspects of tool communication.
Defining a Tool as Classes in SOM
The first step to use SOM is to define a class interface . Suppose we want to define an edit tool called Lpex in SOM . We begin by defining the set of operations that are supported by the tool . The result is then represented in an IDL:
#include <somobj .idl > interface FileObject; interface Lpex : SOMObject { void etart(in FileObject fileObj); // This method starts the Lpez // editor on the file object void save(in FileObject fileobj); // This method saves the file object void goToLine(in FileObject fileObj); // This method positions the cursor // to a specified line };
The SOM compiler can then be run to produce binding files and an implementation template for the Lpex class . Code can then be added to the implementation template to implement the tool operations. For example, the start method implementation might contain code to display an edit screen.
Notice that each method takes a FileObject as an input parameter. The FileObject is another SOM object that is used to store file-related information. One of the attributes is a file name. We will look at FileObject in more detail later on.
Tool Invocation
A client can use offset resolution to invoke the methods in the Lpex class . For example, another tool program that is written in C++ can use the following to invoke a method in the Lpex class.
#include <FileObject .zh> #include <Lpex .xh> int main(int argc, char *argv[] ) { Environment *env; FileObject *file; Lpex *lpez; env = somGetGlobalEnvironment(); // create an instance of FileObject // set file name to test . c file = new FileObject ; file->_set_fileName(env, "test .c"); // create an instance of Lpex lpex = new Lpez; // invoke start method lpex->start(env, file); // invoke goToLine method lpex->goToLine(env,file); ... // Free the instances delete file; delete 1pez; }
Offset resolution is the fastest way to invoke a method but is also the most restrictive. The client must know the name of the class and the name of the method at compile time. The client must also have the bindings (.xh files) for the tool IDL. Therefore, this scheme is most suitable for tightly coupled tools. An example of tightly coupled tools is an edit, compile and debug tool set. In the next section, we will
show how to use metaclasses and dispatch function resolution to achieve more flexibility for less tightly coupled tools.
Using Metaclasses
The methods we defined above for the Lpex class are instance methods. These are methods that can be performed by an instance of the Lpex class . Every class in SOM is also associated with a class, which is referred to as metaclass . SOMCIass is the root class for all SOM metaclasses and is also the metaclass for the above Lpex class .
The metaclass of a class provides the class methods for the class. Class methods are used to perform global operations on a class . For example, class methods can be provided to create instances, delete instances and keep track of information about the instances of the class . Since soMClass is the root class for all metaclasses, it provides generic class methods to create new instances . For a detailed description of soMClass, see the SOM Toolkit Reference Manuai[6] .
We will create a new metaclass for our Lpex class. The new IDL becomes:
#include <somobj.idl> interface FileObject; interface MetaLpext; // forward declaration interface Lpez : SOMObject { void start(in FileObject fileObj); // This method starts the Lpex editor on // the file objec t void save(in FileObject fileobj) , // This method saves the file object void goToLine(in FileObject fileObj) , If This method positions the cursor t o // a specified line #ifdef _SOMIDL_ implementation { metaclass = MetaLpex ; dllname = "lpex .dll" ; } ; endif; }; interface MetaLpex; { Lpex createLpex(in FileObject fileObj); // This method creates and returns a new // instance of the Lpex class };
The Lpex class now contains a class method createLpex that is used to create an instance of an Lpex class . The class relationships are shown in Figure 1. In the next section,we will show how a class method facilitates the use of runtime method resolution.
FIGURE 1. Parent class and Metaclass relationships
Using Dispatch to Invoke an Object's Method
In this section, we show how a program that does not have knowledge about the Lpex class at compile time can use the runtime method resolution in SOM to invoke an Lpex instance method. To invoke an instance method, we have to first obtain an instance pointer to the Lpex class. This can be done by calling the class method createLpex. However, in order to call createLpex, we must have a pointer to the class object.
SOM provides a somFindClass method that allows one to dynamically create a class object. Using the pointer to the class object, we can then use somDispatch to invoke the class method createLpex to create a new Lpex instance . This is shown in the following code segment:
// creates a class object for Lpex // and stores the pointer in classPtr classPtr = somFindClass { SOMClassMgrObject, somxdFromString("Lpex"),0,0); // invokes createLpex class method to // create an Lpex instance 1pexPtr = eomDispatch(claeePtr, somldFromstring("createLpex"), 0);
The instance pointer 1pexPtr can then be used to invoke any of the Lpex instance methods . The following invokes the start method:
somDispatch(lpexPtr, somldPromString("start"), "test .c");
This approach provides maximum flexibility for tools to interact with each other . A tool can determine at runtime what other tools it needs to invoke in order to accomplish its task . For example, the class name Lpex and the method names createLpex and start can all be input from the caller. The tool logic can then use the dispatching mechanism to invoke the requested tool.
Using DSOM
Perhaps the biggest advantage of wrapping a tool as SOM objects is to prepare the tool for remote access . The DLL "Ipex .dll" that implements the class Lpex can be used unchanged in a distributed environment . This is because DSOM provides a generic server that includes code for server activation, loading the Lpex DLL, object creation, and data marshalling. By registering the Lpex class with the generic server program, the Lpex program is ready to be accessed by clients from different machines.
For a client to send requests to a remote tool, the interface for the operations remains the same. The only differences lie in the creation and deletion of the tool instance . In the Lpex example, the local Lpex creation statement :
lpex = new Lpex ;
should be replaced by a call to find the Lpex server. If the server is found, a server proxy object is returned . The server proxy can then be used to create an Lpex instance . This is shown in the code segment below:
server = somdFindAnyServerByClass( SOMD_ObjectMgr, "Lpex") ; lpex = somdCreateObj(server, "Lpex") ;
The Lpex proxy that is returned from somdCreateObj can be used by the client to invoke operations on the remote Lpex. To destroy the Lpex proxy, somdDestroyObject should be used instead of somFree.
Tool Composition
We will now look at how to build new tools based on existing tools. Tool Composition has four components:
- Data Definition
- Tool Definition
- Method to Implementations Mapping
- Runtime Generation.
Data Definition
This is used to define object types and the operations that operate on an object type. Object types are used to represent the user's world at the interface level. For example, a FileObject can be used to represent file information and a PrintObject can be used to represent printer information . Each object type can have its own data and can perform a set of actions . The same action might be supported by different object types. The behavior of the action is determined by the providing object. This is known as polymorphism.
To make use of SOM for tool composition, we translate each object type into an IDL. For example, the IDL for a FileObject can be as follows:
#include <somobj.idl> interface FileObject: SOMObject; { attribute string fileName ; void edit(); // invokes editor on file void checkIn(); // checks file into library void checkOut(); // checks file out from library };
An object type can also be defined using existing object types. This is known as inheritance. A derived type can override the methods in its parent's class. For example, a DocObject can be derived from a FileObject and overrides the edit method.
At this stage, we have not yet specified the implementations for each method . We simply provide an interface for a user to interact with objects. Later on, we will show how to provide the implementations for the different methods.
Tool Definition
This is used to describe a tool, the data type it works with, and its entry points . The Lpex class example above is an example of a tool definition. For the purpose of illustration, we will define another tool Cmvc. Cmvc is a configuration management tool. Its IDL is shown below:
#include <eomobj .idl > interface Cmvc : SOMObject ; { void checkIn(in FileObject fileObj); // This method checks a file into Cmvc void checkOut(in FileObject fileObj); // This method checks a file out // from Cmvc };
8.3 Method to Implementations Mapping
This is used to define different implementations for a method on an object type . In effect, this defines how an object behaves when a request is received. For example, the edit method on a FileObject and a DocObject might behave differently as shown in Table l.
Table 1. Different Implementation for edit
Object Type | Method | Implementations |
---|---|---|
FileObject | edit | Lpex->start |
DocObject | edit | Framemaker->start |
Using this technique, an organization can start configuring tools based on its needs . To complete the implementation mappings for a FileObject, an organization might map the checkIn and checkOut methods to the check in and check out implementations in Cmvc:
Table 2. Simple Mappings for FileObject
Object Type | Method | Implementations |
---|---|---|
FileObject | checkIn | Cmvc->checkin |
FileObject | checkOut | Cmvc->checkOut |
Over time, the organization might want to perform the following additional functions on check in and check out :
- A file can be checked in if the complexity level is below a certain number. A Metric tool with the IDL below is to be used for complexity analysis.
interface Metric: SOMObject; { long analyse(in FileObject fileObj); // Return ok if the complexity of the // file is below 10 };
- After a file is checked out, invoke the Lpex editor on the file.
This organization will therefore redefine its mappings for the checkln and checkOut method:
Table 3. Complex mappings for FileObject
Object Type | Method | Implementations |
---|---|---|
FileObject | checkIn | Metric->analyse
Cmvc->checkIn |
FileObject | checkOut | Cmvc->checkOut
Lpex->start |
Clearly, it is important that such a migration is transparent to the users . Any program such as graphical user interfaces (GUIs) that were written to use the checkln and checkOut methods should not need to be recompiled or relinked. In the next section, we will see how a new runtime can be generated to replace the old runtime to make such a migration completely transparent.
8.4 Runtime Generation
Based on the Method to Implementations mapping, the implementations for each object type can be generated. For example, the C++ implementations for the FileObject in the initial configurations shown in Table 1 and Table 2 will look like the following:
#include <Cmvc.xh> #include <Lpex.xh> SOM_Scope void SOMLINK checkln ( Fileobject * somSelf, Environment *ev) { Cmvc *citric ; // create a new instance cmvc = new Cmvc; // invoke cmvc checkln method cmvc->checkln(somself, ev); }
SOM_Scope void SOMLINK checkOut ( FileObject *eomself, Environment *ev) { Cmvc *citric ;
// create a new instance cmvc = new Cmvc; // invoke Cmvc checkout method cmvc->checkOut(somSelf, ev); }
SOM_Scope void SOMLINK edit ( FileObject *somSelf, Environment *ev) { Lpex *lpex ; // create a new instance lpex = new Lpex ; // invoke Lpex start method lpex->start(somself, ev) ; }
A DLL can be built for the FileObject and distributed to clients. When a new configuration is defined, one can regenerate a new implementation for the FileObject. The new implementation is highlighted in italics below :
#include <Cmvc .xh> #include <Lpex .xh> #include <Metric.xh> SOM_Scope void SOMLINK checkln ( Fileobject *somSelf, Environment *ev ) { Cmvc *cmvc; Metric *metric; // create new instances cmvc = new Cmvc; metric = new Metric; // check complexity before check in // file if ( metric->analyee(oomSelf, ev) ) { // invoke cmvc checkln metho d cmvc->checkln(somself, ev) ; } } SOM_Scope void SOMLINK checkout ( FileObject *somself, Environment *ev ) { Cmvc *cmvc; Lpex *lpex; come = new Cmvc; lpox = new Lpex;
//invoke Cmvc checkout method cmvc->checkout(somSelf, ev);
// invoke Lpex start method lpex->atart(eomself, ev); }
SOM_Scope void SOMLINK edit ( FileObject *somSelf, Environment *ev) { Lpex *lpex ; // create a new instance lpex = new Lpex; // invoke Lpex start method lpex->start(somSelf, ev); }
Any client programs that were developed to use the DLL will execute the new configurations when the DLL is replaced.