REXX-ercising Your Applications - Part 1
Автор: Gordon Zeglinski
Дата: June 1994
Источник: EDM/2
Contents
- 1 Introduction
- 2 Some Basics
- 2.1 More About Functions
- 3 About RXSTRINGs
- 4 Starting Rexx/2 From Within an Application
- 4.1 Executing REXX Procedures From a File
- 5 External Functions
- 5.1 Things to remember
- 5.2 Registering Your External Function
- 6 Putting it all Together
- 7 Summary
Introduction
Rexx/2 provides an excellent mechanism by which developers can easily add scripting, macro, and programming abilities into their products. Yet there is still some cloud of mystery surrounding the use of REXX in general applications. It is hoped that this article will dispel this cloud and encourage other developers to incorporate REXX abilities into their apps.
After reading this article, you should:
- Understand a bit more about how .cmd files are executed.
- Know how to start Rexx/2 from within an application.
- Be able to extend Rexx/2 by adding your own external functions to it.
Note: The REXXSAA.H file shipped with the 2.1 Toolkit and the C++ patched 2.0 toolkit are not C++ compatible. A C++ compatible version is included with this issue for use with the C-Set++ compiler.
Some Basics
In REXX, there are three different categories of executable statements: instructions, commands, and functions. Instructions are built into REXX and cannot be supplemented. Commands and functions are definable by the application evoking the REXX interpreter. However, functions are more flexible in nature than commands because they can be integrated into expressions and can be added at run time via the (built-in) RxFuncAdd() function. This article will focus on functions.
Before we look closer at REXX functions, we should look a bit at Command (.CMD) files. Command files are REXX programs that are meant to be executed under the "CMD" REXX environment. The "CMD" environment is a subcommand handler registered with REXX using the name "CMD". It is important to note that command files are not able to be executed unless the "CMD" command handler is used when starting REXX if these files contain commands like "dir", "copy", etc. This discussion will be continued in part 2.
More About Functions
In REXX, functions come in two flavors: internal and external functions. From the REXX programmers point of view, once the external function has been registered, there is no difference between the two. However, xternal functions can either be based in DLL's or within the executable itself. Take the following REXX program, SHOWDIR.CMD .
/******************************************/ /* Simple Rexx/2 file that opens the */ /* default view for the current directory */ /******************************************/ Call RxFuncAdd 'SysLoadFuncs', 'REXXUTIL', 'SysLoadFuncs' Call SysLoadFuncs DIR=directory(); If SysSetObjectData(DIR,"OPEN=DEFAULT") Then Say DIR "has been opened." Else Say DIR "was not opened."
SHOWDIR.CMD illustrates several important points:
- External functions must be registered before they can be used. In the case of DLL-based external functions, they can either be registered by using a REXX function or by the application.
- There are two methods to call functions, either using call or (). Unlike in C or C++, if the () method is used then the line must follow the same syntax as as line 4 in the above example, DIR=directory();. Strictly speaking in REXX terminology, if the call method is used then the procedure being called is referred to as a subroutine. If the () method is used, then the procedure being called is referred to as a function.
About RXSTRINGs
The RXSTRING structure is used through out REXX and warrants a detailed examination. The maximum length of a RXSTRING is 4 gigabytes (the maximum number that can be held in a ULONG variable). Unlike C, where strings are zero terminated, the string component of an RXSTRING is not zero-terminated. Additional important points about RXSTRING's are listed below.
typedef struct { ULONG strlength; // Length of string PCH strptr; // Pointer to string } RXSTRING;
- An RXSTRING is said to have a value if the strptr field is not NULL. Consequently, it is said to be empty if the strptr field is NULL.
- An RXSTRING is said to be a null string if the strptr field points to the value "" and the strlength field is zero.
- The length stored in the strlength field does not include the terminating null character.
- Conveniently, the REXX interpreter adds the terminating zero character to the strptr field when it calls external functions, subcommand handlers and exit handlers. This allows one to use the C library string functions on this field. However, there is no guarantee that this will be the only zero character. The developer will have to decide whether the data could have extra zero characters.
- When a return value is expected from an external function, subcommand handler or exit handler, the REXX interpreter sets up a default RXSTRING of length 256. If the value to be returned is requires less space than this, it is simply copied into the strptr buffer and the strlength filed is set to the shorter length. If the default RXSTRING is too small, a new RXSTRING can be created using DosAllocMem(). The REXX interpreter will free the memory used by the new RXSTRING.
Starting Rexx/2 From Within an Application
We will start our trek into the work of REXX by looking at how we can start the Rexx/2 interpreter from within our apps. The function RexxStart() allows us to do this.
(LONG)RexxStart(ArgCount, ArgArray, Name, Instore, Environment, RXCOMMAND, ExitHandler, &RC, &ReturnString);
(Because the RexxStart() function can do so many things, this is bound to be some what bewildering. Don't panic, examples will be used to explore the various parameter combinations in this and future articles in the series.)
- ArgCount (LONG) - input
- The number of elements in the ArgList array. This value will be returned by the ARG() built-in REXX function. ArgCount should include RXSTRINGs which represent omitted arguments. Omitted arguments will be empty RXSTRINGs (strptr will be NULL). See the Discussion on RXCOMMAND for further information.
- ArgArray (PRXSTRING) - input
- An array of RXSTRING structures that are the REXX program arguments.
- Name (PSZ) - input
- Points to the ASCIIZ "name" of the REXX procedure and can have many different interpretations depending on the value of Instore. If Instore is NULL, then it is the name of the file containing the REXX procedure. If Instore is not NULL, then it is either the name of the subroutine or function in the Macrospace.
If Name is the name of a file, a default extension of .CMD is used if none is supplied. Optionally, "name" could be the fully qualified filename.
- Instore (PRXSTRING) - input
- An array of two RXSTRINGs that store REXX procedures in memory.
If both RXSTRINGs are empty, the interpreter searches for REXX procedure Name in the macrospace. If the procedure is not found in the macrospace, the RexxStart() function returns an error code. If either RXSTRING is not empty, Instore is used to execute a REXX procedure directly from memory.
- Instore[0] input
- An RXSTRING containing an REXX procedure stored in the exact same manner as a disk file. The image must be complete with carriage returns, line feeds, and end-of-file characters.
- Instore[1] input and output
- If Instore[1] is empty, the REXX interpreter will return the tokenized image in here.
If Instore[1] is not empty, interpreter will execute the tokenized image directly. Instore[0] can be empty in this case, provided the REXX program does not require meaningful data from the SOURCELINE built-in REXX function. If Instore[0] is empty and SOURCELINE function is used, SOURCELINE will return null strings for the REXX procedure source lines. If Instore[1] is not empty, but does not contain a valid REXX tokenized image, unpredictable results can occur. The REXX interpreter might be able to determine that the tokenized image is incorrect and retokenize the source. If the procedure is executed from disk, the Instore pointer must be NULL. If the first argument string in Arglist contains the string "//T" and CallType is RXCOMMAND, the interpreter will tokenize the procedure source and return the tokenized image without running the program. The program using the RexxStart() function must release Instore[1] using DosFreeMem() when the tokenized image is no longer needed. The format of the tokenized image may not be the same across interpreter versions. Therefore, the tokenized image should not be stored to disk or transferred to other systems. It is meant to be used multiple times with in the same application in which it was created to allow the faster execution of the REXX program during the second and subsequent executions.
- Environment (PSZ) - input
- Address of the ASCIIZ initial ADDRESS environment name. The ADDRESS environment is a subcommand handler registered using RexxRegisterSubcomExe() or RexxRegisterSubcomDll(). Environment is used as the initial setting for the REXX ADDRESS instruction.
If Environment is NULL, the file extension is used as the initial ADDRESS environment. The environment name cannot be longer than 250 characters. If Environment is set to "CMD", .CMD files may be executed from within the application.
- CallType (LONG) - input
The type of REXX procedure execution. Allowed execution types are:
- RXCOMMAND
- The REXX procedure is an OS/2 operating system command or application command. REXX commands normally have a single argument string. The REXX PARSE SOURCE instruction will return "COMMAND" as the second token.
- RXSUBROUTINE
- The REXX procedure is a subroutine of another program. The subroutine can have multiple arguments and does not need to return a result. The REXX PARSE SOURCE instruction will return "SUBROUTINE" as the second token.
- RXFUNCTION
- The REXX procedure is a function called from another program. The subroutine can have multiple arguments and must return a result. The REXX PARSE SOURCE instruction will return "FUNCTION" as the second token.
- ExitHandler (PRXSYSEXIT) - input
- An array of RXSYSEXIT structures defining exits the REXX interpreter will use.
- RC (PLONG) - output
- If Result is a whole number in the range of -(2**15) to 2**15-1, it will be converted to a numeric form and and returned in RC as well as Result.
- Result (PRXSTRING) - output
- The string returned from the REXX procedure with the REXX RETURN or EXIT instruction. A default RXSTRING can be provided for the returned result. If a default RXSTRING is not provided or the default is too small for the returned result, the REXX interpreter will allocate an RXSTRING using DosAllocMem(). The caller of the RexxStart() function must release the RXSTRING storage with DosFreeMem().
The REXX interpreter does not add a terminating zero to Result.
Executing REXX Procedures From a File
If we were to look at code samples to illustrate all of the various things that RexxStart() can do, we wouldn't be able to get to creating external functions in this issue. Therefore, we will look at one of the more fundamental uses of RexxStart() - executing file-based REXX procedures. The code below illustrates this:
VOID RunScript(PSZ ScriptFile) { LONG return_code; // interpreter return code RXSTRING argv[1]; // program argument string RXSTRING retstr; // program return value SHORT rc; // converted return code argv.strptr=NULL; argv.strlength=0; retstr.strptr=new char [1024]; retstr.strlength=1024; return_code = RexxStart(0, // No arguments argv, // dummy entry ScriptFile, // File name NULL, // NULL InStore "CMD", // use the "CMD" command processor RXCOMMAND, // execute as a command NULL, // No exit handlers &rc, // return code from REXX routine &retstr); // return string from REXX routine delete [] retstr.strptr; }
In the above example, the return value string is set to be 1K in size. (it is assumed that the REXX interpreter will not require more space than this.) It should also be noted that because REXX commands typically do not execute quickly that the above function should be called from a separate thread in PM programs so that it will not hang the message queue.
External Functions
Finally, we can look at creating external functions. There are two types of external functions, EXE-based and DLL-based. The EXE-based version must be registered with REXX by the application in which they reside and can only be used by REXX programs started from within that application. Typically, one would use this type of external function to support macro and scripting features within the application.
DLL-based functions are mainly used to provide additional utility libraries for REXX. Once registered, all REXX programs have access to DLL-based functions. Both DLL- and EXE-based functions follow the same basic declaration syntax. The following sample function will clear the screen if called from a VIO-based REXX program.
ULONG _System SysCls(UCHAR *name, ULONG numargs, RXSTRING args[], PSZ *queuename, RXSTRING *retstr) { BYTE bCell[2]; // If arguments, return non-zero to indicate error if (numargs) return 1; bCell[0] = 0x20; bCell[1] = 0x07; VioScrollDn(0, 0, (USHORT)0xFFFF, (USHORT)0XFFFF, (USHORT)0xFFFF, bCell, NULLSHANDLE); VioSetCurPos(0, 0, NULLSHANDLE); // return 0 to the caller retstr.strlength=1; strcpy(retstr.strptr,"0"); return 0; }
Things to remember
External REXX functions must use the C calling convention.
When creating DLL-based external functions, remember to export the function and use multiple data segments (DATA MULTIPLE NONSHARED).
The default return string is 255 bytes long.
Registering Your External Function
Having previously seen how to register DLL-based external functions from within REXX, we will now look at way in which you can register external functions from within an application. The function RexxRegisterFunctionDll() is used to register DLL-based external functions and RexxRegisterFunctionExe() is used to register external EXE-based functions.
(APIRET)RexxRegisterFunctionDll(PSZ pszFunction, PSZ pszModule, PSZ pszEntryPoint); (APIRET)RexxRegisterFunctionExe(PSZ pszFunction, PFN pfnEntryPoint);
For RexxRegisterFunctionDll(), the following parameters are specified:
- pszFunction
- Points to the string specifying the name of the function as it will be known by REXX.
- pszModule
- Points to the string specifying the DLL containing the function to be registered.
- pszEntryPoint
- Points to the exported function name within pszModule which specifies the function to be registered.
For RexxRegisterFunctionExe(), the following parameters are specified:
- pszFunction
- Points to the string specifying the name of the function as it will be known by REXX.
- pfnEntryPoint
- Points to the function to be registered.
Both functions return RXFUNC_OK, RXFUNC_DEFINED, or RXFUNC_NOMEM to indicate successful completion, function is already defined, and out of memory, respectively.
Additionally, there are functions to query the existance of a function and to deregister a function.
(APIRET)RexxQueryFunction(PSZ pszFunction); (APIRET)RexxDeregisterFunction(PSZ pszFunction);
For both functions, pszFunction points to the function name (as registered using one of the above functions) and return FXFUNC_OK or RXFUNC_NOTREG to indicate that the function exists and that the function does not exist, respectively.
So now we've seen how to start Rexx/2 from within our applications, and seen how to register external functions. It's time to put our newly acquired knowledge to use.
Putting it all Together
In this section, we will create a simple application that will register an external function and execute .CMD files. The following program (included as REXXSAMP.CPP) illustrates the procedure of registering an external EXE-based function and starting the Rexx/2 interpreter.
#define INCL_RXFUNC /* external function values */ #define INCL_VIO #include#include #include ULONG _System SysCls(UCHAR *name, ULONG numargs, RXSTRING args[], PSZ *queuename, RXSTRING *retstr) { BYTE bCell[2]; // If arguments, return non-zero to indicate error if (numargs) return 1; bCell[0] = 0x20; bCell[1] = 0x07; VioScrollDn(0, 0, (USHORT)0xFFFF, (USHORT)0XFFFF, (USHORT)0xFFFF, bCell, NULLSHANDLE); VioSetCurPos(0, 0, NULLSHANDLE); // return 0 to the caller retstr.strlength=1; strcpy(retstr.strptr,"0"); return 0; } INT main(VOID) { char Input[CCHMAXPATH]; LONG return_code; /* interpreter return code */ RXSTRING argv[1]; /* program argument string */ RXSTRING retstr; /* program return value */ SHORT rc; /* converted return code */ RexxRegisterFunctionExe((PSZ)"EDM_SysCls",(PFN)&SysCls); cout << "Sample EDM/2 REXX Demonstration Program" << endl << "by Gordon Zeglinski" << endl; cout << "Type rexxsamp to execute supplied smaple" << endl; cin >> Input; if (!strlen(Input)) strcpy(Input,"REXXSAMP.CMD"); cout << "Executing Sample Program " << Input << endl; cout << "-----------------" << endl; retstr.strptr=new char [1024]; retstr.strlength=1024; return_code = RexxStart(0, // No arguments argv, // dummy entry Input, // File name NULL, // NULL InStore "CMD", // use the "CMD" command processor RXCOMMAND, // execute as a command NULL, // No exit handlers &rc, // return code from REXX routine &retstr); // return string from REXX routine delete [] retstr.strptr; return 0; }
For those of you who can compile the sample code, feel free to experiment with it and a debugger.
Files Included With This Issue
REXXSAMP.EXE | Compiled version of REXXSAMP.CPP |
REXXSAMP.CPP | Source code to sample Rexx/2 invocation program |
REXXSAMP.MAK | Makefile for C-Set++ |
REXXSAMP.DEF | Definition file for use with linker |
REXXSAMP.CMD | Sample REXX program to be started from REXXSAMP.EXE |
Summary
This concludes our look at Rexx/2 for this issue. You should now be able to start Rexx/2 from with in an application, and create external functions that are either DLL- or EXE-based. Future issues will explore the other features of RexxStart(), macroSpaces and sub-command handlers.
As usual, question and comments are welcome.