Object-Oriented Programming Using SOM and DSOM/Writing Your Own Emitter

From EDM2
Jump to: navigation, search

One of the features of SOM is that it allows you to do object-oriented design and programming without restricting you to a particular programming language. In addition, SOM allows applications to access objects, regardless of the programming languages in which they were created. This means that a C++ program can use classes developed in Smalltalk and vice-versa. These characteristics are often referred to as language neutral. It is believed that this will significantly increase reuse and promote better inter-operability between programming languages.

To be language neutral, the interface for a class must be defined separately from its implementation. In SOM, the class interface is defined using SOM IDL. The IDL is then compiled by the SOM compiler to create an implementation file where the class implementation is added. To make it easier for programmers to implement SOM classes, and clients to use SOM classes, the SOM compiler can also invoke specific emitters to produce language specific bindings. Bindings are a set of macros and procedures that tailor the IDL interface to a particular programming language. For example, the C bindings allows C programs to invoke methods on SOM objects in the same way they make ordinary procedure calls. The C++ bindings allow C++ programs to invoke methods on SOM objects in the same way they invoke methods on C++ objects.

Currently, C and C++ bindings are available for the IBM and Borland compilers. Vendors of other language compilers may offer their own language bindings in the future. To help implementors write their own language bindings, SOM provides an Emitter Framework. The Emitter Framework is a collection of SOM classes that allows programmers to write their own emitters.

What is an Emitter?

We have seen a number of emitters already; for example, the Interface Repository emitter (ir), the C binding files and implementation template emitters (h, ih, c), the C++ binding files and implementation template emitters (xh, xih, xc), and the export file emitter (del). The term emitter can be thought of as the back-end output component of the SOM compiler:

  • The input to an emitter is information about an IDL interface. This information is produced by the SOM compiler as it parses an IDL file.
  • The output from an emitter is a file that contains information translated from an IDL interface file into a different format that is specific to the purpose of the emitter.

What this means is that programmers do not have to write their own IDL parsers if they want to understand or analyze an IDL file. Instead, they write an emitter using the classes in the Emitter Framework. The parsing is handled by the SOM compiler. The parsed information is passed to the emitter as an object. The emitter determines what information is needed for output, and writes it out accordingly.

Developing an Emitter

The development of an emitter involves the following steps.

  1. Run the newemit program to generate a complete working emitter.
  2. Customize the output template.
  3. Customize the emitter implementation file.
  4. Build the emitter.
  5. Invoke the emitter via the SOM compiler.

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

The newemit Program

The newemit program is an emitter generator that generates a complete working emitter. You can then customize the emitter to your needs. The newemit program takes two parameters: the name of the emitter class and a file stem. The file stem represents the name of your emitter. The newemit program generates the following files in your current directory:

  • <filestem>.idl — The IDL definition for your new emitter. Your emitter is always derived from the SOMTEmitC class. The SOMTEmitC class provides overall control for the emitting process. The IDL specifies that the somtGenerateSections method is overridden.
  • <filestem>.c* — The C implementation file for your emitter class. This file contains a default implementation for the somtGenerateSections method. (*The newemit program in the SOMobjects Developer Toolkit 2.0 generates the emitter implementation file and the emitter driver program in C. If you install CSD202 or higher, then you can choose to generate a C++ implementation file and emitter driver program.)
  • <filestem>.efw — A sample output template file.
  • emit<zfilestem>.c — An emitter driver program. Notice that the driver program name is always emit followed by the specified file stem. Therefore, the length of your file stem should not be more than four characters long on systems that only support an eight character ifie name.
  • emit.zfilestem>.def — An export file that contains export entries.
  • Makefile — A Makefile for creating a DLL for the new emitter.

For example, the following command creates an emitter class ReportEmitter and the name of the emitter is rep.

newemit ReportEmitter rep

The files that are generated are rep.idl, rep.c, rep.efw, emitrep.c, emitrep.def, and Makefile.

The Output Template

The Emitter Framework provides a template facility that allows developers to specify the form of an output file in a readable and maintainable manner. Information about how the output file should look can be placed in a template file. It does not need to be specified in the emitter code. The template file is divided into sections. Each section specifies the desired output format for each syntactic unit of the interface definition.

Output templates are stored in files with an .efw (stands for Emitter Framework) extension. The newemit program generates a generic template file <filestem>.efw. This file contains all the standard sections with sample text for each section. This file must be edited so it contains your desired output format.

Figure 9.1 is a partial listing of a generated template file. It contains the sections classS, attributePrologS, attributeS and attributeEpilogS.

:classS
Section: classS

  className = "<className>"
  class DLScopedName = "<classi DLScopedName>"
  classCScoped Name = '<classCScopedName>"
  classComment = "<-- classComment>"
  classinclude = "<classinclude>"
  classLineNumber = '<class Line Number>"
  classMods = "<classMods, ..>"
  classMajorVersion = "<classMajorVersion>"
  classMinorVersion = "<classMinorVersion>"
  classReleaseOrder = "<classReleaseOrder, ...>"
  classSourceFile = '<classSourceFile>"
  classSourceFileStem = "<classSourceFileStem>"

:attribute PrologS
Section: attributePrologS

:attributeS
Section: attributeS

  attributeDeclarators = "<attributeDeclarators, ...>"
  attributeBaseType = "<attributeBaseType>"
  attributeComment = "<-- attributeComment>"
  attributeLineNumber = "<attributeLineNumber>"
  attributeMods = '<attributeMods, ...>"

:attribute EpilogS
Section: attributeEpilogS

Figure 9.1 Sample template file

A new section is denoted by a line that starts with the colon. By convention, section names end in capital "S". The section classS represents the class section. The section attributePrologS represents the prolog for the attribute section. It is only emitted once regardless of how many attributes there are. The section attributeS represents the repeating portion. It is repeated once for every attribute. The section attributeEpilogS represents the epilog for the attribute section. It is only emitted once regardless of how many attributes there are.

A symbol is specified using angle brackets. It is used to represent a corresponding value. The output template above contains the symbols className, classlDLScopedName, classCScopedName, etc. When a section is emitted, these symbols are replaced with their actual values. For example, the symbol className will be replaced by the actual name of the class, and the symbol classIDLScopedName will be replaced by the scoped name of the class using "::" as delimiters.

The symbols classMods, ... and classReleaseOrder, ... represent a list substitution of symbols. The '.." indicates that list substitution is used and the symbol's value must consist of a sequence of items. The character "," is used as the separator character.

The symbols -- classGomment and -- attributeComment represent a comment substitution of symbols. When the "--" precedes a symbol name, it indicates that comment substitution is used and the symbol's value is emitted in comment form. You can control the style and format of the comment in your emitter program. For example, you can control whether comment uses the C++ style ("II"), or the C style (a/*" and "*/")

Using the Simple IDL of Figure 9.2 and the output template of Figure 9.1, the output of Figure 9.3 is produced.

#include <somobj.idl>

interface Simple: SOMObject                  //Simple IDL Interface
{
   attribute short al;                       //attribute 1
   attribute long a2;                        //attribute 2
 
   #ifdef SOMIDL
   implementation
   {
     releaseorder : _get_al, _set_al,
                   _get_a2, —set a2;
   };
   #endif
};

Figure 9.2 The simple IDL

Section: class

 className = "Simple"
 classlDLscopedName "::Simple"
 classcscopedName = "Simple"
 classComment = '7/ Simple IDL Interface"
 Classinclude = "<simpleidi>"
 classLineNumber = "0000003"
 classMods = "releaseorder = -get-al,-set-al,-get-a2,-set-a2,
                             filestem = simple"
 classMajorversion = "0"
 classMinorvers ion = "0"
 classReleaseOrder = " -get _al, set al -get-a2, _set _a2"
 classSourceFile = "simpleicil"
 classSourceFileStem = "simple"

Section: attributeProlog5

Section: attributeS
 attribute Declarators = "al'
 attributeBaselype = "short"
 attributeComment "1/ attribute 1"
 attributeLineN umber = "0000005"
 attributeMods

Section: attributeS

 attributeDeclarators = 'a2"
 attributeBaselype = "long"
 attributeComment = "II attribute 2"
 attributeLineNumber = "0000006"
 attributeMods =

Section: attribute EpilogS

Figure 9.3 The output template for simple

The Emitter Implementation

The newemit program generates an IDL definition and a default C implementation for your emitter. Your emitter is a subclass of SOMTEmitC and overrides the somtGenerateSections method. The somtGenerateSections method determines which sections of the output template are emitted, and in what order. Typically, you will customize the default implementation of somtGenerateSections. The somtGenerateSections method is called by the emitter driver program emit<filestem>.c, when the emitter is invoked by the SOM compiler.

Figure 9.4 shows the default implementation of somtGenerateSections for the emitter class ReportEmitter.

SOM_Scope boolean  SOMLINK somtGenerateSections(ReportEmitter somSelf)
{
    ReportEmitterData *somThis = ReportEmitterGetData(somSelf);
    SOMTClassEntryC cls = __get_somtTargetClass(somSelf);
    SOMTTemplateOutputC template = __get_somtTemplate(somSelf);
    ReportEmitterMethodDebug("ReportEmitter","somtGenerateSections");

    /*
     * Setup symbols that are common to the whole file 
     */
    _somtFileSymbols(somSelf);

    _somtEmitProlog(somSelf);

    if (cls != (SOMTClassEntryC) NULL) {
         _somtScanBases(somSelf,
                       "somtEmitBaseIncludesProlog",
                       "somtEmitBaseIncludes",
                       "somtEmitBaseIncludesEpilog");
         
         _somtEmitMetalnclude(somSelf);

         _somtEmitClass(somSelf);

         —somtScan Bases (somSelf,
                          "somtEmitBaseProlog",
                          "somtEmitBase",
                          "somtEmitBaseEpilog");
        _somtEmitMeta(somSelf);
    }
 
    _somtScanConstants(somSelf, 'somtEmitConstantProlog",
                       "somtEmitConstant", "somtEmitConstantEpilog");

    _somtScanTypedefs(somSelf, "somtEmitTypedefProlog",
                      "somtEmitTypedef", "somtEmitTypedefEpilog");

   _somtScanStructs(somSelf, "somtEmitStructProlog",
                    "somtEmitStruct", "somtEmitStructEpilog');

   _somtScanUnions(somSelf, "somtEmitUnionProlog",
                   "somtEmitUnion", "somtEmitUn ion Epilog');
   

   _somtScanEnums(sQmSeIf, 11somtEmitEnumprojog
                  'somtEmjtEnum 'somtEmitEnumEpjjog);

   if (cls != (SOMTClassEntryC) NULL){
       _somtScanAttributes(somSelf, "somtEmitAttributeprolog",
                                  "somtEmitAttribute", "somtEmitAttributeEpilog");

      _somtscan Methods (somSelf,
                         "somtImplemented",
                         "somtEmitMethodsprolog",
                         "somtEmitMethod",
                         "somtEmitMethodsEpilog",
                         0);

      _somtEmitRelease(somSelf);

      _somtScanpassthru(somSelf, 1,
                        "somtEmitPassthruProlog",
                        "somtEmitPassthru",
                        "somtEmitPassthruEpilog");

      _somtScanpassthru(somSelf, 0,
                        "somtEmitPassthruprolog",
                        "somtEmitpassthru",
                        "somtEmitPassthruEpilog);

      _SomtScanData(somSef,
                    "somtEmitDataprolog",
                    "somtEmitData",
                    "somtEmitDataEpilog");
  }

if (__get somtTargetModule(somSelf) != (SOMTModuleEntryC) NULL){

         _somtscanInterfaces (somSelf, "somtEmitInterf aceProlog",
                              "somtEmitInterface", "somtEmitInterfaceEpilog");

        _somtScanModules(somSelf, "somfEmitModuleProlog",
                        "somtEmitModule", "somtEmitModuleEpilog");
}

_somtEmitEpilog(somSelf);

return (TRUE);
}

Figure 9.4 The default implementation of somtGenerateSections

The somtEmit<Section> methods emit a particular section from an emitter's template. For example, the somtEmitClass method emits the class section.

The somtScan<Section> methods iterate through a repeating section and call the section-emitting methods whose names are specified in the somtScan<Section> method. For example, the somtScanConstants method iterates through the constants declarations and emits the constantS section for each constant. If a constantPrologS section is defined, it will be emitted before the first constant. If a constantEpilogS section is defined, it will be emitted after the last constant.

The default implementation of somtGenerateSections emits the template sections in the following order:

  1. Prolog—text before any other sections
  2. Base Includes—base (parent) class include statements
  3. Meta Includes—metaclass include statements
  4. Class—class information
  5. Base—base (parent) classes information
  6. Meta—metaclass information
  7. Constant—user-defined constants
  8. Typedef—user-defined types
  9. Struct—user-defined structs
  10. Union—user-defined unions
  11. Enum—user-defined enumerations
  12. Attribute—attributes of the class
  13. Method—methods of the class
  14. Release—release order statement
  15. Passthru—passthru statements
  16. Data—internal instance variable of the class
  17. Interface—interfaces in a module
  18. Module—module information
  19. Epilog—text after all other sections

You can change the order of these sections, or omit any section not relevant to your emitter.

Building the Emitter

The newemit program generates a Makefile that you can use to build your emitter. When you invoke NMAKE, the emitter driver program and the emitter C implementation will be compiled and linked to create a DLL for your emitter. The name of the DLL is emit<filestem>. This DLL should be placed in a directory that can be reached by your LIBPATH statement.

Invoking the Emitter

To invoke the emitter, run the SOM compiler using the -s option, specifying the name of the emitter. For example, the following command invokes the rep emitter on the test.idl file.

sc -srep test.idl

This will produce the file test, rep whose format is defined in the output template file rep.efw.

The entire development process for an emitter is shown in Figure 9.5.

Fig-9 5.png

Figure 9.5 The development process for creating an emitter

Emitter Framework Classes

The Emitter Framework consists of a number of classes as shown in Figure 9.6. The SOMTEmitC class manages the overall activity of an emitter. All emitters are derived from this class. The SOMTEmitC class provides the somtEmit<Section> and somtScan<Section> methods for emitting different sections of an output file.

Fig-9 6.png

Figure 9.6 Emitter Framework class hierarchy

The SOMTTemplateOutputC class controls the formatting part of the emitter process by providing a template facility. The template is defined in a file with an .efw extension and consists of section names and symbol names. When the emitter is run, the symbols are replaced by the appropriate values. The SOMTTemplateOutputC class predefines a set of section and symbol names. It also provides methods where an emitter can define new sections and symbols. We will see an example of this in our Report Emitter.

The SOMTEntryC class provides an abstraction for returning information about an IDL interface definition. When the SOM compiler parses an IDL file, it produces an object graph. Each node (entry) in the object graph is derived from some portion of the IDL definition. The SOMTEntryC class and its subclasses provide attributes and methods to access the corresponding entry in the object graph. For example, a SOMTClassEntryC object represents a complete class interface definition and provides methods for accessing the constants, types, structs, unions, enums, sequences, attributes, and methods defined within an interface statement.

The code fragment in Figure 9.7 shows how you can retrieve the class name and the list of attributes and their types. The somtTargetClass attribute returns the target class for the emitter. The cls object can then be used to retrieve the class information.

SOMTClassEntryC cls = _get_somtTargetClass(emitter);
SOMlAttributeEntryC attrb;
SOMTDataEntryC      myEntry;
SOMTEntryC          attrType;

printf("Class Name: %s\n", get_somtEntryName(cls));

for ( attrb = _somtGetFirstAttribute(cls); attrb;
      attrb = _somtGetNextAttribute(cls))
{
     // Handles the case where there is a list of declarators for an attribute type.
     // For example: attribute short al, a2, a3;
     for ( myEntry = _somtGetFirstAttributeDeclarator(attrb); myEntry;
           myEntry = _somtGetNextAttributeDeclarator(attrb))
     {
         printf ("Attribute Name: %s", _get_somtEntryName(myEntry));
     }

     attrType = _get_somtAttribType(attrb);
     printf( "Attribute Type %s\n", _get_somtEntryName(attrType));
}

Figure 9.7 Example to show how to use the Emitter Framework classes

A Report Emitter

In this section, we will build a report emitter. The report emitter produces a report that lists the following information:

  • Class name
  • Class comment
  • Class parent name
  • Attribute names and their types
  • Method names, their parameters, and return types
  • The total number of attributes and methods in an IDL

The format for the output template is shown in Figure 9.8.

Note that the section summaryS is not a pre-defined section, and the symbols totalAttributes and totalMethods are not the standard symbols. We will show how to set the values of these symbols in our emitter.

:classS
 Report on class <className>
=====================================================
? Description:     <classComment>
  IDL source file: <classSourceFile>
  Parent Name:     <baseName>

:attributePrologS
 Attribute Definitions
=====================================================
:attributeS
<attributeDeclarators, ...> <attributeBaseType>
:attributeEpilogS

:methodsPrologS
 Method Definitions
=====================================================
:methodsS
<methodType> <methodName> <methodIDLParamList, ...>
:methodsEpilogS

:summaryS
 Summary
=====================================================
Total number of attributes: <totalAttributes>
Total number of methods:    <totalMethods>

Figure 9.8 The output template for our report

Invoke the newemit program using the following command:

newemit ReportEmifter rep

This generates the file rep.efw. Edit this file so that its format is the same as the output template given in Figure 9.8.

The file rep.idl contains the IDL definition for ReportEmitter. We will add the following to this file:

  • somtEmitMethod—this method is redefined so that it can count the number of methods the emitter emits.
  • somtEmitAttribute—this method is redefined so that it can count the number of attributes the emitter emits.
  • somlnit—this method is redefined to initialize instance variables.

The changes that are made to the IDL are shown in Figure 9.9 in bold.

#ifndef ReportEmitter_idl
#define ReportEmitter_idl

#include <scemit.idl>
interface ReportEmitter : SOMTEmitC
{
  #ifdef __SOMIDL__
  implementation 
  {
    //# Class Modifiers
    callstyle = oidl;
  
    //# Method Modifiers
    somtGenerateSections: override;

    somtEmitMethod: override;
    somtEmitAttribute: override;
    somInit: override;

    short numOfAttributes;
    short numOfMethods;
  };
  #endif /* __SOMIDL__ */
};

#endif  /* ReportEmitter_idl */

Figure 9.9 The ReportEmitter IDL

The file rep.c contains the C implementation for the ReportEmitter. We modify the default implementation of somtGenerateSections so that it only generates the sections that we need in our output template. We also add the implementation for somtEmitMethod, somtEmitAttribute, and somlnit. The modified rep.c appears in Figure 9.10.

/*
 *         File:    rep.c
 *       Author:    SOMObjects Emitter Framework.
 *     Contents:    Generic framework implementation for ReportEmitter.
 *         Date:    Mon Jan  3 18:24:38 1994.
 */

#define ReportEmitter_Class_Source
#include <rep.ih>
#include <stdio.h>

SOM_Scope boolean  SOMLINK somtGenerateSections(ReportEmitter somSelf)
{
    ReportEmitterData *somThis = ReportEmitterGetData(somSelf);
    char buf[50];

    SOMTClassEntryC cls = __get_somtTargetClass(somSelf);
    SOMTTemplateOutputC template = __get_somtTemplate(somSelf);
    ReportEmitterMethodDebug("ReportEmitter","somtGenerateSections");

    /*
     * Setup symbols that are common to the whole file 
     */
    _somtFileSymbols(somSelf);

    if (cls != (SOMTClassEntryC) NULL) {
         _somtScanBases(somSelf,
                       "somtEmitBaseProlog",
                       "somtEmitBase",
                       "somtEmitBaseEpilog");
         _somtEmitClass(somSelf);

        _somtScanAttributes(somSelf, "somtEmitAttributeProlog",
                            "somtEmitAttribute", "somtEmitAttributeEpilog");

        _somtScanMethods(somSelf,
                         "somtImplemented",
                         "somtEmitMethodsProlog",
                         "somtEmitMethod",
                         "somtEmitMethodsEpilog",
                         0);

        sprintf(buf, "%d", _numOfAttributes);
        _somtSetSymbolCopyBoth(template, "totalAttributes", buf);

        sprintf(buf, "%d", _numOfMethods);
        _somtSetSymbolCopyBoth(template, "totalMethods", buf);
        
        _somtOutputSection(template, "summaryS");
    }

    return (TRUE);
}

SOM_Scope void  SOMLINK somtEmitMethod(ReportEmitter somSelf, 
                                       SOMTMethodEntryC entry)
{
    ReportEmitterData *somThis = ReportEmitterGetData(somSelf);
    SOMTTemplateOutputC template = __get_somtTemplate(somSelf);

    ReportEmitterMethodDebug("ReportEmitter","somtEmitMethod");
   
    ReportEmitter_parent_SOMTEmitC_somtEmitMethod(somSelf, entry);

    _numOfMethods++;
}


SOM_Scope void  SOMLINK somtEmitAttribute(ReportEmitter somSelf, 
                                          SOMTAttributeEntryC att)
{
    ReportEmitterData *somThis = ReportEmitterGetData(somSelf);
    ReportEmitterMethodDebug("ReportEmitter","somtEmitAttribute");

    ReportEmitter_parent_SOMTEmitC_somtEmitAttribute(somSelf, 
                                                     att);
    _numOfAttributes++;
}

SOM_Scope void  SOMLINK somInit(ReportEmitter somSelf)
{
    ReportEmitterData *somThis = ReportEmitterGetData(somSelf);
    ReportEmitterMethodDebug("ReportEmitter","somInit");

    ReportEmitter_parent_SOMTEmitC_somInit(somSelf);

    _numOfAttributes = 0;
    _numOfMethods = 0;
}

Figure 9.10 The ReportEmitter class implementation

Notice how we use the somtSetSymbolCopyBoth method to set the values of the symbols totalAttributes and totalMethods. The somtOutputSection method is then used to output the summaryS section.

The emitter can then be built by invoking NNAKE. This creates the DLL emitrep.dll.

Invoking the Report Emitter

Make sure the file emitrep.dll is placed in a directory that is specified on your LIBPATH. Also make sure the output template rep.efw is placed in a directory that is specified by the environment variable SMINCLUDE. For example, the following setting will cause the SOM compiler to search the current directory and then the SOM include directory for the output template:

set SMINCLUDE=.;%SOMBASE%\INCLUDE

The test.idl in Figure 9.11 is provided as a test case.

#include <somobj.idl>

interface Test: SOMObject
{
  const unsigned short MAXSIZE = 50;
  union Foo switch (long)
  {
     case 1: long x;
     case 2: float y;
     default: char z;
  };

  attribute Foo myfoo;
  attribute double mydouble;
  attribute any anyvalue;
  attribute sequence<long,MAXSIZE> longList;

  void add(in string name);
  string query(in short index, inout octet aByte);
  long print(out boolean status);

  #ifdef __SOMIDL__
  implementation
  {
    releaseorder : _get_myfoo, _set_myfoo,
                   _get_mydouble, _set_mydouble,
                   _get_anyvalue, _set_anyvalue,
                   _get_longList, _set_longList,
                   add, query, print;
  };
  #endif
};

Figure 9.11 An IDL to illustrate the report emitter

To run the report emitter on test.idl, invoke the SOM compiler using the -s option.

sc -srep test.idl

This produces the file test.rep listed in Figure 9.12.

 Report on class Test
=====================================================
 IDL source file: test.idl
 Parent Name: SOMObject

 Attribute Definitions
=====================================================
 myfoo Foo
 mydouble double
 anyvalue any
 longList /* seq<50> */ _IDL_SEQUENCE_long

 Method Definitions
=====================================================
Foo _get_myfoo
void _set_myfoo in Foo myfoo
double _get_mydouble
void _set_mydouble in double mydouble
any _get_anyvalue
void _set_anyvalue in any anyvalue
/* seq<50> */ _IDL_SEQUENCE_long _get_longList
void _set_longList in /* seq<50> */ _IDL_SEQUENCE_long longList
void add in string name
string query in short index, inout octet aByte
long print out boolean status

 Summary
=====================================================
Total number of attributes: 4
Total number of methods:   11

Figure 9.12: Output from the report emitter.