Using REXX for scripting

REXX is an interpreted computer language, like Java or BASIC. It can be easily used as a scripting/macro language for any program written in most any other language. REXX is a good alternative to Windows Active Scripting without all of the hassles of COM, nor royalty payments.

By utilizing REXX, you offer the user of your program a way to automate tasks, extend the feature set of your program, build self-running demos, seamlessly integrate with other REXX-aware programs, etc. And he has a fairly flexible, relatively easy-to-learn script language for these purposes.

You don't have to reinvent the wheel. REXX has all the basics that you'd want from a script language -- variables, looping, decision making (ie, conditionals), math (including floating point), file reading/writing/creating, a complete set of built-in functions, the ability to launch other programs, etc. The REXX interpreter does the vast amount of work of executing the script, with a minimal amount of work from you. (As a side benefit, the user can also write standalone REXX scripts. So, he gets to use REXX in other useful ways besides with your program). The documentation and examples for this script language are also already written and freely distributable. There are a variety of free, add-on packages to extend the power of the language, and they all can transparently work with your program. The user can create his REXX script with any text editor (such as Notepad) so there's no involved support needed on your part. It's cheap and easy for you to use REXX, and yet powerful and flexible for the user.

But best of all, you have the added benefit of being able to easily extend the REXX language yourself to add your own "REXX commands". For example, you can give your user a set of new REXX commands that are particular to your program's purpose. He can use these new commands in his script, and you implement them within your own program -- in the language of your choice. So, you can extend the REXX language itself in the language of your choice. This easy extensibility of REXX makes it very good as a scripting choice.

All you need is a REXX interpreter in Dynamic Link Library (DLL) form (such as the freeware REXX interpreter, Reginald), Furthermore, your program must be able to access a DLL. For example, a program written in C or C++ for most operating systems can access a DLL, so it can use REXX as its scripting language. So too can a program written in assembly language, Pascal, Fortran, or many other compiled languages.


Table of Contents

Setting up for development
Simple example of running a REXX script
Passing arguments to the script
Receiving data from the script
Receiving numeric return from the script
Forcing a REXX script to return a value
Running a script in memory
Adding your own functions to REXX
Setting/Querying REXX variables in the script
Reginald's options
Modifying the REXX interpreter's behavior
Support for non-console (graphical) apps
Another way of adding your own commands to REXX
REXXSAA API errors and error messages
Reginald's internal macro table
Halting execution of a script
The REXX queue
Dynamic Linking to support several interpreters
Setting Reginald's environment (ie, scripts path, settings, etc)

Setting up for development

Before you start using REXX with your program, you need a couple things for development:

  1. You need a REXX interpreter installed upon your system. This runs the REXX script (with optional assistance from your program). A good choice is the Reginald REXX interpreter available from the REXX Users Page. It's absolutely free, and comes in a package that is easily installed/uninstalled. A user of your program will also have to install such a REXX interpreter upon his system, and Reginald is an easy one to use. (Plus, you can freely distribute Reginald with your program, so that he doesn't have to hunt around for one himself, nor perhaps even explicitly install it himself). Most of the examples in this tutorial will use Reginald for reference.

  2. You need a "header file" that declares the functions in the REXX interpreter that your program may call. Your source code references this file so that your compiler knows the names of those functions, and how to call them. Also, any particular data structures you use with the interpreter are defined in this file. So far, such a header file exists only for C or C++ and it is named REXXSAA.H. You should find it in the developer materials for your REXX interpreter. You copy this file to where your C compiler keeps its "include" files. (If you can translate this file for the compiler of another language, then you're all set to go with that other language. I'd welcome hearing from developers who do create such a file for other languages). Then in your C source, put the following line toward the top of your source:
    #include <rexxsaa.h>
    
  3. If you're statically linking to the interpreter (discussed later), you may need the interpreter's LIB file (depending upon which operating system you're using). This is fed to your linker (just like any object file) so that the linker also knows the names of those functions, and how to call them. Windows C development systems need this. Other languages/operating systems may or may not need such a file. You should find it in the developer materials for your REXX interpreter.

Reginald has a "Developer's Kit" that includes the header and LIB files for C programmers. It also contains the example C source code in this tutorial. The Windows version even includes Visual C++ 4.0 project files, so that you can quickly get up and running with the examples. You can download it from the Reginald Developer's Page. The examples in this tutorial will be in C (but that doesn't mean that the REXX interpreter is restricted to use by C programs), and some will be for MS Windows specifically.

Remarks that are operating system specific will be prefaced with the operating system name in red. For example, a note about support under MS Windows operating system will begin with Windows.


Simple example of running a REXX script

All REXX interpreters have a standard set of functions that a host program can call. Collectively, these are referred to as the REXXSAA API.

For example, the REXX interpreter has one function that you call to run a REXX script. (That's right. This isn't Active Scripting. You don't need to use dozens of calls to get a COM interface up and running before you can run your script). This REXXSAA API is called RexxStart().

When you call RexxStart(), the REXX interpreter runs the REXX script. (This includes loading it from disk if necessary). RexxStart() doesn't return until the REXX script has finished running.

Our first example will be to run a REXX script that simply prints "Hello World" to a console window.

First, let's create the REXX script that we're going to run. Using any text editor (such as Notepad), enter the following 3 lines and save to a text file named text.rex. (If you installed the Reginald Developer's Kit, then this REXX script, as well as the other files for this example, are already in the Script1 directory).

/* */
SAY "Hello World"
EXIT

Now let's create the C source code that runs this REXX script.

RexxStart() does take a lot of arguments, and explaining them all will be one focus of this tutorial. But for our first example, we're going to supply default values of 0 for certain arguments that we don't care about right now. The most important argument is the third arg to RexxStart(). This is the nul-terminated name of the REXX script to run. It may be a fully qualified path if desired (for example, "C:\Program Files\Examples\test.rex"). We're also going to supply a value of RXSUBROUTINE as the sixth arg. For now, don't worry about what this means.

RexxStart() returns an error number. This will be RX_START_OK (ie, 0) if all went well and the script ran. Otherwise, it will be one of the numbers listed in REXXSAA.H for RexxStart(). Later on, we'll discuss these more.

Create the following C source file named main.c.

#ifdef WIN32
#include <windows.h>
#endif
#include <stdio.h>
#include <rexxsaa.h>

int main(int argc, char **argv)
{
   APIRET   err;
   char     chr;

   /* Run the REXX Script named "test.rex" in the current directory */
   err = RexxStart(0, 0,
                   "test.rex",    /* name of REXX script */
                   0, 0, RXSUBROUTINE, 0, 0, 0);

   /* Was there an error running the script? */
   if (err)
   {
      /* Yes. Here you would deal with that */
   }

   /* Wait for user to press ENTER before ending */
   printf("Press ENTER to end...\n");
   scanf("%c", &chr);

   return(0);
}

Windows: To make it easy, our C source is going to statically link with Reginald. This requires that you feed the REGINALD.LIB file to your linker. Otherwise, the linker will complain that it doesn't know where RexxStart() is. When you static link with a DLL under Windows, this simply means that you don't need to explicitly call LoadLibrary() on the DLL. Windows will do that for you, automatically, when it runs your program. Static linking limits your program to using only one interpreter -- Reginald (rather than any of the available Windows REXX interpreters). Later, we'll look at dynamic linking to support the other Windows interpreters.

Compile the above C source code into an executable.

Windows: Under the Windows operating system, you must create this C example as a console application. If you're using the supplied Microsoft Visual C++ project files in Reginald's Developer's Kit, all this is already set to go.

Now run the executable. If all goes well, you should see a console window open and display the message "Hello World". (It also displays a message telling you to press ENTER to end the program. When you press ENTER, the console window should disappear and your program should end).

Wasn't that easy? You can create a whole bunch of REXX scripts -- even complex ones, and run them as above. It may be advantageous to read up on The REXX Language and try your hand at creating some scripts.


Passing arguments to the script

You can pass arguments to a REXX script. You need to use a structure called an RXSTRING (defined in REXXSAA.H) for each argument that you wish to pass. These RXSTRING structures must be arranged into one array. So, if you wish to pass the script 3 arguments, you need an array of 3 RXSTRINGs.

An RXSTRING has two fields. The first field (strptr) contains a pointer to the buffer containing the data to be passed to the script. The second field (strlength) contains the length of that data in (8-bit) bytes.

You must initialize your array of RXSTRINGs before calling RexxStart(), and then you pass a pointer to the base of the array as the second arg to RexxStart(). As the first arg to RexxStart(), you also pass the count of how many RXSTRINGs there are.

As an example, let's pass 3 arguments to a REXX script. The REXX script will simply determine how many args it has been passed, and print those out. (ie, We won't be passing anything but ascii data in our args, so it makes sense when we print it out).

First, let's create the REXX script that we're going to run. Using any text editor, enter the following 6 lines and save to a text file named text.rex. (If you installed the Reginald Developer's Kit, then this REXX script, as well as the other files for this example, are already in the Script2 directory). This script uses the ARG() function to determine how many args were passed, and also to retrieve each arg.

/* */
count = ARG()
DO i = 1 TO count
   SAY ARG(i)
END
EXIT

Now let's create the C source code that runs this REXX script.

Create the following source file named main.c.

#ifdef WIN32
#include <windows.h>
#endif
#include <stdio.h>
#include <rexxsaa.h>

/* Here are our 3 data buffers for the 3 args */
char Data1[] = {'A','r','g',' ','1'};
char Data2[] = {'A','r','g',' ','2'};
char Data3[] = {'A','r','g',' ','3'};

int main(int argc, char **argv)
{
   APIRET   err;
   RXSTRING args[3];   /* Here's our array of 3 RXSTRINGs */
   char     chr;

   /* Initialize the first arg to Data1[] (ie, "Arg 1") */
   args[0].strptr = Data1;
   args[0].strlength = sizeof(Data1);

   /* Initialize the second arg to Data2[] (ie, "Arg 2") */
   args[1].strptr = Data2;
   args[1].strlength = sizeof(Data2);

   /* Initialize the third arg to Data3[] (ie, "Arg 3") */
   args[2].strptr = Data3;
   args[2].strlength = sizeof(Data3);

   /* Run the REXX Script named "test.rex" in the current directory */
   err = RexxStart(3,             /* How many args we're passing */
                   &args[0],      /* The base of our array of args */
                   "test.rex",    /* name of REXX script */
                   0, 0, RXSUBROUTINE, 0, 0, 0);

   /* Was there an error running the script? */
   if (err)
   {
      /* Yes. Here you would deal with that */
   }

   /* Wait for user to press ENTER before ending */
   printf("Press ENTER to end...\n");
   scanf("%c", &chr);

   return(0);
}

Windows: Once again, we're going to statically link with Reginald, so supply REGINALD.LIB to your linker. Also, you must create this C example as a console application.

Compile the above C source code into an executable.

Now run the executable. If all goes well, you should see a console window open and display "Arg 1", "Arg 2", and "Arg 3" when the REXX script determines that 3 args have been passed, and displays each on its own line. (It also displays a message telling you to press ENTER to end the program. When you press ENTER, the console window should disappear and your program should end).

One thing to note is that REXX does not deal with nul-terminated strings like C does. An arg passed to REXX does not need to be nul-terminated. REXX determines the length of the data solely via the RXSTRING's strlength field. Indeed, a data buffer could even contain embedded bytes whose value is 0.

NOTE: You are responsible for eventually freeing any buffers (and the RXSTRING array) you allocate to pass args to the REXX script. The interpreter does not do this for you. You'll free those buffers only after RexxStart() returns. (You can allocate them using any method you desire, for example, in the above code, we simply declared the RXSTRING array on the stack, and the data buffers were global static data).

NOTE: If you want to pass several args to a script, but you want one of them to be "unspecified" (ie, its value isn't set), then set that arg's RXSTRING strptr field to 0. This arg will still be counted by the REXX script, but it will have no value. (So, the REXX script can determine that it was "skipped" by using the 'E' option of ARG(), and take action accordingly, for example, using some default value instead).


Receiving data from the script

You can receive one buffer of data back from the script (which it explicitly returns to you when the script ends). You'll need another RXSTRING for that purpose. For this RXSTRING, you don't have to supply the data buffer. RexxStart() will supply one for you, and will return the script's data in that buffer. But before calling RexxStart(), you do have to clear the RXSTRING's strptr field. You pass a pointer to this RXSTRING as the last arg to RexxStart().

After RexxStart() returns, this RXSTRING's strptr field will point to a buffer containing the data that the script returned to you (if any). One thing to note is that REXX does not return nul-terminated strings like C does. A data buffer returned by REXX does not need to be nul-terminated. Indeed, the data buffer could contain embedded nul bytes. (For this reason, you shouldn't make assumptions about the contents of the buffer. But in our example below, we know that the script is returning all printable characters). REXX lets you know the length of the data solely via the RXSTRING's strlength field, This field will tell you how many bytes long the data is.

NOTE: You can force the returned data buffer to be nul-terminated using Reginald's proprietary NULTERM OPTION.

But, a REXX script is not required to return data to you (unless you run it as RXFUNCTION). If the script does not explicitly return data, then RexxStart() will leave your RXSTRING's strptr field set to 0.

When a REXX script returns data to your script, this is referred to as "returning a value" in most REXX documentation. So, the buffer that a script returns is the script's "return value". Think of a single REXX script as analagous to a C function. You can pass several args to it, and it can return one value (ie, a data buffer of any length).

One thing that you have to do is free any data buffer that RexxStart() returns to you (after you're done with it). You do this by calling an interpreter function called RexxFreeMemory(). (If dealing with an interpreter other than Reginald, see RexxFreeMemory() issues).

As an example, let's write a REXX script that returns the string "Hi, sailor". The REXX script returns this to us by specifying "Hi, sailor" after the EXIT (or RETURN) keyword. And then our C example will print out this returned data.

First, let's create the REXX script that we're going to run. Using any text editor, enter the following 2 lines and save to a text file named text.rex. (If you installed the Reginald Developer's Kit, then this REXX script, as well as the other files for this example, are already in the Script3 directory).

/* */
EXIT "Hi, sailor"

Now let's create the C source code that runs this REXX script.

Create the following source file named main.c.

#ifdef WIN32
#include <windows.h>
#endif
#include <stdio.h>
#include <rexxsaa.h>

int main(int argc, char **argv)
{
   RXSTRING rx;
   APIRET   err;
   char     chr;

   /* Let RexxStart() return the buffer */
   rx.strptr = 0;

   /* Run the REXX Script named "test.rex" in the current directory */
   err = RexxStart(0, 0,
                   "test.rex",    /* name of REXX script */
                   0, 0, RXSUBROUTINE, 0, 0, &rx);

   /* Was there an error running the script? */
   if (err)
   {
      /* Yes. Here you would deal with that */

      /* It's our responsibility to free this RXSTRING's
         data buffer. The interpreter shouldn't have returned anything,
         but let's play it safe and avoid a memory leak */
      if (rx.strptr) RexxFreeMemory(rx.strptr);
   }

   /* Display what the script returned */
   else
   {
      /* Did the script return something? It may not have,
         in which case rx's strptr field will still be 0 */
      if (rx.strptr)
      {
         /* It's not necessarily nul-terminated, so we can't treat
            it as a C string, and pass it directly to printf() */
         for (err = 0; err < rx.strlength; err++)
         {
            printf("%c", rx.strptr[err]);
         }
         printf("\n");

         /* It's our responsibility to free this RXSTRING's
            data buffer */
         RexxFreeMemory(rx.strptr);
      }

      else
         printf("Script didn't return anything.\n");
   }

   /* Wait for user to press ENTER before ending */
   printf("Press ENTER to end...\n");
   scanf("%c", &chr);

   return(0);
}

Compile the above C source code into a (console application) executable.

Now run the executable. If all goes well, you should see a console window open and display "Hi, sailor", which the REXX script returned to you.


If you prefer to allocate your own data buffer for the script to return its value, you can do so. Allocate the buffer using whatever function you like. (It can be on your stack, gotten via malloc(), gotten via some OS specific function, etc). Then just put a pointer to your buffer in the RXSTRING's strptr field before calling RexxStart(). Also, you must set the RXSTRING's strlength field to the size (in bytes) of this buffer. RexxStart() will try to return the script's data in that buffer. I say "try" because, if the script is returning too much data to fit into the buffer you supplied, then the interpreter will go ahead and supply a new buffer as above. So, you still have to check if you need to free the returned buffer. And of course, you always have to free your original buffer too when you're done with it.

When RexxStart() returns, it will have modified your RXSTRING's strlength field to tell exactly how many bytes of data were copied to your data buffer. But even if you supply your own data buffer, a REXX script is still not required to return data to you (unless you run it as RXFUNCTION). If the script does not explicitly return data, then RexxStart() will set your RXSTRING's strptr (and usually strlength) field to 0.

One useful trick is to supply a buffer that will be at least one char larger than the largest amount of data you expect returned from the script. You can use that extra space to add a nul byte to the end of the data, and thereby create a C string. (But if you're going to support only Reginald, an even easier alternative is Reginald's NULTERM OPTION).

Here's the above example, modified so that we pass our own buffer to RexxStart() for the script to return "Hi, sailor". (It's in the Script4 directory).

#ifdef WIN32
#include <windows.h>
#endif
#include <stdio.h>
#include <rexxsaa.h>

int main(int argc, char **argv)
{
   RXSTRING rx;
   APIRET   err;

   /* Create our buffer right on the stack */
   char     buffer[80];

   rx.strptr = buffer;
   rx.strlength = sizeof(buffer);

   /* Run the REXX Script named "test.rex" in the current directory */
   err = RexxStart(0, 0,
                   "test.rex",    /* name of REXX script */
                   0, 0, RXSUBROUTINE, 0, 0, &rx);

   /* Was there an error running the script? */
   if (err)
   {
      /* Yes. Here you would deal with that */

      /* It's our responsibility to free this RXSTRING's
         data buffer if it's not the same one we supplied */
      if (rx.strptr && rx.strptr != buffer) RexxFreeMemory(rx.strptr);
   }

   /* Display what the script returned */
   else
   {
      /* Did the script return something? It may not have,
         in which case rx's strptr field will be 0 */
      if (rx.strptr)
      {
         /* It's not necessarily nul-terminated, so we can't treat
            it as a C string. Do we have room to add a nul byte? */
         if (rx.strlength < sizeof(buffer))
         {
            /* Yeah! Turn it into a C string */
            buffer[rx.strlength] = 0;
            printf("%s\n", buffer);
         }
   
         else
         {
            for (err = 0; err < rx.strlength; err++)
            {
               printf("%c", rx.strptr[err]);
            }
            printf("\n");
         }

         /* It's our responsibility to free this RXSTRING's
             data buffer if it's not the same one we supplied */
         if (rx.strptr != buffer) RexxFreeMemory(rx.strptr);
      }

      else
         printf("Script didn't return anything.\n");
   }

   /* Wait for user to press ENTER before ending */
   printf("Press ENTER to end...\n");
   scanf("%c", &buffer[0]);

   return(0);
}

Receiving numeric return from the script

Let's consider the following script:

/* */
EXIT 0

What data would this return to you? The answer is that it would stuff the ascii character '0' into the returned buffer (ie, a byte with a value 0x30). It would not put the binary value 0 in the buffer. And that's another thing to note about REXX. Numeric values are expressed in string form in REXX. (Like Java, REXX has no particular data types. Everything is expressed in string form internally). For example, here's what the returned buffer would look like (in C) if the script did an 'EXIT -100.4':

char buffer[] = {'-', '1', '0', '0', '.', '4'};
You'd have to use the C library's strtod() function to convert the returned data to an actual binary (double) value of -100.4 (but only after you converted the returned data to a nul-terminated string for strtod() as well).

RexxStart() provides a (dubiously) helpful shortcut for you. If the REXX script is going to be returning a whole number (ie, no fractional component) in the range of -32,768 to 32,767, then you can supply RexxStart() with a pointer to some SHORT variable in your program (as the next to last arg to RexxStart()). RexxStart() will internally convert the script's return value to a binary SHORT and stuff that into your variable. (RexxStart() will also return the data buffer itself if you provided a return RXSTRING as well). So, you won't have to call atoi().

Of course, if the script returns something like "Hi, sailor", it doesn't make much sense to supply a variable to return its equivalent binary value. In that case, RexxStart() sets your variable to 0 if the script doesn't return a numeric string.

NOTE: Using Reginald's proprietary CHECKNUM OPTION, RexxStart() will verify whether the script is returning a whole number in the appropriate range, and if not, RexxStart() returns RX_NOT_NUMERIC.

As an example, let's write a REXX script that returns the value "-30". The REXX script returns this to us by specifying this after the EXIT keyword. And then our C example will print out this returned RXSTRING. Our C example will also supply a variable for RexxStart() to return the binary equivalent of the returned numeric string. And we'll print that out too.

First, let's create the REXX script that we're going to run. Enter the following 2 lines and save to a text file named text.rex. (If you installed the Reginald Developer's Kit, then this REXX script, as well as the other files for this example, are already in the Script5 directory).

/* */
EXIT -30

Now let's create the C source code that runs this REXX script.

Create the following source file named main.c.

#ifdef WIN32
#include <windows.h>
#endif
#include <stdio.h>
#include <rexxsaa.h>

int main(int argc, char **argv)
{
   RXSTRING rx;
   APIRET   err;
   char     chr;
   short   val;

   /* Let RexxStart() return the buffer */
   rx.strptr = 0;

   /* Run the REXX Script named "test.rex" in the current directory */
   err = RexxStart(0, 0,
                   "test.rex",    /* name of REXX script */
                   0, 0, RXSUBROUTINE, 0, &val, &rx);

   /* Was there an error running the script? */
   if (err)
   {
      /* Yes. Here you would deal with that */

      /* It's our responsibility to free this RXSTRING's
         data buffer. The interpreter shouldn't have returned anything,
         but let's play it safe and avoid a memory leak */
      if (rx.strptr) RexxFreeMemory(rx.strptr);
   }

   /* Display what the script returned */
   else
   {
      /* Did the script return something? It may not have,
         in which case rx's strptr field will still be 0 */
      if (rx.strptr)
      {
         /* It's not necessarily nul-terminated, so we can't treat
            it as a C string, and pass it directly to printf() */
         for (err = 0; err < rx.strlength; err++)
         {
            printf("%c", rx.strptr[err]);
         }
         printf("\n");

         /* It's our responsibility to free this RXSTRING's
            data buffer */
         RexxFreeMemory(rx.strptr);

         /* Now let's print out what RexxStart() converted this
            returned numeric string to */
         printf("Script returned a numeric value of %d\n", val);
      }

      else
         printf("Script didn't return anything.\n");
   }

   /* Wait for user to press ENTER before ending */
   printf("Press ENTER to end...\n");
   scanf("%c", &chr);

   return(0);
}

Compile the above C source code into an executable.

Now run the executable. If all goes well, you should see a console window open and display the value "-30" followed by "Script returned a numeric value of -30", which the REXX script returned to you.


Forcing a REXX script to return a value

In our previous examples, we passed an arg of RXSUBROUTINE to RexxStart(). This arg describes how we want the script to be run. Passing RXSUBROUTINE causes the script to run as a REXX subroutine. That means that the script may be passed any number of args, and it may or may not return a value to you.

There are two other choices for how to run a script. RXCOMMAND limits you to passing the script only one arg (ie, one RXSTRING in the array). The script may or may not return a value to you.

RXFUNCTION allows you to pass the script any number of args, and the script must return a value to you. (This may be useful if you expect the script to return some data to you, and you wish to make sure that you're notified if it doesn't).

Some interpreters relax the above rules. So, for example, a script called as RXFUNCTION may not necessarily return a value. Most programmers just use RXSUBROUTINE. (Reginald enforces the above rules. For example, a script that is invoked as RXFUNCTION, but doesn't return a value causes RexxStart() to return RX_NO_RETURN).


Running a script in memory

With the preceding examples, we ran a REXX script that was saved to disk. Alternately, you can pass RexxStart() a REXX script in some memory buffer within your program. In this case, RexxStart() will run that instead of looking for and loading some script from disk. In this case, the name of the script you supply to RexxStart() is the name that the interpreter gives to your RAM-based script (so that it can be added to the interpreter's internal macro table).

You need two more RXSTRINGs to pass RexxStart() your REXX script in memory. They must be in an array of two RXSTRINGs. (We'll refer to the first RXSTRING as Instore[0], and the second RXSTRING as Instore[1]). Before calling RexxStart(), set the first RXSTRING's strptr field to point to the buffer containing your REXX script. Set that RXSTRING's strlength field to the size of the REXX script (in bytes). Set the second RXSTRING's strptr to 0. You then pass the base of this RXSTRING array as the fourth arg to RexxStart().

The reason why you pass that second RXSTRING is because the interpreter adds the REXX script to its internal macro table, and then returns a tokenized version of it in the second RXSTRING. Therefore, if you make a second call to RexxStart() using this same RXSTRING array (without reinitializing it), then the interpreter will use the already-tokenized version for even faster script execution (well, the startup of the script anyway -- perhaps not the execution time taken by the script itself). Also, in some interpreters, other scripts you subsequently run may then call these macros you added to the interpreter's internal macro table, so the interpreter won't need to locate and load some script off of disk, and also execution can be faster.

But there are several caveats with using REXX scripts in RAM, unfortunately. This is because running a RAM script wasn't so standardized between interpreters, so some of them do things differently. You can read about these issues in Caveats with RAM-based REXX Scripts.

The most important caveat to note here concerns the buffer containing that tokenized version of the script. Who allocated it? The REXX interpreter. Who is responsible for freeing it? Well, that was never standardized. Theoretically, you are supposed to do that, but just like with freeing any return value from the script, there was never a standard for how you should free it. You really should use RexxFreeMemory().

For this next C example, we won't create a REXX script on disk. We'll code a REXX script directly in our C source, and pass that to RexxStart(). This REXX script will simply print "Hello World". (It's in the Script6 directory).

Create the following source file named main.c.

#ifdef WIN32
#include <windows.h>
#endif
#include <stdio.h>
#include <rexxsaa.h>

/* Here's our REXX script. It doesn't have
   to be nul-terminated, but we'll make it so
   just because it's easier to declare here in C */
char myScript[] = {"SAY 'Hello World';EXIT"};

int main(int argc, char **argv)
{
   RXSTRING instore;
   APIRET   err;
   char     chr;

   /* Initialize the RXSTRING to pass RexxStart() a script in RAM */
   instore[0].strptr = myScript;
   instore[0].strlength = strlen(myScript);
   instore[1].strptr = 0;

   /* Run the REXX script in RAM. Name it "Test Script" */
   err = RexxStart(0, 0, "Test Script",
                   &instore[0],        /* Here we pass our script directly */
                   0, RXSUBROUTINE, 0, 0, 0);

   /* Was there an error running the script? */
   if (err)
   {
      /* Yes. Here you would deal with that */
   }

   /* We must free the tokenized version when we're done with it.
      We wouldn't do this yet if we intended to call RexxStart() again. */
   if (instore[1].strptr) RexxFreeMemory(instore[1].strptr);

   /* Wait for user to press ENTER before ending */
   printf("Press ENTER to end...\n");
   scanf("%c", &chr);

   return(0);
}

Compile the above C source code into an executable.

Now run the executable. If all goes well, you should see a console window open and display the message "Hello World".

Of course, you could pass this script args, and get a return value from it, just like with scripts on disk.

NOTE: You can allocate the buffer containing your REXX script using whatever function you like. (It can be on your stack, gotten via malloc(), gotten via some OS specific function, static global data as in our above example, gotten via RexxAllocateMemory(), etc). But you are responsible for eventually freeing this buffer. Some interpreters, such as Reginald, let you free your data buffer after you first use it with RexxStart(). ie, Upon subsequent calls to RexxStart() using the same Instore[] RXSTRING's, you don't need to resubmit the source. But if you decide to free your buffer for other purposes, you should zero the Instore[0] strptr field in that case).


Adding your own functions to REXX

In our previous examples, we simply ran a REXX script. Sure, a REXX script can do a lot on its own, and you can pass data to it and receive one data buffer back from it. So, there is already a basis for some uses as a macro language for your program.

But REXX also allows you to code functions inside of your program (written in C, for example) that a REXX script can directly call. In this way, you can implement a whole set of new "REXX commands" specific to your program which the REXX script can use. For example, maybe you want to give the script a MyMenu() function which the script can call and pass some args. The first arg could indicate which one of your program's operations the script wants to invoke. Any remaining args could be any additional, needed args for that operation. Plus, your function can return one buffer of data to the script. In this way, a single REXX script could make multiple calls to your function, and thereby perform an entire series of operations on your program, perhaps to add new functionality (by retrieving data from some of your functions, manipulating the data, and then passing it back to your program via other functions), or create a self-running demo, or to interface your program's operation/data with some other REXX-aware program, etc. There's a lot of potential here.

Your REXX-callable function can be written in any language of your choice. But, it must be written to accept 5 particular args, and it must be "registered" once with the interpreter so that the interpreter knows of its existence. We call this REXX-callable function an External Function.

Your function must be able to accept 5 particular args, passed to it (by the REXX script) on the stack per C language calling convention. The 5 arguments are as follows:

name

A C-style (nul-terminated) string which is the name of the function.

numargs

The number of args that the script is passing to your function.

args

An array of RXSTRING structs containing those args passed by the script.

queuename

A C-style (nul-terminated) string which is the name of the current queue. (We'll talk more about that later).

retstr

An RXSTRING struct which contains a buffer into which you may return data to the script.

REXXSAA.H contains the declaration for your function. It must be of type APIRET APIENTRY. It must return the number RXFUNC_OK (ie, 0) to the interpreter if everything goes well. (Otherwise, it may return some non-zero error number, usually RXERR_INCORRECT_CALL, ie, 40, which is a generic error. Other possible errors are listed in REXXSAA.H).

Here then is the skeleton of a C function in your program that a REXX script may call. We'll call it "TestFunction".

APIRET APIENTRY TestFunction(CONST CHAR *name, ULONG numargs, RXSTRING args[], CONST UCHAR *queuename, RXSTRING *retstr)
{
   /* Tell the interpreter that everything went OK */
   return(RXFUNC_OK);
}

How does the REXX script call your function? It looks remarkably like a C call. Here's an example of a REXX script calling TestFunction() with 4 args, and assigning the contents of TestFunction()'s returned data buffer to a variable called 'MyReturn'.

MyReturn = TestFunction('Arg One', 12, MyVariable, "Arg 4");

Remember how the data buffer that the script returns to RexxStart() isn't nul-terminated? Well, one nice thing that the interpreter does is make sure that each arg passed to your function is nul-terminated. (That doesn't mean the data may not have some embedded nul bytes in it, so you have to be careful in your assumptions about data passed to your function). So you can treat it like a C-style nul-terminated string (assuming that it doesn't contain embedded nul bytes). But note that the RXSTRING's strlength field does not reflect this extra, terminating nul byte in its count.

Another thing to remember is that REXX treats numbers as numeric strings. So, you see that second argument of 12 passed to TestFunction()? It is actually passed in a nul-terminated data buffer that looks like this in C:

char buffer[] = {'1', '2', 0};

Here then is a version of TestFunction() that prints out the args passed to it:

APIRET APIENTRY TestFunction(CONST CHAR *name, ULONG numargs, RXSTRING args[], CONST UCHAR *queuename, RXSTRING *retstr)
{
   ULONG i;

   /* For each of the args (ie, RXSTRINGs) passed to us by the script... */
   for (i = 0; i < numargs; i++)
   {
      /* Print out the data buffer. We assume that its ASCII
       * data for this example. But it may not be.
       */
      printf("Arg #%ld = %s\n", i + 1, args[i].strptr);
   }

   /* Tell the interpreter that everything went OK */
   return(RXFUNC_OK);
}

But there is one more thing to consider. A REXX script could "omit" or "skip" an arg passed to your function. What this means is that, where the arg would appear, the REXX script simply leaves blank space. Below, the REXX script omits that second arg of 12. Notice that there is just blank space before the comma where the 12 used to be:

MyReturn = TestFunction('Arg One', , MyVariable, "Arg 4")

How is this reflected in the args passed to your function? Well, 'numargs' will still be set to 4. But the RXSTRING for that second arg will have its strptr field set to 0. This alerts you to the fact that the REXX script omitted this arg.

Here then is a version of TestFunction() that properly checks if an arg has been omitted before printing out each arg:

APIRET APIENTRY TestFunction(CONST CHAR *name, ULONG numargs, RXSTRING args[], CONST UCHAR *queuename, RXSTRING *retstr)
{
   ULONG i;

   for (i = 0; i < numargs; i++)
   {
      if (args[i].strptr)
         printf("Arg #%ld = %s\n", i + 1, args[i].strptr);
      else
         printf("Arg #%ld is omitted\n", i + 1);
   }

   return(RXFUNC_OK);
}

If you're using an interpreter other than Reginald, you may wish to read about some limitations with your functions.


If you want to return data to the script, you must use the supplied 'retstr' RXSTRING. This points to a buffer into which you can place any data you desire (upto the size limit of the supplied buffer. It will be at least 256 bytes, but you can check the strlength field to query how big the supplied buffer actually is). You must then set the RXSTRING's strlength to reflect how many bytes you copied into the buffer. Here then is how TestFunction() would return a string "Hello World" to the script:

APIRET APIENTRY TestFunction(CONST CHAR *name, ULONG numargs, RXSTRING args[], CONST UCHAR *queuename, PRXSTRING retstr)
{
   /* Copy "Hello World" to the supplied buffer */
   strcpy(retstr->strptr, "Hello World");

   /* Set how many bytes we copied */
   retstr->strlength = strlen(retstr->strptr);

   return(RXFUNC_OK);
}

Note that the data you return to the script does not need to be nul-terminated (although we did so in the example above because it was easier that way). And the strlength should not reflect any extra nul byte if you do nul-terminate the data.

How does the script get this value from TestFunction()? It does so just like a C function would. Here's how the REXX script would assign this returned value from TestFunction() to a variable 'MyReturn' and then display it:

MyReturn = TestFunction();
SAY MyReturn;

If you wish more information about how a REXX script must deal with your return value, you may read about issues with a return value, although this is applicable only to the person writing a REXX script.

If you don't have any other particular value to return to the REXX script, then you should at least return an empty string. You do this simply by setting the retstr's strlength to 0. (But don't clear the strptr field, or else you'll be returning no value at all, and then the REXX script must call your function as a subroutine). Here is an example of a function that returns an empty string to the script:

APIRET APIENTRY TestFunction(CONST CHAR *name, ULONG numargs, RXSTRING args[], CONST UCHAR *queuename, RXSTRING *retstr)
{
   /* Return an empty string */
   retstr->strlength = 0;

   return(RXFUNC_OK);
}

You may be wondering "What if I need to return more than RXAUTOBUFLEN bytes of data?". Well, then you have to allocate a new buffer (using RexxAllocateMemory()), and stuff that pointer into retstr's strptr field. The interpreter will take care of freeing that buffer when it's done with it. Here's an example of TestFunction() returning a 1,000 byte buffer filled with zeroes.

APIRET APIENTRY TestFunction(CONST CHAR *name, ULONG numargs, RXSTRING args[], CONST UCHAR *queuename, RXSTRING *retstr)
{
   /* Allocate a new data buffer and stuff the
      pointer into the retstr RXSTRING */
   if (!(retstr->strptr = RexxAllocateMemory(1000)))
   {
      retstr->strlength = 0;

      /* An error. Let the interpreter know */
      return(RXERR_STORAGE_EXHAUSTED);
   }

   /* Clear the buffer */
   memset(retstr->strptr, 0, 1000);

   /* Set the strlength */
   retstr->strlength = 1000;

   /* Return successfully */
   return(RXFUNC_OK);
}

Ok, so how do you register your function with the interpreter so that the script will be able to call it? You do this once with a call to the REXXSAA API RexxRegisterFunctionExe(). This is passed a pointer to the function you wish to register, and also the name you wish the REXX script to use when calling this function. (ie, You can give the function any "REXX name" you choose -- it doesn't have to be the same name as the name you used in your C source code. The REXX script must use the name you supply). The name is expressed as a nul-terminated C string. RexxRegisterFunctionExe() returns RXFUNC_OK (ie, 0) if the function is registered successfully. (If not, RexxRegisterFunctionExe() will return a non-zero error number. These numbers and their meanings can be found in REXXSAA.H).

For example, here's how we would register our TestFunction() function above with a name of "TestFunction":

#define RX_STRONGTYPING
APIRET err;

err = RexxRegisterFunctionExe("TESTFUNCTION", TestFunction);
if (err)
{
   printf("Error #%ld registering TestFunction()\n", err);
}

You can call RexxRegisterFunctionExe() as many times as desired to register as many functions as you like (giving them each a different name). You should register all your functions once before making any calls to RexxStart(). (You don't have to register the functions each time before you call RexxStart(). You only have to register them once before any calls to RexxStart(). So, a good place to register your functions is when your program first starts).

Before your program ends, you also need to deregister each one of your functions. You can do this with calls to RexxDeregisterFunction(). It takes one arg -- the nul-terminated REXX name of the function you want to deregister. So, here's how we deregister TestFunction():

APIRET err;

err = RexxDeregisterFunction("TESTFUNCTION");
if (err)
{
   printf("Error #%ld deregistering TestFunction()\n", err);
}

A good place to deregister your functions is right before your program ends.

If using an interpreter other than Reginald, you may wish to read about RexxRegisterFunctionExe() issues.


Let's talk a little about the return value from your function. As mentioned, it should be 0 if everything went well. It should be non-zero if there is a error. The standard is to return the value 40 (which is defined as RXERR_INCORRECT_CALL in Reginald's REXXSAA.H). So what does this do? Well, if the script is trapping the SYNTAX condition, then the interpreter triggers that condition. The error message that the script retrieves via CONDITION('D') is a rather ambiguous "Incorrect call to function". If the script isn't trapping SYNTAX, then the interpreter aborts the script, displaying that same error message to the user.

Well, that isn't very informative, is it? Newer interpreters substitute the error message "External function <name> failed" where <name> is replaced with the name of your function. That's a little bit more informative, but it still falls far short of giving you the ability to report what specifically went wrong. This is an oversight of the design of REXX.

But Reginald comes to the rescue here. Reginald allows you to return a wide array of error values (listed under the heading "EXTERNAL FUNCTION HANDLERS" in REXXSAA.H). For example, by returning the value RXERR_INV_SUBKEYWORD, Reginald will report an error message of:

EXE function <name> reported "Invalid sub-keyword found"

This gives a bit more information about why your function has failed. But, there's more. There are also some "pre-fabricated" error messages that, combined with the MAKEFUNCARG() macro, allow you to construct a message with your own information inserted into the message. For example, returning a value of RXERR_ARGZEROONE allows you to invoke the prefabricated message:

EXE function <name> reported "argument <argnumber> is not 0 or 1; found "<value>"

<name> will be the name of your function. <argnumber> tells which argument was in error, where 1 is the first argument passed to you. <value> will be the value of the erroneous argument that the script passed to you. In this way, the user gets very specific information about why your function failed.

You need to tell Reginald which argument number was in error. How do you tell Reginald which arg number was the erroneous one? You OR the argument number with your return value of RXERR_ARGZEROONE, and use the MAKEFUNCARG() macro. For example, to specify that you want Reginald to report that the second arg passed to your function was in error because it was supposed to be 0 or 1, but was some other value, you simply return the following:

   return(RXERR_ARGZEROONE | MAKEFUNCARG(2));

It's that easy. And the truly nice thing about is, if the MSGBOX OPTION is enabled, the error message pops in a box with a "Help" button which the user can click upon, and Reginald will bring up a help page specifically about that error message with helpful hints as to what the error is about and how to fix it.

And if these pre-fabricated error messages are not enough, Reginald even has a specific error return that allows you to return your own error message. You simply copy your error message into the return RXSTRING -- just like you were returning some data to the script -- and then return the value RXERR_EXTFUNC_ERR. For example, here's how you return the error message "You're an idiot!":

APIRET APIENTRY TestFunction(CONST CHAR *name, ULONG numargs, RXSTRING args[], CONST UCHAR *queuename, RXSTRING *retstr)
{
   strcpy(retstr->strptr, "You're an idiot!");
   retstr->strlength = strlen(retstr->strptr);
   return(RXERR_EXTFUNC_ERR);
}

Reginald will then post an error message of:

EXE function "TestFunction" reported "You're an idiot!"

If the user clicks on the Help button, this will bring up a page that explains to him that this is a error message from an external function, but it can't give him helpful hints about what likely caused it and how to fix it. So, you should use the pre-fabricated messages wherever possible.

This is all proprietary to Reginald, but it should not bother any other properly written interpreter. If you return RXERR_ARGZEROONE | MAKEFUNCARG(2) to some other interpreter, for example, the other interpreter should simply post the error message "External function <name> failed". In other words, the worst that the interpreter will do is ignore your return and yield that generic, uninformative error message. So, it should be safe to utilize these Reginald features in your external functions so that, when run under Reginald, your external functions will sport robust, informative error reporting.


If you've installed the Reginald Developer's Kit, the Script7 directory contains an example of a C program that registers two functions. One function is called MyAdd(). The script passes two numeric args, and MyAdd() adds the args together and returns the sum to the script. The other function is called DisplayArgs() and it simply prints out the args passed to it just like TestFunction() above. And there is a REXX script called test.rex which makes calls to those two functions. The test script deliberately passes bad args for one of the calls, to show you how Reginald's error message box can be useful to your external function. Spend some time perusing this relatively small, simple example to see how you can add your own functions to REXX.

At this point, you now know the basics of how to use REXX as a macro language for your program. From here on in, the rest of the tutorial mostly concerns refining your REXX interface and adding more esoteric features. (But, for programmers writing Windowed apps under MS Windows, there other important considerations discussed in Reginald's support for non-console apps and Dynamic Linking to support several interpreters).


Setting/Querying REXX variables in the script

There is another way to exchange data with a running script. You can set the value of some REXX variable in the script using the REXXSAA API RexxVariablePool(). This API also allows you to query the value of some REXX variable in the script. But RexxVariablePool() on most interpreters is limited to being called while the script is running. (An exception may be with some aspects of the RXSHV_PRIV operation. Reginald allows querying the special "QUENAME" or "VERSION" variables at any time). So, typically, you can call it only from one of your registered External Functions (or in an Exit Handler or Subcom Handler, discussed later).

RexxVariablePool() is passed one arg -- a SHVBLOCK structure described in REXXSAA.H. You initialize this structure before you pass it to RexxVariablePool() in order to tell what you want done. (Actually, the SHVBLOCK has a shvnext field to which you can link a second, third, etc, SHVBLOCK if you wanted to do a series of operations with a single call to RexxVariablePool(). Sometimes that can be less overhead than calling RexxVariablePool() numerous times). If you're passing only one SHVBLOCK to RexxVariablePool(), you'll set its shvnext field to 0.

One field you'll initialize is the shvcode field. This tells what sort of operation you want RexxVariablePool() to perform. Depending upon what operation you choose, other SHVBLOCK fields may need to be initialized as well. shvcode can be one of the following values:

RXSHV_SYSET

Set a variable's value. The SHVBLOCK's shvname.strptr points to the name of the REXX variable. (It does not need to be nul-terminated). The SHVBLOCK's shvname.strlength field is the length (in bytes) of that name. The SHVBLOCK's shvvalue.strptr points to a data buffer containing the new value for the variable. The SHVBLOCK's shvvalue.strlength field is the length (in bytes) of the data in that buffer. Again, the data does not need to be nul-terminated.

RXSHV_SET

As above, but no "tail substitution" is performed on the variable name. This is discussed later.

RXSHV_SYFET

Fetch (query) a variable's value. The SHVBLOCK's shvname.strptr points to the name of the REXX variable. The SHVBLOCK's shvname.strlength field is the length (in bytes) of that name. When RexxVariablePool() returns, it will have set the SHVBLOCK's shvvalue.strptr to point to a data buffer containing the variable's current value. The SHVBLOCK's shvvalue.strlength field will be the length (in bytes) of the data in that buffer. You are responsible for freeing that data buffer with RexxFreeMemory(). (Alternately, you can provide a data buffer for the value to be returned).

RXSHV_FETCH

As above, but no "tail substitution" is performed on the variable name.

RXSHV_SYDRO

DROP's a variable (as if the script did a DROP instruction on it). The SHVBLOCK's shvname.strptr points to the name of the REXX variable. The SHVBLOCK's shvname.strlength field is the length (in bytes) of that name.

RXSHV_DROPV

As above, but no "tail substitution" is performed on the variable name.

RXSHV_PRIV

Fetch (query) the values of some special variables that the interpreter maintains. These include being able to query how many args were passed to the script, and what the values of those args are. Also, you can query the name of the current queue, the version of the interpreter, the operating system name, how the program was run (ie, RXSUBROUTINE, etc), and the name of the script. The SHVBLOCK's shvname.strptr points to the name of the special variable to query. The SHVBLOCK's shvname.strlength field is the length (in bytes) of that name. When RexxVariablePool() returns, it will have set the SHVBLOCK's shvvalue.strptr to point to a data buffer containing the variable's current value. The SHVBLOCK's shvvalue.strlength field will be the length (in bytes) of the data in that buffer. You are responsible for freeing that data buffer with RexxFreeMemory(). (Alternately, you can provide a data buffer for the value to be returned).

The names of variables that you can query are "PARM" (ie, how many args the script was passed), PARM.x (ie, the value of a particular arg -- where x is the arg number, for example, the first arg would have a variable name of "PARM.1"), "SOURCE" (ie, query the operating system name, how the script was run, and the name of the REXX script -- each piece of information separated by a space), "VERSION" (ie, query the version information of the interpreter), and "QUENAME" (ie, the current queue name).

RXSHV_NEXTV

Enumerate (ie, list) all of the current variables in the REXX program (ie, both their names and current values). To enumerate all variables, you'll have to make repeated calls to RexxVariablePool(). Each time that you call RexxVariablePool(), the next variable is enumerated. When RexxVariablePool() returns, the variable's name is returned in the SHVBLOCK's shvname.strptr and shvname.strlength fields. The variable's current value is returned in the SHVBLOCK's shvvalue.strptr and shvvalue.strlength fields. You are responsible for freeing both the data buffer and the variable name with RexxFreeMemory(). (Alternately, you can provide data buffers for the name and the value to be returned).

Like with most APIs, RexxVariablePool() returns 0 for success, and non-zero are errors (listed in REXXSAA.H). The error return is defined in terms of bits. Each bit of the return value signifies a different type of error. For example, bit #0 (RXSHV_NEWV) is set if the variable didn't actually exist before the operation was performed upon it. (ie, If you set the value of a variable that doesn't yet exist in the script, it is created anew and the RXSHV_NEWV bit is set of the error return. You may or may not consider RXSHV_NEWV an error for some operations). This implies that a single call to RexxVariablePool() could result in it reporting several different errors. (ie, Several of the bits could be set in the error return).

RexxVariablePool() may sound like a handful, but it isn't that difficult to use. Let's take an example. We'll write a program that registers an External Function called "SetMyVar". In SetMyVar(), we'll set the value of the REXX variable named "MyVar" to the string "Hi there". Finally, we'll write a REXX script that simply calls SetMyVar(), and then prints out MyVar. If you installed Reginald's Developer's Kit, the example files are in the Script11 directory. Let's just look at how our External Function sets the value of MyVar:

APIRET APIENTRY SetMyVar(CONST CHAR *name, ULONG numargs, RXSTRING args[], CONST UCHAR *queuename, RXSTRING *retstr)
{
   SHVBLOCK block;
   APIRET   err;

   /* Return an empty value */
   retstr->strlength = 0;

   /* Let's set the value of MyVar */

   /* We're using only 1 SHVBLOCK (ie, 1 operation) */
   block.shvnext = 0;

   /* The variable name is "MyVar". Doesn't have
      to be nul-terminated, but it's easier here */
   block.shvname.strptr = "MyVar";
   block.shvname.strlength = strlen(block.shvname.strptr);

   /* Set the value to "Hi there". Again, doesn't
      have to be nul-terminated */
   block.shvvalue.strptr = "Hi there";
   block.shvvalue.strlength = strlen(block.shvvalue.strptr);

   /* The operation is to set the variable */
   block.shvcode = RXSHV_SYSET;

   /* Do that operation */
   err = RexxVariablePool(&block);

   /* An error? We ignore the situation where the
     variable didn't yet exist (ie, we created it anew) */
   if (err > 1)

      /* Let the interpreter know by triggering SYNTAX
	     condition with an error message that there
		 was an interpreter error. */
      return(RXERR_INTERPRETER_FAILURE);

   /* Return successfully */
   return(RXFUNC_OK);
}

Run the example and it should print "Hi there" to the console window.


Now let's write an External Function that fetches the value of "MyVar". For this example, we'll write a program that registers an External Function called "GetMyVar". In GetMyVar(), we'll fetch the value of the REXX variable named "MyVar" and then printf() it. Finally, we'll write a REXX script that simply sets "MyVar" to the string "Hi there", and calls GetMyVar(). If you installed Reginald's Developer's Kit, the example files are in the Script12 directory. Let's just look at how our External Function queries the value of MyVar:

APIRET APIENTRY GetMyVar(CONST CHAR *name, ULONG numargs, RXSTRING args[], CONST UCHAR *queuename, PRXSTRING retstr)
{
   SHVBLOCK block;
   APIRET   err;

   /* Return an empty value */
   retstr->strlength = 0;

   /* Let's query the value of MyVar */

   /* We're using only 1 SHVBLOCK (ie, 1 operation) */
   block.shvnext = 0;

   /* The variable name is "MyVar". Doesn't have
      to be nul-terminated, but it's easier here */
   block.shvname.strptr = "MyVar";
   block.shvname.strlength = strlen(block.shvname.strptr);

   /* Let the interpreter provide the data buffer for the value */
   block.shvvalue.strptr = 0;
   block.shvvalue.strlength = 0;  /* Some interpreters want the strlength field 0 too */

   /* The operation is to query the variable */
   block.shvcode = RXSHV_SYFET;

   /* Do that operation */
   err = RexxVariablePool(&block);

   /* An error? In this case, we don't ignore
     the situation where the variable doesn't yet exist */
   if (err)
   {
      /* It's our responsibility to free this RXSTRING's data buffer,
         but first check if RexxVariablePool() allocated anything */
      if (block.shvvalue.strptr) RexxFreeMemory(block.shvvalue.strptr);

      /* If we expected the script to have explicitly set the
         variable's value before we fetched it, let's tell the interpreter
         to trigger SYNTAX condition with an error message that we have an
         invalid variable reference. But you may not want to consider
         RXSHV_NEW as an error. This is just an illustration. */
      if (err & RXSHV_NEW) return(RXERR_INVALID_VAR);

      /* For others, just let the interpreter trigger SYNTAX
         condition with an error message that there was
         an interpreter error. */
      return(RXERR_INTERPRETER_FAILURE);
   }

   /* Print the value */
   printf("GetMyVar() = ");

   /* It's not necessarily nul-terminated, so we can't treat
      it as a C string, and pass it directly to printf() */
   for (err = 0; err < block.shvvalue.strlength; err++)
   {
      printf("%c", block.shvvalue.strptr[err]);
   }
   printf("\n");

   /* It's our responsibility to free this RXSTRING's data buffer */
   RexxFreeMemory(block.shvvalue.strptr);

   /* Return successfully */
   return(RXFUNC_OK);
}

Run the example and it should print "Hi there" to the console window.

Alternately, we could provide a buffer for RexxVariablePool() to return the value. This should look familiar to you because it's the same sort of thing as providing a buffer to RexxStart() to return the script's value. But, there's an important difference. If the buffer you supply is not big enough to contain the data, the interpreter does not allocate a new data buffer. Rather, it returns only as much data as can fit into your buffer, and sets the RXSHV_TRUNC bit of the error return. One other thing it does is to set your SHVBLOCK's 'shvvaluelen' field to how big (in bytes) your buffer should be. So, you can actually query how big a buffer you need to supply for a given variable by setting the SHVBLOCK to point to some buffer, but also set strlength to 0. For example:

APIRET APIENTRY GetMyVar(CONST CHAR *name, ULONG numargs, RXSTRING args[], CONST UCHAR *queuename, RXSTRING *retstr)
{
   SHVBLOCK block;
   APIRET   err;

   /* Return an empty value */
   retstr->strlength = 0;

   /* Let's find out how big MyVar's value is */

   block.shvnext = 0;

   block.shvname.strptr = "MyVar";
   block.shvname.strlength = strlen(block.shvname.strptr);

   /* Point to anything and set length to 0 */
   block.shvvalue.strptr = "";
   block.shvvalue.strlength = 0;

   block.shvcode = RXSHV_SYFET;

   err = RexxVariablePool(&block);

   /* An error? In this case, we don't ignore
     the situation where the variable doesn't yet exist */
   if (err && err != RXSHV_TRUNC)
   {
      if (block.shvvalue.strptr) RexxFreeMemory(block.shvvalue.strptr);

      return(RXERR_INTERPRETER_FAILURE);
   }

   /* Print how big its value is */
   printf("MyVar's size = %ld\n", block.shvvaluelen);

   /* Return successfully */
   return(RXFUNC_OK);
}

So, here is how you would supply your own buffer to fetch the value of "MyVar":

APIRET APIENTRY GetMyVar(PUCHAR name, ULONG numargs, RXSTRING args[], PSZ queuename, PRXSTRING retstr)
{
   SHVBLOCK block;
   APIRET   err;
   char     buffer[256];  /* Here's my buffer */

   retstr->strlength = 0;

   /* Let's query the value of MyVar, supplying our own buffer */

   block.shvnext = 0;

   block.shvname.strptr = "MyVar";
   block.shvname.strlength = strlen(block.shvname.strptr);

   /* I provide the data buffer for the value */
   block.shvvalue.strptr = buffer;
   block.shvvalue.strlength = sizeof(buffer);

   block.shvcode = RXSHV_SYFET;

   err = RexxVariablePool(&block);

   /* An error? Could be RXSHV_TRUNC too */
   if (err)
   {
      return(RXERR_INTERPRETER_FAILURE);
   }

   /* Print the value */
   printf("GetMyVar() = ");

   for (err = 0; err < block.shvvalue.strlength; err++)
   {
      printf("%c", block.shvvalue.strptr[err]);
   }
   printf("\n");

   return(RXFUNC_OK);
}

When querying a variable's value, and RexxVariablePool() successfully completes the operation, it will always return some data to you. The data will be the variable's actual value if it exists. If the variable does not yet exist, then its value ends up being the same as its name. For example, if you query the value of "MyVar", and the script has not actually assigned MyVar a value, then you may indeed get a successful return value of "MYVAR". (REXX uppercases the name of a variable whose value is not yet set).

For RXSHV_SYFET operation with a compound variable, this returned name may not be the same name that you passed to RexxVariablePool(), because this returned name may have "tail substitution" (discussed below). So, if doing a RXSHV_SYFET operation upon a compound variable, and the error return has the RXSHV_NEWV bit set, you should use this returned "value" as the variable name in any further calls to RexxVariablePool() which operate on this variable. Of course, RXSHV_FETCH does not perform tail substitution as we'll see below, so this is a moot point with that operation.


One thing that you may have to deal with when you're fetching/setting the value of REXX compound variables (ie, variable names with tails) using RXSHV_SYFET/RXSHV_SYSET is what is called "symbolic tail substitution". In a nutshell, when a tail name is not a numeric string (such as "1"), then REXX regards it as the name of another (simple) REXX variable whose value is to be substituted in the compound name. For example, consider the following REXX line. Here, the script sets the value of the simple variable "MyTail" to the string "something".

MyTail = "something"

Now, let's assume that the REXX script subsequently calls your SetMyVar() function, which sets the value of "MyVar.MyTail" using RXSHV_SYSET as so:

APIRET APIENTRY SetMyVar(CONST CHAR *name, ULONG numargs, RXSTRING args[], CONST UCHAR *queuename, RXSTRING *retstr)
{
   SHVBLOCK block;
   APIRET   err;

   retstr->strlength = 0;

   block.shvnext = 0;

   /* The variable name is "MyVar.MyTail" */
   block.shvname.strptr = "MyVar.MyTail";
   block.shvname.strlength = strlen(block.shvname.strptr);

   /* Set the value to "Hi there" */
   block.shvvalue.strptr = "Hi there";
   block.shvvalue.strlength = strlen(block.shvvalue.strptr);

   block.shvcode = RXSHV_SYSET;
   err = RexxVariablePool(&block);
   if (err > 1)
      return(RXERR_INTERPRETER_FAILURE);
   return(RXFUNC_OK);
}
What has happened? Well, RexxVariablePool() noticed that you're using RXSHV_SYSET. RexxVariablePool() also notices that "MyTail" is not a numeric string. So, it is assumed to be a simple variable name. Therefore, RexxVariablePool() substitutes the value of "MyTail" in the name. You'll remember that the REXX script set MyTail's value to the string "something". The net result is that you have actually set the value of the variable named "MyVar.something". If you were to fetch the value of "MyVar.MyTail" using RXSHV_SYFET, you'd actually fetch the value of the variable named "MyVar.something", because again, tail substitution is performed by RexxVariablePool().

There are some implications here. One is that, if the script changes the value of the simple variable 'MyTail' inbetween calls to your function, you may end up setting/fetching the value of some entirely different compound variable name each time your function is called. Let's look at an example REXX script to illustrate that point:

MyTail = "something"
SetMyVar()  /* This sets "MyVar.something" */
MyTail = "another"
SetMyVar()  /* This sets "MyVar.another" */
So, you have to be careful that the script writer using your function understands tail substitution, and doesn't mistakenly use simple variable names that are the same as some tail name, when that is not what he wants.

One technique that one can use to avoid an "accident" is to have the script writer use tail names that he would not likely be also using for simple variable names. If he uses tail names that always start with a legal, but odd, character such as an exclamation mark, then it's unlikely that he would inadvertently have a simple variable with the same name elsewhere in his script. For example, he could use the compound name "MyVar.!MyTail". Yes, RexxVariablePool() still assumes that "!MyTail" is another simple variable, and attempts to substitute the value of it. But, if the script has not set "!MyTail" itself to some value, then its default value is simply "!MyTail". The net result is that your function does indeed fetch/set a compound variable named "MyVar.!MyTail".

Of course, if you set/fetch compound variables whose tail names are numeric, then no tail substitution is performed. For example, if you set the value of "MyVar.134", then no substitution is performed on "134" because 134 is not a legal, simple variable name. It's a numeric string.

Another alternative is to use RXSHV_SET/RXSHV_FETCH instead of RXSHV_SYSET/RXSHV_SYFET. The former two do not perform tail substitution. For example, in SetMyVar(), replace the line:

block.shvcode = RXSHV_SYSET;
...with...
block.shvcode = RXSHV_SET;

Now what happens? RexxVariablePool() does not substitute the value of "MyTail" in the name. The net result is that you have indeed set the value of the variable named "MyVar.MyTail".

But you have to be careful here too. If the script writer wants tail substitution, you've defeated that by using RXSHV_SET. Consider this script:

MyTail = "something"
SetMyVar()  /* This sets "MyVar.MyTail" */
SAY MyVar.MyTail /* This prints the value of MyVar.something,
             which is not the same variable as what SetMyVar() set */

There is one other caveat when setting/fetching a compound variable using RXSHV_SET/RXSHV_FETCH. Tail names are case-sensitive when setting/fetching variables via RXSHV_SET/RXSHV_FETCH. If the script doesn't quote the tail names, then REXX will upper-case them. For example, consider following line in a script:

MyVar.MyTail = 1 /* The actual variable name is MYVAR.MYTAIL */

Now consider if the script calls one of your functions where you attempt to fetch the value of this variable using RXSHV_FETCH, as so:

APIRET APIENTRY GetMyVar(CONST CHAR *name, ULONG numargs, RXSTRING args[], CONST UCHAR *queuename, PRXSTRING retstr)
{
   SHVBLOCK block;
   APIRET   err;

   retstr->strlength = 0;

   /* Let's query the value of MyVar.MyTail */

   block.shvnext = 0;

   /* We use a variable name of "MyVar.MyTail" */
   block.shvname.strptr = "MyVar.MyTail";
   block.shvname.strlength = strlen(block.shvname.strptr);

   block.shvvalue.strptr = 0;
   block.shvvalue.strlength = 0;

   block.shvcode = RXSHV_FETCH;

   err = RexxVariablePool(&block);

   if (err)
   {
      if (block.shvvalue.strptr) RexxFreeMemory(block.shvvalue.strptr);

      if (err & RXSHV_NEW) return(RXERR_INVALID_VAR);

      return(RXERR_INTERPRETER_FAILURE);
   }

   /* Print the value */
   printf("GetMyVar() = ");

   for (err = 0; err < block.shvvalue.strlength; err++)
   {
      printf("%c", block.shvvalue.strptr[err]);
   }
   printf("\n");

   RexxFreeMemory(block.shvvalue.strptr);

   return(RXFUNC_OK);
}

What does the above display? You may think that it would display "1", since that's what the script set the variable's value to, but not really. Because the script did not quote the tail name, the variable's name was really "MYVAR.MYTAIL", but the variable whose value you queried was actually "MYVAR.MyTail". (REXX automatically upper cases the stem part of the variable name, as it also does with simple variable names. But it does not upper case any tail names). So since the script did not actually set the value of "MYVAR.MyTail", what happens is that you've created a new variable whose default value is its entire name upper-cased (with any symbolic tail substitutions). So, the value you receive back is "MYVAR.MYTAIL". Had you passed that as the variable name originally, then you would have gotten back a value string of "1".

So, it's best to always use upper case variable names when utilizing RXSHV_SET/RXSHV_FETCH with compound variables. Then you don't really have to deal with case issues (provided that the REXX script writer doesn't quote variable names. If he does, tell him to use upper case as well).


Reginald's options

Reginald allows setting its OPTIONS via a proprietary API called RexxSetOptions(). The first argument to RexxSetOptions() is a string containing the options like what a REXX script would specify after the OPTIONS keyword. But, the options must be upper case (ie, "CONSOLE", not "console"). The string does not need to be nul-terminated. The second arg is the length of the string. All of these particular options are proprietary to Reginald (although some other interpreters may support some of them).

You can turn on/off any of the OPTIONS that a REXX script can. These options are:

BUFFERS

Adds the BUFTYPE()/DESBUF()/MAKEBUF()/DROPBUF() built-in functions to Reginald's list of built-in functions.

CACHEEXT

Allows Reginald to cache pointers to External Functions for faster execution. If you change some External Function function address (via RexxRegisterFunctionExe()) after it has already been registered and while the script is running, you may need to turn this off. Normally, you should not do this.

EXT_COMMANDS_AS_FUNCS

Allows a script to call some external program the same way that it would call an External Function. For example, a script could call the operating system's DIR program to get a listing of the some directory into one REXX variable by the line MyVar = dir('C\:MyDir').

FAST_LINES_BIF_DEFAULT

The LINES() function returns 1 if there are any more lines in a stream. This is faster than counting how many more lines are still in the stream.

FIND_BIF

Adds the FIND() function to Reginald's list of built-in functions.

FLUSHSTACK

When retrieving output lines of text from an externally-run program, those lines are pushed directly onto the REXX queue (instead of pushed onto a temporary queue).

INTERNAL_QUEUES

Disables support for external QUEUEs (on networked computers). Reginald does not yet support external queues.

LINEOUTTRUNC

LINEOUT() truncates a stream at the current position after writing out a line.

OS

Gives more specific information about the operating system name when the script does a "PARSE SOURCE", or you query the special VERSION variable via RexxVariablePool(). For example, when this flag is not set, all Windows versions of Reginald report the OS name as "WIN32". When this flag is set, the OS name may be reported as "WIN95", "WIN98", "WINME", "WINNT", "WIN2K", or "WIN32" for all others.

TRACE_FUNCS

Reginald's debugger traces any calls to RexxVariablePool() (ie, any variables you query or set via RexxVariablePool() are displayed in the debugger's RESULTS box).

STDOUT_FOR_STDERR

Error messages are written to stdout rather than stderr. Not applicable if MSGBOX is on, or you have an RXSIO Exit Handler, or are using RexxSetErrorHandler().

TRACE_OPTS

Allows multiple TRACE options to be set simultaneously (instead of only one option at a time).

BYPASS

Bypasses Reginald's memory manager when Reginald returns memory to you. You should not turn this on except for debugging purposes.

MEM

Reginald does not free any memory returned to it by your program. You should not turn this on except for debugging purposes.

MSGBOX

Reginald displays error messages (including those generated via RexxSetCondition()) in a pop-up message box with a Help button that opens up a help page for a specific error.

SOURCE

When this option is turned off, Reginald throws away the source of a script after parsing. This can reduce memory usage while the script is running, and improve speed. But a script may not use the SOURCELINE() function, nor the TRACE instruction, after the line where the SOURCE option is turned off.

To turn off an option, preface it with "NO", for example "NOMSGBOX".

For example, here's how to turn on the CONSOLE and NULTERM options, and turn off the BYPASS option: (All other options remain unchanged).

CONST CHAR MyOptions[] = "CONSOLE NULTERM NOBYPASS";

if ((err = RexxSetOptions(&MyOptions[0], sizeof(MyOptions) - 1)))
{
   printf("RexxSetOptions() returned an error: %u\n");
}

Note that a REXX script may subsequently change any of the above options while it is running. (These changes last only until the script finishes. Then the options revert back to how you last set them). Also, Reginald offers the user the ability to set these options to defaults for all hosts (via environment variables, or under Windows, with the Reginald Administration Tool). So, normally, you would set them only if you were running REXX scripts that you programmatically created yourself. (But, even those scripts can also do an OPTIONS instruction).

Note that if you specify several options, including NOSOURCE, then NOSOURCE must appear first, as so:

CONST CHAR MyOptions[] = "NOSOURCE LABELCHECK";
There are a few Reginald options that only you can turn on. They are as follows:

NULTERM

RexxStart() nul-terminates any return value from a script.

CONSOLE

Reginald will open a console only if the script needs it to do input/output. Reginald will also close that console when the script is done running. This is very useful if you're writing a non-console program -- even one in which you have an RXSIO Exit Handler.

HOLD

Used in conjunction with CONSOLE option. Allows Reginald to intelligently determine if it needs to keep the console window open after a script finishes, until the user presses any key.

Some REXX scripts are written under the assumption that, after the script ends, the console window will remain open. Therefore, the script may SAY something to the console window right before the script ends, expecting that the console window will remain open for the user to read this message. The script does not put a final PULL instruction before its EXIT, to keep the console open.

So what happens with Reginald's CONSOLE option? Well, as soon as the script finishes, Reginald closes the console. This means that the user won't have time to read that final message. This is where the HOLD option comes into play. If HOLD is enabled, Reginald will intelligently figure out whether it needs to keep the console window open for the user to read anything. If so, Reginald will hold the window open, display a message telling the user to press any key to close the window, and then wait for the user to do that before your call to RexxStart() returns. On the other hand, if Reginald intelligently determines that it doesn't need to do this, Reginald will close the window as soon as the script ends and immediately return from RexxStart().

By using the HOLD option, you eliminate the need for the script to use any final PULL instruction when it wants to display some "closing message" to the user. And yet, for scripts that have no such "closing message", Reginald will not keep the window open.

If you use Reginald's CONSOLE option, you should also allow the user of your software to toggle the HOLD option on or off at his will. That way, he need not be frustrated by not being able to see "closing messages" of a script. And yet, if he doesn't care about such messages, he can toggle off Reginald's HOLD option.

NOTE: Regardless of whether you set the HOLD option, when an untrapped error occurs in the script, and Reginald prints an error to the console window, it will keep the window open until the user presses any key. This is so that the user definitely does not miss any error message displayed to the console window. Of course, if you have your own error handler set with RexxSetErrorHandler(), then Reginald does not bother holding open the window.

HOLD option has no effect upon any console window that your program itself opens.

CHECKNUM

When you enable the CHECKNUM option and supply RexxStart() with a SHORT variable to return the script's value, RexxStart() will verify whether the script is returning a whole number in the range -32768 to 32767, and if not, RexxStart() returns RX_NOT_NUMERIC. This provides conclusive verification that a script is returning an appropriate numeric value.

NULL

When you enable the NULL option, Reginald prevents a script from using ADDRESS to send commands to any Subcom Handler except the one you supply as your 'EnvName' argument to RexxStart(). Any commands the script tries to send to other environments are silently discarded. This provides the same level of security as registering your own RXCMD Exit Handler, except that:

  1. It's more efficient than an RXCMD Exit Handler, and easier to enable.
  2. Since you don't need an RXCMD Exit Handler, you therefore don't need to waste time and code dealing with commands that aren't intended for you.

Furthermore, if you turn on the MSGBOX option, then the script is not allowed to turn that option off. (Also, if you have your own error handler set via RexxSetErrorHandler(), then a script can't turn on the MSGBOX option).

If you do not explicitly turn on one of these 5 above options, then they are turned off by default.

You need set the options only once. They will remain as you set them until you explicitly call RexxSetOptions() once more to change them. So, you only need call RexxSetOptions() once at the start of your program.


Modifying the REXX interpreter's behavior

You'll note that a REXX script defaults to outputting to a console window (via the SAY instruction), and inputting from the enduser through the same window (via the PULL instruction).

But you have the option of modifying this behavior. Indeed, you have the option of modifying several aspects of the interpreter's behavior. The REXX interpreter allows you to install "hooks" into its REXXSAA API so that you can replace some of the interpreter's own functionality. A hook is simply some function in your own program that the interpreter calls. Your hook can be written in any language of your choice. But like with External Functions, it must be written to accept certain args, and it must be "registered" with the interpreter so that the interpreter knows of its existence and purpose.

We call these hooks "Exit Handlers". I don't know why, but we do. There are several types of Exit Handlers. That is to say that there are various Exit Handlers for various purposes. For example, there is an Exit Handler that the interpreter calls right before it starts to run the script (but after the script has been loaded and setup to run). There is an Exit Handler that the interpreter calls right after it finishes running the script (but before unloading the script's variables and returning from RexxStart()). There is an Exit Handler that the interpreter calls whenever it needs to print a line to the console window (for example, with the SAY instruction, or trace output). There is an Exit Handler that the interpreter calls whenever it needs to fetch a line from the user (for example, with the PULL instruction, or interactive trace input). There are a couple of other types of Exit Handlers too. Each type of Exit Handler has specific args that are passed to it.

Let's examine each type of Exit Handler. The first type is the RXSIO Exit Handler. This is called whenever the interpreter needs to print one line as a result of a SAY instruction or one line of the trace output. (Tracing may be turned on in the script to allow the interpreter to print out detailed information about its operation while executing the script).

TO BE DONE: Discussion of Exit Handlers


Support for non-console (graphical) apps

REXX is inherently a language based around a console window. That is, REXX expects you to open a console window and set the handles to both stdout and stdin to point to this window. Typically, a REXX script outputs text by writing characters to the stdout handle (via the SAY instruction or CHAROUT()/LINEOUT() built-in functions). The REXX script inputs text from the user by reading characters from the stdin handle (via the PULL instruction or CHARIN()/LINEIN() built-in functions). That's fine if you're writing a console application.

But most modern operating systems, such as Windows, have a Windowing API that is not based around a text-based console window where output/input is done via writing/reading characters with a file handle. For example, if you create a windowed application for Windows (ie, in Visual Studio, instead of "console application", you specify an MFC or "Win32 application"), then no console window is created/opened. Rather, you create your own windows and dialogs via functions such as CreateWindowEx(), DialogBox(), etc. And you pass "messages" to graphical "controls" in order to display text or retrieve text from those windows.

The problem here is that REXX will not inherently output/input to your windows/dialogs created with those latter functions. There is at least one solution for all operating systems and interpreters. But, depending upon your operating system support, it can involve a bit of coding on your part. The good news is that it eliminates the need for a console window altogether. It involves installing an RXSIO Exit Handler. This solution allows you to modify the interpreter's behavior so that it can automatically output/input to windows/dialogs created with some Windowing API such as Windows' CreateWindow(), DialogBox(), etc. Of course, since you yourself (in your RXSIO Exit Handler) will be doing the actual display of text, and getting text input from the user, you'll have to write some operating specific code for that. (For example, maybe you'll use the MS Windows MessageBox() API to display a line of text that the script wants to print with a SAY instruction. On some other operating system, you may need to use a different method).

But, there are some caveats to an RXSIO Exit Handler. If the script uses the CHARIN()/LINEIN()/CHAROUT()/LINEOUT() built-in functions to specifically output to stdin/stdout, this bypasses the RXSIO Exit Handler. So, this output/input still needs some sort of console window if it is to work properly. Some REXX programmers even intermix SAY instructions with calls to CHAROUT() in order to try to "format" their text output. This will create nightmares for your RXSIO Exit Handler. Furthermore, upon some interpreters, if the REXX script happens to call some External Function, SubCom Handler, or Exit Handler that calls RexxStart() to launch another script, the invocation of this "child script" won't use your RXSIO Exit Handler (or any other Exit Handlers. Reginald does not suffer from this behavior. Reginald Exit Handlers are inherited, except for RXFNC). So again, the child script will need a console window open. Finally, there is an issue with some interpreters in regards to RexxStart() errors and Exit Handlers which I'll discuss later -- error messages may be "lost" or directed to a console window regardless of whether you have an RXSIO Exit Handler. In conclusion, an RXSIO Exit Handler is not sufficient for working around all situations of needing a console window open (nor a sufficiently reliable way to trap absolutely all input/output).

Windows: If you installed the Reginald Developer's Kit, the Script9 directory shows you how to create an MS Windowed app that doesn't open a console window at all when running a REXX script. It uses its own RXSIO Exit Handler to display messages output from the interpreter or script using MessageBox(). It inputs a line of text from the user using its own custom dialog containing an edit control.

If your operating system allows you to create your own console window, then you have another, easier solution. You can create that console window immediately before you call RexxStart(), set stdout/stdin to point to it, call RexxStart(), and then close the console window when RexxStart() returns. This will give the REXX script a window for its output/input. The good news is that it solves that issue with the CHARIN()/LINEIN()/CHAROUT()/LINEOUT() built-in functions bypassing an RXSIO Exit Handler, as well as child scripts launched via RexxStart(), and other issues with RexxStart().

Windows: MS Windows has a Console API which can be used from a Windowed app. So, you can call AllocConsole() to open a console window (which automatically sets up stdout and stdin for Reginald), call RexxStart(), and afterwards call FreeConsole() to close the console. (It is also recommended that you use SetConsoleMode() to set stdin's mode to ENABLE_ECHO_INPUT and ENABLE_PROCESSED_INPUT. Furthermore, you should use RexxSetHalt() to allow the user to abort the REXX script by pressing CTRL-C, or allow the script to trap this via the HALT condition). If you installed the Reginald Developer's Kit, such an example is in the Script8 directory.

But, this easy solution may not work on some interpreters. This is one good reason to use Reginald for your REXX support if you're writing windowed apps (and most programs for Windows today are).

Another problem with the above approach is that you always have a console window open while the script is running. If the script never needs to display any text, nor get input from the user, this window isn't needed (and may be confusing to the user since it will be just a blank window).

Reginald offers a third, proprietary solution for this (on supported platforms only. Currently, only Windows is supported). Instead of opening your own console, you can simply set Reginald's CONSOLE OPTION. With this option, Reginald will open a console only when and if the script needs that (and will also close the console when the script ends). You simply call RexxStart() and let Reginald take care of any console window. Reginald's CONSOLE option is especially useful for several scenarios:

  1. You're going to create a Windowed app that has its own special External Functions to let a script do output/input to/from your own windows (in lieu of doing things like SAY, PULL, etc), but you also want to be able to run other scripts that do use SAY, PULL, etc. Reginald's CONSOLE OPTION gives you the best of both worlds -- support for jettisoning the console window when you don't want/need it, and support for creating/managing it when you do need it.

  2. You want to launch a REXX script that runs "silently" in the background without any window being visible. As long as the script doesn't do a PULL or SAY, the script remains "out of view".

  3. You want to write a REXX script that uses some sort of "graphical" External function library (such Rexx/Tk or Rexx Dialog) instead of using SAY, PULL, etc, and so you don't need nor want some ugly, useless console window hanging around. (You can use Reginald's Script Launcher to run your program. Script Launcher is a good example of a program that utilizes these new Reginald features. If your script doesn't do any SAY or PULL, you won't see a console window using Script Launcher).

The example in the Script10 directory shows you how to set Reginald's CONSOLE option. Even if you do use your own RXSIO Exit Handler to handle output/input of text, you'll probably still want to enable Reginald's CONSOLE option so that it handles the issue with CHARIN()/LINEIN()/CHAROUT()/LINEOUT() being used with stdout/stdin. (But Reginald's proprietary RexxSetErrorHandler() is so much easier and more flexible than an RXSIO Exit Handler, so you may as well use that. We'll discuss that later). Compare the complexity of the examples Script10 and Script9 and you'll see how much easier Reginald's CONSOLE option is -- plus it circumvents inherent problems with RXSIO Exit Handlers.

In conclusion, Reginald's CONSOLE option, albeit proprietary to Reginald, is a foolproof way to eliminate any need for you to determine when a console window needs to be open, and manage one. It also offers the ability to jettison the console window when you wish to write a "graphical" REXX interface.


Another way of adding your own commands to REXX

TO BE DONE: Discussion of Subcom Handlers


REXXSAA API errors and error messages

Each REXXSAA API has its own defined return value(s). You can discover what type of return each REXXSAA API returns, and what values that return can encompass, by looking through the REXXSAA.H include file. Most of the API return an error number that tells you whether the API succeeded or failed. Usually, success is indicated by the value 0. Errors are non-zero numbers.

The error numbers that most API's return are fairly standardized across different interpreters (although there may be some deviations), with the exception of RexxStart().

RexxStart() returns RX_START_OK (ie, 0) if everything goes well. It returns non-zero values for errors. Although the RexxStart() for all interpreters seems to return 0 for success, unfortunately, it can differ somewhat when it comes to the other error numbers. So, you shouldn't assume what particular numbers mean unless you're using only one interpreter. The error numbers that Reginald may return for RexxStart() are listed in its REXXSAA.H file, along with comments as to what each number means. Reginald's RexxStart() error returns are a bit more explicit and numerous than other interpreters because Reginald was written with particular attention to such details. It gives you a lot of feedback so that you can give your user a lot of feedback when an error occurs. Other interpreters may have fewer error numbers defined, and they may mean different things.

There is another issue. When an error occurs during execution of a REXX script (ie, during RexxStart()), the interpreter usually writes an error message to the console window (or your RXSIO Exit Handler) and aborts the script (unless the script has trapped the error condition). This then causes RexxStart() to return with an error number. So, RexxStart() has already displayed an error message to the user, and then returned an error number.

So, you're thinking that it isn't necessary for you to display any error message if RexxStart() fails, because the interpreter has already displayed the error message? Not necessarily, and that's the problem. Some interpreters don't display error messages for all errors that RexxStart() returns. So, you're left with the dubious prospect of always displaying an error message, which may mean displaying two messages for essentially the same error. (And the error message you display may not be nearly as explicit as the interpreter's error message, so it can be confusing to the user).

This issue becomes even more frustrating if your program tries to install an RXSIO Exit Handler so that you can do your own display of output. Remember that the interpreter defaults to displaying output in a console window. So if RexxStart() needs to do some book-keeping before it gets around to processing the 'Exits' array passed to it, and encounters an error, most interpreters will try to display a message to the console window rather than calling your RXSIO Exit Handler. And if you don't even have a console window open (for example, with a Microsoft Windowed app), then the message won't ever be seen. That's really frustrating.

And some interpreters are equally inconsistent with displaying error messages for other APIs. For example, for certain errors in RexxVariablePool(), some interpreters will display messages, but not for other RexxVariablePool() errors. (The Regina interpreter is an example).

Reginald does a lot of work to solve the above problems (while also improving the consistency of error reporting with older apps). First of all, Reginald's behavior is very consistent. Reginald never displays error messages for any API except RexxStart() (and RexxRaiseCondition() when you direct it to do so). So, for all other APIs, you are expected to display any error message if desired.

For RexxStart(), Reginald will always display an error message for all errors. But there is one caveat. It concerns the case of where you're writing a windowed app that doesn't open a console window (nor use Reginald's proprietary CONSOLE option or RexxSetErrorHandler() API). Instead, you're planning on using a RXSIO Exit Handler to trap all output. For error numbers less than RX_START_SPECIAL, Reginald will not call your RXSIO Exit Handler. Instead, it will try to display the message to the console window. If you don't have a console window open (and aren't using Reginald's proprietary CONSOLE option or RexxSetErrorHandler()), this message will not be seen. So, you simply have to take charge of displaying a message whenever RexxStart() returns an error number less than RX_START_SPECIAL in this case. On the other hand, if you have a console window open, or are using Reginald's CONSOLE option, or are using RexxSetErrorHandler(), then you can safely assume that RexxStart() always displays an error message for all errors.

Windows: If you installed the Reginald Developer's Kit, the Script9 directory shows you how to create an MS Windowed app that doesn't open a console window at all when running a REXX script. It uses its own RXSIO Exit Handler to display messages output from the interpreter or script using MessageBox(). It inputs a line of text from the user using its own custom dialog containing an edit control. It also shows you how to deal with RexxStart() error returns less than RX_START_SPECIAL in the case of using an RXSIO Exit Handler in lieu of an open console window. But if you're writing a windowed app, and intend to use only Reginald for your REXX support, then do what the example in Script10 does. It's much easier and works around the inherent limitations in RXSIO Exit Handlers.


Reginald's internal macro table

As alluded to earlier, Reginald offers some handy extensions to RexxStart() that allow you to query if a macro with a given name is already in Reginald's internal macro table. Reginald also offers an extension to delete a macro from the table. In this way, you can control what is in Reginald's macro table, and circumvent certain problems. These are not standard REXXSAA API implementations, and can only be used with Reginald.

As far as Reginald is concerned, it allows you to use the same name for two or more scripts in RAM. Names are case-insensitive. A script named 'BLORT.REX' is the same as one named 'Blort.rex'.

If you tokenize a script, and then alter it and pass it back to RexxStart(), Reginald will re-tokenize it only if you also zeroed out the second RXSTRING's strptr. (Remember to RexxFreeMemory() the old version first). Even if you do re-tokenize a script, the old version still remains in Reginald's table (although it won't be used), so it's best to use Reginald's extensions to delete the old macro first before re-tokenizing it with the same script name.

To query if a macro with a given name is in Reginald's internal table, you pass the 2 "Instore" RXSTRINGs to RexxStart() -- just as if you were executing a RAM script. But, you set the first RXSTRING to point to a nul (zero) byte and set its strlength to 1. The script name that you pass to RexxStart() is the name of the macro of interest. Reginald's RexxStart() will return RX_START_OK (ie, success) if a macro with that name is in Reginald's internal table. If not, RexxStart() will return RX_SCRIPT_NOT_FOUND. Here's an example to query if a macro named "Blort.rex" is in Reginald's table:

   RXSTRING instore;
   APIRET   err;

   /* Initialize the RXSTRING to query if a macro exists */
   instore[0].strptr = "";
   instore[0].strlength = 1;
   instore[1].strptr = 0;

   /* Query if "Blort.rex" is in the table */
   err = RexxStart(0, 0, "Blort.rex",
                   &instore[0],
                   0, RXSUBROUTINE, 0, 0, 0);

   /* Did it exist? */
   if (!err)
   {
      /* Yes it did */
      printf("Blort.rex is in Reginald's macro table\n", err);
   }
   else
   {
      /* No it didn't */
      printf("Blort.rex is not in Reginald's macro table\n", err);
   }

It is also allowable to leave the second RXSTRING pointing to a previously tokenized script when querying for the existence of a macro. In this way, you'll match only that particular instance of the macro in Reginald's table (not some other macro which happens to have the same name).

To delete a macro with a given name from Reginald's internal table, you pass the 2 "Instore" RXSTRINGs to RexxStart() -- just as if you were executing a RAM script. But, you set the first RXSTRING to point to any item (ie, you can set its strptr to 0xFFFFFFFF) and set its strlength to 0. The script name that you pass to RexxStart() is the name of the macro of interest. The tokenized version of the script must be in the second RXSTRING. Reginald's RexxStart() will return RX_START_OK (ie, success) if a macro with that name is in Reginald's internal table, and was deleted. If not, RexxStart() will return RX_SCRIPT_NOT_FOUND. Here's an example to delete a macro named "Blort.rex" in Reginald's table:

   RXSTRING instore;
   APIRET   err;

   /* Initialize the RXSTRING to delete a macro */
   instore[0].strptr = 0xFFFFFFFF;
   instore[0].strlength = 0;
   /* instore[1] contains the tokenized script */

   /* Delete "Blort.rex" in the macro table */
   err = RexxStart(0, 0, "Blort.rex",
                   &instore[0],
                   0, RXSUBROUTINE, 0, 0, 0);

   /* Success? */
   if (!err)
   {
      /* Yes */
      printf("Blort.rex is deleted from Reginald's macro table\n", err);
   }
   else
   {
      /* No */
      printf("Blort.rex is not in Reginald's macro table\n", err);

      /* We'll have to delete the tokenized version ourselves */
      if (instore[1].strptr) RexxFreeMemory(instore[1].strptr);
      instore[1].strptr = 0;
   }

When you delete a macro as above, Reginald automatically does the RexxFreeMemory() on the buffer containing the tokenized script, so you do not need to do that yourself (unless the tokenized version is not in Reginald's table).


The RexxMacroXXX APIs are supported by a few different REXX interpreters, including Reginald. RexxAddMacro() or RexxLoadMacroSpace() allow you to load REXX scripts from disk and put them into the internal macro table. (ie, The RexxMacro APIs do the loading of the REXX script. There is no facility to pass a script that you have already loaded into RAM, or created in RAM). RexxDropMacro() allows you to remove a macro from the table. RexxQueryMacro() allows you to check if a macro has been added to the table. RexxSaveMacroSpace() allows you to save one or more macros to a single file on disk in a format that lets them be more quickly loaded by RexxLoadMacroSpace(). It is not a text format such as an ordinary script.

To load a script (from disk) and add it to the macro table, you call RexxAddMacro(). This is passed the filename of the script. The filename may include the full path. The first arg to RexxAddMacro() is the name that you wish to use when running the macro. (ie, You can give the macro a name other than its filename on disk). The last arg to RexxAddMacro() is RXMACRO_SEARCH_BEFORE if you would like the macro to be used in lieu of any similiarly named script on disk. Otherwise, it will be RXMACRO_SEARCH_AFTER if you wish any similiarly named script on disk to supercede that macro.

Here is an example of loading a script named "C:\MyDirectory\MyScript.rex" in the macro table and giving it a macro name of "SomeName".

   APIRET   err;

   /* Add "C:\MyDirectory\MyScript.rex" to the table as "SomeName" */
   err = RexxAddMacro("SomeName", "C:\MyDirectory\MyScript.rex", RXMACRO_SEARCH_BEFORE);

   /* An error? */
   if (err)
      printf("Error adding script to macro table: %d\n", err);
To subsequently run that macro, you can call RexxStart() with that macro name, just as if you were running a script on disk:
   APIRET   err;

   /* Run "SomeName" */
   err = RexxStart(0, 0, "SomeName", 0, 0, RXSUBROUTINE, 0, 0, 0);
So too, any other script you run can call that macro just as if the script were calling any external function or subroutine, optionally passing it args and/or receiving back a value:
CALL SomeName /* Call it with no args, and toss away any return */
myreturn = SomeName() /* Call it as a function with no args, and store its return value */
If using Reginald, then you can pass 0 for the macro name to RexxAddMacro(), and Reginald will use the script's name minus any path and extension, for example, the following call registers the macro with a name of "MyScript":
   APIRET   err;

   /* Add "C:\MyDirectory\MyScript.rex" to the table as "MyScript" */
   err = RexxAddMacro(0, "C:\MyDirectory\MyScript.rex", RXMACRO_SEARCH_BEFORE);

   /* An error? */
   if (err)
      printf("Error adding script to macro table: %d\n", err);
Each macro must have a unique name. So, if there is already a macro named "SomeName" added to the internal table, then RexxAddMacro() will fail with RXMACRO_ALREADY_EXISTS. Note that macro names are case-insensitive. (If you're going to also use RexxStart()'s InStore to add macros from RAM to the macro table, then it is best to give each macro a unique name).

Here is an example of querying whether there is a macro named "SomeName" in the macro table:

   APIRET   err;
   PUSHORT  order;

   /* Query "SomeName" macro */
   err = RexxQueryMacro("SomeName", &order);

   /* An error? */
   if (err)
      printf("Error querying macro: %d\n", err);

   else
   {
       printf("Macro found: ");
       if (order == RXMACRO_SEARCH_BEFORE)
          printf("RXMACRO_SEARCH_BEFORE\n");
       else
          printf("RXMACRO_SEARCH_AFTER\n");
   }
Note: RexxQueryMacro() will also work to query if a macro has been adding via RexxStart()'s InStore.

Here is an example of removing a macro named "SomeName" from the macro table:

   APIRET   err;

   /* Delete "SomeName" macro */
   err = RexxDropMacro("SomeName");

   /* An error? */
   if (err)
      printf("Error dropping macro from table: %d\n", err);
Note: RexxDropMacro() will also work to remove a macro that has been added via RexxStart()'s InStore, but Reginald does not automatically RexxFreeMemory() on the buffer containing the tokenized script, so you need to do that yourself.

RexxReorderMacro() can be used to change a macro from RXMACRO_SEARCH_BEFORE to RXMACRO_SEARCH_AFTER, or vice versa. Here, we change the "SomeName" macro to RXMACRO_SEARCH_AFTER, meaning that when we try to run a macro named "SomeName", if there is also a script named "SomeName" on disk, then the script will be run instead of the macro.

   APIRET   err;

   /* Reorder "SomeName" macro */
   err = RexxReorderMacro("SomeName", RXMACRO_SEARCH_AFTER);

   /* An error? */
   if (err)
      printf("Error reordering macro: %d\n", err);

Halting execution of a script

The REXXSAA API RexxSetHalt() is called while a script is running. It raises the HALT condition. If the script is not trapping the HALT condition, then this results in the script terminating, the interpreter printing an error message that the script has been aborted, and RexxStart() returning an error number. RexxSetHalt() is therefore designed to allow you to notify the script that an operation should be aborted, or if the script isn't trapping the HALT condition, to terminate the script.

RexxSetHalt() takes two args, a process ID (number) and a thread ID. These are obviously platform-specific (so this wasn't exactly a good design decision). You'll have to consult your interpreter documentation to determine how you're supposed to obtain these numbers under a given operating system. (Some interpreters don't even bother using these args. Reginald doesn't).

Some interpreters restrict you to calling RexxSetHalt() from the same thread that called RexxStart() to launch the script. Therefore, you would obviously have to call RexxSetHalt() from an Exit Handler, External Function, or Subcom Handler.

Reginald offers an additional, proprietary function, RexxRaiseCondition(), which allows a lot more flexibility in raising conditions and reporting errors to a script. Besides raising the HALT condition, RexxRaiseCondition() can alternately raise the SYNTAX, NOVALUE, NOTREADY, ERROR, or FAILURE conditions with either one of the ANSI REXX General Error numbers/messages, or a message of your own choosing. You'll find the ANSI GE numbers defined in REXXSAA.H under the "EXTERNAL FUNCTION HANDLERS" section (for example, RXERR_STORAGE_EXHAUSTED). That allows you to abort the script's execution (if it doesn't trap SYNTAX) with a specific, meaningful error message. But RexxRaiseCondition() allows one additional feature. It allows you to utilize Reginald's error message box transparently, and more fully. If you try to raise SYNTAX, and the script isn't trapping that condition, Reginald's error message box will pop up (if MSGBOX OPTION is enabled). You can specify a specific page in your own online help file that describes why you're raising the condition, and Reginald will automatically present this page when the user clicks on the "Help" button. In this way, your own External Functions, Subcom Handlers, and Exit Handlers, will seem completely integrated with Reginald's extensive online help.

Or, RexxRaiseCondition() can raise the HALT condition which will also abort the script (if it doesn't trap HALT), but you can specify any signal number (ie, not just SIGINT).

TO BE DONE: Examples of RexxRaiseCondition()

If you are calling RexxSetHalt() or RexxRaiseCondition() from an external function, it is advisable to return RXFUNC_OK after your call to RexxSetHalt() or RexxRaiseCondition(), so that you don't get a superfluous error message. Likewise, an Exit Handler should return RXEXIT_HANDLED, and a Subcom Handler should return RXSUBCOM_OK.

In conclusion, Reginald's RexxRaiseCondition() offers the ability to report a much wider range of error messages/numbers to the script, and thereby report much more detailed, accurate error feedback to the user.


The REXX queue

TO BE DONE


Dynamic Linking to support several interpreters

If there are various interpreters available for your platform, and you wish to support them all, you should not link with the .LIB file for any given interpreter. Doing otherwise is "static linking", and ties your REXXSAA API calls to that one interpreter for which you have a .LIB file. For example, if you've looked at the Microsoft Visual C++ Projects that accompanied this tutorial, you'll see that we linked with REGINALD.LIB. When you link with REGINALD.LIB, it inserts a small amount of "stub code" that allows Windows to automatically load REGINALD.DLL (ie, Reginald's REXX interpreter) and go through your program's executable, resolving your calls to Reginald's REXXSAA API.

But if you want to support more than one interpreter, you should instead use dynamic linking. You do not use the .LIB file at all. This will usually require you to do some operating specific stuff. Consult the developer documentation with your system about dynamic linking to libraries.

Windows: If you're writing a program for Windows, then you can use the following techniques to create a program that works with various interpreters for windows.

First, you need to know the names of the DLLs for all the various interpreters you wish to support. For example, Reginald's interpreter is contained in a DLL called "reginald.dll". Regina's interpreter is contained in a DLL called "regina.dll". Object REXX's interpreter is in a DLL called "rexx.dll". And there are a few others.

You need to call the Windows API LoadLibrary() passing it the name of an interpreter DLL whose REXXSAA API you'd like to use. If that interpreter is located upon your system (in a place where programs can find it), then Windows will return a handle to that DLL. (ie, Its DLL will be loaded for you). You can then proceed to use the REXXAPI functions inside of that DLL.

If that interpreter DLL cannot be found, then LoadLibrary() returns a 0. At that point, you can try the name of another one of the interpreter DLLs you support. Etc. Hopefully, one of your supported interpreter DLLs will load, and you'll be able to use its REXXSAA API. Otherwise, you're out of luck, and your program should terminate with an error message that it can't find any of its supported REXX interpreters.

Assuming that one of your supported interpreter DLLs does load, you can then get the addresses of each one of its REXXSAA APIs you're interested in, by using the Windows API GetProcAddress(). You pass the handle to your loaded interpreter DLL, and a string containing the nul-terminated name of the API (for example "RexxStart"). If this REXXSAA API is found inside of the DLL, its address is returned. You should save this address in a global variable in your program. Then whenever you wish to call this REXXSAA API, you use that variable to fetch its address. In other words, you can't directly hardcode a call to a REXXSAA API such as RexxStart() in your program. Rather, you call the function indirectly, using a pointer to it.

Here's an example of how we try to load the Reginald DLL, and get a pointer to its RexxSetHalt() API. We then call RexxSetHalt() using the pointer to it. REXXSAA.H has typedefs you can use to declare your pointers to various APIs. To declare a pointer to some REXXSAA API, simply append Ptr to the API name. For example, you'll store a pointer to the RexxSetHalt API in a variable of the type RexxSetHaltPtr. In my example below, I name this variable "RexxSetHaltA", but you can give your variable any name you desire.

   RexxSetHaltPtr *RexxSetHaltA;
   HANDLE   RexxHandle;

   /* Try to open Reginald's interpreter DLL */
   if ((RexxHandle = LoadLibrary("reginald")))
   {
      /* Ok, Reginald's interpreter DLL loaded */

      /* Get a pointer to the RexxSetHalt() API */
      if ((RexxSetHaltA = (RexxSetHaltPtr *)GetProcAddress((HINSTANCE)RexxHandle, "RexxSetHalt")))
      {
          /* Call RexxSetHalt() using the pointer */
          (*RexxSetHaltA)((LONG)GetCurrentProcess(), (LONG)GetCurrentThread());
      }
      else
      {
         /* A problem. Let's pretend the DLL didn't even load */
         FreeLibrary(RexxHandle);
         RexxHandle = 0;
      }
   }

   /* Did Reginald interpreter DLL not load ok? */
   if (!RexxHandle)
   {
      /* Reginald's interpreter DLL can't be found. Here you'd
       * try LoadLibrary() on another one of your supported DLLs,
       * and if it loads, use GetProcAddress() to obtain a pointer
       * to its RexxSetHalt() function, as above.
       */
   }

   /* We have to close the DLL when we're done with it. We
    * wouldn't do this yet if we intended to call more REXXSAA APIs
    */
   if (RexxHandle) FreeLibrary(RexxHandle);

If you're using REXX as a macro language for your program, and that feature is not necessary for your program to run, then you definitely want to use dynamic linking. After all, if you use static linking for a given interpreter, and that interpreter's DLL is not installed on a computer system, then Windows won't even startup your program. Instead, Windows will report that it can't find a needed DLL. With dynamic linking, your program can choose to disable its macro feature if none of your supported interpreter DLLs can be found, but otherwise, your program will run as normally. You should use dynamic linking even if you intend to support Reginald's DLL only. That way, even if Reginald is not installed, your program can run without REXX scripting.

If you installed the Reginald Developer's Kit, the Script13 directory shows a version of Script9 with dynamic linking to support several Windows interpreters. The Script14 directory shows a version of Script10 with support only for Reginald, but uses dynamic linking. Note that neither example links with the REGINALD.LIB file.


Setting Reginald's environment (ie, scripts path, settings, etc)

Your program can modify many of Reginald's default settings by installing an RXENV Exit Handler. When you call RexxStart() with such an Exit Handler installed, Reginald will call your RXENV Exit Handler several times (before the script actually starts running) to get the values of some Reginald "environment variables". By returning values for these variables, you can determine some of Reginald's settings. Here are the variable names that Reginald passes to your RXENV Exit Handler for which you may return values:

REXX_MACROS

A single directory where REXX scripts are stored on disk. This directory is searched first when a REXX script calls another REXX script. Reginald may subsequently search other "common directories" specified by the user when he sets up Reginald. But this one directory should be specific to your program.

REXXFORM

Should be either "E" (for Engineering) or "S" (for SCIENTIFIC).

REXXFUZZ

Numeric Fuzz setting. Should be a numeric string.

REXXDIGITS

Numeric Digits setting. Should be a numeric string.

REXXTRACE

TRACE options. Should be a string like what a REXX script would specify after the TRACE keyword. If you want to set multiple options, you should also use RexxSetOptions() to turn on TRACE_OPTS.

For example, here is an example RXENV Exit Handler that sets the scripts search path to "C:\MyDir", and sets NUMERIC FORM to engineering.

LONG APIENTRY rxenv_exit(LONG ExNum, LONG Subfun, PEXIT PBlock)
{
   RXENVSET_PARM *penvset;

   penvset = (RXENVSET_PARM *)PBlock;

   /* Interpreter wants us to return a variable's value? */
   if (Subfun == RXENVGET)
   {
      char   *ptr;

      /* Which variable does the interpreter want? Note
       * that Reginald nul-terminates the variable name. For the above names,
       * Reginald also upper cases, so you can use strcmp().
       */
       ptr = penvset->rxenv_name.strptr;

      /* All of the above names start with "REXX". Check that first */
      if (penvset->rxenv_name.strlength > 4 && !memcmp(ptr, "REXX", 4))
      {
         ptr += 4;

         /* He wants the scripts path? */
         if (!strcmp(ptr, "_MACROS"))
         {
            /* Return "C:\MYDIR". This doesn't need to be nul-terminated,
             * but it's easier to do this. NOTE: The interpreter passes
             * a 256 byte data buffer to store the value. If we needed
             * more room, we'd have to allocate a new buffer via
             * RexxAllocateMemory(), and stuff it into rxenv_value.strptr
             */
            ptr = "C:\\MyDir";
its_set:    strcpy(penvset->rxenv_value.strptr, ptr);
            penvset->rxenv_value.strlength = strlen(ptr);

            /* We're returning a value for this variable */
            return(RXEXIT_HANDLED);
         }

         /* He wants the numeric form? */
         if (!strcmp(ptr, "FORM"))
         {
            /* Return the form string. See NOTE above */
            ptr = "E";
            goto its_set;
         }
      }

      /* We don't return values for any other variables, so return
       * RXEXIT_NOT_HANDLED. Reginald will seek values elsewhere
       */
   }

   /* Let the interpreter handle this variable */
   return(RXEXIT_NOT_HANDLED);
}

Settings you make here override any settings made by Reginald's environment variables (or the Administration Tool). But, a script can subsequently change the NUMERIC FORM/FUZZ/DIGITS, and TRACE.


Re-entrancy Issues

TO BE DONE: Discussion about calling the API from multiple threads -- not threadsafe. Also discussion about calling RexxStart() from an External Function. Exit Handler, or Subcom Handler -- RexxStart() may inherit the Exit Handlers of its caller by default as in Reginald, although may be overridden. Exception is with Function Exit Handler)