SOM and Object REXX

From EDM2
Jump to: navigation, search

by Dr. Willis Boughton

SOM and Object REXX

The System Object Model (SOM) probably is best known as the object framework for OS/2's Workplace Shell (WPS). OS/2 desktop objects are instances of WPS classes, which are subclasses of SOM classes. IBM stopped development of SOM in 1997, but it remains a fundamental programming framework for OS/2 (or eCS). Object REXX is the object-oriented version of REXX, OS/2's integrated scripting language. Object REXX is also available on Linux and Microsoft Windows and is a portable scripting language with many capabilities.

It is on OS/2, though, that Object REXX becomes most useful, because it is integrated with SOM. Specifically, a SOM class can be used directly by an Object REXX program. This capability is often used in Object REXX scripts that manipulate WPS objects. The integration of SOM and Object REXX is more that just a tool for manipulating the WPS, though. It has important consequences for system design and programmability. It means, essentially, that if a program is written as SOM classes, it is reprogrammable with Object REXX. The original program is just one way of using these classes; through Object REXX programming, you can make another program with these same classes. The concept of a "programming API" disappears. There are no custom "commands", there is only Object REXX programming. Furthermore, you can develop new functionality by subclassing the SOM classes in Object REXX; you can reuse the SOM classes and add functionality, in Object REXX. In practice there are limits to this approach, but the concept is elegant and powerful.

Scope of this Article

This article demonstrates use of SOM with Object REXX in a situation not involving the WPS. This article does not address the big issue of designing an entire program using SOM classes, to make it reprogrammable using Object REXX. Addressed is a much smaller but related issue: how to make C or C code usable from Object REXX. This article demonstrates development of a SOM class, Sdb, that "wraps" a commercial C class, so that it can be used by Object REXX programs. The C class is from the C/Database Toolchest (CDT) sold by Mix Software, Inc.. CDT is a library for indexed databases and includes many features such as variable field types, variable record length, multiple indexes, and record locking. CDT was selected simply because I was using it; I have no association with Mix Software, Inc.. If you want to get runnable code for the Sdb class in this article, you will need to purchase CDT. This article describes the development of the Sdb SOM class but is not a tutorial on SOM or Object REXX. For SOM, refer to the documentation in the Warp Developer's Toolkit, which you need installed to do SOM development. Another reference is the book , which has an outstanding presentation of SOM and C object orientation. Since the Sdb listing is lengthy, this article includes only samples; the complete source is available on hobbes. [Editor: The source doesn't seem to be uploaded yet, but check shortly].

Before going into the development of the SOM wrapper class, it may be helpful to show what you can do with it. The following is an Object REXX program that uses Sdb:

 /* REXX */
 db = .Sdb~new
 if (db~create("Example", "name,s;number,u", 512)) then do
 db~createIndex("Index1", "name,s")
 db~setStrField("Name", "Name1")
 db~setUShortField("Number", 1)
 db~addrec
 db~setStrField("Name", "Name2")
 db~setUShortField("Number", 2)
 db~addrec
 db~setStrField("name", "Name1")
 db~findRec
 db~getrec(0)
 say db~strField("name") db~UShortField("number")
 db~setStrField("name", "Name2")
 db~findRec
 db~getrec(0)
 say db~strField("name") db~UShortField("number")
 db~close
 end
 exit
 ::class Sdb external "SOM Sdb"
 

This program does the following:

  • Creates empty database "Example". Each database record consists of a string field named "Name" and an unsigned short integer field named "Number."
  • Creates database index "Index1" consisting of the Name field only.
  • Opens the database for shared read and write access.
  • Adds two records with different field values.
  • Retrieves each record using the index and for each record writes the Name and Number fields to the screen
  • Closes the database.

CDT Summary

The CDT manual fully explains design and use of a CDT database. A quick summary is necessary for understanding the SOM wrapper class. A CDT database consists of a datafile and index file. Initially an index file has only a physical index, but you can add any number of field-based indexes, and each index can involve multiple fields. Your program selects what index is in use. The field types supported are string, case-sensitive string, short int, unsigned short int, long int, float, double, and fixed-length binary. A field is defined by a string that includes the field name and type, e.g., "cost,u" defines a unsigned integer field named "cost". Records are variable length. CDT provides C and C APIs. In C , a CDT database is an instance of the ISAM class (isam.hpp header file). This class has many methods; the CDT ISAM Method Summary Table summarizes a few. Basically, to use CDT you identify fields and indexes by name and manipulate fields by type, e.g., string, int, or float. To add, delete, and retrieve records, you work with a memory buffer and the "current record". Each index has its own current record. To add a record, for example, you set the field values in the buffer, then add the buffer, as a record, to the database. This makes that record the current one. To retrieve a record, you select the index, set the field values, find the matching record, which makes it the current record, then get it into the buffer. Error handling is by status return, not exceptions.

CDT ISAM Method Summary Table*

Method Description
int addRec() Adds a record from the memory buffer.
int create(Str name, StrList fieldDescriptions) Creates a database.
int delRec() Deletes the current record for the selected index.
int findRec() Sets the current record for the selected index to match the memory buffer fields.
int getField(Str name, float& value) Gets a float field value from the memory buffer. There are many get methods, for the various field types.
int getRec(int lock) Gets the current record for the selected index into the memory buffer. lock specifies the record locking.
int index(Str name, StrList fields) Creates a named index involving the specified fields.
int modRec() Modifies the current record for the selected index to match the memory buffer fields.
open(Str name, int mode) Opens the specified database. mode is shared or read only.
int selectIndex(Str name) Selects an index.
int setField(Str name, int i) Sets an int field value in the memory buffer. There are many set methods, for the various field types.
* typedef const char* Str;
typedef const Str* StrList;

SOM Development Process Summary

To develop a SOM class, you need the Warp Developer's Toolkit and a C or C compiler. I use VisualAge C V3. I do not know if there any SOM or CDT issues with other compilers. If you use VisualAge C V3, make sure you do not use the SOM toolkit that comes with it, since it is out of date. The easiest approach is to install the compiler first. The SOM development process involves the following steps (see the Toolkit SOM Programming Guide):

  1. Define each SOM class in Interface Definition Language (IDL).
  2. With the SOM compiler, compile each IDL file to generate a C or C stub for each class and to add the class to your SOM Interface Repository (IR).
  3. Fill in the class stubs with your code and compile your C or C classes.
  4. Link your classes into a DLL.
  5. Repeat these steps as necessary. When you rerun the SOM compiler, it updates the output file only if necessary. For example, if you add a new method to the IDL, the stub for that method will be added to the C or C source file but the rest of the file will be unchanged.

The main SOM class design issue related to Object REXX is passing of method parameters and returns. A SOM method parameter is in (input only), out (output only), or inout (input and output). As long as you use only in parameters, which is the "pure" object orientation approach, many IDL types can be accessed directly in Object REXX (see the Object REXX Programming Guide). Probably the most commonly used are void, short, ushort, long, ulong, float, double, boolean, char, string, octet. An IDL struct can be passed to and from Object REXX, but the struct elements cannot be accessed in Object REXX. So, when designing a SOM class for use with Object REXX, you must use void, short, etc. parameters and returns if you want to directly use the value in Object REXX. If the parameter is just a passthru, you can use a struct. In the case of a SOM wrapper class, e.g., Sdb, your SOM methods must do conversions as necessary between the IDL parameters and those required by the code being wrapped. For example, the ISAM create method has a parameter that is an array of char* pointers (see the CDT ISAM Method Summary Table). The array strings are not accessible in Object REXX directly. The Sdb create method therefore replaces the char* array parameter with a single string, which must contain the individual strings separated by delimiters. The Sdb create method converts this string parameter into the char* array required by the ISAM class.

The following sections summarize SOM development for the CDT wrapper.

CDT Wrapper IDL

Shown below is the IDL for the Sdb class. Most but not all CDT ISAM methods are wrapped, e.g., ones to display error messages are not. A couple of methods are added, e.g., a method to import records from a delimited ASCII file. There is an enum for ISAM status. static CDT ISAM methods are wrapped using the SOM Metaclass SdbMeta. Its IDL is shown after the Sdb IDL. The CDT SOM wrapper therefore consists of two SOM classes, Sdb and SdbMeta. I put each class in a separate IDL file, but they could be combined in one.

 // IDL for Sdb class
 #ifndef _Sdb_
   #define _Sdb_
   #include
   #include
   #include "sdbmeta.idl"
   interface Sdb : SOMObject {
    enum Status {OK, ERROR, NOLOCK, ATINDEXSTART, ATINDEXEND,
               FOUND, NOTFOUND, TOOMANYFIELDS, TYPEMISMATCH,
               NOFIELDS};
     boolean create(in string dbName, in string fieldSpecs,
               in short blockSize);
     boolean open(in string dbName, in boolean readOnly,
               in boolean share);
     boolean close();
     boolean destroy(in string dbName);
     boolean flush();
     boolean lock(in boolean readPermitted);
     boolean unlock();
     boolean copy(in string curName, in string copyName,
               in short blockSize);
     string fileName();
     Status lastStatus();
     boolean addRec();
     boolean getRec(in boolean lock);
     boolean delRec();
     boolean findHeadRec();
     boolean findNextRec();
     boolean findPrevRec();
     boolean findTailRec();
     boolean findRec();
     boolean modRec();
     boolean importRecs(in string fileName, in char delimiter);
     boolean createIndex(in string indexName,
                 in string indexFields);
     boolean selectIndex(in string indexName);
     boolean deleteIndex(in string indexName);
     short numFields();
     string fieldSpec(in string fieldName);
     string fieldSpecs();
     short binaryFieldLen(in string fieldName);
     short numIndexes();
     short numIndexFields();
     string indexSpec(in string indexName);
     boolean clearFields();
     boolean setStrField(in string fieldName, in string value);
     boolean setShortField(in string fieldName, in short value);
     boolean setUShortField(in string fieldName,
                    in unsigned short value);
     boolean setLongField(in string fieldName, in long value);
     boolean setFloatField(in string fieldName, in float value);
     boolean setDoubleField(in string fieldName, in double value);
     boolean setBinaryField(in string fieldName, in string value);
     string strField(in string fieldName);
     short shortField(in string fieldName);
     unsigned short ushortField(in string fieldName);
     long longField(in string fieldName);
     float floatField(in string fieldName);
     double doubleField(in string fieldName);
     string binaryField(in string fieldName);
     #ifdef __SOMIDL__
     implementation {
       void* iDb;
       short iResult;
       short iCurIndex;
       short iLastLockType;
       boolean iReadOnly;
       boolean iShare;
       char iName[80];
       char iCurIndexName[80];
       releaseorder: create, open, close, destroy, lastStatus,
         fieldNum, setStrField, setShortField, setUShortField,
         setLongField, addRec, getRec, setFloatField,,
         strField, shortField, delRec, ushortField, longField,
         floatField, doubleField, findHeadRec, findNextRec,
         findPrevRec, createIndex, selectIndex, deleteIndex,
         findRec, findTailRec, flush, numFields, fieldSpec,
         fieldSpecs, fileName, numIndexes, numIndexFields, indexSpec, setDoubleField,
         lock, unlock, clearFields, modRec, copy, setBinaryField,
         binaryField, binaryFieldLen, importRecs;
       metaclass = SdbMeta;
       majorversion = 0;
       minorversion = 0;
       dllname = "sdb.dll";
       somDefaultInit: override, init;
       somDestruct: override;
     };
   #endif
   };
 #endif
 // IDL for SdbMeta class
 #include
 #include
 interface Sdb;
 interface SdbMeta : SOMClass {
   void setLocking(in short tries, in long delay, in long timeout);
   short lockTries();
   long lockDelay();
   long lockTimeout();
   boolean setBuffers(in short numBufs, in short bufSize);
   boolean renameDb(in string oldName, in string newName);
   #ifdef __SOMIDL__
   implementation {
     short iBufferCount;
     short iBufferSize;
     short iLockTryCount;
     long iLockDelayMs;
     long iLockTimeoutMs;
     releaseorder: lockTries, lockDelay, lockTimeout, setLocking,
               setBuffers, renameDb;
     majorversion = 0;
     minorversion = 0;
     dllname = "sdb.dll";
     somDefaultInit: override, init;
     somDestruct: override;
   };
   #endif
 };
 

SOM Compilation

SOM compilation involves two actions: generating the stubs and generating the Interface Repository. You can do either first; here I will start with the stubs. For each IDL file, you can generate C (.c) or C (.cpp) stubs by selecting the compiler emitter. Do not mix C and C stubs. I used C , for which the corresponding emitter is xh;xih;xc. You can specify the emitter in more than one way; probably the easiest is with SMEMIT=xh;xih;xc in config.sys (see the SOM Programming Guide). For each class the stub will contain an empty C method for each IDL method. If you have specified the emitters with SMEMIT, the compilation command is


sc -u nnn.idl

where nnn is the class name. The -u parameter specifies updating of the existing stub, if any. You can specify more than one IDL file. By default the stubs are output to the current directory. To generate the C stubs for Sdb and SdbMeta, the command is:


sc -u sdb.idl sdbmeta.idl

Absence of an error message indicates successful compilation. The following is the start of the sdb.cpp stub:

 /**  This file was generated by the SOM Compiler and Emitter
 Framework.
 *  Generated using template emitter:
 *      SOM Emitter emitxtm: 2.23.1.9
 */
 #ifndef SOM_Module_sdb_Source
 #define SOM_Module_sdb_Source
 #endif
 #define Sdb_Class_Source
 #define SdbMeta_Class_Source
 #include "sdb.xih"
 SOM_Scope boolean  SOMLINK create(Sdb *somSelf,  Environment *ev,
 string dbName, string fieldSpecs,
 short blockSize)
 {
 SdbData *somThis = SdbGetData(somSelf);
 SdbMethodDebug("Sdb","create");
 /* Return statement to be customized: */
 { boolean retVal;  return (retVal); }
 }
 ... more stubs follow
 

Each SOM class must also be compiled into an Interface Repository, using the compiler's ir emitter. To specify your IR, first add the full pathname for file nnn.ir to the end of the SOMIR variable in config.sys, where nnn is the name of one of your SOM classes. So for Sdb, add sdb.ir (full pathname) to the end of SOMIR. The SOM compiler updates only the last IR in SOMIR, so your IR file must be the last one. Probably the easiest way to run the ir emitter is with the command


sc -sir nnn.idl

where nnn again is the SOM class name. So for the CDT wrapper classes the command would be:


sc -sir sdb.idl sdbmeta.idl

Completing the SOM Stubs

There is nothing specific to Object REXX in completing the SOM stubs. You code what needs to be done. The completed Sdb create method is shown below. The completed sdb.cpp file has about 1000 lines of code, excluding comments. sdb.cpp uses the IBM Open Class Library, so it would have to be modified if Open Class were not used.


SOM_Scope boolean SOMLINK create(Sdb *somSelf,  Environment *ev,
          string dbName, string fieldSpecs, short blockSize) {
  SdbData *somThis = SdbGetData(somSelf);
  SdbMethodDebug("Sdb","create");
  IString specsCopy(fieldSpecs);
  uint numFields = unpackTokens(specsCopy.operator char*(),
                     FIELDDELIM, SdbFields);
  if (numFields > MAXFIELDS) {
    somThis->iResult = Sdb_TOOMANYFIELDS   ERROROFFSET;
  } else if (numFields == 0) {
    somThis->iResult = Sdb_NOFIELDS   ERROROFFSET;
  } else {
      ISAM* db = (ISAM*) somThis->iDb;
      if (blockSize == 0) blockSize = DEFAULT_BLKSIZE;
      somThis->iReadOnly = false;
      somThis->iShare = false;
      strcpy(somThis->iName, dbName);
      somThis->iResult = db->create(dbName, SdbFields, blockSize);
      if (somThis->iResult == I_OK) {
      somThis->iResult = somSelf->selectIndex(ev,
                          PHYSICALINDEXNAME);
      }
  }
  return (somThis->iResult == I_OK);
}

C Compiling and Linking

There is nothing specific to SOM in compiling the wrapper classes. Make sure all Toolkit include directories are in the include path. The Toolkit installation does this automatically. You must link a SOM class into a DLL, and you can have any number of classes in a DLL. You can use the SOM compiler's def emitter to generate the export list, e.g.:

sc -sdef sdb.idl

This command outputs the sdb.def file, containing the following:

LIBRARY sdb INITINSTANCE
DESCRIPTION 'Sdb Class Library'
PROTMODE
DATA MULTIPLE NONSHARED LOADONCALL
EXPORTS
SdbCClassData
SdbClassData
SdbNewClass

To put multiple classes in a DLL, run the def emitter multiple times and use a text editor to combine the def files into one. You also need to add the SOMInitModule export by hand. The full def file for the Sdb DLL is:

LIBRARY sdb INITINSTANCE
DESCRIPTION 'Sdb Class Library'
PROTMODE
DATA MULTIPLE NONSHARED LOADONCALL
EXPORTS
SOMInitModule
SdbCClassData
SdbClassData
SdbNewClass
SdbMetaCClassData
SdbMetaClassData
SdbMetaNewClass

When linking, you must include somtk.lib and os2386.lib as well the libraries are required by your code specifically. For the CDT wrapper, these libraries are isam.cpp, isamcpp.lib and cdt.lib. If all files are in the link path, the following command links the Sdb DLL with VisualAge C V3:

ilink /packd /packc /exepack /align:16 /noi /out:sdb.dll sdb.obj
  sdbmeta.obj isam.lib cbt.lib isamcpp.lib somtk.lib os2386.lib
  sdb.def

Object REXX Programming

You must do two things to use a SOM class in Object REXX:

  • Put the SOM DLL in the LIBPATH
  • In your Object REXX program, import the SOM class with the statement
 ::class Sdb external "SOM nnn" 

where nnn is the SOM class name.

See the example program at the start of this article. This program creates in the current directory the file example.db, which is the CDT database file, and file example.idx, the database index file. The program output is:

Name1 1
Name2 2

Another example Object REXX program using Sdb is:

 /* REXX */
 db = .Sdb~new
 if (db~create("example", "name,s;binary,b100", 512) > 0) then do
   BinaryFile = .stream~new("data")
   BinaryData = BinaryFile~charin(1, BinaryFile~chars)
   say BinaryData~length
   db~createIndex("index1", "name,s")
   db~selectIndex("index1")
   db~setStrField("name", "name1")
   db~setBinaryField("binary", BinaryData)
   db~addRec
   db~findRec
   db~getRec(0)
   ABinary = db~binaryField("binary")
   say ABinary
   say db~strField("Name")
   say db~numFields
   say db~numIndexes
   say db~fieldSpecs
   say db~fieldSpec("name")
   say db~binaryFieldLen("binary")
   say db~numIndexFields
   say db~indexSpec('index1')
   db~close
 end
 exit
 ::class Sdb external "SOM Sdb"
 

This program reads the text file "data", adds its contents to a CDT database as a binary field, retrieves the record and displays the binary field, and displays database information. If the data file contains

Data 1
Data 2
Data 3
Data 4
Data 5

the program output is

40
Data 1
Data 2
Data 3
Data 4
Data 5
name1
2
2
name,s;binary,b100;
name,s
100
1
name,s

In Object REXX you can subclass a SOM class, i.e., you can subclass Sdb to create a custom database class. For example, say you want a CDT database with the following capabilities:

  • Whenever a database is created, a long int field named "id" is automatically included.
  • There is a method that returns the number of records.

An Object REXX class with these capabilities is

 class MyDb public subclass Sdb
 ::method create
   use arg dbName, fieldSpecs, blocksize
   fieldSpecs = fieldSpecs||';id,l'
   return self~create:super(dbName, fieldSpecs, blockSize)
 ::method numRecs
   count = 0
   if (self~findHeadRec) then do
     count = count   1
     do while (self~findNextRec)
       count = count   1
     end
   end
   return count
 class Sdb external "SOM Sdb"
 

C Programming

The SOM wrapper class can be used in C as well as Object REXX. You must use SOM, however, not just C . Listed below is a C program that does exactly what the Object REXX program at the start of this article does, with one exception: the C code accesses the SdbMeta class, to demonstrate how it is done in SOM.

 #include
 #include
 int main(int argc, char ** argv) {
   Environment *ev = somGetGlobalEnvironment();
   Sdb* db = new Sdb;
   Sdb_Status status = db->create(ev, "example",
                       "name,s;number,u", 0);
   if (status == Sdb_OK) {
     db->createIndex(ev, "Index1", "name,s");
     db->setStrField(ev, "name", "Name1");
     db->setUShortField(ev, "number", 1);
     db->addRec(ev);
     db->setStrField(ev, "name", "Name2");
     db->setUShortField(ev, "number", 2);
     db->addRec(ev);
     db->setStrField(ev, "name", "Name1");
     db->findRec(ev);
     db->getRec(ev, 0);
     somPrintf("name=%s number=%u\n", db->strField(ev, "name"),
             db->ushortField(ev, "number"));
     db->setStrField(ev, "name", "Name2");
     db->findRec(ev);
     db->getRec(ev, 0);
     somPrintf("name=%s number=%u\n", db->strField(ev, "name"),
             db->ushortField(ev, "number"));
     /* Access metaclass object, for demonstration only */
     somPrintf("lockTries=%i\n",
             SdbClassData.classObject->lockTries(ev));
     db->close(ev);
   }
   exit(0);
 }
 

Conclusion

SOM code is in some ways more complex than C code. Using SOM, though, makes C classes directly usable by Object REXX on OS/2, which is an advantage for system programmability.