Object-Oriented Programming Using SOM and DSOM/Hello World in SOM

From EDM2
Jump to: navigation, search

In this chapter, we are going to create our first SOM object. This SOM object will print "Hello World". This exercise has two purposes:

  1. To introduce you to the steps that are required to develop a SOM object
  2. To demonstrate the language inter-operability provided by SOM

Some Basic Terminology

Many definitions of objects and object related terms can be found in different publications and literature. For the purpose of this book, we will use the following terminology:

  • An object is an encapsulated entity that provides one or more services that can be requested by a client. A SOM object is a run-time entity with a specific set of methods and instance variables. The methods can be called by a client program, and the instance variables are used by the object to store its state.
  • An interface describes the information that the client of an object must know in order to use the object. An object interface is described using SOM IDL.
  • A class defines the implementation of an object. Every SOM object is an instance of a single SOM class.
  • Inheritance is the technique for developing or deriving new classes from existing classes. The original class is called the base class or the parent class. The derived class is called a child class or a subclass.

Some of the meanings behind these definitions may not be very clear at this point. Do not worry about this. In this and the next two chapters, the meanings of these terms will become clearer as we step through the features and design of SOM.

Game Plan

Figure 2.1 Client usage of a class is not dependent on the implementation language

We mentioned in Chapter 1 that a SOM class can be implemented in one programming language then used in another programming language through the use of language bindings. We will demonstrate this capability in the remaining sections of this chapter. Specifically, we will implement the SOM Hello class in C++ and show how two clients, one written in C and the other written in C++, can invoke a method on a Hello object. This is shown in Figure 2.1.

Development Steps

The development of a SOM class involves the following steps.

  1. Define the interface for the object by creating an IDL source file. This file must have a .idl extension.
  2. Run the SOM compiler on the .idl file to produce binding files and an implementation template.
  3. Customize the implementation by adding code to the implementation template.
  4. Create a client program that uses the class.
  5. Compile and link the client program with the c1ass implementation.
  6. Execute the client program.

We will go through each of these steps in our implementation of the Hello class. The Hello class has one method, printHello. When a client invokes the printHello method, it will print out the string "Hello World from SOM!".

Define Hello Interface

The first step is to define the interface for the Hello object. This interface is defined in hello.idl whose listing is given in Figure 2.2.

In the following paragraphs, we examine the hello.idl source file.

Include Section

The first line, #include <somobj.idl>, tells the SOM compiler where to find the interface definition (.idl files) for this class's parent classes. Our Hello class is derived from the SOMObject class whose IDL is defined in somobj.idl.

SOMObject is the root class for all SOM classes. Every SOM class must be derived from SOMObject or from some other class that is derived from SOMObject. We will discuss SOMObject in Chapter 3.

#include <somobj.idl>
interface Hello:SOMObject
{ 
  attribute string msg;

  void printHello();

  #ifdef __SOMIDL__
  implementation
  {
    releaseorder: _get_msg, _set_msg,
                  printHello;
  };
  #endif
};

The interface statement specifies the name of the interface and the inheritance hierarchy of the interface. In SOM, objects are implemented using classes, and therefore the name of the interface is used as the name of the class. The inheritance specification in the interface statement represents the parent class names.

Attribute SecHon

The attribute section defines the attributes for an interface. Each attribute must be of a valid IDL type.

Attributes are instance variables for which get and set methods will automatically be defined. These get and set methods can be used directly by a client program. In our Hello class, declaring the attribute msg causes SOM to generate the following methods:

 string   _get_msg();
 void     _set_msg(in string msg);

In SOM, there is another way of declaring instance variables. You can declare instance variables in the implementation section of the SOM IDL. This is discussed in Section 3.5 on page 32.

Method (Operation) Section

The method (operation) section defines the interface for each method that is introduced by this interface. Our Hello class contains one method, printHello, which prints the message string.

Implementation Section

The implementation section defines the implementation information for the Hello class. The releaseorder statement specifies the order in which the SOM compiler will place the class's methods in the data structures that it builds to represent the class.

The releaseorder must include every method name introduced by the class, including the get and set methods that are automatically generated for each attribute. When the class is first created, the order of the method names on the list is unimportant. Once the class has client programs, the method names on the list must not be reordered. If a method is removed from the class, the method name must not be removed from the list. If new methods are added to the class in a future release, the new methods must be added to the end of the list.

The releaseorder is the key to maintaining backward binary compatibility of your class. By following the rules described here, you can avoid recompilation of client programs when your class changes. This topic will be discussed in more detail in Chapter 4, A Complement to C++.

SOM Compile Hello.idl

After you have defined the interface, the next step is to run the hello.idl source file through the SOM compiler to generate the binding files and an implementation template. At this point, you need to decide in what language you would like to implement your class. Earlier, we said that we will implement our Hello class in C++. For the purpose of illustration, we will show the SOM compile steps for both C++ and C implementations.

Implementing the Class in C++

To implement your Hello class in C++, you need to generate a C++ usage binding file (hello.xh), a C++ implementation binding file (hello.xih) and a C++ implementation template file (hello.cpp).

The C++ usage binding file is a header file that is to be included in any C++ client programs that use the class. It is also included by the C++ implementation binding file. The C++ usage binding file contains, among other things, a class wrapper that looks like the code shown in Figure 2.3.

This may look odd, but you can see how a C++ client can now use the C++ notation to invoke a SOM object by including the C++ usage binding file. Thanks to the language binding, programmers do not need to know the underlying SOM APIs, and never need to look at the generated binding files.

The C++ implementation binding file is a header file that is included in the C++ implementation template file. It contains macros for accessing the class's instance variables, invoking parent methods, etc.

The C++ implementation template is a source file that contains stub procedures for each new, and overridden, method in the class. Their bodies are to be filled in by the class implementor.

To generate these files, run the SOM compiler using the sc command as follows:

sc -sxh hello.idl     (generates usage binding: hello.xh)
sc -sxih hello.idl    (generates implementation binding: hello.xih)
sc -sxc hello.idl     (generates implementation template: hello.cpp)

Or you can use:

sc -sxh;xih;xc hello.idl

as a quicker variation.

class Hello : public SOMObject
{
   public:

   void ·operator new(size_t size)
   {
     SOM_IgnoreWarning(size):
     if (IHelloCiassData.classObject)
     HelloNewCiass(Hello_MajorVersion,Hello_MinorVersion);
     return (void *)
     ((somTD_SOMCiass_somNew)
     somresolve_((SOMObject *)((void*)    (HelloCiassData.classObject)),
     SOMCiassCiassData.somNew))
     ((SOMCiass *)((void*)(HelloCiassDataclassObject)));
     }

     void operator delete( void *obj)
     {
     ((SOMObject *)obJ)->somFree();
     }

     /* public method: _get_msg */
     string _get_msg(Environment *ev)
     {
      return SOM_Resolve(this,Hello,_get_msg)
      (this,ev);
     }

     /* public method: _set_msg */
     void _set_msg(Environment ¨*ev, string msg)
    {
     SOM_Resolve(this,Hello,_set_msg)
     (this,ev,msg);
     }

    /* public method: printHello */
    void printHello(Envtronment *ev)
   (
    SOM_Resolve(this,Hello,printHello)
    (this,ev);
   }
}

Figure 2.3 Excerpt from the Hello C++ usage binding file

Implementing the Class in C

To implement your Hello class in C, you need to generate a C usage binding file (hello.h), a C implementation binding file (hello.ih), and a C implementation template file (hello.c).

The C usage binding file is a header file that must be included in all C client programs that use the class. It is also included by the C implementation binding file. Similar to the C++ usage binding file, the C usage binding file includes macros to invoke the SOM APIs. A fragment that shows the printHello method is given below, in Figure 2.4. Note that this is close to, but not exactly, what is generated.

The C implementation binding file is a header file that is included in the C implementation template file. It contains macros for accessing the class's instance variables, invoking parent methods, etc.

The C implementation template is a source file that contains stub procedures for each new and overridden method in the class. Their bodies are to be filled in by the class implementor.

To generate these files, run the SOM compiler using the sc command as follows:

sc -sh hello.idl    (generates usage binding: hello.h)
sc -sih hello.idl   (generates implementation binding: hello.ih)
sc -sc hello.idl    (generates implementation template: hello.c)

Or you can use:

sc -sh;ih;c hello.idl

as a quicker variation.

/*
* New Method: printHello
*/
typedef void SOMLINK somTP Hello_printHello(Hello somSelf, Environment •ev);
#pragma linkage(somTP Hello_printHello, system)
typedef somTP Hello_printHello •somTD_Hello_printHello;
#define Hello_printHello(somSelf,ev) \
     (SOM_Resolve(somSelf, Hello, printHello) \
     (somSelf,ev))
#define _printHello Hello_printHello

Figure 2.4 Excerpt from the Hello C usage binding file

Customize the printHello Method

The next step is to add code to the implementation template. The C++ implementation template that is generated from the last step is shown in Figure 2.5.

The terms SOM_Scope and SOMLINK appear in the prototype of all stub procedures and represent internal information for SOM. For each method procedure, the first parameter is always somSelf, which is a pointer to the target object. In this example, somSelf is a pointer to the Hello object. The second parameter is always ev (unless the call style is OIDL), which is a pointer to an Environment structure that can be used to return error information.

The first statement in the stub procedure initializes a local variable, somThis, to point to the structure representing the instance variables introduced by this class. Note that if the class introduces no instance variables, this line will be commented out.

The second statement, HelloMethodDebug, is a macro that will produce a message every time the method is entered, if debugging is turned on. Debugging can be turned on by setting the SOM_TraceLevel global variable to a non-zero value.

To invoke another method that is introduced in this class, you can use the notation:

somSelf-><method.Name> (args)

where args are the arguments to the method.

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

#define Hello_Class_Source
#include <hello.xih>

SOM_Scope void SOMLINK printHello(Hello *somSelf, Environment *ev)
{
  HelloData *somThis = HelloGetData(somSelf);
  HelloMethodDebug ("Hello","printHello");
}

Figure 2.5 The Hello C++ implementation template file

To access an attribute that is introduced in this class, you can use the notation:

somThis-><attributeName>

We will now add code (see Figure 2.6) to the printHello method. This method prints out the contents of the msg attribute.

Create Client Program

We now write two client programs, one in C and one in C++. Both programs will create an instance of the Hello class and then invoke the printHello method.

C Client Program

A client program that is written in C and uses the C usage binding is shown in Figure 2.7.

Recall that a pointer to an Environment structure is required for every method call. There are several ways to allocate this structure. One way, as shown here, is to use the function somGetGlobalEnvironment. Section 3.14 on page 60 describes the other ways of allocating this structure and shows how one can set and get values from the Environment structure.

HelloNew() is a macro that is defined in hello.h. It is used for creating an instance of the Hello class. For programmers that use the C usage binding, SOM provides the <className>New() macro for creating instances of the class <className>.

The _set_msg method is invoked to set the msg attribute in the Hello class. Notice that the _set_msg method begins with an underscore. Therefore, following the C method calling convention, to invoke the _set_msg method, two leading underscores are required. This is true for all the get and set methods that are generated for each attribute in a SOM class.

The printHello method is then invoked to print out the msg attribute.

When the client is finished with the myHello object, it should be freed by invoking the somFree function.

SOM_Scope void SOMLINK printHello(Hello ·somSelf, Environment ~ev)
{
   HelloData •somThis = HelloGetData(somSelf);
   HelloMethodDebug("Hello", "printHello");

   cout << somThis->msg « "\n";

Figure 2.6 Customize the printHello method

#include <hello.h>

main()
{
  Hello       myhello;
  Environment *ev;

  ev = somGetGlobalEnvironment(); 
  myHello = HelloNew();

  __set_msg(myHello, ev, "Hello World from SOM!");
  _printHello(myHello, ev);

  _somFree(myHello);
}

Figure 2.7 A C client program that uses the Hello class

C++ Client Program

A client program that is written in C++ and uses the C++ usage binding is shown below (Figure 2.8):

#include <hello.xh>

main()
{
   Hello       *myhello;
   Environment *ev;

   ev = somGetGiobaiEnvironment();
   myHello = new Hello;

   myHello->_set_msg( ev, "Hello World from SOM!");
   myHello->printHello( ev);

   delete myHello;
}

Figure 2.8 A C++ client program that uses the Hello class

Similar to the C client, the somGetGlobalEnvironment function is called to allocate an Environment structure.

SOM recognizes the standard C++ new operator. Therefore, the new operator can be used to create an object of the specified class.

The _set_msg is invoked to set the msg attribute in the Hello class. The printHello method is then invoked to print out the msg attribute.

When the client is finished with the myHello object, it should be freed by invoking the delete operator.

Compile and Link

We have written the C++ code that implements the Hello class, and we have created two sample client programs. We are ready to compile and link the client programs with the class implementation to produce two executables. The SOM run-time must be linked in by including the somtk.lib.

The makefile that is used to create the class and the client programs are listed in Figure 2.9. Invoke NMAKE to build the executables.

Execute the Program

Finally, we are ready to say "Hello World from SOM". Type:

> chello
Hello World from SOM!

Type:

> cpphello
Hello World from SOM!

Summary

Language bindings allow SOM objects to be used by different programming languages. They hide the SOM APIs from the programmers and allow the programmers to use SOM objects in the most natural notation. When other language bindings, such as Smalltalk, become available, a C++ program can use or subclass the SOM objects developed in Smalltalk as if they are C++ objects. Similarly, SOM objects developed in other languages can appear as Smalltalk objects, and the Smalltalk browser can be used to browse these objects.

Figures 2.10 and 2.11 summarize the SOM development process when using C++ or C.

SUFFIXES:
.SUFFIXES: .cpp .obj .idl .xih .xh .c .h

all: cHello.exe cppHello.exe

.cpp.obj:
    icc -I. /c+ $<
.c.obj:
    icc -I. /c+ $<

.idl.xh:
    sc -sxh $*.idl
.idl.xih:
    sc -sxih $•.idl
.idl.cpp:
    sc -sxc $*.idl
.idl.h:
    sc -sh $*.idl

# Link cHello
cHello.exe: hello.obj cMain.obj
    icc -Fe"cHello.exe" hello.obj cMain.obj somtk.lib

# Link cppHello
cppHelto.exe: hello.obj cppMain.obj
    icc -Fe"cppHello.exe" hello.obj cppMain.obj somtk.lib

# Compile Hello SOM class
hello.obj: hello.cpp hello.xih hello.xh hello.idl

# Compile C client
cMain.obj: cMain.c hello.h

# Compile C++ client
cppMain.obj: cppMain.cpp hello.xh

Figure 2.9 The Hello class Makefile

So what happens if you have added code to the implementation template and then decide to change the interface definition file (the .idl file)? You will need to re-run the SOM compiler to regenerate the binding files and the implementation template. What happens to the existing code? Will it be overwritten?

OOP-SOM-Fig-2 10.png

Figure 2.10 The SOM development process using C++

OOP-SOM-Fig-2 11.png

Figure 2.11 The SOM development process using C

The SOM compiler is smart enough to recognize that an implementation file already exists, and it will not regenerate a new one. Instead, the SOM compiler will make updates to the existing file. New methods will be added, and changes to any existing prototypes will be reflected. These updates will not delete any existing code.