------------------------------------------------- | | | Rexx Function Host Package | | Version 1.2 | | 02-Sep-89 | | | | | | | | Copyright © 1989, Donald T. Meyer | | | ------------------------------------------------- The Rexx Function Host Package, hereafter abreviated as FHP, is a 'C' source code module which facilitates writing function hosts for ARexx. The intent of this document is to give some background on ARexx function hosts, and then describe how the FHP handles the implementation of them. The FHP will handle most of the details involved in writting your own function host. The code in "rexxfunchost.c" can be modified if desired, but what was intended is a module that can be compiled once and then linked into any and all function hosts that you write. In most cases, the code in "rexxfunchost.c" can be used as is, with the specific routines you write to handle ARexx function invocations contained in a seperate module or modules (i.e. "rh_demo.c"). ARexx and External Functions ARexx has the ability to use external functions contained either in a standard Amiga shared library, or in a special program called a function host. Either of these must be specialy written for use with ARexx. You cannot directly use a "normal" library, such as icon.library, intuition.library, etc. For ARexx to "know about" a library or function host, you must first add it to a special list kept by ARexx, the Library List. This can be done either by issuing a command from the CLI, or via a function from within an ARexx program. Once the library or function host has been added to the ARexx Libray List, whatever functions they contain will then be available to ARexx programs. The function host is responsible for function "binding". ARexx passes a message to the function host, which then performs an internal search for the desired function name. If that function is contained within the host, it is called to handle the function's activitys. Function Hosts vs. Librarys Using either a function host or a library will achieve almost the same end result: adding new function calls to ARexx. There are some fairly major differences however. Disadvantages: Calls are not easily made reentrant Must be explicitly run Advantages: Easier to debug Inherent call queueing Individual stack Individual priority Asynchronous operation possible First, the disadvantages. Consider what happens if two ARexx programs call the same external function at the same time. In a properly written library, both will execute at the 'same' time. In a function host, the first one that makes the call will get the use of the function host, and the second call will have to wait until the first completes. Although it would be possible to start a task to handle each function called, if multiple calls need to execute simultaneously, a library would probably be a better choice in this case. A function host must be explicitly executed prior to being accessed. This would normally be done in the startup-sequence script. A library must merely reside in the LIBS: directory. On the other hand, the library is subject to being removed from memory if not open, whereas a function host is not. This could actually be an advantage depending on the circumstances. Now, the advantages which a function host has over a library. From a 'C' and source-level debugger standpoint, function hosts are easier to debug since they are essentially just normal programs, happily recieving messages and returning them. If you wish to do things which may require interaction with the user other than through just calling a function, such as allowing the user to manipulate a scrollgadget for things displayed via calls to the function host. If the functions are such that only one ARexx program could use them at a time, the function host provides an inherent locking and queueing mechanism. This is due to the fact that function requests arrive as messages, which are automaticly queued by Exec. A typical example of something non-shareable would be a hardware device such as a printer. When a library function is called, the stack used is the caller's. Since a function host is a totally seperate processs, the stack used is it's own. This also applies to process priority, although this is fairly easy to adjust "on the fly" if needed. Another possible benefit of being a seperate process, is that of asynchronous execution. A function host can be written such that it returns the message back to ARexx before the desired function actions have been taken. Although most of the advantages provided by a function host can be incorporated into a library through clever programming, they happen "naturally" in the function host. Even if the desired end result is a function library, implementing a function host for ease of testing may still be worthwhile. What a Function Host Does A typical function host will perform the following steps: 1) Start executing, normally as a detached background task. 2) Open neccessary libraries, allocate needed memory, etc. 3) Open a public Exec message port. 4) Wait for a RexxMsg message from ARexx. 5) Dispatch the function call. 6) Process the function call. 7) Return the message to ARexx. 8) Wait for the next message... 9) Cleanup and exit. Here are the details about what the code in "rexxfunchost.c" does to perform the above steps. 1) Start executing, normally as a detached background task. Invocation can be from either CLI or WorkBench. We probably want a specific stack size and execution priority. If we are launched by the Workbench, the icon .info file can contain the desired stack size. This does not help us on priority, though we can change that "manualy" by making an exec library call. But from CLI, we would wind up with whatever stack the CLI defaulted to, and we would also prevent that CLI from accepting further command until we ended our execution. Lattice and Manx 'C' address these problems quite handily with startup code which detaches us from the CLI. *** what _exactly_ happens from workbench with cback.o? *** This startup code allows us to explicitly request our stack size, priority, and also frees the CLI for further command entry. There are some slight differences in the way Lattice and Manx handle this. The FHP is written for Lattice, but should compile under Manx with minor changes. If there is already an instance of this function host running, some additional actions must take place. If launched from the WorkBench we assume that the reason our icon has been double-clicked again is to remove the function host. To do this, we send a break signal to our "twin", and then exit ourselves. To cause this to happen from the CLI, we must use a command line option "-q" to cause the termination of an existing "twin". The FHP handles all the logical decisions involved in deciding what to do, and displays appropriate messages telling the user what is being done. These messages are defined in your code, so as to allow easy control of just what is displayed. 2) Open neccessary libraries, allocate needed memory, etc. The FHP always opens the rexx library. A call is then made to a function named client_init() which should be in your code. This is the place to do any initialization required by the functions in your function host. Returning a pointer to a string will cause it to be displayed, and the FHP to abort. A NULL return indicates success. 3) Open a public Exec message port. This is the way that ARexx will send function calls to function hosts. This must be a public port, so that ARexx can FindPort() it dynamicly as it searches the Library List. At this point, the FHP will also automaticly add itself to the ARexx Library List. This is done by sending a message to the ARexx resident process. The FHP will also handle removing itself from this list when it terminates. 4) Wait for a RexxMsg message from ARexx. With all setup operations complete, the FHP now Wait()s for one of two events. Upon recieving a function invocation message at the public port, it will proceed to process the function call. The other event which will wake up the FHP is the ctrl-C break signal. This signal will normally be sent by another invocation of this function host, although it can be sent via Signal() from any Amiga task. In the 1.2 release of this module, a ULONG variable client_event_flags has been added. This will allow client code to set events it needs to Wait() on as well. A client even will result in a call to the client_event_handler() function, which should be in the client code. 5) Dispatch the function call. When a function invocation message is recieved, the FHP will handle several of the details. First and foremost, the FHP code will determine whether or not the function being called is in our function host. It does this based on parameters stored in the array func_table. This array, which is defined in your code, contains four parameters for each function which control how the FHP handles the function dispatching. First, the name of the function is defined, followed by the address of the 'C' routine which will perform this function's actions. Next, a numeric parameter which specifys how many arguments this function call should have. If the number of arguments passed in the ARexx function call is incorrect, the FHP will return an error to ARexx. This can also be set to -1 to indicate that the FHP should perform no checking of the parameter count. And last, a boolean flag indicating whether or not this function name is case-sensitive or not. If the case flag is set to FALSE, than case will be ignored when searching for the function name. Once the FHP has determined that the desired function is here, and that the parameter count is correct, whatever 'C' routine you have written to handle the function is called, with a pointer to the rexxmsg as the sole argument. 6) Perform the function The function handler routine in your code is passed a pointer to a RexxMsg structure. Using this pointer, the arguments to the function (if any) can be accessed, and proper return code or value can be set. The arguments are passed as pointers to strings. These pointers are in the RexxMsg structure member rm_Args[]. There can be 15 arguments passed here (argument 0 is a pointer to the function name itself). These arguments are accessed thusly: rexxmsg->rm_Args[1] The details of how arguments are passed and results returned are contained in the ARexx manual. The example functions in "rh_demo.c" demonstrate recieving basic arguments, and returning both errors and result strings. To ease the task of returning a value string to ARexx, a helper function is included in the FHP. This function, SetResultString(), takes a pointer to a RexxMsg (which should of course be the one passed from the FHP initialy) and a pointer to a string. This function will then allocate the RexxArg and place it into the RexxMsg structure. If a NULL is given as the string pointer (not to be confused with a null string!), a general failure code will be set in the RexxMsg structure. An error will also be set if the allocation of the RexxArg fails. This should happen only in cases of low or fragmented system memory. 7) Return the message to ARexx. Upon returning from your 'C' routine, the FHP will return the message to ARexx and wait for the next function call to arrive. 8) Wait for the next message... Loop back to step #4. The FHP stays in this loop until a Control-C break signal is recieved. 9) Cleanup and exit. This step is reached upon recieving the termination signal. Again, a function defined in your code, client_cleanup() is called. This is where any libraries opened, memory allocated, etc. by the call to client_init() should be closed or freed. NOTE: This routine will be called regardless of the reason for the FHP's exiting. Your cleanup routines should be prepared to be called before or after client_init() has executed.