"earthrexx.library" DOCUMENTATION ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ IMPLEMENTING AN AREXX INTERFACE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ===================================================================== Opening and closing the ARexx interface (also known as the struct RexxPort) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This document describes how to implement an ARexx interface to any application. The interface allows you both to recieve commands from the outside world, and to send commands out to Rexx. You can also send and recieve function invokations, as well as rexx messages in general. To give your application an ARexx interface, you need a struct RexxPort. This allows you to send and recieve ARexx messages, and allows the lib to keep track of what's going on. All the hard work is done for you. You get a struct RexxPort by calling the function OpenRexxPort(). This function can fail (for instance, if there isn't enough memory, or if the application is running on a machine which does not have ARexx) but you don't actually need to check the return value, since the lib functions are quite tolerant about being given a NULL pointer instead of a RexxPort address. Keep this return value somewhere safe, as most of the Rexx interface functions in this library need to know it. Your application should call OpenRexxPort() as part of its initialisation. In addition, you MUST call CloseRexxPort() as part of your shutdown. WHAT DOES THIS GIVE YOU? A RexxPort is much more sophisticated than a simple Exec MsgPort. For a start, the lib has to take reentrancy into account: "What if two instances of the application run in parallel?", "What if the application is made Resident or ARes?", and so on. Unlike other rexx interface libraries, "earthrexx.library" is completely PURE. This means that a PURE application can use the library and still remain PURE. Much attention has gone into this seemingly small detail, so it's worth explaining what it all means. Suppose your application opens a rexx port called "MYPORT". Now suppose that two instances of your application are running in parallel. Both of them will OpenRexxPort() using exactly the same name! The library copes with this contingency. The details are explained later in this document under the heading "THE MULTITASKING MODEL", but for now, suffice it to say that your application does not need to know or care about any such details - it can merrily assume it is the only instance in the system! The OpenRexxPort() function requires you to pass a structure called a struct NewRexxPort as an entry parameter. This is the structure in which you tell the library exactly how you want your rexx interface to behave. struct NewRexxPort is defined in "earth/earthrexx.i" and "earth/earthrexx.h", but more complete documentation on how to fill in the structure is contained in the autodoc for OpenRexxPort(). It is, however, extremely easy, and most of the parameters can be given as NULL or zero to select default settings. The standard way of calling OpenRexxPort() is to have a STATIC struct NewRexxPort as part of your program's source, and then just passing the address of it. Note that the lib will not modify this static structure in any way at all - it will copy all the relevant details into the struct RexxPort it returns. This means that your program can remain PURE. (The equivalent function in "rexxapp.library" modifies the structure you pass it, so you can't make it static without sacrificing reentrancy!). You may have noticed that the mechanism by which OpenRexxPort() is called is modelled upon the Intuition OpenWindow() function. In OpenWindow() you pass a struct NewWindow in, and get a struct Window out. To call CloseWindow() you pass the address of the struct Window. OpenRexxPort() behaves in a similar manner - you pass a NewRexxPort in, you get a RexxPort out. You pass the RexxPort to CloseRexxPort(). ===================================================================== Recieving Rexx messages ~~~~~~~~~~~~~~~~~~~~~~~ Assuming that OpenRexxPort() returned non-NULL, you will need to modify your IDCMP loop wait mask by ORing it with the rp_WaitMask field of the returned RexxPort. If you're not sure how to do this (or even what it means) then see the document IDCMPLoop.doc. Finally, you should call the function ProcessRexx() within your IDCMP loop. This will process all messages which have arrived at your RexxPort. You can call ProcessRexx() as often as you like. If there are no messages at the port then ProcessRexx() returns immediately, so it's quite fast. If any messages are present then ProcessRexx() will deal with ALL of them, leaving the port empty. It is at this point that commands and functions recieved from the outside world get dealt with. Every command which your program "understands" has a function to execute it. These functions will be part of your application's source. ProcessRexx() will call the relevant function for each command it processes. ProcessRexx() also does other housekeeping tasks, such as freeing replies from messages which the lib sent out earlier. It does all the hard work so that you don't have to. ===================================================================== How to process a Rexx message ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Whenever the lib recieves a rexx message which your application is capable of understanding it will call a function in your application. These application functions are called HANDLERS, to use the earthrexx jargon. If the message was a command invokation then you get the command line as one of the entry parameters, so "understanding" it is quite simple. If the message was a function invokation, however, then the function arguments are spread throughout the message. Fortunately there is a lib function to help you read the message. The function InterfaceRexx() uses a format string to move the function arguments into machine code registers (or the stack if you program in C) in an arbitrarily complex way. For instance, the first argument may be a string you need in register a2, the second may be a hexadecimal number which you need in register d1 (after conversion to integer), the third may be a packed 4-byte address which you need in register a0 (after extracting the packed address), and so on. InterfaceRexx() can do just about anything when it comes to interpretting a rexx message. It is THE function to use if you are handling function invokations. Complementary to the problem of getting the entry parameters OUT of the message, there is the problem of putting the return values IN. The lib makes this as easy for you as possible by interpretting the return value from your handler in a sensible way. (See the autodoc for ProcessRexx() for specific information). In other words, if the return value is simple enough, the return value of your handler can determine the two return values required in the message. We recognise, however, that you may occasionally need to return more general values, so we've provided the function SetResults(). SetResults() installs two return values into the rexx message. It is not quite the inverse function of InterfaceRexx(), since it does not need a format string, however it can certainly install results of any type, doing arbitrary conversions on the way. ===================================================================== Sending Rexx commands out ~~~~~~~~~~~~~~~~~~~~~~~~~ There are four functions to create and send Rexx messages. Two of these concern commands, one concerns functions, and the last is for messages in general. The functions which concern command invokations are called SyncRexx() and ASyncRexx(). As their names suggest, one sends out commands synchronously, while the other does it asynchronously. Programmers who are upgrading from rexxapp should note that "rexxapp.library" contains two functions called SyncRexxCmd() and ASyncRexxCmd(). These are NOT functionally identical to our functions, so be careful! In point of fact, the "rexxapp.library" functions are BOTH asynchronous, so rexxapp's SyncRexxCmd() is most DEFINATELY misnamed. This leads to the confusing fact that our function ASyncRexx() is very similar to rexxapp's function SyncRexxCmd(). This will almost definately confuse programmers who are in the process of upgrading, but I can't really help that. To try to sort out the confusion, let me define what is meant by the terms "synchronous" and "asynchronous": SYNCHRONOUS means that two events are SYNCHRONISED - that is, they take place simultaneously. [It's Latin, see. SYN=SAME; CHRON=TIME]. In the computer world, a child process is said to be launched SYNCHRONOUSLY if the return from the launch routine of the parent process is synchronised with the termination of the child process. In simple english: the launch routine waits for the child process to finish. ASYNCHRONOUS means that two events are NOT synchronised - they can happen in any order. In the computer world, a child process is said to be launched ASYNCHRONOUSLY if the return from the launch routine of the parent process is NOT synchronised with the termination of the child process. In plain english: the launch routine does NOT wait for the child process to finish. These terms are fairly well understood in the Amiga world, and are used extensively in Commodore's own documentation. It is fair to ask, therefore, why "rexxapp.library" should have so misused the word "synchronous" - or why "MinRexx" got it wrong (since rexxapp was based on MinRexx). It seems that their concept of how their equivalent of SyncRexx() would be used is largely to blame. They envisioned that an ARexx program somewhere in the outside world would send us a message. That message would arrive at the the application's rexx port, and that the application would then process that message synchronously (correct usage) by forcing the Rexx program in the outside world to wait until the application had finished with the message. HOWEVER, THIS IS NOT WHAT SyncRexx() IS INTENDED FOR! ProcessRexx() is quite capable of doing that job on its own, thank you very much. SyncRexx() (THIS library's version) transmits a message FROM the application TO Arexx, and then waits for that message to come back! Far more useful, and, more to the point, genuinely deserving of its name. If you want to know how SyncRexx() works, see the autodoc for that function. Here I shall merely state that SyncRexx() is completely safe to use. It will NOT deadlock the system, even if Rexx needs to send further messages synchronously to the rexx port. If you don't understand this then you probably don't need to know about it. If you do and you're confused or curious, read the SyncRexx() autodoc. Of the other functions I mentioned, CallRexx() is also synchronous. SyncRexx() and ASyncRexx() are for sending commands; CallRexx() is for sending functions, and SendRexx() is for sending general rexx messages of any kind. ===================================================================== Closing down the Rexx Interface ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You should call CloseRexxPort(), and then close the "earthrexx.library". ===================================================================== The multitasking model ~~~~~~~~~~~~~~~~~~~~~~ The library presents you with a choice of two possible multitasking models (modes of behaviour). The simplest of these is not the default. The simplest model is called the "SINGLE PORT" model. In this mode of behaviour you get (as the name suggests) just one port dedicated to your application. If a process attempts to run a second copy of your application while the original copy is already running then the second process is doomed to failure. (Its call to OpenRexxPort() will return NULL). This mode of behaviour is very simple, and also very suitable for some applications. The ARexx resident process itself is an example of the single port model. If you run REXXMAST twice then the second rexxmast will NOT create a second Rexx port called "REXX" (or indeed called anything else). If you envisage your application as a sort of background process that just sits there in the background doing system-wide things on command then the single port model would probably suit your needs exactly. This behaviour is not the lib's default. If you want this behaviour you must explicitly turn it on. You do this by setting the RPF_SINGLE flag in the nrp_Flags variable of your NewRexxPort structure, prior to calling OpenRexxPort(). Compare this with the "MULTIPLE PORT" model, which is the default behaviour of the library (and is initiated by OpenRexxPort() if RPF_SINGLE is reset). In this model, an indefinate number of copies of your application can run simultaneously, each thinking it has the machine to itself. The model is based on Intuition's user-interface, and the way that Intuition handles input and output. Under Intuition there may be many different windows present, but there is only one keyboard, - so how does Intuition distribute the single source of key presses throughout its many windows? The answer is that it maintains an "Active" window. Under Intuition, only one window can be active at a time, and only the active window can read from the keyboard (though ALL windows can output at all times). The user of Intuition has the option to activate or deactivate windows at any time using the mouse. Applications can also activate their window using the function ActivateWindow() in "intuition.library". The multipleport model maintained by this lib follows the Intuition model quite closely. If you have several copies of your application running simultaneously then only one of them can be the "Active" copy at any time. ALL copies can send messages to rexx, and can recieve commands back in response, but only ONE copy (the "Active" one) can recieve messages transmitted blindly by an external application in the outside world. To make this clear, suppose that you call OpenRexxPort() with a port name of "FRED". Suppose also that several other copies of your program do exactly the same thing. Now the chances are that you will end up with a RexxPort called something like "FRED.4" or whatever (although your application can ignore this fact). Your port is still perfectly good for transmitting to ARexx, and for recieving messages back in response, but an external application which doesn't know (or care) about your individual port will still send its messages to "FRED" - not "FRED.4". THE LIBRARY WILL FORWARD SUCH MESSAGES ON TO THE CORRECT (ACTIVE) PORT, so if you are the active port you will recieve them, otherwise you won't. The lib provides three functions for gaining and relinquishing active status. The functions are called ActivatePort(), ShakePort() and DeactivatePort(). Even if there is only one copy of your program in the system, calling ActivatePort() and DeactivatePort() can still be useful for switching on and off Rexx capability!!! The suggested implementation of these functions is as follows: Make your IDCMP loop capable of recieving messages of class ACTIVEWINDOW (and optionally INACTIVEWINDOW). Then whenever you recieve an ACTIVEWINDOW event you should call ActivatePort(). Optionally, you could also call either ShakePort() or DeactivatePort() whenever you recieve an INACTIVEWINDOW event. This mechanism will put everything in the end user's hands. The application with the active RexxPort will be the application with the active window, and the user will be able to switch between them at will. Wonderful! If you never call any of these functions then the default action is for the first started application to become the active port. It retains active status until it shuts down, at which point the lib will ShakePort() (attempt to give active status to someone else). You can modify this behaviour by setting one of two flags in the nrp_Flags field of your NewRexxPort structure, prior to calling OpenRexxPort. The flags are RPF_ACTIVE and RPF_INACTIVE. See the autodoc for OpenRexxPort() for more information. ===================================================================== The functions related to this section are as follows: OpenRexxPort() ProcessRexx() DispatchRexx() SetResults() InterfaceRexx() FreeRexxMsg() ASyncRexx() SyncRexx() CallRexx() SendRexx() CloseRexxPort() And the user functions: UserDispatchFn() UserCmdHandler() UserFuncHandler() UserReturnFn() UserPassFn() UserFailFn() UserCleanupFn() =====================================================================