REXX-ercising Your Applications - Part 1

From EDM2
Jump to: navigation, search

Written by Gordon Zeglinski

Introduction

Rexx/2 provides an excellent mechanism by which developers can easily add scripting, macro, and programming abilities into their products. Yet there is still some cloud of mystery surrounding the use of REXX in general applications. It is hoped that this article will dispel this cloud and encourage other developers to incorporate REXX abilities into their apps.

After reading this article, you should:

  1. Understand a bit more about how .cmd files are executed.
  2. Know how to start Rexx/2 from within an application.
  3. Be able to extend Rexx/2 by adding your own external functions to it.

Note: The REXXSAA.H file shipped with the 2.1 Toolkit and the C++ patched 2.0 toolkit are not C++ compatible. A C++ compatible version is included with this issue for use with the C-Set++ compiler.

Some Basics

In REXX, there are three different categories of executable statements: instructions, commands, and functions. Instructions are built into REXX and cannot be supplemented. Commands and functions are definable by the application evoking the REXX interpreter. However, functions are more flexible in nature than commands because they can be integrated into expressions and can be added at run time via the (built-in) RxFuncAdd() function. This article will focus on functions.

Before we look closer at REXX functions, we should look a bit at Command (.CMD) files. Command files are REXX programs that are meant to be executed under the "CMD" REXX environment. The "CMD" environment is a subcommand handler registered with REXX using the name "CMD". It is important to note that command files are not able to be executed unless the "CMD" command handler is used when starting REXX if these files contain commands like "dir", "copy", etc. This discussion will be continued in part 2.

More About Functions

In REXX, functions come in two flavors: internal and external functions. From the REXX programmers point of view, once the external function has been registered, there is no difference between the two. However, xternal functions can either be based in DLL's or within the executable itself. Take the following REXX program, SHOWDIR.CMD .

/******************************************/
/* Simple Rexx/2 file that opens the      */
/* default view for the current directory */
/******************************************/

Call RxFuncAdd 'SysLoadFuncs', 'REXXUTIL', 'SysLoadFuncs'
Call SysLoadFuncs

DIR=directory();
If SysSetObjectData(DIR,"OPEN=DEFAULT") Then
   Say DIR "has been opened."
Else
   Say DIR "was not opened."

SHOWDIR.CMD illustrates several important points:

  • External functions must be registered before they can be used. In the case of DLL-based external functions, they can either be registered by using a REXX function or by the application.
  • There are two methods to call functions, either using call or (). Unlike in C or C++, if the () method is used then the line must follow the same syntax as as line 4 in the above example, DIR=directory();. Strictly speaking in REXX terminology, if the call method is used then the procedure being called is referred to as a subroutine. If the () method is used, then the procedure being called is referred to as a function.

About RXSTRINGs

The RXSTRING structure is used through out REXX and warrants a detailed examination. The maximum length of a RXSTRING is 4 gigabytes (the maximum number that can be held in a ULONG variable). Unlike C, where strings are zero terminated, the string component of an RXSTRING is not zero-terminated. Additional important points about RXSTRING's are listed below.

typedef struct {
   ULONG strlength;     // Length of string
   PCH strptr;          // Pointer to string
} RXSTRING;

  • An RXSTRING is said to have a value if the strptr field is not NULL. Consequently, it is said to be empty if the strptr field is NULL.
  • An RXSTRING is said to be a null string if the strptr field points to the value "" and the strlength field is zero.
  • The length stored in the strlength field does not include the terminating null character.
  • Conveniently, the REXX interpreter adds the terminating zero character to the strptr field when it calls external functions, subcommand handlers and exit handlers. This allows one to use the C library string functions on this field. However, there is no guarantee that this will be the only zero character. The developer will have to decide whether the data could have extra zero characters.
  • When a return value is expected from an external function, subcommand handler or exit handler, the REXX interpreter sets up a default RXSTRING of length 256. If the value to be returned is requires less space than this, it is simply copied into the strptr buffer and the strlength filed is set to the shorter length. If the default RXSTRING is too small, a new RXSTRING can be created using DosAllocMem(). The REXX interpreter will free the memory used by the new RXSTRING.

Starting Rexx/2 From Within an Application

We will start our trek into the work of REXX by looking at how we can start the Rexx/2 interpreter from within our apps. The function RexxStart() allows us to do this.

(LONG)RexxStart(ArgCount,
                ArgArray,
                Name,
                Instore,
                Environment,
                RXCOMMAND,
                ExitHandler,
                &RC,
                &ReturnString);

(Because the RexxStart() function can do so many things, this is bound to be some what bewildering. Don't panic, examples will be used to explore the various parameter combinations in this and future articles in the series.)

ArgCount (LONG) - input
The number of elements in the ArgList array. This value will be returned by the ARG() built-in REXX function. ArgCount should include RXSTRINGs which represent omitted arguments. Omitted arguments will be empty RXSTRINGs (strptr will be NULL). See the Discussion on RXCOMMAND for further information.
ArgArray (PRXSTRING) - input
An array of RXSTRING structures that are the REXX program arguments.
Name (PSZ) - input
Points to the ASCIIZ "name" of the REXX procedure and can have many different interpretations depending on the value of Instore. If Instore is NULL, then it is the name of the file containing the REXX procedure. If Instore is not NULL, then it is either the name of the subroutine or function in the Macrospace.

If Name is the name of a file, a default extension of .CMD is used if none is supplied. Optionally, "name" could be the fully qualified filename.

Instore (PRXSTRING) - input
An array of two RXSTRINGs that store REXX procedures in memory.

If both RXSTRINGs are empty, the interpreter searches for REXX procedure Name in the macrospace. If the procedure is not found in the macrospace, the RexxStart() function returns an error code.

If either RXSTRING is not empty, Instore is used to execute a REXX procedure directly from memory.
Instore[0] input
An RXSTRING containing an REXX procedure stored in the exact same manner as a disk file. The image must be complete with carriage returns, line feeds, and end-of-file characters.
Instore[1] input and output
If Instore[1] is empty, the REXX interpreter will return the tokenized image in here.

If Instore[1] is not empty, interpreter will execute the tokenized image directly. Instore[0] can be empty in this case, provided the REXX program does not require meaningful data from the SOURCELINE built-in REXX function. If Instore[0] is empty and SOURCELINE function is used, SOURCELINE will return null strings for the REXX procedure source lines. If Instore[1] is not empty, but does not contain a valid REXX tokenized image, unpredictable results can occur. The REXX interpreter might be able to determine that the tokenized image is incorrect and retokenize the source. If the procedure is executed from disk, the Instore pointer must be NULL. If the first argument string in Arglist contains the string "//T" and CallType is RXCOMMAND, the interpreter will tokenize the procedure source and return the tokenized image without running the program. The program using the RexxStart() function must release Instore[1] using DosFreeMem() when the tokenized image is no longer needed. The format of the tokenized image may not be the same across interpreter versions. Therefore, the tokenized image should not be stored to disk or transferred to other systems. It is meant to be used multiple times with in the same application in which it was created to allow the faster execution of the REXX program during the second and subsequent executions.

Environment (PSZ) - input
Address of the ASCIIZ initial ADDRESS environment name. The ADDRESS environment is a subcommand handler registered using RexxRegisterSubcomExe() or RexxRegisterSubcomDll(). Environment is used as the initial setting for the REXX ADDRESS instruction.
If Environment is NULL, the file extension is used as the initial ADDRESS environment. The environment name cannot be longer than 250 characters.

If Environment is set to "CMD", .CMD files may be executed from within the application.

CallType (LONG) - input
The type of REXX procedure execution. Allowed execution types are:
  • RXCOMMAND
The REXX procedure is an OS/2 operating system command or application command. REXX commands normally have a single argument string. The REXX PARSE SOURCE instruction will return "COMMAND" as the second token.
  • RXSUBROUTINE
The REXX procedure is a subroutine of another program. The subroutine can have multiple arguments and does not need to return a result. The REXX PARSE SOURCE instruction will return "SUBROUTINE" as the second token.
  • RXFUNCTION
The REXX procedure is a function called from another program. The subroutine can have multiple arguments and must return a result. The REXX PARSE SOURCE instruction will return "FUNCTION" as the second token.
ExitHandler (PRXSYSEXIT) - input
An array of RXSYSEXIT structures defining exits the REXX interpreter will use.
RC (PLONG) - output
If Result is a whole number in the range of -(2**15) to 2**15-1, it will be converted to a numeric form and and returned in RC as well as Result.
Result (PRXSTRING) - output
The string returned from the REXX procedure with the REXX RETURN or EXIT instruction. A default RXSTRING can be provided for the returned result. If a default RXSTRING is not provided or the default is too small for the returned result, the REXX interpreter will allocate an RXSTRING using DosAllocMem(). The caller of the RexxStart() function must release the RXSTRING storage with DosFreeMem().

The REXX interpreter does not add a terminating zero to Result.

Executing REXX Procedures From a File

If we were to look at code samples to illustrate all of the various things that RexxStart() can do, we wouldn't be able to get to creating external functions in this issue. Therefore, we will look at one of the more fundamental uses of RexxStart() - executing file-based REXX procedures. The code below illustrates this:

VOID RunScript(PSZ ScriptFile) {
   LONG      return_code;  // interpreter return code
   RXSTRING  argv[1];      // program argument string
   RXSTRING  retstr;       // program return value
   SHORT     rc;           // converted return code

   argv.strptr=NULL;
   argv.strlength=0;

   retstr.strptr=new char [1024];
   retstr.strlength=1024;

   return_code = RexxStart(0,            // No arguments
                           argv,         // dummy entry
                           ScriptFile,   // File name
                           NULL,         // NULL InStore
                           "CMD",        // use the "CMD" command processor
                           RXCOMMAND,    // execute as a command
                           NULL,         // No exit handlers
                           &rc,          // return code from REXX routine
                           &retstr);     // return string from REXX routine

   delete [] retstr.strptr;
}

In the above example, the return value string is set to be 1K in size. (it is assumed that the REXX interpreter will not require more space than this.) It should also be noted that because REXX commands typically do not execute quickly that the above function should be called from a separate thread in PM programs so that it will not hang the message queue.

External Functions

Finally, we can look at creating external functions. There are two types of external functions, EXE-based and DLL-based. The EXE-based version must be registered with REXX by the application in which they reside and can only be used by REXX programs started from within that application. Typically, one would use this type of external function to support macro and scripting features within the application.

DLL-based functions are mainly used to provide additional utility libraries for REXX. Once registered, all REXX programs have access to DLL-based functions. Both DLL- and EXE-based functions follow the same basic declaration syntax. The following sample function will clear the screen if called from a VIO-based REXX program.

ULONG _System SysCls(UCHAR *name,
                     ULONG numargs,
                     RXSTRING args[],
                     PSZ *queuename,
                     RXSTRING *retstr)
{
  BYTE bCell[2];

  // If arguments, return non-zero to indicate error
  if (numargs)
    return 1;

  bCell[0] = 0x20;
  bCell[1] = 0x07;
  VioScrollDn(0,
              0,
              (USHORT)0xFFFF,
              (USHORT)0XFFFF,
              (USHORT)0xFFFF,
              bCell,
              NULLSHANDLE);
  VioSetCurPos(0, 0, NULLSHANDLE);

  // return 0 to the caller
  retstr.strlength=1;
  strcpy(retstr.strptr,"0");

  return 0;
}

Things to remember

External REXX functions must use the C calling convention.

When creating DLL-based external functions, remember to export the function and use multiple data segments (DATA MULTIPLE NONSHARED).

The default return string is 255 bytes long.

Registering Your External Function

Having previously seen how to register DLL-based external functions from within REXX, we will now look at way in which you can register external functions from within an application. The function RexxRegisterFunctionDll() is used to register DLL-based external functions and RexxRegisterFunctionExe() is used to register external EXE-based functions.

(APIRET)RexxRegisterFunctionDll(PSZ pszFunction,
                                PSZ pszModule,
                                PSZ pszEntryPoint);
(APIRET)RexxRegisterFunctionExe(PSZ pszFunction,
                                PFN pfnEntryPoint);

For RexxRegisterFunctionDll(), the following parameters are specified:

pszFunction
Points to the string specifying the name of the function as it will be known by REXX.
pszModule
Points to the string specifying the DLL containing the function to be registered.
pszEntryPoint
Points to the exported function name within pszModule which specifies the function to be registered.

For RexxRegisterFunctionExe(), the following parameters are specified:

pszFunction
Points to the string specifying the name of the function as it will be known by REXX.
pfnEntryPoint
Points to the function to be registered.

Both functions return RXFUNC_OK, RXFUNC_DEFINED, or RXFUNC_NOMEM to indicate successful completion, function is already defined, and out of memory, respectively.

Additionally, there are functions to query the existance of a function and to deregister a function.

(APIRET)RexxQueryFunction(PSZ pszFunction);
(APIRET)RexxDeregisterFunction(PSZ pszFunction);

For both functions, pszFunction points to the function name (as registered using one of the above functions) and return FXFUNC_OK or RXFUNC_NOTREG to indicate that the function exists and that the function does not exist, respectively.

So now we've seen how to start Rexx/2 from within our applications, and seen how to register external functions. It's time to put our newly acquired knowledge to use.

Putting it all Together

In this section, we will create a simple application that will register an external function and execute .CMD files. The following program (included as REXXSAMP.CPP) illustrates the procedure of registering an external EXE-based function and starting the Rexx/2 interpreter.

#define INCL_RXFUNC       /* external function values */
#define INCL_VIO
#include <rexxsaa.h>
#include <iostream.h>
#include <string.h>

ULONG _System SysCls(UCHAR *name,
                     ULONG numargs,
                     RXSTRING args[],
                     PSZ *queuename,
                     RXSTRING *retstr)
{
  BYTE bCell[2];

  // If arguments, return non-zero to indicate error
  if (numargs)
    return 1;

  bCell[0] = 0x20;
  bCell[1] = 0x07;
  VioScrollDn(0,
              0,
              (USHORT)0xFFFF,
              (USHORT)0XFFFF,
              (USHORT)0xFFFF,
              bCell,
              NULLSHANDLE);
  VioSetCurPos(0, 0, NULLSHANDLE);

  // return 0 to the caller
  retstr.strlength=1;
  strcpy(retstr.strptr,"0");

  return 0;
}

INT main(VOID) {
  char      Input[CCHMAXPATH];
  LONG      return_code;  /* interpreter return code    */
  RXSTRING  argv[1];      /* program argument string    */
  RXSTRING  retstr;       /* program return value       */
  SHORT     rc;           /* converted return code      */

  RexxRegisterFunctionExe((PSZ)"EDM_SysCls",(PFN)&SysCls);

  cout << "Sample EDM/2 REXX Demonstration Program" <<
  endl << "by Gordon Zeglinski" << endl;

  cout << "Type rexxsamp <ENTER> to execute supplied smaple"
  << endl;
  cin >> Input;

  if (!strlen(Input))
     strcpy(Input,"REXXSAMP.CMD");

  cout << "Executing Sample Program " <<
      Input << endl;
  cout << "-----------------" << endl;

  retstr.strptr=new char [1024];
  retstr.strlength=1024;

  return_code = RexxStart(0,            // No arguments
                          argv,         // dummy entry
                          Input,        // File name
                          NULL,         // NULL InStore
                          "CMD",        // use the "CMD" command processor
                          RXCOMMAND,    // execute as a command
                          NULL,         // No exit handlers
                          &rc,          // return code from REXX routine
                          &retstr);     // return string from REXX routine

  delete [] retstr.strptr;

  return 0;
}

For those of you who can compile the sample code, feel free to experiment with it and a debugger.

Files Included With This Issue

REXXSAMP.EXE Compiled version of REXXSAMP.CPP
REXXSAMP.CPP Source code to sample Rexx/2 invocation program
REXXSAMP.MAK Makefile for C-Set++
REXXSAMP.DEF Definition file for use with linker
REXXSAMP.CMD Sample REXX program to be started from REXXSAMP.EXE

Summary

This concludes our look at Rexx/2 for this issue. You should now be able to start Rexx/2 from with in an application, and create external functions that are either DLL- or EXE-based. Future issues will explore the other features of RexxStart(), macroSpaces and sub-command handlers.

As usual, question and comments are welcome.