REXX-ercising Your Applications - Part 2

From EDM2
Jump to: navigation, search

Written by Gordon Zeglinski

Introduction

In part 1, we seen how to extend REXX by creating external functions and how to execute REXX programs from within our applications. RexxStart() wasn't thoroughly explored in part 1, so in this issue we will examine two more permutations of RexxStart(). Also, in this issue, we will look at using external command handlers and using the REXX macrospace.

Enough preamble, let's get to it!

More on RexxStart()

Last time, we set the INSTORE parameter to NULL, to execute REXX procedures from files. By permuting the INSTORE parameter, we can execute REXX procedure from memory or the macrospace.

If you remember REXXSAMP.CPP from part 1, the variable Prog[] in the main() function, declares an in-memory version of this program. Prog[] is then assigned to the RXSTRING INSTORE[0].

#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);

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;                     /* Space Character            */
  bCell[1] = 0x07;                     /* Default Attrib             */
  VioScrollDn( 0, 0, (USHORT)0xFFFF, (USHORT)0XFFFF,
                     (USHORT)0xFFFF, bCell, (HVIO) 0);/* CLS         */
  VioSetCurPos(0, 0, (HVIO) 0);        /*                Pos cursor  */

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


  return 0;
}

int main(){
  char Input[200];
  LONG      return_code;  /* interpreter return code    */
  RXSTRING  argv[1];      /* program argument string    */
  RXSTRING  retstr;       /* program return value       */
  SHORT     rc;           /* converted return code      */
  RXSTRING  INSTORE[2];
  char      Prog[]=
       "/* RexxSamp.cmd   */\r\n"
       "/* Sample EDM/2 REXX program by Gordon Zeglinski */\r\n"
       "\r\n"
       "Call RxFuncAdd \'SysSleep\', \'REXXUTIL\', \'SysSleep\'\r\n"
       "\r\n"
       "say \"Sample EDM/2 REXX program started\"\r\n"
       "\r\n"
       "/*execute a cmd based command */\r\n"
       "\'dir /w\'\r\n"
       "\r\n"
       "/* wait 5 seconds */\r\n"
       "Call SysSleep 5\r\n"
       "\r\n"
       "/*clear screen using sample function */\r\n"
       "call EDM_SysCls\r\n"
       "\r\n"
       "say\r\n"
       "say\r\n"
       "say \"Screen cleared by sample external REXX function\"\r\n\x1A";

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

  INSTORE[0].strptr=Prog;
  INSTORE[0].strlength=strlen(Prog);
  INSTORE[1].strptr=NULL;
  INSTORE[1].strlength=0;

  cout<<"Sample EDM/2 Rexx Demonstration Program Part 1"<<
      endl<<"by Gordon Zeglinski"<<endl;

  cout<<"Executing REXX programs via INSTORE parameter"<<endl;
  cout<<endl<<"INSTORE[1].strptr\t"<<(void*)INSTORE[1].strptr;
  cout<<endl<<"INSTORE[1].strlength\t"<<
      INSTORE[1].strlength<<endl;

  if(!strlen(Input))
     strcpy(Input,"RexxSamp.cmd");

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

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

  return_code = RexxStart(0,             // No arguments
                        argv,            // dummy entry
                        Input,           // File name
                        INSTORE,         // InStore
                        "CMD",           // use the "CMD" command processor
                        RXCOMMAND,       // execute as a command
                        NULL,            // No exit handlers

                        & amp.rc,      // return code from REXX routine

                        & amp.retstr); // return string from REXX routine

  delete [] retstr.strptr;

  cout<<"After Execution";
  cout<<endl<<"INSTORE[1].strptr\t"<<
      (void*)INSTORE[1].strptr;
  cout<<endl<<"INSTORE[1].strlength\t"<<
      INSTORE[1].strlength<<endl;

return 0;
}

Figure 1. Using INSTORE to execute REXX procedures in memory

The above program should have the same output as REXXSAMP.CMD. We haven't done anything too revolutionary yet, but it's a good start. Next we'll see how to set up INSTORE to execute macros.

#define INCL_RXFUNC       /* external function values */
#define INCL_RXMACRO
#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);

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;                     /* Space Character            */
  bCell[1] = 0x07;                     /* Default Attrib             */
  VioScrollDn( 0, 0, (USHORT)0xFFFF, (USHORT)0XFFFF,
                     (USHORT)0xFFFF, bCell, (HVIO) 0);/* CLS         */
  VioSetCurPos(0, 0, (HVIO) 0);        /*                Pos cursor  */

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

  return 0;
}

int main(){
  char Input[200];
  LONG      return_code;  /* interpreter return code    */
  RXSTRING  argv[2];      /* program argument string    */
  RXSTRING  retstr;       /* program return value       */
  SHORT      rc;           /* converted return code      */
  RXSTRING  INSTORE[2];
  char NumArg;

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

  INSTORE[0].strptr=NULL;
  INSTORE[0].strlength=0;
  INSTORE[1].strptr=NULL;
  INSTORE[1].strlength=0;

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

  cout<<"Type rexxsamp.cmd <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;

  argv[0].strptr="";
  argv[0].strlength=0;

  cout <<"Storing Macro"<<endl;

  return_code =RexxAddMacro("MACRO1", Input, RXMACRO_SEARCH_BEFORE);

  cout <<"Executing Macro"<<endl;

  return_code = RexxStart(0,             // No arguments
                        argv,            // dummy entry
                        "MACRO1",        // File name
                        INSTORE,         // 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;
}

Figure 2. Using INSTORE to Execute Macros

Now we have explore two new ways to use RexxStart(). In part 1 and part 2 of this article, we have now seen how to use RexxStart() to:

  • execute REXX programs from a file
  • execute REXX programs from memory
  • execute REXX programs from the macrospace

So what is this macrospace thing and what does RexxAddMacro() function do?

For the answer to these and other exciting questions, keep reading...

The REXX Macrospace

The macrospace is a storage area in memory where REXX can keep functions for rapid execution. The application can add, remove, query, and change the search order of macros using the functions:

  • (ULONG)RexxAddMacro(PSZ FunctionName,PSZ SourceFile,ULONG SearchOrder)
  • (ULONG)RexxDropMacro(PSZ FunctionName)
  • (ULONG)RexxQueryMacro(PSZ FunctionName,PUSHORT Position)
  • (ULONG)RexxReorderMacro(PSZ FunctionName,ULONG Position)

For RexxAddMacro(), the following parameters are specified:

FunctionName
This is the name of the function that is to be used in the RexxStart function call
SourceFile
This is the name of the file in which the macro's source code is stored.
SearchOrder
Either RXMACRO_SEARCH_BEFORE (search before external functions or source files) or RX_MACRO_SEARCH_AFTER (search after external functions or source files)

Return Value:

RXMACRO_OK                 no error
RXMACRO_NO_STORAGE         macrospace is full
RXMACRO_SOURCE_NOT_FOUND   SourceFile is invalid
RXMACRO_INVALID_POSITION   SearchOrder is invalid

For RexxDropMacro(), the following parameters are specified:

FunctionName
This is the name of the function that was used in the RexxAddMacro() function call

Return Value:

RXMACRO_OK          no error
RXMACRO_NOT_FOUND   macro name is not registered

For RexxQueryMacro(), the following parameters are specified:

FunctionName
This is the name of the function whose existence is being queried
Position
If the macro is found, its search order is returned here.

Return Value:

RXMACRO_OK          no error
RXMACRO_NOT_FOUND   macro name is not registered

For RexxReorderMacro(), the following parameters are specified:

FunctionName
This is the name of the function that is to be used in the RexxStart() function call
SearchOrder
either RXMACRO_SEARCH_BEFORE or RX_MACRO_SEARCH_AFTER

Return Value:

RXMACRO_OK                 no error
RXMACRO_NOT_FOUND          macro name is not registered
RXMACRO_INVALID_POSITION   SearchOrder is invalid

Manipulating the MACRO Storage Area

In addition to manipulating the functions within the storage area, REXX also allows us to manipulate the storage area itself. The storage area can be saved to disk, loaded from disk and erased using the functions:

  • (ULONG)RexxSaveMacroSpace(ULONG Num,PSZ* List,PSZ FileName)
  • (ULONG)RexxLoadMacroSpace(ULONG Num,PSZ* List,PSZ FileName)
  • (ULONG)RexxClearMacroSpace(VOID)

For RexxSaveMacroSpace(), the following parameters are specified:

Num
The number of function names in List (if Num is 0, then save the whole macrospace)
List
List of function names to be saved (if Num is 0, then this can be NULL)
FileName
The name of the file in which the macrospace will be saved.

Return Value:

RXMACRO_OK                   no error
RXMACRO_NOT_FOUND            a name in List is not registered
RXMACRO_EXTENSION_REQUIRED   FileName must have an extension
RXMACRO_FILE_ERROR           An error occurred while accessing
                             the file given by FileName

The parameters for RexxLoadMacroSpace() are the same as those for RexxSaveMacroSpace()

Return Value:

RXMACRO_OK                no error
RXMACRO_NO_STORAGE        macrospace ran out while loading in
                          the saved file
RXMACRO_NOT_FOUND         a name in List is not in the saved file
RXMACRO_ALREADY_EXISTS    a name in List is already in the macrospace
RXMACRO_FILE_ERROR        an error occurred while accessing the
                          file given by FileName
RXMACRO_SIGNATURE_ERROR   the file is not a valid saved macrospace
                          file or it is corrupted.

The use of these three functions are illustrated in the file rexx23.cpp. The macrospace saved files are not portable across different versions of the REXX interpreter. In this case, the original source files would have to be read in using RexxAddMacro().

External Subcommand Handlers

Unlike the macrospace functions, which is more for the user than the application developer, the external subcommand handler functions are used by the developer to extend the REXX language. The following functions are provided to the developer:

  • (ULONG)RexxRegisterSubcomDll(PSZ Name,PSZ ModuleName,PSZ FunctionName,PUCHAR Data,ULONG Drop)
  • (ULONG)RexxRegisterSubcomExe(PSZ Name,PFN FunctionAdr,PUCHAR Data)
  • (ULONG)RexxDeregisterSubcom(PSZ Name,PSZ ModuleName)
  • (ULONG)RexxQuerySubcom(PSZ Name,PSZ ModuleName,PUSHORT Flag,PUCHAR Data)

For RexxRegisterSubcomDll(), the following parameters are specified:

Name
environment name
ModuleName
name of the DLL
FunctionName
name of the function in the DLL
Data
8 bytes of user data. (Can be NULL)
Drop
either RXSUBCOM_DROPPABLE (any process can deregister the handler) or RXSUBCOM_NONDROP (only the current process can deregister the handler)

Return Value:

RXSUBCOM_OK        no error
RXSUBCOM_DUP       the environment name has already been defined
                   (to address this handler, its library name
                   must be specified)
RXSUBCOM_NOEMEM    Not enough memory
RXSUBCOM_BADTYPE   Drop is invalid

For RexxRegisterSubcomExe(), the following parameters are specified:

Name
environment name
FunctionAdr
the address of the function
Data
8 bytes of user data. (Can be NULL)

Return Value:

RXSUBCOM_OK       no error
RXSUBCOM_DUP      the environment name has already been defined
RXSUBCOM_NOTREG   not registered because of duplicate name
RXSUBCOM_NOEMEM   not enough memory

For RexxDeregisterSubcom(), the following parameters are specified:

Name
environment name
ModuleName
name of the DLL in which the handler function resides

Return Value:

RXSUBCOM_OK          no error
RXSUBCOM_NOTREG      the environment name has not been registered
RXSUBCOM_NOCANDROP   drop permission is not granted
RXSUBCOM_BADTYPE     Subcom is invalid

For RexxQuerySubcom(), the following parameters are specified:

Name
environment name
ModuleName
name of the DLL in which the handler function resides
Flag
either RXSUBCOM_OK or RXSUBCOM_NOTREG depending upon whether Name is registered or not.
Data
The address to an 8 byte location in memory to receive the user data

Return Value:

RXSUBCOM_OK       no error
RXSUBCOM_NOTREG   the environment name has not been registered
RXSUBCOM_BADTYPE

Creating External Subcommand Handler Functions

External command handlers must use the following format:

ULONG _System command_handler(PRXSTRING Command,PUSHORT Flag, PRXSTRING Retstr)

The arguments have the following meaning:

Command
a null-terminated RXSTRING containing the issued command
Flag
the subroutine uses this flag to return the completion status of the command. It must be either RXSUBCOM_OK, RXSUBCOM_ERROR, or RXSUBCOM_FAILURE
Retstr
Address of an RXSTRING for the return code. Retstr is a character string return code that will be assigned to the REXX special variable RC when the subcommand handler returns. By default, Retstr points to a 256-byte RXSTRING. If necessary, a longer RXSTRING can allocated with DosAllocMem(). If the subcommand handler sets Retval to an empty RXSTRING, REXX will assign the string "0" to RC.

Flag Interpretation

RXSUBCOM_OK
Indicates that the command was successfully executed.
RXSUBCOM_ERROR
Indicates that the command is recognized by the handler but a syntax error occurred (incorrect parameters for instance). The REXX interpreter will raise this condition if SIGNAL ON ERROR or CALL ON ERROR traps have been created. If TRACE ERRORS has been issued, REXX will trace the command when the subcommand handler returns.
RXSUBCOM_FAILURE
Incidactes that a subcommand failure has occurred. Usually this is used if Command is unrecognized. The REXX interpreter will raise this condition if SIGNAL ON FAILURE or CALL ON FAILURE traps have been created. If TRACE FAILURES has been issued, REXX will trace the command when the subcommand handler returns.

If the command has parameters, it is up to the subcommand handler to parse the command for the arguments. The following simple subcommand handler processes the command "CLS".

ULONG _System EDM_Handler(PRXSTRING Command,PUSHORT Flags,PRXSTRING
retstr){

   BYTE bCell[2];

   if(!strcmp("CLS",Command->strptr ) ){
      bCell[0] = 0x20;
      bCell[1] = 0x07;
      VioScrollDn( 0, 0, (USHORT)0xFFFF, (USHORT)0XFFFF,
                     (USHORT)0xFFFF, bCell, (HVIO) 0);
      VioSetCurPos(0, 0, (HVIO) 0);

      *Flags=RXSUBCOM_OK;
      retstr->strlength=1;
      strcpy(retstr->strptr,"0");
      return 0;
   }
   *Flags=RXSUBCOM_FAILURE;
    retstr->strlength=1;
    strcpy(retstr->strptr,"1");
    return 1;
}

Putting It All Together

The file rexx24.cpp contains a sample program demonstrating the use of external subcommand handlers, external functions, and executing in memory REXX programs. It is rather long, and very similar to the other sample REXX programs that are included in this article, so it won't be included here. However, let's look at two code snippets that illustrate new points.

The external subcommand handler is registered by the function call:

RexxRegisterSubcomExe((PSZ)"EDM",(PFN)& EDM_Handler,NULL);

The external subcommand handler we have seen in the previous section is registered under the name "EDM". To use this handler, our RexxStart() must be modified to specify the "EDM" environment. The following section of code illustrates this:

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

Functions, Macros, and Commands

We have now examined a good portion of the features REXX provides developers. A few questions now arise like: what's the difference between commands and functions, and where do macros fit into this?

Let's start by looking at the difference between functions and commands. Functions require that the user (the person using your application/library) to use a bulky syntax. For example, to call a function that clears the screen, let's call it "CLS", the user would have to use the either of the following syntaxes.

call CLS

or

dummy=CLS()

In the first case, CLS() returns a result in the special REXX variable RESULT. In the second case, the variable dummy is assigned the return value from CLS(). Both expressions are rather bulky considering that CLS() takes no arguments, and really doesn't have any return values other than TRUE or FALSE If developing a REXX programming environment, CLS would be a prime candidate for command status, requiring only the following statement to execute:

CLS

The return value for the command would be placed in the special REXX variable RC. As previously mentioned, subcommand handler must parse their own commands. So if arguments are needed, it is less work for the programmer to use functions.

As mentioned before, macros really aren't there for the programmer, they are for the user. REXX provides several functions to allow the programmer to manipulate the macrospace. Macros are written in REXX, while external functions and subcommand handlers are written in C/C++, or some other compiled language.

Summary

This concludes our look at Rexx/2 for this issue. Building upon the REXX information in part 1, we have covered new uses for RexxStart(), external subcommand handlers, and the REXX macrospace interface. We have not looked at exit handlers or the various REXX interfaces for interactive debugging.

As usual, question and comments are welcome.