Object-Oriented Programming Using SOM and DSOM/Distributing Your Objects

From EDM2
Jump to: navigation, search

Distributed SOM (DSOM) allows applications to access SOM objects across address spaces. There are two flavors of DSOM: Workstation DSOM and Workgroup DSOM. Using Workstation DSOM, an application can access objects in other processes, or other address spaces, in a single-machine environment. Currently, the Workstation DSOM Enabler is available for both OS/2 and AIX. The Workstation DSOM Enabler for Windows has been recently announced and will be available in the summer of 1994.

Using Workgroup DSOM, an application can access objects distributed across a network of machines. In the current release, Workgroup DSOM supports distribution across local area networks (LANs) comprising both OS/2 and AIX systems. Workgroup DSOM for Windows has also been recently announced, and this allows you to distribute your objects across networks comprising OS/2, AIX, and Windows. Future releases of DSOM may support other platforms, including large, enterprise-wide networks.

The SOMobjects Developer Toolkit version 2.0 for OS/2 as used in this book includes the Workstation DSOM for OS/2. The examples included on the diskette run on OS/2. However, if you install the Workgroup Enabler for OS/2 and AIX, then you will be able to move the SOM objects to AIX and access them from OS/2. This architecture is important because it allows developers to develop their distributed objects in a single-machine environment and then deploy the objects in a distributed environment by switching to the Workgroup Enabler.

DSOM Overview

DSOM supports transparent, remote access to distributed objects. The DSOM run-time routes requests to objects that are outside the address space of a client. In effect, DSOM is a remote method capability. It allows a client to run methods on remote objects. Figure 5.1 shows the DSOM structure.

Figure 5.1

DSOM is typically used when you want to allow objects to be shared by multiple processes. In DSOM terminology, the objects exist in a DSOM server, and different clients can access the objects via the DSOM run-time. Another situation where DSOM should be used is when you want to divide your application so that a failure in one part of the application will not cause the entire application to crash. One potential example of such usage is the OS/2 Workplace Shell. By implementing the Workplace Shell as a DSOM server, the Workplace Shell can avoid crashing due to Workplace Shell application errors.

The development of a DSOM application involves the following steps.

  1. Define the interface for your objects and write code to implement your objects.
  2. Create a server program that executes and manages the objects you defined in step one.
  3. Create a client program that accesses the objects.
  4. Configure your DSOM environment.
  5. Build and register your classes and your server program.
  6. Run the client program.

The following sections provide details on each of the above steps.

Define and Implement Your Objects

DSOM has been designed so that it is easy for you to migrate your non-distributed objects into a distributed environment. This means that you can build a class library following the steps described in "Creating a DLL for SOM Classes" on page 79 without knowing that your class library would ever be accessed remotely. You define your class interfaces in IDL and then implement your classes. You do not need to derive your classes from any special DSOM classes.

Keep the following steps in mind when you design and implement your classes so that you will not need to make any changes to your classes when you want to use them in a distributed environment:

  1. Set the dllname modifier in the implementation section of each class IDL to the name of the dynamic library that contains the class.
  2. Avoid printing to standard output. This is because the server that is running your objects might not have a display.
  3. If you are creating or deleting objects in your methods, you might need to determine whether the objects you are creating are local or remote. This is because the calls to create local objects are different from the calls to create remote objects.
  4. If an object wants to preserve a data value that is created by a client, it must explicitly allocate memory and make a copy of it. The client copy will be freed at the end of the method call.
  5. If you are passing structures with embedded pointers as parameters, you will need to manage your own pointer de-referencing. DSOM does not copy pointer values when copying the values of the structure fields.

Server Program

In DSOM, a server program is a program that manages objects. Upon a request from a client, a DSOM server will load the DLL for the class, create or locate the target object and handle the demarshalling and marshalling of requests and results. A server program has to be registered with DSOM in an Implementation Repository. The Implementation Repository is a database that is used by DSOM to find and activate servers, when a client invokes a method on a remote object. The mechanism to register a server program with the Implementation Repository is described in Section 5.6.2 on page 101.

A server program consists of three parts:

  • A main program that provides a process for the objects in the class libraries that the server manages.
  • A server object, derived from SOMDServer class that provides methods to manage the objects in the server process.
  • One or more class libraries that provide the object implementations. Usually these libraries are built as DLLs so that they can be loaded and linked by the server program dynamically.

Using the Generic Server Program

DSOM provides a generic server program that is already compiled and linked. It can be used in many environments. The generic server simply receives requests and executes them synchronously until the server is stopped. The generic server program will find and load any DLL automatically if it has not already been loaded.

If you build your classes following the practices described above and then use the generic server program, you can very easily distribute your classes without additional programming cost.

The name of the generic server program is somdsvr. The path for the program is %SOMBASE%\bin\somdsur.exe on OS/2.

Writing your own Server Program

Sometimes the generic server program is not sufficient for some applications. For example, an application might want a server to behave as both client and server. Such a server can respond to incoming requests, as well as access remote objects that are managed by other servers. In this case, one can write their own application specific server program. A server program must include the following functions.

  1. Initialize the DSOM run-time environment.
  2. Initialize the SOM Object Adaptor, SOMOA. The SOMOA handles all communications between the client and server.
  3. Initialize the ImplementationDe{ object. The lmplementationDef object represents the implementation definition of a server. It contains attributes that describe the server's ID, server's alias, host name, classes that the server manages, etc. The ImplementationDef object can be retrieved from the Implementation Repository by passing the server's ID or the server's alias.
  4. Indicate that the server is ready to process requests.
  5. Start processing requests loop.

The code in Figure 5.2 shows an example of a server program.

#include <Somd.xh>
#include <Somoa.xh>
#Include <implrep.xh>
#include <Stdio.h> 

int main(int argc, char *argv)
{
  Environment ev;
  long rc;

  //Initialize DSOM environment
  SOM_InltEnvironment(&ev);
  SOMD_Init(&ev);

  // Initialize SOM Object Adapter
  SOMD_SOMOAObject=new SOMOA;

  // get lmplemenlationDef from Implementation Repository using the input Implementation ld
  SOMD_ImpiDefObject=SOMD_ImpiRepObject->find_impk:fef(&ev,(lmplld) argv[1]);

  // Indicate that the server is ready to process requests
  SOMD_SOMOAObject->impl_is_ready(&ev, SOMD_ImpiDefObject);
 
  // Start processing requests
  for (;;)
  {
    // Add application specific logic
    rc = SOMD_SOMOAObjecl->execute_next_request(&ev, SOMD_WAIT) ;
    // Add application specific logic
  }
}

Client Program

For the most part, a client program in DSOM looks very much like a client program in SOM. The differences lie in the initialization of the run-time environment, the creation and destruction of an object, as well as the finding of existing objects or servers. A client program does not need to know where an object is located or what communication protocols are used between systems.

DSOM Initialization

First, every DSOM program must include the file <Somd.xh> (or <somd.h> if you are using C bindings). This file defines the constants, global variables, and runtime interfaces that are used by DSOM. Next, the client must call the DSOM initialization function, SOMD_Init, to initialize the DSOM run-time environment.

When SOMD_Init is called, a run-time object, called the DSOM Object Manager is created. The DSOM Object Manager provides the services for clients to create, find, and destroy objects in the DSOM run-time environment. A pointer to this object is stored in the global variable SOMD_ObjectMgr.

Before the process exits, the SOMD_Uninit function should be called to free the DSOM resources. A typical implementation for DSOM initialization is presented below:

#include <Somd.xh>
main()
{
  Environment *ev;
 
 ev = SOM_CreatelocaiEnvironment();     // create and initialize an Environment structure
 SOMD_Init(ev) ;                        // initialize DSOM runtime
 SOMD_Uninit(ev);                       // Free DSOM resources
 SOM_ DestroylocaiEnvi ron ment( ev);   // Free Environment structure
}

Creation and Destruction of Remote Objects

After the DSOM environment is initialized, you can create a remote object. You can either create an object on any server that implements the class, or you can find a specific server and create an object on that server. H an object is created, DSOM returns a proxy to the object. A proxy is a local representation for a remote target object. A proxy inherits the target object's interface. Therefore, once you obtain a proxy, you can invoke the methods on the target object using the proxy. The operations invoked on the proxy are not executed locally, they are forwarded to the real target object for execution.

Using A Random Server

To create an object in an arbitrary server, a client will call the somdNewObject from the DSOM Object Manager. The following shows the syntax for the method in C.

  myObject=_somdNewObject(SOMD_ObjectMgr,        // global instance
                            env,                 // pointer to Environment structure
                            objectClassName,     // name of the class
                            hints);              // create options( optional)

The parameter SOMD ObjectMgr is a pointer to the DSOM Object Manager that is created during DSOM initialization. The parameter objectClassName is a string that represents the class name of the object. The parameter enu is a pointer to the client's Environment structure. The parameter hints is an optional string that can be used to specify creation options. Here is how you will use somdNewObject to create a remote object. Say you use the following new operator to create a local Person object:

person= new Person() ;

You will replace it with the following to create a remote Person object:

person=(Person*)SOMD_ObjectMgr->somdNewObject(ev,"Person","");

If no object could be created, NULL is returned and an exception is raised. Otherwise, a Person proxy is returned in the variable person. From this point on, the client would use the proxy to invoke methods on the object in the same way it would if the object was local. For example, the client can invoke the _set_name method on the Person proxy:

person-> _set_name (ev, "Mary~);

This results in a message being sent to the server process that contains the object. The DSOM run-time at the server decodes the message and invokes the method on the target object. The result is then returned to the client process in a message. The DSOM run-time at the client decodes the message and returns the result to the caller.

Using A Specific Server

When a client calls somd.NewObject to create a remote object, it places no constraints as to where the remote object is created. However, a client might want more control over the distribution of objects. In this case, the client can use the methods provided by the DSOM Object Manager to find specific servers for creating their objects.

A client can ask the DSOM Object Manager to find a server by name, by ID, or by a class it supports. Recall that a server has to be registered with DSOM in an Implementation Repository. The registration information contains, among other things, a unique server name (known as "Implementation Alias"), a unique Implementation ID, and the classes associated with the server. The DSOM Object Manager consults the Implementation Repository to find the requested server. If a server is found, a server proxy is returned. The client can then use the server proxy to create or destroy objects in that particular server.

To find a server by name, a client will call the somd.FindServerByName method from the DSOM Object Manager. The following shows the syntax for the method in C.

myServer = _somdFindServerByName( SOMD_ObjectMgr,  //global instance
                                  env,             //pointer to Environment structure
                                  serverName );    // server Name

The parameter serverName is a string that specifies the name of the server. This is the name you define as Implementation Alias when you register your server in the Implementation Repository. To find a server by ID, a client will call the somdFindServer method from the DSOM Object Manager. The following shows the syntax for the method in C.

myServer = _somdFindServer(SOMD_ObjectMgr,// global instance
                           env,           // pointer to Environment structure
                           serverid );    // serverld

The parameter serverld is a string that specifies the Implementation ID of the server. This is the unique ID that is generated when you register your server in the Implementation Repository.

There are two ways to find a server by a class it supports. You can either use somdFindServersByClass to find all the servers that are capable of supporting the specified class. You then choose the server you want to use based on certain characteristics such as the location or the name. Or you can use somdFindAnyServerByClass to find any of the servers that supports the given class. The syntax for both methods inC are shown below:

sequence<SOMDServer> = _somdFindServersByCiass(SOMD_ObjectMgr,  //global instance
                                               env,             //env structure
                                               objectClass);    //class name

myServer = _somdFindAnyServerByCiass(SOMD_ObjectMgr,   //global Instance
                                     env,              //env structure
                                     objectCiass);     //class name

The parameter objectClass is a string that specifies the name of the class that the server needs to be capable of creating. The classes that a server supports are specified during registration.

Once a server proxy is obtained using any of the above methods, the client can create a new object in the requested server using the somdCreateObj method.

 myObject = _somdC reateObj(myServer,         //server proxy
                            env,              //pointer to Environment structure
                            objectClassName.  //name of the class
                            hints) ;          //create options( optional )

The parameter myServer is the pointer to the server that is capable of creating an instance of the specified class. The remaining parameters are the same as the parameters in somdNewObject.

The following shows how you will use somdFindServerByName to locate myPersonServer and then create a Person object on that server.

SOMDServer *myServer;
Person     *person;

myServer = SOMD_ObjectMgr->somdFindServerByName(ev,"myPersonServer");
If (myServer)
    person= (Person*) myServer->somdCreateObj(ev,"Person","");

User Defined Metaclass

Any class that has a user-defined metaclass should not use the somdNewObject method to create a remote object. This is because the somdNewObject simply calls the default constructor. Instead, the somdGetClassObj method should be used to create the class object for the specified class. In Section 3.8 on page 39, we used the Animal IDL to illustrate metaclass usage. We have modified it slightly so that it can be used in a distributed environment. The changes are presented in bold below:

#include <Somobj.idl>
#include <Somcls.idl>

interface Animal :
interface MetaAnimal : SOMCiass
{
   Animal createAnimal(in string name) ;

   #ifdef _ SOMIDL_
   implementation
   {
      releaseorder : createAnimal;
      dllname = "animal.dll";
   };
   #endif
};

interface Animal : SOMObject
{
   attribute string name;

   void sleep();

#ifdef _SOMIDL_
implementation
{
  releaseorder : _get_ name, _set_name, sleep;
  name: noset;
  metaclass = MetaAnimal;
  dllname = "anlmal.dll";
 };
 #endif
};

The noset modifier is specified so that the set_name method can be implemented explicitly in the class. The set_name method allocates memory and makes a copy of the input name so that the value can be preserved after the createAnimal method is called. The implementation for the createAnimal method is the same as before.

A client program will do the following to create a remote Animal object with the name Spot.

// Find the Animal server
server= SOMD_ObjectMgr->somdFindAnyServerByCiass(ev, "Animal") ;

// Get class object for Animal
metaAnimaiCisObj = (MetaAnimal *) server->somdGetCiassObj(ev, "Animal);

// Create remote animal object with the name "Spot"
animal= metaAnimaiCisObj->createAnimal(ev, "Spot") ;

Destroying Remote Objects

A remote object that is created using somdNewObject or somdCreateObj can be destroyed using the method somd.DestroyObject. The somd.DestroyObject method deletes the remote object as well as its local proxy. The following shows how to use somdDestroyObject to destroy the Person object and its proxy.

// Create remote person object
person = (Person •) myServer->somdCreateObj(ev, " Person","") ;

// Destroy remote person object and its proxy
SOMD ObjectMgr->somdDestroyObject(ev, person) ;

If the client only wants to delete the local proxy, but not the remote object, the somd.ReleaseObject method should be used instead. The following shows how to use the somdReleaseObject method to delete the local Person proxy.

// Destroy the local person proxy
SOMD_ ObjectMgr->somdReleaseObject(ev, person);

Finding Existing Objects

In a distributed environment, typically you will want to share objects among different processes. This means that a client might not necessarily want to use somdNewObject or somdCreateObj to create a new object all the time. Instead, a client might want to obtain the object reference to an existing object. To enable clients to share objects, DSOM supports the notion of externalizing proxies. The DSOM Object Manager provides methods to convert a proxy to a string ID, and vice-versa. The string ID can be saved in some persistent store. As long as the server that maintains the object exists, the string ID can be converted back to the proxy that points to the corresponding object. An example of this is given in the Distributed Calendar application in Section 5.10 on page 108.

To convert a proxy to a string ID, use the s

objectld = _somdGetldFromObject(SOMD_ObjectMgr,  // global instance
                                env,             // pointer to Environment structure
                                objectPtr);      // pointer to object

The parameter objectPtr is a proxy for which a string ID is returned. To convert the string ID back to a proxy, use the somdGetObje ctFromld method:

objectPtr = _somdGetObjectFromld(SOMD_ObjectMgr, // global instance
                                 env,            //pointer to Environment structure
                                 objectld);      //string ld representing object

Configuration

There are a few environment variables that need to be set before you can register or run your DSOM application. Table 5.1 lists all the environment variables related to OS/2 and indicates the ones that must be set. The following shows an example of setting the variables on OS/2.

set USER=mary
set HOSTNAME=steafth
set SOMIR=d:\mydir\project.ir
set SOMDDIR=d:\mydir\somddir

Building and Registering the Classes

Once you have implemented your classes, your server (optional) and your client program, you have to compile them and register your classes and your server before they can be used. You should compile your classes into dynamic link libraries following the steps described in "Creating a DLL for SOM Classes" on page 79. Your server program should be compiled and linked with somtk.lib. Your client program should be complied and linked with somtk.lib and any other necessary libraries.

You must register the class's interface and implementation in the Interface Repository before an object can be accessed remotely by DSOM. You must also register a description of a server's implementation in the Implementation Repository before the server program, and the class libraries that it manages, can be used.

Table 5.1 DSOM Environment Variables on OS/2
Environment Variables Optional Description
HOSTNAME no Identifies the name of each machine running DSOM.
USER no Identifies the name of a DSOM user running a client program.
SOMIR no Specifies a list of files which, together, make up the Interface Repository
SOMDDIR yes Specifies the directory where the files that make up the Implementation Repository should be located. This variable must be set before you reg1ster your server's implementation. If th1s variable is not set, DSOM will use the default directory %SOMBASE%\ etc\DSOM.
SOMDPORT yes Specifies a well-known port number. The same well-known port number must be used on all machines in a workgroup in order for an application to establish connections successfully.
SOMSOCKETS yes Specifies the name of a class that Implements communication services. This value is ignored by Workstation DSOM.
SOMDTIMEOUT yes Specifies how long a receiver should wait for a message or how long a sender should wait for an acknowledgement. The default value is 600 seconds.
SOMDDEBUG yes Enable DSOM run-time error reporting if set to 1. If set to 0, error reporting is disabled.
SOMDTRACELEVEL yes Enable DSOM run-time trace if set to 1. If set to 0, tracing is disabled.
SOMDMESSAGELOG yes Set to the name of the file where DSOM run-time error reporting or tracing messages are recorded. If not set, messages will be sent to the standard output device.

Registering your Classes in the Interface Repository

DSOM uses the information stored in the Interface Repository extensively. The DSOM servers consult the Interface Repository to find the name of the DLL for a dynamically loaded class. Recall that you set the DLL name for a class in the dllname modifier in the implementation section of the class IDL. DSOM also uses the Interface Repository when transforming local method calls on proxies into messages that are sent to the remote objects.

The Interface Repository is a file that is located by lhe environment variable SOMIR. To populate the Interface Repository, you use the Interface Repository emitter, which can be invoked when you run the sc command with the -u option. If SOMIR is not set, the Interface Repository emitter creates a file named "som.ir" in the current directory. The following compiles the IDL file person.idl and creates an m called "person.ir":

set SOMIR=c:\mydir\person.ir
sc -sir -u person.idl

Each class in the DLL must be compiled into the Interface Repository before running your DSOM application. DSOM uses the Interface Repository to find information on method parameters and return type. If a class has not been compiled into the Interface Repository, DSOM will generate a run-time error when an attempt is made to invoke a method from that class. The Interface Repository is described in more detail in Chapter 8.

The SOMobjects Developer Toolkit includes the utility irdump that can be used to find out if a class exists in the Interface Repository. For example, the following displays the definition of SOMObject:

irdump SOMObject

Registering your Server in the Implementation Repository

DSOM uses the Implementation Repository to find out information about a server's implementation. The Implementation Repository contains information such as the name of the server, the name of the executable for the server program, the name of the machine on which the server program is stored, and the classes that are associated with this server. DSOM uses this information to locate a server, and, if necessary, to activate the server so that clients can invoke methods on it.

The Implementation Repository consists of four files:

somdimpl.dat
somdimpl.toc
somdcls.dat
somdcls.toc

The location of the Implementation Repository is specified by the environment variable SOMDDIR. If SOMDDIR is not set, DSOM will attempt to use the default directory %SOMBASE%\etc\dsom on OS/2.

There are two ways that yo u can populate the Implementation Repository. The first is a static mechanism using the regimpl registration utility. The second is a dynamic mechanism where you can access and update the Implementation Repository using a programmable interface at run-time. These are described below.

Using the regimpl Registration Utility

The regimpl utility can be run either interactively or as a command. To run in interactive mode, type regimpl at the system command prompt. This brings up the DSOM Implementation Registration Utility menu, where you will be prompted for information. The command interface is perhaps more useful since you can invoke it from a makefile.

The following lists all the information that you can specify using the regim.pl utility. The command flag that corresponds to each function is given in parentheses.

  • Implementation alias (-i)

This is the name you defined for your server.

  • Server program name (-p)

This is the name of the program that will execute as the server. The default program name is somdsvr, which is the DSOM generic server. If you have implemented your own application specific server, enter the full pathname for the program. If the program is located in PATH, then only the program name needs to be specified.

  • Server class name (-v)

This is the name of the class that will manage the objects in the server. The default is SOMDServer.

  • Multi-threading (-m)

This option specifies whether or not the server expects each method to be run in a separate thread. The default is no.

  • Class name (-c)

Use this option to specify the list of classes that are associated with this server.

  • Host machine name (-h)

This is the name of the machine on which the server program code is stored. The same name should be set in the HOSTNAME environment variable.

  • Reference data file name (-f)

This is an optional parameter that contains the full pathname of the file that is used to store ReferenceData * associated with object references created by this server.

  • Backup reference data file name (-b)
  • This is an optional parameter that contains the full pathname of the backup file that is used to mirror the primary ReferenceData fi]e. This IDe can be used

to restore the primary copy if it becomes corrupted.

Each of these options can add to, update, or delete from the Implementation Repository. For example, the fo11owing commands add the server, myServer, to the Implementation Repository. The name of the server program jg servprog.exe, and the c1asses that are assoicated with myServer are Classl, Class2, and Class3.

regimpl -A -i myServer -p servprog.exe
regimpl-a -i myServer -c Class1 -c Class2 -c Class3

(Reference data is application-specific data if it ia associated with an object reference. It can contain information such as the object location or state thaL is specific to the application. The SOMOA class provides the create and create_constant methods to create and associate reference data with the object reference.)

You can list the information in the Implementation Repository. For example, the command,

regimpl -L -i myServer

displays the list shown in Figure 5.3.

The Implementation ID is a unique ID that is generated by DSOM. This is the ID that you use when you invoke the somFindServer method to locate a server by its Implementation ID.

You can also list the classes associated with a server. For example, the command,

regimpl -1 -i myServer

displays the c1asses shown in Figure 5.4.

Using the Programmable Interface

The Implementation Repository can also be accessed and updated dynamically, using the programmable interface provided by the lmplRepository class. The following summarizes the methods provided by the ImplRepository class:

Implementation id .............. : 2cc7eff7-067c6140-7f-00-0100007fOOOO
Implementation alias ........... : myServer
Program name ................... : servprog.exe
Multithreaded .................. : No
Server class ................... : SOMDServer
Object reference file .......... :
Object reference backup ........ :
Hostname ....................... : localhost

Figure 5.3 The Implementation Repository for myServer

Implementation alias Classes in this implementation
myServer Class1
Class2
Class3

Figure 5.4 The classes associated with myServer

  • add_impldef-adds an implementation definition to the Implementation Repository.
  • add_class_to_impldef-associates a class with a server.
  • remove_class_from_impldef-removes the association of a class with a server.
  • delete_impldef--deletes an implementation definition from the Implementation Repository.
  • update_impldef-updates an implementation definition in the Implementation Repository.
  • find_impldef-retums a server implementation definition given its ID.
  • find_impldef_by_alias-returns a server implementation definition given its alias.
  • find_impldef_by _class-returns a sequence of implementation definitions for servers that are associated with the specified class.
  • find_classes_by_impldef-returns a sequence of class names associated with a server.

The code example of Figure 5.5 illustrates how you can retrieve a server implementation given the server's alias. The variable SOMD_ImplRepObject is initialized by SOMD_Init to point to the ImplRepository object. The ImplementationDef object contains attributes for setting and accessing the server's implementation definition.

Execution

Once you have properly set up your environment variables and performed the necessary registration, you can run your DSOM application. To run a DSOM application, the DSOM daemon, somdd, must be started on each server machine. Client-only machines do not require an active DSOM daemon. The daemon can be started from a command line or from a start-up script. For example, start somdd can be used to start the daemon on OS/2.

The DSOM daemon is responsible for establishing a connection between a client process and a server. When a request is made to a server, if the server is not already running, the DSOM daemon will automatically start the server. Once the DSOM daemon is running, the DSOM application can be started.

A Checklist for DSOM Configuration

Configuring your environment for DSOM can be difficult and error-prone. The following checklist provides tips for configuring your DSOM and network environment. Refer to the product's Installation Guide for additional details.

1. Workstation DSOM can be run on a single OS/2 or AIX without any networking hardware, if it is not used with the Event Manager Framework. In this case, Workstation DSOM uses shared memory for communications.

#include <Stdlib.h>
#include <String.h>
#include <somd.Xh>
#include <implrep.xh>
#include <stdio.h>

main(int argc, char **argv)
{
  Environment *ev;
  ImplementationDef ·impiDef;
  sequence(string) classes;
  short i;
  
  ev = SOM_CreatelocaiEnvironment();
  SOMD_Init(ev);

  // Find the lmplementationDef object for the specified server alias
  impiDef = SOMD_lmpiRepObject->find_impldef_by_alias(ev, argv[1 ]);

  // Find the sequence of classes associated with this server ld
  classes = SOMD_ImpiRepObject->find_classes_by_impldef(ev, impiDef-> _get_impl_id(ev));
  
  printf{"Server : %s\n", argv[1]);
  printf("Program Name: %s\n", impiDef-> _get_impl_program(ev));
  printf("Server Class: %s\ n", impiDef-> _get_impl_server_class(ev));
  printf("Host Name: %s\n", impiDef-> _get_impl_hostname(ev));

for (i=O; i < sequencelength(classes); i+-+-)
{
   printf("%s\n", sequenceEiement(classes,i) ) ;
}
  SOMD_ Uninit(ev);
  SOM _ UninitEnvironment( ev) ;
}

Figure 5.5 A program that accesses the Implementation Repository

2. Workstation DSOM, if used with the Event Manager Framework, requires TCP/IP to be installed.

3. Workgroup DSOM requires networking software for its operations. Table 5.2 lists the network products that are supported. Make sure at least one of them is installed and started before you use Workgroup DSOM.

+Table 5.2 Network software requirements for Workgroup DSOM
OS/2 AIX
TCP/IP Version 1.2.1 with CSD UN34109. This product includes Network File System {NFS). TCP/IP and Network File System {NFS), Version 3.2, as shipped with the AIX Base Operating System Network Facilities.
NetWare Requester for OS/2 v2.01. This product includes Novell IPX/SPX protocols. Novell IPX/SPX transport, as shipped with Netware for AIX/6000 from IBM v3.11.
Network Transport Services/2 (NTS/2), v1.0. This product includes the NetBIOS protocol. N/A

4. Workgroup DSOM requires each machine to have a unique network address (Internet address), and a unique hostname. The association between hostnames and Internet address is maintained in a "hosts" file. The hosts" file must be created on each machine, and must contain an entry for both the client and the server machines.

If you are running NetWare, you also need an "ipxaddrs" file for each machine on the network. The "ipxaddrs" file entry maps an Internet address to a native Novell IPX/SPX address.

If you are running NetBIOS, verify thai you do not have a "resolv" file.

1. When using the Novell, or the NetBIOS protocol, there is an additional step in configuring Workgroup DSOM. This is because there is a software layer that maps Internet addresses onto local Novell, or native NetBIOS network addresses. If you are using the Novell protocol, you must run "ipxconf" which is supplied with the NetWare support for SOMobjects. If you are using the NetBIOS protocol, you must run "nbconf" which is supplied with the NetBIOS support of SOMobjects.

2. The environment variable ETC must be set on AIX for NetWare and on OS/2 for all supported protocols. It should be set to the pathname for the directory that contains the "hosts" file and the "ipxaddrs" file if Net Ware is used.

3. The environment variable SOMSOCKETS must be set when using one of the communication protocols. The values are TCPIPSockets for TCP/IP, IPXSockets for NetWare, and NBSockets for NetBIOS. Note that this variable is ignored when using Workstation DSOM in a single machine and Event Management Framework is not used.

4. The following DSOM environment variables must be set when using either Workstation or Workgroup DSOM. They do not have default values.

HOSTNAME=<name>
MALLOCTYPE=3.1 (AIX ONLY)
SOMIR=<file{s)>
USER=c:::name>

5. The following DSOM environment variables have default values that can be redefined by the user if desired.

SOMDDIR=<directory>
SOMDPORT =<integer>

For Workgroup DSOM, the client and the server machines must have SOMDPORT set to the same value in order for connections to be established.

6. IDL must be compiled into the Interface Repository for all application classes. For Workgroup DSOM, the client and server machines must both have the application IDL in their Interface Repositories.

7. A server implementation (server program and class libraries) must be registered with DSOM by running the implementation registration utility, regimpl. For Workgroup DSOM, the client and server machines must share the Implementation Repository via a shared file system or have identical copies. Specifically, the automatically generated implementation ID for a given implementation must be the same on both machines. The Implementation ID is used by DSOM to do the binding between client and server. They must be identical. Note that if you simply run regi.mpl on both machines, the ID that is generated might not be identical. Therefore, it is recommended that you share the Implementation Repository using a shared file system.

8. When moving a server from one machine to another, you should run regimpl with the update option to update the information associated with the server implementation. Do not simply create a new server implementation. This is because it will invalidate any existing references to the previous Implementation ID.

9. All class libraries and networking libraries must be in directories specified in LIBPATH. For Workgroup DSOM, both the client and server machines need the DLL that contains the classes. Note that for machines that will only be used to run client programs, a "stub" DLL may be built. A client-side "stub" DLL can be created by emiting the .xh, .xih and .cpp files or the .h, .ih and .c files from the IDL for a class and compiling these files in the same way that regular DLLs are compiled. A "stub" DLL provides the support for creating local proxy for the remote object.

10. The DSOM daemon, somdd, must be started on each server machine. Note that if the environment variable SOMIR has the value "... ;som.ir" on OS/2 or " ... :./som.ir" on AIX, the client programs, server programs and somdd must be started in the same directory as the "som.ir" file. Therefore, it is recommended that SOMIR contains fully-qualified file names. For example " ... ;d:\myir\som.ir" on 08/2, or " ... :/u/joe/myir/som.ir" on AIX.

Dynamic Invocation Interface

CORBA defines the dynamic invocation interface (DII), which allows the dynamic creation and invocation of requests to objects. A client using the DII to send a request to an object obtains the same semantics as a client that is using the language bindings which are generated from the interface specification. The DII is useful for applications that need to invoke objects without knowing all the object interfaces at compile-time.

DSOM supports the CORBA DII. Currently, you can use DII to invoke remote objects only. The somDispatch method, which is non-CORBA, can be used to invoke methods dynamically on either local or remote objects. The somDispatch method is discussed in Section 3.12.3.

To invoke a method using the DII, a client has to perform two steps:

  1. Construct the request by using the create_request method from the SOMDObject class. A request comprises an object reference, an operation, and a list of parameters. The parameters are supplied as elements of an NVList object. The create_request method returns a Request object.
  2. Initiate the request by invoking the invoke or the send method on the Request object. The invoke method will wait for the operation to complete, whereas the send method will not wait for the operation to complete. If send is used, the client can later call the get_response method on the Request object to determine if the operation has completed.

A Distributed Calendar

To illustrate some of the concepts we have discussed in this chapter, we will develop a simple calendar system allowing you to record activities for a day. The following list is a summary of the functions:

  • Allows a user to enter or delete activities
  • Provides a graphical user interface (GUI) for user's actions
  • Supports multiple users that are located across a network
  • Allows sharing of any user's calendar

Since the purpose of this example is to illustrate the DSOM Framework, and not so much of the calendar functions, we will limit our calendar capability to support only hours, omitting minutes. Also, our calendar will support only a week's events. Nevertheless, one should easily be able to extend this example to a more realistic implementation.

What the User Sees

In this example, the client program is called PLANNER. When you invoke PLANNER, you can supply an optional parameter that specifies which calendar you want to work with. If you do not specify the parameter, it will default to my (Chris) calendar. The screen for my calendar is displayed in Figure 5.6.

The title of the screen indicates whose calendar it is. For example, if you type:

> PLANNER Kevin

This will invoke PLANNER with Kevin's calendar, and the title will show "Kevin Weekly Calendar".

SOM-DSOM-Figure 5 6.png

Figure 5.6 Chris weekly calendar

Use the Start and End spin button control to select. the start and end time for an activity, and type a description for the activity in the Description field. The Item menu allows you to Add or Delete an activity from your calendar. When you click on Add, the current activity will be added to the list. To delete an activity from the list, click on the activity to highlight it. Then select Delete from the Item menu and the activity will be deleted.

The Day menu lets you select what day of the week you want to work with. You can select either Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, or Saturday. Initially, it is set to Monday. When you select a different day, the description text on the screen will be updated. If there are any activities for that day, they will be displayed. The Day menu also contains a Quit button that you can select to exit t.he PLANNER program.

Calendar Sharing

Multiple users can work with a particular calendar. For example, I can add activities to my calendar. Someone else can look at my calendar entries as well as make changes to them. Changes are local to each calendar. That is, if I add an activity to my calendar, the activity will only be added to my calendar and not to another person's calendar.

High-Level Design

To design our calendar application, we need to decide what our classes look like. Our classes need to capture information, like the start and end time for an activity, as well as the description of an activity. The classes also need to keep track of the activities for a particular day, and maintain activity lists for an entire week. Finally, the classes need to support multiple calendar users and allow sharing between them.

We also need to design the GUI front end. It is important to recognize that the GUI front end is the client of the classes that we used to model our calendar. The GUI client is not the calendar object. The GUI client will perform tasks such as creating the calendar object and exporting the corresponding proxy, so that the calendar object can be shared between multiple clients (users).

Our calendar is modeled using three classes: a work item class (Workltem), a day class (Day), and a calendar directory class (CalendarDir). The Workltem class is used to encapsulate the start time, the end time, and the description of an activity. The Day class is used to encapsulate the list of activities for a particular day. Individual activities can be added to, or deleted from, a Day object. The CalendarDir class is used to encapsulate a week so we can support up to seven days of activities.

The three classes are packaged into a DLL called calendar.dll. We will use the generic DSOM server program (somdsvr). To support different calendars for different users, we need to register multiple calendar servers. For example, we will have a server with alias "Chris" for my calendar, and another server with alias "Kevin" for Kevin's calendar.

Recall that the generic server program handles requests in a synchronous manner, using a FIFO queue. This ensures that when multiple updates are made to a calendar server, the requests will be queued and processed sequentially.

The GUI client is built using the IBM User Interface Class Library. Although it is not necessary to understand GUI in order to understand SOM, we believe future applications will make use of both the SOM Frameworks and the User Interface Frameworks. Therefore, it is included here for those who are interested in seeing how the two can be used together.

Implementation Details

This section will provide the details of the design and implementation of each of the components in our calendar application.

The Work.Item Class

The IDL for the Workltem class is given in Figure 5.7. It introduces one new method and overrides two methods.

  • The mkEntry method allocates storage for the strings startTime, endTask, and then makes a copy of the input value. It is necessary to explicitly allocate memory for the strings so that the object will retain the values even after the caller falls out of scope.
  • The somInit method is overridden to initialize the attributes to NULL.
  • The somUninit method is overridden to free any storage that is allocated for the attributes when the object is destroyed.
#ifndef workitem_idl
#define workltem_idl

#include <somobj.idl>
interface Workltem : SOMObject
{
   attribute string startl1me;
   attribute string endlime;
   attribute string task;

   void mkEntry(in string slime. in string el1me, in string taskDesc);
 
  #ifdef _SOMIDL_
  implementation
  {
        releaseorder : _get_startl1me, _set_startl1me,
                       _get_endTime. _set_endTime,
                       _get_task, _set_task,
                       mkEntry;
 
        somInit: override;
        somUninit: override;
        dllname = "calendar.di!";
   };
   #end if
};
#endif

Figure 5.7 The Workltem IDL

The C++ code that implements the methods for the Workltem class is presented in Figure 5.8.

The Day Class

Figure 5.9 gives the IDL for the Day class. The Day class is used to capture the list of todo's for the day. It introduces two new methods and overrides two others.

  • The book method adds a Workltem object to the sequence workList. The sequence workList is a bounded sequence with a maximum of 50 Workltem objects. We will restrict ourself to working a maximum of 50 tasks per day. Should you desire more, you can use an unbounded sequence and modify the implementation to manage storage.
/*
* This file was generated by the SOM Compiler and Emitter Framework.
* Generated using:
*SOM Emitter emitxtm: somc/smmain.c
*/

#define Workltem_Class_Source
#include <Workitem.xih>

SOM_Scope void SOMLINK mkEntry(Workltem *somSelf, Environment *ev,
                               string slime, string elime, string taskDesc)
{
WorkltemData •somThis = WorkltemGetData(somSelf);
Work Item Method Debug("Work Item", "mkEntry");

  if (somThis->startlime) SOMFree(somThis->startTime);
  somThis->startTime = (string)SOMMalloc(strlen(sTime)+ 1 );
  strcpy (somThis->startlime, slime):

  if (somThis-> endlime) SOMFree(somThis->endlime);
  somThis->endTime = (string)SOMMalloc(strlen(eTime)+ 1 );
  strcpy (somThis->endTime, eTime);

  if (somThis->task) SOMFree(somThis->task);
  somThis->task = (string)SOMMalloc(strlen(taskDesc)+ 1 );
  strcpy (somThis->task, taskDesc);
}

SOM_Scope void SOMLINK somlnit(Workltem 'somSelf)
{
  WorkltemData •somThis = WorkltemGetData(somSelf);
  WorkltemMethodDebug("Workltem","somlnif);
 
  Workltem_parent_SOMObject_somlnit(somSelf);
  
  somThis->startTime =NULL;
  somThis->endTime =NULL;
  somThis->task = NULL;
}

SOM_Scope void SOMLINK somUninit(Workltem •somSelf)
{
   WorkltemData •somThis = WorkltemGetData(somSelf),
   WorkltemMethodDebug("Workltem", "somUninir);
 
   if (somThis->startlime) SOMFree(somThis->startTime);
   if (somThis->endlime) SOMFree(somThis->endlime);
   if (somThis->task) SOMFree(somThis->task);
 
   Workltem_parent_ SOMObject_somUninit(somSelf);
}

Figure 5.8 The Workitem class implementation

  • The removeltem method removes a Workltem object from the sequence workLisl. It searches the sequence for a Workltem object that has the same start'IYme, endTime, and task values as the input parameters. If one is found, it will be removed from the sequence.
  • The somlnit method is overridden to initialize the sequence workList. It allocates storage for the sequence and sets the length and maximum field of the sequence.
  • The somUninit method is overridden to free the storage that is allocated for the sequence workList when the object is destroyed.

The C++ code that implements the methods for the Day class is shown in Figure 5.10.

The CalendarDir Class

The IDL for the CalendarDir class is given in Figure 5.11. The CalendarDir class maintains a directory for a week of activities. It introduces two new methods and overrides two methods.

  • The add.Day method adds a Day object to the sequence weekList. The sequence

weekList is a bounded sequence with a total of seven elements, one for each day of the week.

  • The getDay method returns a Day object for the requested day.
  • The somlnit method is overridden to initialize the sequence weekList. It allocates

storage for the sequence, and sets the length and maximum field of the sequence.

  • The somUninit method is overridden to free the storage that is allocated for

the sequence weekList when the object is destroyed.

The C++ code that implements the methods for the CalendarDir class is given in Figure 5.12.

#ifndef day_idl
#define day_ldl

#include <somobj.idl>

interface Workltem;
interface Day : SOMObject
{
   const unsigned long MAXITEM =SO;  //today's date
   attribute long date;              //list of todo's
   attribute sequence<Workltem,MAXITEM> worklist;

  short book(in Workltem entry);
  short removeltem(in string start, in string end, in string desc):

#ifdef _ SOMIDL_
implementation
{
  releaseorder : _get_date, _set_date,
                 _get_worklist, _set_worklist,
                 book, removeltem;

  somInit: override;
  somUninit: override;
  dllname = "calendar.di!":
 };
 #endif
} ;
#endif

Figure 5.9 The Day IDL

The GUI Client The PLANNER program is the client that uses the above classes in a distributed fashion. The source files that make up the PLANNER program are planwin.hpp andplanwin.cpp. The listing for planwin.hpp is given in Figure 5.13.

The class DayPlannerWindow is derived from lhe IFrameWindow, ICommandHandler, and ISelectHandler classes from the IBM User Interface Class Library. The DayPlannerWindow class handles the creation of the main window and the processing of user events. It also handles the creation and management of calendar objects. A summary of what each member function does is given below:


/*
* This file was generated by the SOM Compiler and Emitter Framework.
* Generated using:
* SOM Emitter emitxtm: somc/smmafn.c
*/

#define Day_Class_Source
#Include <day.xih>
#include <workitem.xh>
#include <Slring.h>

SOM_Scope short SOMLINK book(Day *somSelf, Environment *ev,
                              Workltem* entry)
{
     DayData *somThis = DayGetData(somSelf);
     short rc;
     DayMethodDebug("Day", "book");
 
     if (sequencelength (som This->worklist) < sequenceMaximum( som This->worklist))
     {
      sequenceElement(somThis->workllst, sequencelength(somThis->worklist)) =entry;
sequencelength(somThis->workllst)++;
      rc= O;
     }
     else
     {
      rc = -1 ;
     }
     return rc;
}

SOM_Scope short SOMLINK removeltem(Day *somSelf, Environment *ev,
                                   string start, string end, string desc)
{
         DayData *somThis = DayGetData(somSelf);
         short i;
         Workltem *item;
         DayMethodDebug("Day","removeItem");
 
         for (i=O; i<sequenceLength(somThis->worklist); i++)
         {
          item = sequenceElement(somThls->workList,i) ;

          if ( (strcmp(start, item-> get_startTime(ev)) == 0) &&
          (strcmp(end, item-> _get_endTime(ev)) = 0) &&
          (strcmp(desc, item->_get_task(ev)) == 0))
{
          // Found item. Delete it from the list.
          sequencelength{som This->worklist)-;
          for (i; I< sequenceLength(somThis->worklist); i++)
          {
             sequenceElement(somThls->worklist,i) = sequenceEfement(somThis->worklist, i+ 1 );
          }
          return 0; 
     }
}
return -1;    // item not found
}

SOM_ Scope void SOMLINK somlnit(Day •somSelf)
{
     DayData ·somThis = DayGetData(somSelf);
     DayMethodDebug("Day", "somlnir);
 
     Day_parent_SOMObject_somlnit(somSelf);
 
     sequenceMaximum(somThis->workList) = MAXITEM;
     sequencel ength(somThis->Worklist) = 0;
     somThis->worklist_buffer =(WorkItem**) SOMMalloc(sizeof(Workltem *) * MAXITEM); 
}


SOM_Scope void SOMLINK somUninit(Day *somSelf)
{
     DayData *somThis = DayGetData(somSelf);
     DayMethodDebug("Day", "somUninit");
 
     if (somThls->worklist._buffer)
       SOMFree(somThis->worklist._buffer);
 
     Day_parent_SOMObject_somUninit(somSelf);
}

#ifndef caldir_idl
#define caldir_idl

#include <somobj.idl>

interface Day;
interface CalendarDir : SOMObject
{
  const unsigned long MAXDAY = 7;
  attribute sequence<Day, MAXDAY> weekUst;
 
  long addDay(in short daynum, in Day entry);
  Day getDay(in short daynum);

  #ifdef _SOMIDL_
  implementation
  {
     releaseorder : _get_weeklist, _set_weeklist,
                    addDay, getDay;
 
     somInit: override;
     somUninit: override;
     dllname = "calendar.dll";
   };
   #end if
};
#end if

Figure 5.10 The Day class implementation

  • The constructor constructs the frame window. It initializes the title bar using the name of the calendar server and creates the menu bar. It calls the setupClient function to set up the client window. It then calls the setupData function to set up the DSOM environment. Finally, it calculates the size of the main window, sets focus to the main window, and displays it.
  • The command function processes commands from the menu bar. The case MI_ADD handles the Add request. It adds the values from the input fields to the list box and calls the book function to add the item to the current activity list. The case MI_DEL handles the Delete request. It deletes the selected item from the list box as well as from the current activity list.
/*
* This file was generated by the SOM Compiler and Emitter Framework.
* Generated using:
* SOM Emitter emitxtm: somc/smmain.e
* /

#define CalendarDir_Class_Source
#include <caldir.xih>

SOM_ Scope long SOMLINK addDay(CalendarDir ·somSelf, Environment ·ev,
                               short daynum, Day* entry)
{
  CalendarDirData *somThis = CalendarDirGetData(somSelf);
  long rc;
  CalendarDirMethodDebug(•CalendarDir","addDay");
 
  if (sequeneelength(somThis->weeklist) < sequeneeMaximum(somThis->weeklist))
  {
    sequeneeElement(somThis->weeklist, sequencelength(somThis->weeklist)) =entry;
    sequeneelength(somThis->weeklist)++;
    rc= 0L;
  }
else
 {
  rc = -1L;
 }
return rc;
}
SOM_Scope Day* SOMLINK getDay(CalendarDir *somSelf, Environment *ev, short daynum)
{
   CalendarDirData *somThis = CalendarDirGetData(somSelf);
   CalendarDirMethodDebug ("CalendarDir", "getDay");

   return ( sequeneeElement(somThis->weeklist, daynum) );
}

SOM_Scope void SOMLINK somlnit(CalendarDir ·somSelf
{

  CalendarDirData •somThis = CalendarDlrGetData(somSelf);
  CalendarDirMethodDebug("CalendarDir", "somlnir);
 
CalendarDir _parent_SOMObject_somlnit(somSelf);

sequenceMaximum(somThis->weekList) = MAXDAY;
sequencelength(somThis->weekList) = 0;
somThis->weekList._buffer = (Day**) SOMMalloc(sizeof (Day*)* MAXDAY);
}

SOM_Scope void SOMLINK somUninit(CalendarDir ·somSelf)
{
  CalendarOrrData ·somThis = CalendarDirGetData(somSelf);
  CalendarDi rMethod Debug("CalendarDi r'', "som U ninit") ;

  if (somThis->weekList._buffer)
       SOMFree(som This->weekList. _buffer);
 
  CalendarDir _parent_SOMObject_somUninit(somSelf);
}

Figure 5.12 The CalendarDir class implementation

The cases MI_ SUN, MI_MON, etc., handle the selection of the day of the week. For instance, if Sunday is selected, ihe static field on the screen is updated to reflect the current day, and the showltems function is called to display the list of activities for Sunday.

The case MI_QUIT closes the application.

  • The selected function handles the selected command from the list box. It sets the list box cursor to the selected item.
  • The setupClient function creates a multi-cell canvas control as the client window.

It also creates the spin-button controls, the static text controls, the entry field control, and the list box control, placing them on the multi-cell canvas.

  • The setupData function initializes the DSOM environment and locates the specified calendar server. It then calls the findProxy function to determine if there is already an existing object reference to CalendarDir object that is managed by this server. If an object reference already exists, it will be used.

Otherwise, a new CalendarDir object is created, and its reference is converted into a string ID. The string ID is written to a file to allow the sharing of the CalendarDir object.

A simple one-to-one mapping convention is used to identify the object reference for a calendar server. For example, if "PLANNER Kevin" is invoked for the first time, a new CalendarDir object will be created for the Kevin calendar server. The reference to this object is converted to a string ID and stored in the file "Kevin". Later, when "PLANNER Kevin" is invoked again (say, by another user), the findProxy function will retrieve the string ID from the file "Kevin" and convert it back to the object reference so the second user can access Kevin's calendar.

  • The findProxy function checks to see if there is already a string ID for the CalendarDir object that is managed by this server. If there is an existing string ID, it will be restored back into the object reference.
  • The showltems function displays the list of activities for a particular day.
  • The book function creates a new Workltem object and adds it to the activitiy list for the day.
  • The destructor performs cleanup. If this client has created a file to contain the string ID for the CalendarDir object, we assume it is the last remaining client and we call the somd.DestroyObject method to destroy all the local proxies and the remote objects. The file is also erased.

If this client is not the one that created a file, we assume that there are existing clients out there that are still working with the calendar objects.

Therefore, we will not destroy the remote objects. Instead we call the somdReleaseObject method to release the local proxies.

  #ifndef PLANWIN_HPP
  #define PLANWIN_HPP
  
  #include <iframe.hpp>
  #include <icmdhdr.hpp>
  #include <imcelcv.hpp>
  #include <ilistbox.hpp>
  #include <iselhdr.hpp>
  #include <istrlng.hpp>
  #include <istattxt.hpp>
  #include <imenubar.hpp>
  #include <ispinbt.hpp>
  #include <ientryfd.hpp>

  #include <somd.xh>
  #include "week.h"
  #include "workltem.xh"
  #include "day.xh"
  #include "caldir.xh"

  {
     public:
     DayPlannerWindow (unsigned long windowld, char *name);
     - DayPlannerWindow();
 
     protected:
      virtual Boolean command(ICommandEvent& cmdevt);
      virtual Boolean selected(IControlEvent& evt);
 
     private:
      setupClient();
      setupData();
      short findProxy();
      showllems(short day);
      book(char •start, char ·end, char *desc);

  IEntryField *descT;
  IStaticText *weekday, *start, *end, •desc;
  ISpinButton *startT, *endT;
 
  IMultiCellCanvas *mc;
  IMenuBar *menuBar;
  short menuFlag;
  short createFile;

  IListBox *listBox;
  IListBox::Cursor *cursor;
 
  // DSOM related variables
  SOMDServer ·server:
  char *serverName;
  Environment *ev;
  CalendarDir *curDirEntry;
  Day *curDay;

};
#endif

Figure 5.13 The DayPlannerWindow header file

The listing for the source file planwin.cpp is given in Figure 5.14.

#include <ifont.hpp>
#include <ititle.hpp>
#include <iostream.h>       
#include <fstream.h>        
#include <imsgbox.hpp>     

#include <somd.xh>
#include "planwin.hpp"

void main(int argc, char *argv[], char *envp[])
{
  IString filename;
  if (argc == 2)
  {
     filename = argv[1];
  }
  else
  {
     filename = "Chris";   // default to my calendar
  }
  DayPlannerWindow mainWindow(WND_MAIN, filename);
  IApplication::current().run(); 
}

DayPlannerWindow :: DayPlannerWindow(unsigned long windowId, 
                                     char *name)
  : IFrameWindow (IFrameWindow::defaultStyle(), windowId)
  , serverName(name)
  , createFile(0)
{
   IString title(" Weekly Calendar");

   ICommandHandler::handleEventsFor(this);

   title = serverName + title;
   ITitle(this, title);

   menuBar = new IMenuBar(WND_MAIN,this);
   menuBar->setAutoDeleteObject();  // automatic delete when window is closed
   menuBar->checkItem(MI_BASE);
   menuFlag = MI_BASE;

   setupClient();
   setupData();

   if ( IWindow::desktopWindow()->size() > ISize(1000,700) )
   {
      sizeTo(ISize(460,330));
   }
   else  // VGA
   {
      sizeTo(ISize(360,240));
   } 

   setFocus();
   show();
}

DayPlannerWindow :: setupClient()
{
  mc = new IMultiCellCanvas(WND_CANVAS, this, this);
  mc->setAutoDeleteObject();
  setClient(mc);

  weekday = new IStaticText(WND_TEXT, mc, mc);
  weekday->setAutoDeleteObject();
  weekday->setText("Monday");

  start = new IStaticText(WND_TEXT, mc, mc);
  start->setAutoDeleteObject();
  start->setText("Start  ");

  startT = new ISpinButton(WND_START,mc,mc);
  startT->setAutoDeleteObject();
  startT->setInputType(ISpinButton::numeric);
  startT->setRange(IRange(1,24));
  startT->setCurrent(8);
  startT->setLimit(2);
  startT->readOnly;

  end = new IStaticText(WND_TEXT, mc, mc);
  end->setAutoDeleteObject();
  end->setText("End    ");

  endT = new ISpinButton(WND_END,mc,mc);
  endT->setAutoDeleteObject();
  endT->setInputType(ISpinButton::numeric);
  endT->setRange(IRange(1,24));
  endT->setCurrent(6);
  endT->setLimit(2);
  endT->readOnly;

  desc = new IStaticText(WND_TEXT, mc, mc);
  desc->setAutoDeleteObject();
  desc->setText("Description");

  descT = new IEntryField(WND_DESC, mc, mc);
  descT->setAutoDeleteObject();

  listBox = new IListBox(WND_LISTBOX,mc,mc,
                         IRectangle(),
                         IListBox::defaultStyle() |
                         IControl::tabStop);
  listBox->setAutoDeleteObject();

  cursor = new IListBox::Cursor(*listBox);
  ISelectHandler::handleEventsFor(listBox);

  mc->addToCell(weekday,2,2);
  mc->addToCell(start,  2,4);
  mc->addToCell(startT, 2,5);
  mc->addToCell(end,    4,4);
  mc->addToCell(endT,   4,5);
  mc->addToCell(desc,   6,4);
  mc->addToCell(descT,  6,5);
  mc->addToCell(listBox,2,7,5,1);

  mc->setRowHeight(3,2,true);
  mc->setRowHeight(6,2,true);
  mc->setRowHeight(8,2,true);
  mc->setColumnWidth(3,2,true);
  mc->setColumnWidth(5,2,true);
  mc->setColumnWidth(7,2,true);
}

DayPlannerWindow :: setupData()
{
  short i, found;
  Day   *day;
  
  string objectId;

  ev = SOM_CreateLocalEnvironment();            
  SOMD_Init(ev);
                                              
  WorkItemNewClass(0,0);
  DayNewClass(0,0);
  CalendarDirNewClass(0,0);

  server = SOMD_ObjectMgr->somdFindServerByName(ev,serverName);   
  found = findProxy();
  
  if (!found)
  {
    //************************************************************
    // Create a new CalendarDir object and add 7 Days to the list
    //************************************************************
    curDirEntry = (CalendarDir *) server->somdCreateObj(ev,
                                              "CalendarDir",
                                              NULL);        

    //************************************************************
    // Convert proxy to a string and save to a file with the same
    // name as the current server Name.
    //************************************************************
    objectId = SOMD_ObjectMgr->somdGetIdFromObject(ev,curDirEntry); 

    ofstream  outfile(serverName);
    outfile << objectId;
    outfile.close();

    createFile = 1;     // remember this client created the file
                                                
    for (i=0; i<7; i++ )
    {
       day = (Day *) server->somdCreateObj(ev, "Day", NULL);
       day->_set_date(ev,i);
       curDirEntry->addDay(ev, i, day);
    }
  }

  // Set current day to Monday and show any existing activities
  curDay = curDirEntry->getDay(ev,1);
  showItems(1);
}

short DayPlannerWindow :: findProxy()
{                                                            
  ifstream infile(serverName);
  string   objectId;
  char     buffer[256];
  short    found = 0;
                                                             
  if (infile)    // proxy exist
  {                                                          
    //***************************************
    // restore proxy from its string form
    //***************************************
    objectId = (string) buffer;                              
    infile >> objectId;          
    curDirEntry = (CalendarDir *) SOMD_ObjectMgr->somdGetObjectFromId(ev, 
                                                 objectId);           
    return 1;
  }
  return 0;
}

DayPlannerWindow :: ~DayPlannerWindow()
{
  short i,j;
  _IDL_SEQUENCE_WorkItem alist;

  if (createFile)     // this client writes proxy to file
  {
     //**************************************
     // perform clean up: delete the file
     //**************************************
     IString buffer("erase ");
     buffer = buffer + serverName;
     system(buffer);

     //**************************************
     // Destroy each Day object
     //**************************************
     for (i=0; i<7; i++ ) 
     {
        curDay = curDirEntry->getDay(ev,i);
        alist = curDay->_get_workList(ev);
        if (sequenceLength(alist) > 0 && sequenceLength(alist) < 50)   
        {
           //*********************************************
           // Destroy each WorkItem from the Day object
           //*********************************************
           for (j=0; j < sequenceLength(alist) ; j++)
           {
              SOMD_ObjectMgr->somdDestroyObject(ev,
                                                sequenceElement(alist,j));
           }
        }
        SOMD_ObjectMgr->somdDestroyObject(ev,curDay);
     } 

     // Destroy CalendarDir object
     SOMD_ObjectMgr->somdDestroyObject(ev, curDirEntry);
  } 
  else
  {
     //**************************************                                     
     // Release the proxy for each Day object
     //**************************************
     for (i=0; i<7; i++ )
     {
        curDay = curDirEntry->getDay(ev,i);
        alist = curDay->_get_workList(ev);
        if (sequenceLength(alist) > 0 && sequenceLength(alist) < 50)
        {
           //**************************************************
           // Release proxy for each WorkItem from Day object
           //*****************************************************
           for (j=0; j < sequenceLength(alist) ; j++)
           {
             SOMD_ObjectMgr->somdReleaseObject(ev,
                                               sequenceElement(alist,j));
           }
        }
        SOMD_ObjectMgr->somdReleaseObject(ev,curDay);
     }

     // Release proxy for CalendarDir object
     SOMD_ObjectMgr->somdReleaseObject(ev,curDirEntry);
  }

  // release server proxy                            
  SOMD_ObjectMgr->somdReleaseObject(ev, server);

  SOMD_Uninit(ev);                   
  SOM_DestroyLocalEnvironment(ev);
}

Boolean DayPlannerWindow :: command(ICommandEvent & cmdEvent)
{                                                                           
  IMessageBox msgbox(this);       
       
  switch (cmdEvent.commandId()) 
  {
    case MI_ADD:
      if ( !(descT->text().size()) ) 
      {
         msgbox.show("Enter a description", IMessageBox::okButton);
      } 
      else
      {
        IString str;                            
        IString pad("0");
        IString trial(":00 ");
        IString blank(" ");
        IString sstr(startT->value());
        IString estr(endT->value());

        if ( startT->value() < 10 )
        {
          sstr = pad + sstr;
        }
        if ( endT->value() < 10 )
        {
          estr = pad + estr;
        }
 
        sstr = sstr + trial;
        estr = estr + trial;

        str = sstr + estr + descT->text();
        listBox->addAscending(str);

        book( sstr, estr, descT->text() );
      }
      return true;
      break;                          
      
    case MI_DEL:
      if ( cursor->isValid() )
      {
        IString item, startTime, endTime, task;

        item = listBox->elementAt(*cursor);

        startTime = item.subString(1,6);
        endTime   = item.subString(7,6);
        task      = item.subString(13);
        curDay->removeItem(ev, startTime, endTime, task);

        listBox->removeAt(*cursor);
      }
      return true;
      break;
     
    case MI_SUN:
      weekday->setText("Sunday");
      showItems(0);
      return true;

    case MI_MON:
      weekday->setText("Monday");
      showItems(1);
      return true;

    case MI_TUE: 
      weekday->setText("Tuesday");
      showItems(2);
      return true;
                   
    case MI_WED:
      weekday->setText("Wednesday");
      showItems(3);
      return true;

    case MI_THU:
      weekday->setText("Thursday");
      showItems(4);
      return true;
                        
    case MI_FRI:
      weekday->setText("Friday");
      showItems(5);
      return true;
                        
    case MI_SAT:
      weekday->setText("Saturday");
      showItems(6);
      return true;

    case MI_QUIT:
      close();
      return true;
      break;
  }
  return false;
}

Boolean DayPlannerWindow :: selected(IControlEvent & evt)
{
  cursor->setToFirst();
  return true;
}

DayPlannerWindow :: showItems(short day)
{
  _IDL_SEQUENCE_WorkItem alist;
  short                  i;
  IString                str;
  long                   date;

  listBox->removeAll();             // clear list
  
  menuBar->uncheckItem(menuFlag);   // uncheck previous day
  menuBar->checkItem(MI_BASE+day);  // check selected day
  menuFlag = MI_BASE + day;

  curDay = curDirEntry->getDay(ev,day);
  alist = curDay->_get_workList(ev);

  if (sequenceLength(alist) > 0 && sequenceLength(alist) < 50) 
  {
    for (i=0; i < sequenceLength(alist) ; i++)
    {
      str = "";
      str = str +
            sequenceElement(alist,i)->_get_startTime(ev) +
            sequenceElement(alist,i)->_get_endTime(ev) +
            sequenceElement(alist,i)->_get_task(ev);

      listBox->addAscending(str);
    }
  }
}

DayPlannerWindow :: book(char *start, char * end, char *task)
{
   WorkItem *item;

   item = (WorkItem *) server->somdCreateObj(ev,
                                             "WorkItem",
                                             NULL);
   item->mkEntry(ev, start, end, task);
   curDay->book(ev, item);
}

Figure 5.14 The DayPlannerWindow implementation

Note that in the member function, showltems and lhe destructor, there is an "if' statement to make sure that the length of the sequence is valid before proceeding. Normally, this is not necessary. However, at the time of writing this book, DSOM does not marshall empty sequences properly. AB a result, the length of the sequence will show some huge number when the sequence is empty. Our example provides a quick patch for the problem. Another way is to add an element to the sequence right after the object is created. This ensures that the sequence will never be empty when marshalling occurs. This defect has been reported and should be fixed in future Corrective Service Distribution.

The Makefile

The source files that contain the classes Workltem, Day, and CalendarDir are workitem.cpp, day.cpp, and caldir.cpp. The source files initfunc.cpp and calendar.def contain the initialization function and the module definition file. They are not listed here, but they are included on the diskette. These five files are needed to build the calendar DLL.

The source files planwin.hpp, planwin.cpp, week.re, and week.h are needed to build the GUI client. The first two files are listed above. The file week.re contains the resources for the application, and the file week.h contains constant definitions. They are included on the diskette.

The makefile for building the calendar application is given in Figure 5.15. It includes dependencies to build the Interface Repository and the Implementation Repository. The three classes are compiled into the Interface Repository file that is designated by the SOMIR environment variable. Two calendar servers, "Chris" and "Kevin", are registered with the Implementation Repository that is designated by the SOMDDIR environment variable.

MAKEFILE

Figure 5.15 The distributed PLANNER makefile

.SUFFIXES:
.SUFFIXES: .idl .xih .xh .cpp .obj .def 
#
# Need /Ge- to build DLL
#
OBJS  = workitem.obj day.obj caldir.obj initfunc.obj
UILIB = dde4muii.lib dde4cci.lib dde4mbsi.lib os2386.lib

all: calendar.dll planner.exe som.ir somdimpl

#
# mysvr.cpp contains a sample server. You can use it
# instead of the generic dsom server
#
tstsvr: mysvr.exe myimpl

.cpp.obj:
        icc /c+ /Ge- -I. $<
.idl.xh:
         sc -sxh  $*.idl
.idl.xih:
         sc -sxih  $*.idl
.idl.cpp:
         sc -sxc $*.idl
.idl.def:
         sc -sdef $*.idl

initfunc.obj: initfunc.cpp

clnimpl:
        -regimpl -D -i Chris
        -regimpl -D -i Kevin

workitem.obj: workitem.xih workitem.xh workitem.cpp
workitem.xih: workitem.idl
workitem.xh:  workitem.idl
workitem.cpp: workitem.xih

day.obj: day.xih day.xh day.cpp       
day.xih: day.idl
day.xh:  day.idl                            
day.cpp: day.xih                            

caldir.obj: caldir.xih caldir.xh caldir.cpp       
caldir.xih: caldir.idl                            
caldir.xh:  caldir.idl                            
caldir.cpp: caldir.xih                            

initfunc.obj: initfunc.cpp

#
# Build the DLL
#
calendar.dll: $(OBJS) calendar.def
        icc @<< 
        /Fe"calendar.dll" $(OBJS) calendar.def somtk.lib
<<
        implib calendar.lib calendar.def

#
# Build the executable
#
planwin.obj:  planwin.cpp planwin.hpp week.h
        icc /c+ /Gd+ /Gm+ /Si+ -I. planwin.cpp
planner.exe: planwin.obj week.res
        icc /Fe"planner.exe" planwin.obj /B" /pm:pm /noi" \
        $(UILIB) somtk.lib calendar.lib planner.def
        rc week.res planner.exe

#
# Build the test server.
#
mysvr.obj: mysvr.cpp
        icc /c+ /Gd+ /Gm+ /Si+ mysvr.cpp
mysvr.exe: mysvr.obj
        icc /Fe"mysvr.exe" mysvr.obj somtk.lib
#
# You can use "mySvr" instead of "Chris" or "Kevin"
#
myimpl: mysvr.cpp
        -regimpl -A -i mySvr -p mysvr.exe
        -regimpl -a -i mySvr -c WorkItem -c Day -c CalendarDir
        -regimpl -L -i mySvr

week.res: week.rc week.h
          rc -r week.rc

#
# Put the IDL descriptions into the Interface Repository
#
som.ir: workitem.idl day.idl caldir.idl
        sc -sir -u workitem.idl
        sc -sir -u day.idl
        sc -sir -u caldir.idl
#
# Build the DSOM Implementation Repository.
# Register two servers: Chris and Kevin
#
somdimpl: workitem.idl day.idl caldir.idl
        -regimpl -A -i Chris
        -regimpl -a -i Chris -c WorkItem -c Day -c CalendarDir
        -regimpl -L -i Chris
#
        -regimpl -A -i Kevin
        -regimpl -a -i Kevin -c WorkItem -c Day -c CalendarDir
        -regimpl -L -i Kevin
        @echo x > somdimpl

Building the Application

To build the application, simply invoke NMAKE using the makefile shown above. Before you invoke NMAKE, make sure the following environment variables are set up properly.

  • SOMIR
  • SOMDDIR
  • HOSTNAME

For example, you can use the following settings.

 set SOMIR=%SOMBASE%\etc\som.ir;som.ir
 set SOMDDIR=%SOMBASE%\etc\dsom
 set HOSTNAME=localhost

The file dsomenu.cmd contains the commands you need to set up your environment for this example.

Running the Application

We are ready to run our distributed calendar application. In the following, we assume we are running our application on a single 08/2 with the SOMobjects Workstation Enabler, or the SOMobjects Developer Toolkit installed. Note that it is possible to distribute the calendar objects to a AIX/6000 system by building a shared library for the AIX/6000, and using Workgroup DSOM to access the calendar objects.

Make sure the environment variables listed above are still set to the correct values in the session you are going to start the DSOM daemon. In addition, make sure the environment variable USERID is also set. For example:

set USERID=chris

Start the DSOM daemon by typing:

> start If somdd

This starts the DSOM daemon in a separate window. Figure 5.16 shows the screen when the DSOM daemon is ready. In the same window you started the DSOM daemon, type:

> planner

This starts the PLANNER program using the default ("Chris") server. Figure 5.17 shows the screen after PLANNER is started. Notice that the icon labelled DSOM server somdsur.exe appears on the desktop when PLANNER is started. This is "Chris" server process.

SOM-DSOM-Fig 5 16.png

Figure 5.16 DSOM daemon

SOM-DSOM-Fig 5 17.png

Figure 5.17 Chris Client and Server Object on First Invocation

The file "Chris" should be created in your current directory. Take a look at it. It should contain an entry similar to the following:

SOMl2l2d149bb6-01791457-7f-00-0100007fOOOOICalendarDir14lb0984300

This entry is the string representation for the object reference we created for the object CalendarDir. The object reference is externalized to allow sharing. Add some activities to Chris's calendar. For example, a department meeting is booked for Monday, from 10:00 to 11:00. A tennis game is booked for Tuesday, from 18:00 to 19:00.

In a separate -.v:indow, run dsomenv.cmd to set up the environment variables. Then type "planner" to start a second PLANNER program using the "Chris" server. Notice how all of the activities that were added in the first PLANNER program appear in the second PLANNER program. This is because the two programs share the same calendar objects through the externalization of the proxy. If you add or delete activities from either calendar, they will be reflected on the other calendar when you refresh the list. Figure 5.18 shows the screen capture for the two "Chris Weekly Calendar" windows.

SOM-DSOM-Fig 5 18.png

Figure 5.18 Sharing Chris calendar

In a separate window, run dsomenu.cmd to set up the environment variables. Then type:

 > planner Kevin

to start a PLANNER program using the Kevin calendar server. Figure 5.19 shows the screen after "PLANNER Kevin" is started. Notice that a second icon labelled DSOM server somdsur.exe appears on the desktop when "PLANNER Kevin" is started. This is "Kevin" server process.

The activities that were added to the "Chris" calendar were not shown on the "Kevin" calendar because they do not share objects. The file "Kevin" should be created in the current directory to store the proxy to Kevin's CalendarDir object.

Now terminate the client programs by selecting Quit from the menu to exit. Notice that the two DSOM servers are still around. This is because the somdDestroyObject method only destroys the object that is managed by the server. It does not terminate the server. ff you terminate the DSOM daemon, then the servers will be terminated.

SOM-DSOM-Fig 5 19.png

Figure 5.19 Kevin's calendar

If you want the termination of a server to be controlled by a client, then you will need to implement your own server. You can create a new subclass of SOMDServer and implement a shutdown method in this subclass. The shutdown method should perform the necessary cleanup and then terminate. The client can invoke the shutdown method using the server proxy it obtains from calling the somd.FindServerByName method.

Summary

The Calendar example illustrates some of the key concepts of developing and building a DSOM application. The task of building a distributed application has been greatly simplified with the availability of the Distributed SOM Framework. How many lines of code does a traditional program need to write to provide a similar form of interprocess or intermachine communications?

Notice that our calendars are not persistent. When the server process terminates, the states of the calendar objects are not preserved. In Chapter 6, we will add persistence to our calendar so that our calendar data can be preserved beyond the existence of the server process or the PLANNER program.