Extend Your Programs with REXX

From EDM2
Jump to: navigation, search

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.

Reprint Courtesy of International Business Machines Corporation, © International Business Machines Corporation