One thing that is quite apparent from Figure 12, but that was only touched on earlier is the fact that the Rexx interpreter must load each function from our DLL before it can be used by our Rexx program. When I say that the interpreter "loads" the external function, I'm not presenting a 100% accurate picture of what happens. When the RxFuncAdd function is used in our Rexx program, the specified function is what we call "registered" with the Rexx interpreter. We tell the interpreter the function name, the module it's in and the name we want to use in our Rexx program to invoke it. The interpreter doesn't "load" the function until we call it. The important point, though, is that we must register each external function that we want to use. This can obviously get pretty monotonous if we have many functions in our function package.
The next logical question is "can we write an external function that "registers" all of the functions in our package?" Well, yes. As a matter of fact, the Rexx API offers several functions which makes this task quite easy to do.
Earlier, I mentioned three built-in Rexx functions: RxFuncAdd, RxFuncDrop and RxFuncQuery. These three functions are provided to allow the user to manipulate external functions from the Rexx program. The API also provides these functions, although in a slightly different form. Four functions are provided by the API:
In our context of producing a function package, we will only talk about three of these functions. The RexxRegisterFunctionDll API call is used to register an external function with the Rexx interpreter. The RexxDeregisterFunction API call is used to de-register, or drop a function and the RexxQueryFunction API call is used to check whether or not a given function has been registered. The fourth API call, RexxRegisterFunctionExe, is used to register external functions that exist within application code. An example of this sort of application might be an editor which is using Rexx as an imbedded macro language. More on this and other ways to use Rexx later(see the Conclusion).
So, our goal is to write an external function which automatically registers all of our functions with the interpreter. If we have a function like this, then no matter how many functions our package may have, the user will only ever have to use RxFuncAdd for our single "load" function. Figure 13 shows what this function might look like.
I've added several things to the function package source along with the Load function which will make it easier in future to add new functions to the DLL. First, notice that the RexxRegisterFunctionDll API call requires three parameters, (just like RxFuncAdd), so to make things easier, I've defined an "EXTFUNCTIONS" structure that provides space for each of the three parameters. Having defined the structure "EXTFUNCTIONS", I declare an array of those structures. The array is filled with an entry for each of the functions contained in our DLL.
Next, I define a variable "MAX_FUNCS" to contain the number of entries in the structure array. I follow that with a prototype for the Load function. Now on to the Load function itself.
The Load function is actually quite simple. We simply walk the structure array, using "MAX_FUNCS" as our upper limit. For each entry we use the RexxQueryFunction API call to see if the given entry has already been registered. If it has, then we go on to the next entry. Otherwise, we use the RexxRegisterFunctionDll API call to register the function with the Rexx interpreter.
Upon return from the RexxRegisterFunctionDll call, we check the return code passed to us by the Rexx interpreter. That return code will be one of the following:
For RXFUNC_OK, or for RXFUNC_DEFINED, we just carry on. But if we receive on of the other return codes, then we fail the Load function by returning "ERROR" to the user. Note that we do not pass a bad return code to the Rexx interpreter from our Load function. If we did, then the interpreter would report "error 40: Invalid call to routine", and that is clearly not the case; the user invoked the function properly, it's just that our processing failed.
If we successfully traverse the structure array, then we return "OK" to the user.
Figure 14 shows our new function package complete with the Load function. Also included is an UnLoad function which de-registers all of our functions with the Rexx interpreter. The UnLoad function is virtually identical to the Load function. One exception is the return code "RXFUNC_NOTREG". This return code will be returned if you try to de-register an external function which is not registered. Figure 15 presents the definition file for our new function package and Figure 16 shows the same Rexx program as in Figure 12, except that this Rexx program takes advantage of our new Load function. And don't forget to add your new functions to the definition file.
One final point. How do we get around that 256 byte return string limit? Remember that the RXSTRING contains a *pointer* to a string. Of course, that's really just a pointer to an area of storage. We can allocate our own block of storage and replace the existing pointer with our own. When allocating the storage you need, DO NOT use the malloc C function. Under OS/2 use DosAllocMem to get your storage. This function returns a pointer that can be substituted directly into the RXSTRING return string. Under Windows NT, it's just a little more complicated: alllocate your storage with the GlobalAlloc function. This function will return a "handle" to the storage allocated. To get a "real" pointer, you must use the GlobalLock function:
The first statement declares a variable of type "HANDLE" and associates a 1K block of storage with it. The second statement declares a pointer to an array of type char. The third statement checks that the hStorage handle is valid, and if it is, then calls GlobalLock which returns a pointer to the storage identified by the hStorage handle and assigns that pointer to the pStorage char pointer.
For additional compatibility, you could write your own version of the DosAllocMem function for Windows NT:
Having allocated your storage and having acquired a pointer to that storage, copy in your data, set the length piece of the RXSTRING and return to the interpreter. You need not worry about freeing the storage, as a matter of fact, you cannot free the storage. If you do, then the interpreter won't be able to use it; the Rexx interpreter will free the storage for you once he's done with it.
Figure 17 shows an external function which allocates a 1K block of storage to hold the return value. The function retrieves the current user name via the Win32 GetUserName API call. The parameters passed to the Win32 call specify that up to 1K of data will be accepted, so we need to allocate enough storage for our return string to hold that much data. This function is used in my MERXUtil function package for Personal Rexx for Microsoft Windows NT and is included in the final version of our function package. The source, definition file and makefile appear in Figures 18, 19 and 20.
Return to Contents
Read next section Conclusion
Bill Potvin
bill.potvin@network.com