Extend Your Programs with REXX

by Monte Copeland

REXX is a programming language born on IBM mainframes in the 1980's. Mike Cowlishaw created REXX to improve upon the EXEC2 batch language on VM. IBM decided not to copyright the REXX language. As a result, REXX implementations appeared on other platforms including MVS, PC DOS, DEC VMS, and the Amiga - a tribute to its flexibility.

By 1991, REXX was standard equipment on OS/2 1.3. The OS/2 Command Processor (CMD.EXE) was enabled for REXX, and REXX has been part of OS/2 ever since.

Most consider REXX an easy language to learn, well within the reach of non-programmers. REXX has a complete set of execution control statements, and its data types are simple. In REXX, one basic data type exists: the string. Integers, rational numbers, strings, and arrays are all just strings.

Application Extensibility
An important part of REXX architecture is application extensibility. A REXX-enabled application can call REXX programs written by the user.

Developers should consider REXX as the macro language for their product. Consider those OS/2 users who have already learned some REXX. Those users will be delighted when they find they can extend your application using a language they already know.

REXX solves other sticky language problems such as lexical and syntactical analysis and macro debugging. Interpreters are not trivial programs, so reusing REXX in OS/2 saves writing a ton of code.

RXDRAW.EXE
This article focuses on how to invoke the REXX interpreter from a C program. Included here is a sample Presentation Manager program, RXDRAW.EXE. It calls REXX as a subroutine to draw simple graphics in its main window. Complete sample code is in the "Source Code from The Developer Connection News" category.

Run RXDRAW.EXE with DRAW.CMD in the current directory, and refer to Sample Code 1. When RXDRAW.EXE loads, press the Run option on the menu bar. DRAW.CMD will create a graphic in the RXDRAW window.  // rxdraw.cmd, a REXX program for RXDRAW.EXE // Call a box-drawing subroutine for each of 16 colors

// Trace r shows execution results; good for debugging // Trace r

// This REXX program expects RXDRAW.EXE to be the host environment szEnvironment = address say 'Environment is' szEnvironment if szEnvironment <> "RXDRAW" then do say "Expected RXDRAW environment." return 1 end

say 'draw.cmd starting...'

// Start drawing at x,y; there are 100 points per inch. x = 50 y = 50

do i = 0 to 15 // Set color and draw a box (color 0 is white) call Color i call Box x, y, x+100, y+100

// Draw some text in the current color call move x+100, y call text 'color' i

// Adjust drawing point x = x + 10 y = y + 15 end return

// Box-drawing subroutine // Parameters are x,y lower-left and x,y upper-right Box: x1 = arg(1) y1 = arg(2) x2 = arg(3) y2 = arg(4)

rc = Move( x1, y1) rc = Line( x2, y1) rc = Line( x2, y2) rc = Line( x1, y2) rc = Line( x1, y1) return  Sample Code 1. DRAW.CMD, a REXX program that draws a box in the client window of RXDRAW.EXE. This program expects to run in the RXDRAW environment. It uses special functions Move, Line, Color, and Text that are not available in the usual CMD environment.

DRAW.CMD calls the functions Move, Line, Color, and Text. These primitive graphic functions are available to REXX programs when running under RXDRAW.EXE. Refer to Table 1. Move( x,y ) Moves the current position to coordinate x,y. The coordinate system is "low English";  there are 100 units per inch. Line( x,y ) Draws a line from the current position to coordinate x,y; x,y becomes the new current position. Color( n ) Sets current drawing color. 0 <= n < 16. (CLR_xxx definitions in PMGPI.H) Text( "text to print" ) Draws the text at the current position, and leaves the current position at the end of the string.  Table 1. RXDRAW special functions are graphical drawing primitives. The underlying worker routines are in RXDRAW.EXE. See OBJECT.C.

When you call one of the special RXDRAW functions from DRAW.CMD, REXX routes the call to the underlying worker function in RXDRAW.EXE. No matter how many parameters a special REXX function might have, REXX will "normalize" the parameters then call the corresponding worker function written in C.

REXX APIs and Data Structures
The API RexxRegisterFunctionExe makes the association between REXX function and EXE function. Parameter 1 specifies the function name by which it will be known to REXX programs; parameter 2 is a pointer to a REXX function handler in the EXE. See Sample Code 2.  case WM_CREATE: // Register REXX function handlers with REXX rc = RexxRegisterFunctionExe( "MOVE",  Move  ); rc = RexxRegisterFunctionExe( "LINE",  Line  ); rc = RexxRegisterFunctionExe( "TEXT",  Text  ); rc = RexxRegisterFunctionExe( "COLOR", Color );  Sample Code 2. RexxRegisterFunctionExe associates the name of a RXDRAW REXX function with the underlying C worker function in the EXE.

REXX function handlers in C always have five parameters. See the function prototype for Move in Sample Code 3. The Move prototype was derived from RexxFunctionHandler prototype in REXXSAA.H.  ULONG APIENTRY Move( PSZ       pszName,                      ULONG     lArgCount,                      PRXSTRING prxArgStrings,                      PSZ       pszQueueName,                      PRXSTRING prxRet  );  Sample Code 3. Function prototype for Move, a function available to REXX programs running in the RXDRAW environment. See Table 2 for description of parameters.

Table 2 contains a description of the parameters to REXX function handler. pszName ASCIIZ name of the function as it is known to REXX  lArgCount    Count of prxArgStrings prxArgStrings Pointer to array of one or more RXSTRINGs, a             REXX-defined C structure that describes a REXX string. Each RXSTRING has a string pointer and a length. See REXXSAA.H. pszQueueName Current REXX queue name prxRet       RXSTRING returned to the REXX caller  Table 2. Parameters to a REXX function handler. Your REXX function handler should return a 0 for success; a 40 in the event of bad parameters.

REXX keeps all data items as strings, including numeric data. For example, REXX stores the integer 129 as "129" in a data structure called RXSTRING. The RXSTRING structure has two elements, a pointer to the string and the length of the string. RXSTRING and PRXSTRING are typedef'ed in REXXSAA.H as follows: typedef struct _RXSTRING { ULONG strlength; PCH   strptr; } RXSTRING; typedef RXSTRING *PRXSTRING; A REXX string can contain any data, including null bytes, so the strlength element is necessary. Even so, REXX will place a null byte at the end of the string in most situations.

RexxStart
The RexxStart API invokes the REXX interpreter. There are eight parameters to RexxStart. The REXX Program Reference (REXXAPI.INF) is on your accompanying Developer Connection for OS/2 CD-ROM; view it for a complete description of the parameters. Sample Code 4 uses a subset of the RexxStart parameters.  // Prepare an empty rxstring MAKERXSTRING( rexxretval, NULL, 0 );

// Redirect SAY and TRACE output during REXX execution aExit[0].sysexit_name = CAPTION; aExit[0].sysexit_code = RXSIO; aExit[1].sysexit_name = NULL; aExit[1].sysexit_code = RXENDLST;

// Call the interpreter rcRexxStart = RexxStart( 0,                        NULL,                         globals.szRexxFileName,                         NULL,                         "RXDRAW",                         RXSUBROUTINE,                         aExit,                         &rexxrc,                         &rexxretval );

if( rexxretval.strptr ) { // Free it because REXX allocated it with DosAllocMem rc = DosFreeMem( rexxretval.strptr ); assert( 0 == rc ); }  Sample Code 4. RexxStart and supporting code. REXX will allocate memory at rexxretval.strptr if the REXX program returns a value. Free this memory with DosAllocMem. The aExit[] array specifies the RXDRAW exit for redirecting SAY and TRACE output to a PM window. You must first register this exit with RexRegisterExitExe.

RexxStart parameters 1 and 2 are null because RXDRAW.EXE passes no parameters to the REXX program.

Parameter 3 is the file name of the REXX program to call. OS/2 REXX searches in the current directory and path for the file name. If not found, REXX adds a .CMD extension and searches again. REXX program files can take any extension or none at all.

Parameter 4 is null because RXDRAW.EXE loads the REXX program from disk, not memory.

Parameter 5 is the environment name string. The REXX program can query this string using the REXX function ADDRESS. Because DRAW.CMD (Sample Code 1) expects to be called from RXDRAW environment, it tests the environment before it proceeds. Run DRAW.CMD from a command prompt, and you get an error message.

Parameter 6, RXSUBROUTINE, indicates that the application calls REXX as a subroutine.

Parameter 7 works with RexxRegisterExitExe. In your application, you can hook standard REXX system exits such as SAY, PUSH, and PULL. In Sample Code 5, the HandleExit function displays output from SAY and TRACE in a window. (Normally, the output goes to standard output stream.)  LONG APIENTRY HandleExit( LONG 1Exit, LONG 1Subfunc, PUCHAR pParm ) { PRXSTRING                  prxs; LONG                      index;

switch( 1Exit ){ case RXSIO: switch( 1Subfunc ){ case RXSIOTRC: case RXSIOSAY: // In this case, pParm is a PRXSTRING prxs = (PRXSTRING)pParm; assert( prxs->strptr ); index = (LONG)WinSendMsg( globals.hwndOutputListbox,     LM_INSERTITEM, LIT_END, prxs->strptr ); WinSendMsg( globals.hwndOutputListbox, LM_SETTOPINDEX,     (MPARAM)index, 0 ); break; }   break; } return RXEXIT_HANDLED; }  Sample Code 5. Normally, SAY and TRACE output from REXX goes to the standard output stream. But in the RXDRAW environment, HandleExit redirects this output to a PM window. First, HandleExit is given a name using RexxRegisterExitExe, then RexxStart parameter 7 causes REXX to use it.

Parameter 8 and the RexxStart result code indicate how the REXX interpreter fared.

Parameter 9 is a pointer to an RXSTRING. REXX subroutines can terminate with the REXX instruction RETURN. When you use RETURN with a parameter, REXX allocates space for the parameter using DosAllocMem. The caller should test and free it, or else risk a memory leak for the process.

RXDRAW.EXE Suggested Enhancements
The RXDRAW sample is far from complete. You can add more code; some suggestions follow:

The sample code includes a skeleton REXX subcommand handler, HandleCommand. REXX calls it when a REXX program issues a command to its host environment. For example, a REXX program under CMD can issue "DIR" to its host environment, CMD. REXX calls the subcommand handler in CMD.EXE with the command line, and CMD interprets it. However, in RXDRAW.EXE, HandleCommand does nothing except return success; thus all commands are ignored.

From C, you can get REXX variables using RexxVariablePool if you know the name of the variable. Consider implementing a new graphics function called PolyLine. The parameter to PolyLine is the name of a REXX stem variable containing an array of points. The function handler makes repeated calls to RexxVariablePool to enumerate all the points, then calls GpiPolyLine.

The system exit PULL is not implemented in RXDRAW.EXE. If it were, the REXX programs could be interactive.

Notes on PM and GPI in RXDRAW.EXE
The coordinate system used in RXDRAW.EXE is low English (PU_LOENGLISH, defined in PMGPI.H). There are 100 units per inch. A 100-unit square box should measure approximately one inch on the screen. RXDRAW uses a GPI retained segment. Think of a retained segment as a tape recorder that can record and playback graphical drawing. Graphical recording begins just before calling REXX; it ends after REXX returns. The drawing was recorded, so playback occurs whenever the application receives a WM_PAINT message.

RXDRAW.EXE uses two threads: thread 1 for the visible windows on the desktop; thread 2 for the invisible object window. With this scheme, thread 1 always attends to its message queue. For more information about two-threaded PM application architecture, read "Multithreading PM Applications" from The Developer Connection News, Volume 1 of The Developer Connection News on your accompanying Developer Connection for OS/2 CD-ROM.

For a non-PM program, erase RXDRAW.C and rewrite OBJECT.C. Take out threadmain, put in main, call the register functions, then RexxStart. Consider a function SAY2 that writes to the standard output stream without a trailing new line.

OS/2 Warp
In OS/2 Warp Version 3, REXX is no longer a selectable install option. OS/2 Warp install always installs REXX. Because it used to be selectable, some developers declined to use REXX as their macro language. Now REXX will always be present on OS/2.

REXX is a programming language designed for people-not just machines. In The REXX Language, Second Edition, Prentice Hall, Cowlishaw explains the REXX philosophy, as well as its features. Try extending a C program with REXX. You will think REXX was designed for programmers, too.