August 16, 2004 Dr. W. Boughton is a contributor to OS/2 e-Zine. If you have a comment about the content of this article, please feel free to vent in the OS/2 e-Zine discussion forums. There is also a Printer Friendly version of this page. |
|
SOM and Object REXXThe 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 ArticleThis 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 bookBefore 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:
CDT SummaryThe 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*
SOM Development Process SummaryTo 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):
The following sections summarize SOM development for the CDT wrapper. CDT Wrapper IDLShown 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 SOM CompilationSOM 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 issc -u nnn.idlwhere 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.idlAbsence 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 followEach 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.idlwhere 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 StubsThere 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 LinkingThere 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.idlThis command outputs the sdb.def file, containing the following: LIBRARY sdb INITINSTANCE DESCRIPTION 'Sdb Class Library' PROTMODE DATA MULTIPLE NONSHARED LOADONCALL EXPORTS SdbCClassData SdbClassData SdbNewClassTo 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 SdbMetaNewClassWhen 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 ProgrammingYou must do two things to use a SOM class in Object REXX:
Name1 1 Name2 2Another 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 5the 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,sIn 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:
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++ ProgrammingThe 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 ConclusionSOM 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. |
|||||||||||||||||||||||||||
|