Eric is a developer with Watcom International and the chief architect of the VX-REXX library. He can be reached via the Internet at [email protected]
One way to approach object-oriented programming (OOP) is to ignore the "language wars"--those debates on whether a particular language contains polymorphically correct constructs--and to simply start programming with objects. And if, by choice or by fiat, your language happens to be procedural, you can still use object-oriented techniques. This article discusses using such an approach with the REXX language, as found in OS/2 in the Workplace Shell and also in Watcom's VX-REXX. I'll also take a peek at a true object-oriented REXX.
REXX was developed in the early '80s by Mike Cowlishaw of IBM. It first gained popularity on IBM mainframe systems but has since migrated to several platforms, including OS/2 (a REXX interpreter comes with every copy of OS/2 2.x) and the Amiga.
REXX is a procedural language with all the usual features: control structures, complex variables, operators of various kinds, and functions. Example 1 shows a REXX program to sum the first N integers. REXX is a typeless language; no declarations are needed. Instead, all data is stored as strings. REXX is also designed to be interpreted and includes an INTERPRET statement for run-time interpretation of arbitrary strings, akin to Lisp's equal.
Expandability is REXX's greatest strength. REXX has a subcommand facility that allows it to be linked to an application and used as a macro language. New functions can be written in C and included in your application, or packaged together into a DLL for use by other applications.
The OS/2 Workplace Shell (WPS) is an object-oriented user interface (not to be confused with an object-oriented implementation of such an interface). WPS objects are represented by icons that can be directly manipulated by the user. These objects are not simple files, but independent entities derived from the WPS class hierarchy. Programmers can extend the class hierarchy to build their own object types.
Although WPS objects are geared toward direct user interaction, the system provides an interface that allows REXX programs to create and manipulate these objects. WPS exist independently of any user program, so the REXX interface provides a simple way to customize the OS/2 environment.
The REXX interface is implemented as a set of functions in the REXXUTIL.DLL external function library. To use these functions from REXX, it's necessary to first call the built-in RXFuncAdd function, as in Example 2(a). This must only be done once, usually on system startup in the STARTUP.CMD file, but it doesn't hurt to include these lines in your own REXX programs to ensure the functions are loaded.
The SysCreateObject function is used to create a WPS object; see Example 2(b). The first parameter, upsclass, is the type of object to create. WPS provides an extensive list of classes and allows programmers to create new classes. The parameter title will be the on-screen title of the object (if it is visible). Location is the folder in which the object is to be created--it may be a pathname or a WPS object identifier such as <WP_DESKTOP>. The parameter setupdata is a list of options to use in creating the object--the options depend on the type of object being created. If you specify an object ID as one of the setup options, the replaceflag parameter (Fail, Replace, or Update) tells the WPS what to do if an object with that ID already exists. (For a complete discussion of object IDs and the various setup options available, refer to OS/2 2.1 Unleashed, by David Moskowitz and David Kerr, Sams Publishing, 1933.)
If an object already exists, SysSetObjectData may be used to modify its data, and SysDestroyObject to destroy it. (No function exists to retrieve an object's data, however.) Listings One and Two (Page 100) demonstrate the use of SysCreateObject and SysSetObjectData. Listing One, books.cmd, creates a folder object on the desktop and populates it with program references for all the .INF (Information) files on your system. Clicking on one of these objects invokes the VIEW.EXE program on the respective file. Listing Two, bitmaps.cmd, cycles through all the .BMP (bitmap) files on your system and sets the desktop background to a new bitmap file every minute.
Typically, SysCreateObject is used to install software in appropriate folders. A good example of this is found in buildvrx.cmd, which is available electronically: see: "Availability," page 3.
Watcom's VX-REXX adds Presentation Manager support to the OS/2 REXX interpreter. An object library accessible from REXX lies at the heart of the VX-REXX system. The library defines a complete class hierarchy of window objects similar to what you would find in a traditional object-oriented system like C++. The objects are defined in C using IBM's System Object Model (SOM), allowing programmers to extend the library by subclassing an existing class and packaging it in a DLL.
VX-REXX objects are internal to a given REXX program, but objects are accessed through function calls. Any other method would require changes to the language definition. This is a typical restriction when using object-oriented techniques with procedural languages. The X Window Toolkit, for example, uses similar techniques for programming in C.
VX-REXX objects are created with VRCreate; see Example 2(c). Since most VX-REXX objects represent visible windows, a parent object is specified as the first parameter, followed by the class of object to create. Object properties (state data) may be set at creation time in pairs of arguments (property name and initial value); see Example 3(a).
VRCreate returns an object handle, or a null string if the object could not be created. The object handle uniquely identifies the object, but each object is also identified by its name, a settable property. Names and object handles may be used interchangeably. An object may be destroyed at any time by calling the VRDestroy function, Object properties may be set and retrieved after creation using VRSet and VRGet, as in Example 3(b). However, some properties are read-only after an object has been created.
VX-REXX objects have methods (or "member functions" in C++ parlance). Your program can invoke a method by using VRMethod, as follows: call VRMethod win, 'centerwindow'. Methods are procedures attached to an object, though currently VX-REXX does not allow methods to be defined in REXX.
VX-REXX programs are event driven--once the window objects have been created, the program sleeps until something interesting happens and an event is generated. When an event does occur, it is placed onto a queue for the program to fetch and process. Different objects respond to different events--a push button, for example, generates a Click event when the user clicks on it. The event data returned to the program is set by the user when the object is created, and is typically the name of a procedure to call. Listing Three (page 100) is a simple example of an event-driven program.
There's more to VX-REXX than the object library. A complete design environment allows you to create objects and set their properties by direct manipulation. VX-REXX generates the objects and the program framework for you. You just fill in the event procedures, using VRGet, VRSet, and VRMethod as required.
It's sometimes useful, however, to create objects on the fly, and this is where VRCreate comes in handy. Say you want to create a grid of 20 PictureBox objects on your window to display some bitmaps, and have the window size itself to the screen. Listing Four (page 100) is a procedure to do just that, extracted from the SHOWBMPS.EXE VX-REXX example (available electronically). The example creates the grid dynamically on the main thread while collecting file information on a secondary thread. When the bitmap information is ready, the main thread merely loops through the grid and set the PicturePath property to display the bitmaps, via a call to VRSet, as follows: call VRSet name, 'PicturePath', bitmaps.i. Objects are treated equally whether created in the design environment or at run time.
While this functional approach to object handling has its limitations, it can be used to build complex, GUI-based applications. The best example of this is the VX-REXX design environment itself, a hybrid of REXX and C code that uses the same object library that user programs use.
Although oriented specifically to window objects, the VX-REXX object library could be easily extended to include purely abstract object classes that are manipulated with the same set of functions. The ability to add new methods written in REXX to a class would be another extension. However, at this point the syntax and scoping limitations imposed by the language hardly make it worth the effort. What's really needed is a truly object-oriented REXX (OOREXX).
OOREXX started out as a research project at IBM, primarily at its U.K. laboratories. The idea was to add object-oriented programming concepts to REXX while maintaining maximal compatibility with existing REXX syntax. This meant writing a new interpreter that treated all data, including strings, as objects and transforming all operations into messages that invoke methods. Even simple expressions like: sum=3+4, are transformed into two messages (one to add a number to the "3" object, one to assign the resulting object to the variable). All operators and function calls can be handled this way, ensuring compatibility with procedural REXX. In general, however, messages are sent to an object by using the tilde (~) operator, as in: height=window~get_height('pixels'). Messages can include arguments.
Objects in OOREXX are created from classes definable in REXX. The basic mechanism is to create a new class from the global class object and attach code to it; see Example 4(a). You can then instantiate an object of that class by invoking new method, as in Example 4(b). Of course, inheritance is supported; see Example 4(c).
The self object is defined within a method to represent the object the method was invoked on. This allows the object's other methods and any methods from the superclass to be invoked; see Example 4(d). Objects have their own variable namespaces, as can each method. Variables from the object space can be imported into the method space using the METHOD EXPOSE statement; see Example 4(e).
OOREXX also offers features such as multiple inheritance and support for concurrency. At recent conferences, IBM has been demonstrating an OS/2 version of OOREXX that includes support for manipulating WPS objects directly within the OOREXX framework--quite an improvement over the current REXX support for WPS objects. Though no firm date for OOREXX has been announced, the OS/2 version is expected to be released by IBM sometime in 1994.
n = arg( 1 ) t = 0 do i = 1 to n t = t + i end say "Sum from 1 to" n "is" t exit </PRE> <P> <h4><a name="036d_0009"></a>Example 2: (a) Loading external functions from the REXXUTIL.DLL library; (b) creating a Workplace shell object; (c) creatng a VX-REXX object.</h4><P> <P> <pre class="brush: text; html: collapse;" style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"> (a) call RxFuncAdd 'SysLoadFuncs', 'REXXUTIL', 'SysLoadFuncs' call SysLoadFuncs (b) result = SysCreateObject( wpsclass, title, location, setupdata, replaceflag ) (c) object = VRCreate( parent, classname, [property_1, value_1], ... ) </PRE> <P> <h4><a name="036d_000a"></a>Example 3: (a) Setting object properties at creation time; (b) setting object properties after creation.<a name="036d_000a"></a></h4><P> <P> <pre class="brush: text; html: collapse;" style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"> (a) win = VRCreate( '', 'Window', 'height', 1000, 'width', 1000 ) (b) call VRSet win, 'name', 'MyWindow' height = VRGet( 'MyWindow', 'height' ) </PRE> <P> <h4><a name="036d_000b"></a>Example 4: (a) Creating a class in OOREXX; (b) instantiating an object of class hello; (c) using inheritance in defining a new class; (d) a method that uses messages to self; (e) controlling visibility of methos<a name="036d_000b"></a></h4><P> <P> <pre class="brush: text; html: collapse;" style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"> (a) hello = ~class~new( 'Hello' ) hello~define( 'SAY', 'say "hello"' ) (b) object = hello~new object~say (c) big_hello = ~class~new( 'Big Hello' )~inherit( hello ) big_hello~define( 'SAY', 'say "HELLO"' ) (d) big_hello~define( 'SAY_TWICE', 'self~say; self~say.super' ) (e) counter = ~class~new( 'Counter' ) counter~define( 'INIT', 'method expose count; count = 0' ) counter~define( 'INCREMENT', 'method expose count; count = count + 1' ) </PRE> <P> <HR> <P> <pre class="brush: text; html: collapse;" style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"> <a name="036d_000c"></a><a name="036d_000d"></a><B>[LISTING ONE]</B> /* BOOKS.CMD -- Makes a folder containing all the .INF files on your drives */ call RXFuncAdd 'SysLoadFuncs', 'REXXUTIL', 'SysLoadFuncs' call SysLoadFuncs /* Make a folder object (first erase old one) */ say 'Building Books Folder' call SysDestroyObject '<BOOKS_FOLDER>' classname = 'WPFolder' title = 'All Books' location = '<WP_DESKTOP>' setup = 'OBJECTID=<BOOKS_FOLDER>;' call SysCreateObject classname, title, location, setup, 'r' /* Get the list of local drives starting at C: */ drives = SysDriveMap( 'C', 'local' ) /* For each drive, search for .INF files */ classname = 'WPProgram' location = '<BOOKS_FOLDER>' count = 1 do while( drives <> '' ) parse var drives drive drives say 'Searching disk' drive drop books. call SysFileTree drive || '\*.INF', 'books', 'FSO' if( books.0 > 0 )then do say books.0 '.INF files found on drive' drive do i = 1 to books.0 title = filespec( 'name', books.i ) setup = 'EXENAME=view.exe;' ||, 'PROGTYPE=PM;' ||, 'PARAMETERS='books.i';' ||, 'OBJECTID=BOOK_'count';' call SysCreateObject classname, title, location, setup, 'r' count = count + 1 end end else say 'No .INF files found on drive' drive end say count '.INF files added to Books folder.' <a name="036d_000e"></a><a name="036d_000f"></a>[LISTING TWO]
/* BITMAP.CMD -- show system bitmaps */ call RXFuncAdd 'SysLoadFuncs', 'REXXUTIL', 'SysLoadFuncs' call SysLoadFuncs /* Some magic to put ourselves in the background. We run with PMREXX.EXE * in a minimized state, leaving an icon that the user can click on later... */ if( arg(1) = '' )then do say 'Updating the desktop and running the program in the background' parse source . . program setup = 'EXENAME=PMREXX.EXE;PARAMETERS='program' anyparm;' ||, 'PROGTYPE=PM;MINWIN=DESKTOP;MINIMIZED=YES;' ||, 'OPEN=DEFAULT;OBJECTID=CYCLE_BITMAPS' call SysCreateObject 'WPProgram', 'Cycle Bitmaps', '<WP_DESKTOP>', , setup, 'update' exit end /* Get the list of local drives starting at C: */ drives = SysDriveMap( 'C', 'local' ) bitmaps.0 = 0 /* For each drive, search for .BMP files */ do while( drives <> '' ) parse var drives drive drives say 'Searching drive' drive'...' drop tmp. call SysFileTree drive || '\*.BMP', 'tmp', 'FSO' do i = 1 to tmp.0 j = bitmaps.0 + 1 bitmaps.j = tmp.i bitmaps.0 = j end end /* Now cycle through the list, showing each bitmap for 1 minute */ do forever do i = 1 to bitmaps.0 say 'Displaying bitmap' bitmaps.i call SysSetObjectData '<WP_DESKTOP>', 'BACKGROUND='bitmaps.i';' call SysSleep 60 end end <a name="036d_0010"></a><a name="036d_0011"></a>[LISTING THREE]
/* EVENT.CMD -- A simple event-driven program. Requires VX-REXX. * Run using the command "vrx event" */ /* Build the window and add a pushbutton to it */ win = VRCreate( '', 'Window', 'height', 1000, 'width', 3000 ) pb = VRCreate( win, 'PushButton', 'left', 100, 'top', 100, , 'height', 500, 'width', 2700 ) /* When the window is closed, return an 'exit' */ call VRSet win, 'close', 'exit' call VRMethod win, 'centerwindow' /* When the pushbutton is clicked, return a call statement */ call VRSet pb, 'click', 'call increment' call VRSet pb, 'caption', 'Push me' count = 0 /*---- Event loop: wait for and interpret events ---------*/ do forever interpret VREvent() end exit /*----- Update the pushbutton caption -----------*/ increment: count = count + 1 call VRSet pb, 'caption', 'You pushed me' count 'time(s)' return <a name="036d_0012"></a><a name="036d_0013"></a>[LISTING FOUR]
/* Example of adding 20 PictureBox objects dynamically to a window */ ih = VRGet( 'Window1', 'InteriorHeight' ) iw = VRGet( 'Window1', 'InteriorWidth' ) x_incr = trunc( iw / 5 ) y_incr = trunc( ih / 4 ) top = 0 do i = 1 to 4 left = 0 do j = 1 to 5 x = (i-1) * 5 + j call VRCreate 'Window1', 'PictureBox',, 'name', 'Picture'||x,, 'width', x_incr,, 'height', y_incr,, 'left', left,, 'top', top,, 'resizepicture', 'true',, 'backcolor', 'white',, 'border', 'true',, 'bordercolor', 'black' left = left + x_incr end top = top + y_incr end