REXX-ercising Your Applications - Part 1/2Written by Gordon W. Zeglinski |
IntroductionRexx/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:
Some BasicsIn 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 FunctionsIn 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:
About RXSTRINGsThe 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;
Starting Rexx/2 From Within an ApplicationWe 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.)
The type of REXX procedure execution. Allowed execution types are: Executing REXX Procedures From a FileIf 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 FunctionsFinally, 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 rememberExternal 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 FunctionHaving 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:
For RexxRegisterFunctionExe(), the following parameters are specified:
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 TogetherIn this section, we will create a simple application that will register an external function and execute .CMD files. The following program (included as REXXSAMP.CPP) illustrates the procedure of registering an external EXE-based function and starting the Rexx/2 interpreter. #define INCL_RXFUNC /* external function values */ #define INCL_VIO #include <rexxsaa.h> #include <iostream.h> #include <string.h> ULONG _System SysCls(UCHAR *name, ULONG numargs, RXSTRING args[], PSZ *queuename, RXSTRING *retstr) { BYTE bCell[2]; // If arguments, return non-zero to indicate error if (numargs) return 1; bCell[0] = 0x20; bCell[1] = 0x07; VioScrollDn(0, 0, (USHORT)0xFFFF, (USHORT)0XFFFF, (USHORT)0xFFFF, bCell, NULLSHANDLE); VioSetCurPos(0, 0, NULLSHANDLE); // return 0 to the caller retstr.strlength=1; strcpy(retstr.strptr,"0"); return 0; } INT main(VOID) { char Input[CCHMAXPATH]; LONG return_code; /* interpreter return code */ RXSTRING argv[1]; /* program argument string */ RXSTRING retstr; /* program return value */ SHORT rc; /* converted return code */ RexxRegisterFunctionExe((PSZ)"EDM_SysCls",(PFN)&SysCls); cout << "Sample EDM/2 REXX Demonstration Program" << endl << "by Gordon Zeglinski" << endl; cout << "Type rexxsamp <ENTER> to execute supplied smaple" << endl; cin >> Input; if (!strlen(Input)) strcpy(Input,"REXXSAMP.CMD"); cout << "Executing Sample Program " << Input << endl; cout << "-----------------" << endl; retstr.strptr=new char [1024]; retstr.strlength=1024; return_code = RexxStart(0, // No arguments argv, // dummy entry Input, // File name NULL, // NULL InStore "CMD", // use the "CMD" command processor RXCOMMAND, // execute as a command NULL, // No exit handlers &rc, // return code from REXX routine &retstr); // return string from REXX routine delete [] retstr.strptr; return 0; }For those of you who can compile the sample code, feel free to experiment with it and a debugger. Files Included With This Issue
SummaryThis 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. |