Copyright (c) Hyperion Entertainment and contributors.
Difference between revisions of "AmiWest Lesson 8"
Steven Solie (talk | contribs) |
Steven Solie (talk | contribs) |
||
Line 1,091: | Line 1,091: | ||
This is a simple ARexx script that addresses the AmigaOS command line ("COMMAND"), it uses "RequestString" to get a string from the user and then it sends that inpuyt back to the calling application with the "Return" function. |
This is a simple ARexx script that addresses the AmigaOS command line ("COMMAND"), it uses "RequestString" to get a string from the user and then it sends that inpuyt back to the calling application with the "Return" function. |
||
− | + | <pre> |
|
/* Small macro script for the small arexxclass demo */ |
/* Small macro script for the small arexxclass demo */ |
||
Line 1,111: | Line 1,111: | ||
exit |
exit |
||
− | </pre |
+ | </pre> |
Revision as of 00:21, 17 October 2012
An AmigaOS innovation was the nearly universal support for high level interprocess and scripted communications using "ARexx ports", "ARexx messages" and the interpreted "ARexx" language.
By providing and using ARexx ports, applications are able to send messages to each other. ARexx scripts can also be run by the user or applications providing a level of control that far exceeds simple "recorded" macros found elswhere. As new interpreted languages have been ported to AmigaOS, some - like Python - have been adapted to mimic the ARexx language's ability to send and receive messages.
In this text we will explore the process of having an application to open an ARexx port, send and receive messages and to be able to run ARexx scripts (as application "macros").
Contents
Initialization
Like much of the current AmigaOS, the support of ARexx operations within C programs has been modernized and simplified using the BOOPSI style of object oriented methods. Under AmigaOS4, the requirements to open specific libraries has been further automated with the OS4 SDK. To use ARexx messaging within an application, we need to initialize a few things:
- Create a BOOPSI "ARexx Object".
- Create an hook to a function to receive responses.
- Create the function called with responses.
Obtaining an ARexx Object
After confirming the arexx.library has been automatically opened, one of the first steps to using ARexx involves calling the BOOPSI NewObject function. This general BOOPSI function uses the following list of tags to create an ARexx "object", an ARexx port for your application and get it ready to send/receive messages:
Tag Name Variable Type Required? Notes AREXX_HostName CONST_STRPTR Yes, unless Msg Port is used Sets the "host name" of your application's ARexx port. AREXX_NoSlot BOOL No Determines whether a port counter number is added to the host port name. DEFAULT: FALSE = a number will be appended to the port name. AREXX_DefExtension CONST_STRPTR No Filename extension that will get appended when calling a macro script. DEFAULT: "rexx". AREXX_Commands struct ARexxCmd * Only for receiving commands A structure containing a table of ARexx commands your application understands. More on this below. AREXX_ErrorCode uint32 * No A error code pointer reflecting any issues with creating the new ARexx object. AREXX_ReplyHook struct Hook * Yes for replies, unless Msg Port is used A function hook structure for a function that will be called if your app receives a message or reply to a sent message. AREXX_MsgPort struct MsgPort * No A message port pointer to be used instead of creating and receiving messages in an ARexx port.
Naturally, how these tags are used will depend on the intended functions of the application. Obviously, an app that will only send messages will not need to define a list of commands received. Some apps might want to set the "AREXX_NoSlot" tag to "TRUE" if they want to make sure they will be the only port with a name and it will require yet more code to handle the possible situation if that port name is already in use. The SDK "autodocs" can provide more detail on these tags.
The following simple code example uses these tags where an application only expects to send ARexx messages:
Object *arexx_obj; STATIC struct Hook reply_hook; char port_name[40]; uint32 errorCode; arexx_obj = IIntuition->NewObject(NULL, "arexx.class", AREXX_HostName, "AREXX_SENDER", AREXX_NoSlot, TRUE, AREXX_ErrorCode, &errorCode, AREXX_ReplyHook, &reply_hook, TAG_DONE);
If the NewObject function is successful, it will return a pointer to the new ARexx Object. If the function fails, we can check the contents of the "errorCode" variable against these predefined error codes:
RXERR_NO_COMMAND_LIST No command list was provided. RXERR_NO_PORT_NAME No host base name was provided. RXERR_PORT_ALREADY_EXISTS The class was unable to create a unique name of the base name you provided. RXERR_OUT_OF_MEMORY Not enough free memory.
A Reply Hook
Virtually any application will have incoming messages to deal with. For any message sent, the recipient must send a reply. So a reply hook and function would be used for not only messages sent by other applications, but also responses to messages sent from our application.
To do so, most applications will also define a pointer to an AmigaOS Hook structure ("reply_hook") to receive incoming messages and responses to sent messages. This standard structure allows us to define a function in our application that will be run when our application receives messages or responses. Once we've successfully obtained an ARexx object giving us an ARexx port, we would set up the hook function like this:
STATIC struct Hook reply_hook; [...] reply_hook.h_Entry = (HOOKFUNC)reply_callback; // pointer to function name to be called reply_hook.h_SubEntry = NULL; reply_hook.h_Data = NULL;
In this excerpt, "reply_callback" is the name of a C function we create that will be called when an ARexx message or response is received.
Reply Function
While the function we create to be called by the "reply hook" will be passed a number of arguments, we are only concerned with the ARexx message passed to our function. This "RexxMessage" structure has the following basic structure:
struct RexxMsg { struct Mesage rm_Node; /* Exec message structure */ APTR rm_TaskBlock; /* private global structure */ APTR rm_LibBase; /* private library base */ int32 rm_Action; /* command action code */ int32 rm_Result1; /* primart result (return code) */ int32 rm_Result2; /* secondary result */ STRPTR rm_Args[16]; /* argument block */ }
Additional "extension fields" may also be passed within the RexxMessage structure when starting an ARexx program. Those are defined in the CBM AmigaMail articles and CBM "ARexx Programmers Guide".
In most cases, applications passing typical messages will be focused on the two "Result" fields. The first value ("Result1") will contain an integer where zero means there was no error or it will have an error code number (>0). In the cases where there is no error, the second field ("Result2") will contain a pointer to any result string sent with the message (or zero if none).
The following is an example of a simple implementation of a reply function that just prints out the contents of received messages:
STATIC VOID reply_callback(struct Hook *hook UNUSED, Object *o UNUSED, struct RexxMsg *rxm) { IDOS->Printf("Result1 =>%ld<\n", rxm->rm_Result1); if (rxm->rm_Result1 == 0) IDOS->Printf("Result2 =>%s<\n", (CONST_STRPTR)rxm->rm_Result2); }
Sending ARexx Messages
Once we have initialized a BOOPSI ARexx Object and the reply_hook structure, we are then ready to try to send ARexx messages. Of course, sending messages can be broken into a few steps:
- Finding the ARexx port to send the message to.
- Sending the ARexx message.
- Waiting for a response to our message.
- Handling the response.
Finding the Target Port
To send a message, you have to have a valid name of the target ARexx message port. With the port name in a string, we can use the AmigaOS FindPort function to determine if the port exists. To prevent ports from appearing or disappearing during this search, calling FindPort is "wrapped" with Forbid/Permit functions to interupt multitasking. Obvbiously, this interuption should be kept as short as possible.
The following code excerpt would search for the Workbench ARexx port:
CONST_STRPTR port_name = "WORKBENCH"; IExec->Forbid(); if (IExec->FindPort(port_name)) { Exec->Permit(); IDOS->Printf("Found WORKBENCH ARexx port!\n"); // Here we can go on to send a message. } else { Exec->Permit(); IDOS->Printf("WORKBENCH ARexx port not found.\n"); }
As you can see, we make sure to reenable multitasking as quickly as possible, regardless of whether the port name was found. As with any error trapping, any application should have a graceful way of handling an issue like a missing ARexx port and letting the user deal with the occurence.
Sending the Message
After all our steps of initialization and verifying our target ARexx port, sending our ARexx message is a single call of the BOOPSI IDoMethod function. With the "AM_EXECUTE" method, the IDoMethod function uses the ARexx object, the method name and the following parameters:
Argument Name Variable Type Notes MethodID uint32 The arexx class "method", in this case "AM_EXECUTE". ape_CommandString CONST_STRPTR The command and arguments to be executed - either sent to an ARexx port or the ARexx host. ape_PortName CONST_STRPTR The name of the ARexx port to send the message to, if not the ARexx host. ape_RC int32 * Primary result code if the message was sent to the ARexx host. ape_RC2 uint32 * Secondary result code if the message was sent to the ARexx host. ape_Result STRPTR * The result string if the message was sent to the ARexx host. ape_IO BPTR Alternate IO channel pointer if an executed script is to use a different IO channel.
To read more about these parameters, refer to the "arexx_cl" autodocs.
To send a message, we only need to add the target port name and our message string, as follows:
CONST_STRPTR cmd = "HELP"; int32 RC,RC2; char result[100]; IIntuition->IDoMethod(arexx_obj,AM_EXECUTE,cmd,port_name,NULL,NULL,NULL,NULL);
As you can see all the return & result arguments are given NULL's when we send an ARexx message. Sending ARexx messages is considered "Asynchronous" - in other words, our application will not get an immediate response and will continue operating in the meantime.
Waiting for and Handling Replies
When a response to our sent message is received, it will arrive with an AmigaOS signal to our application's message port that it has received an ARexx message. Like many other signals to applications (Intuition, control code signals, etc.) we need to obtain a mask for receiving ARexx events. Then we can combine all those signals together and call the Wait function to put our app asleep, waiting for those events.
When an event matching any of the Wait function's set of signals occurs, our application will be woken up. Then we have to determine if it was ARexx message that woke up our app and handle that event. At that point, we use the BOOPSI "IDoMethod" function again, this time with the "AM_HANDLEEVENT" method. This will cause the reply_hook & function (defined above) to be called to handle the incoming message.
Here is some example code of waiting for handling an incoming ARexx message:
uint32 rxsig = 0, signal; IIntuition->GetAttr(AREXX_SigMask, arexx_obj, &rxsig); signal = IExec->Wait(rxsig | SIGBREAKF_CTRL_C); if (signal & rxsig) { IIntuition->IDoMethod(arexx_obj, AM_HANDLEEVENT); }
As configured, this Wait function will put our application to sleep until it receives either an ARexx message or "control-c" signal. Of course, a complete application would also check for and respond to a possible "control-c" signal (or anything else) and respond accordingly.
Receiving ARexx Messages
Creating an application for receiving designated ARexx commands is handled in a similar fashion as dealing with replies to sent messages (above), except the "known" commands are defined in the application code and a function is designated to be called when each of those commands is received.
To start with, if we are create an application meant to receive ARexx commands then we will have to adjust our NewObject request for a new ARexx Object. In this case, that code might look like:
arexx_obj = IIntuition->NewObject(NULL, "arexx.class", AREXX_HostName, "AREXX-RECEIVER", AREXX_NoSlot, TRUE, AREXX_ErrorCode, &errorCode, AREXX_Commands, Commands, AREXX_NoSlot, TRUE, TAG_DONE);
The pertinent difference being that we are now defining "AREXX_Command" instead of "AREXX_ReplyHook" in the above sample.
Defining Commands to be Received
In an application where we will be receiving a number of predefined commands, we set up an array of structures that will define those commands, the functions to be called when those command are received and what arguments are accepted. Here are the fields used:
Field Name Variable Type Notes ac_Name CONST_STRPTR The command name sent. Usually all uppercase. ac_ID uint16 A unique identifier number. ac_Func *()(struct ARexxCmd *, struct RexxMsg *) A pointer to a function that will be called when this command is received. ac_ArgTemplate CONST_STRPTR An AmigaDOS style template for the arrguments expected with this command. ac_Flags uint32 Future argument, to be set to NULL now. ac_ArgList uint32 * With Messages: Result of ReadArgs on arguments received based on template above. ac_RC uint32 With Messages: Primary Result variable. ac_RC2 uint32 With Messages: Secondary result variable. This will not be set unless RC > 0. ac_Result STRPTR With Messages: Result string. This will not be set if RC > 0.
The last four fields of this structure are all set to NULL and zero values when the commands are defined. They will only be used when those commands are used to interact with the incoming message and the sender.
In the follow code sample, you can see where we define just such an array of "ARexxCmd" structures.
STATIC struct ARexxCmd Commands[] = { {"NAME", 0, rexx_Name, NULL, 0, NULL, 0, 0, NULL}, {"VERSION", 1, rexx_Version, NULL, 0, NULL, 0, 0, NULL}, {"QUIT", 2, rexx_Quit, NULL, 0, NULL, 0, 0, NULL}, {"SEND", 3, rexx_Send, "TEXT/F", 0, NULL, 0, 0, NULL}, {"DATE", 4, rexx_Date, "SYSTEM/S", 0, NULL, 0, 0, NULL}, {"HELP", 5, rexx_Help, NULL, 0, NULL, 0, 0, NULL}, {NULL, 0, NULL, NULL, 0, NULL, 0, 0, NULL} };
As you can see, two of the commands ("SEND" and "DATE") are configured to also accept arguments. The formatting of the "ArgTemplate" field should follow that used by the ReadArgs function. Please see the autodocs on ReadArgs for more information on how the template is parsed.
Waiting for Messages
The process for waiting for incoming ARexx messages with commands is generally the same as it was in the above example waiting for replies to sent commands. We just obtain the mask ARexx messages, combine it with any other signal masks and call the AmigaOS Wait function. Again when the application is woken up, we check to see when an ARexx message was received and then call the IDoMethod with the "AM_HANDLEEVENT" method.
The difference with sent message responses is that when the IDoMethod function is called with commands, the designated function is called for command received (as set in the Commands array above). For example, if the "SEND" command is received, the "rexx_Send" function will be called.
Functions to Handle Commands
When the IDoMethod function is called with "AM_HANDLEEVENT", the ARexx class will determine which command was received and then call the appropriate function and provide the following arguments:
- STATIC VOID <function name> (struct ARexxCmd *, struct RexxMsg)
As you can see, the ARexx class passes two structures to the function. The first structure is the same format as that configured in the commands array above and we can use it to read incoming data and send things back to the message sender. The pertinent fields are as follows:
Field Name Variable Type Usage ac_ArgList uint32 * An array with the results of ReadArgs filtering the arguments in the sent message. Read these elements to obtain data sent with the message. ac_RC uint32 This argument is the "primary result variable" and is returned to the message sending application to indicate success, warning, errors, etc. ac_RC2 uint32 This is the "secondary result variable" and will only be used if the RC variable is set >0. ac_Result STRPTR This is the "result string" that can be set by our application and will be sent back to the sending app if the above RC variable is = 0.
Typically, "ArgList" entries (like "ArgList[0]") will be read for incoming command parameters. Bear in mind, the contents of those arguments are parsed and filtered by the ReadArgs function using the template set in the commands array, as a command line would be. Please see the autodocs on ReadArgs for more information on how the template is parsed and what results are provided.
An example of a simple function for the "SEND" command entry above could look like this:
STATIC VOID rexx_Send(struct ARexxCmd *ac, struct RexxMsg *rxm UNUSED) { IDOS->Printf(" Received SEND ARexx message.\n"); IDOS->Printf(" with this text:\n"); // Print the text received from the message sender if (ac->ac_ArgList[0]) IDOS->Printf(" %s\n", (CONST_STRPTR)ac->ac_ArgList[0]); }
When the command function is finished, the contents of the three "result" parameters are returned to the sending application. The primary result variable "ac_RC" indicates command success and which of the other two variables are returned to the sending application. If RC=0, then the result string "ac_Result" is sent back to the sending application. If RC>0, then the secondary result variable "ac_RC2" is returned.
Running ARexx Script Macros
Besides simply sending ARexx messages between applications, ARexx provides a complete programming language that can be used to create "scripts" that an application can call and run as "macros".
Call the ARexx Script
To call an ARexx script from within your program, we go through the same initialization as for sending ARexx messages - create an ARexx object, define a reply hook, and then call a simple variation of the IDoMethod function. When calling IDoMethod, instead of providing a command name and target message port name, we provide the name of the ARexx script and allow the port name field to default to the ARexx host. When the IDoMethod is called, the ARexx host finds and runs the designated script. Here is an example of such a call:
IIntuition->IDoMethod(arexx_obj, AM_EXECUTE, "rx_me_demo.rexx", NULL, NULL, NULL, NULL, NULL);
As when sending messages, all the return fields are set to NULL. Just like sending a message, running an ARexx macro is another "asynchronous" function and any feedback will be sent back to our application using an AmigaOS signal. And so the process of dealing with script results is the same: the application obtains an Arexx signal mask, uses Wait function to wait for a response and calls IDoMethod with the "AM_HANDLEEVENT" method if an ARexx signal was received. Finally, results from a called script are sent via a reply message, the reply hook is engaged and the designated reply function called.
Receiving Script Output
When the script finishes, the reply function is called and the output from the script is provided in the "RexxMsg" structure in the following arguments:
Field Name Variable Type Usage rm_Result1 uint32 This argument is the "primary result variable" and indicates success, warning, errors, etc. of calling the script. If Result1 = 0 there was no error; 5=warning, 10=error & 20=severe error. rm_Result2 uint32 This argument is either the error code or pointer to the result string from the script, depending on Result1. If Result1=0, Result2 is a STRPTR; if Result1>0, Result2 is the error number.
The ARexx Script
An ARexx script called by an application can perform a vareity of functions - to interact with the user, to send commands back to the program that called it and/or to interact with other applications via ARexx messages. As a simple scripting language, these are also things that can be added or modified by more experienced users providing a powerful way to extend the functionality of an application.
The simplest way to send output back to the calling application is to provide a string with the ARexx return function at the end of the script. With the primary result ("Result1") equal to zero, the secondary result variable ("Result2") will point to that output string.
With a more sophisticated application, a script could send its own ARexx commands back to our application, thereby automating operations. By default, an ARexx script run by an application is pre-configured to address the port of the application, so it's easy to send messages back to the calling application.
Closing Down ARexx Messaging
Once an application is finished and ready to close down its ARexx port, it is simply a matter of calling the DisposeObject function. This will remove the application's ARexx port and deallocate all related resources, like this:
IIntuition->DisposeObject(arexx_obj);
In case there may still be ARexx messages coming in or which haven't been handled, the "AM_FLUSH" method can be used with IDoMethod to clear them out before disposing the ARexx object, like this:
IIntuition->IDoMethod(arexx_obj, AM_FLUSH);
EXAMPLE ONE: Sending ARexx Messages
The followig is a demonstration program for sending ARexx messages from an application.
This example opens an ARexx port called "AREXX-SENDER", asks the user for the name of a port and a text to send to that port. It is most easily tested by addressing the "WORKBENCH" port (it's almost always there and waiting) and sending it a "HELP" message.
/* ************************************************************ ** ** Created by: Paul Sadlik ** CodeBench 0.23 (17.09.2011) ** ** Project: ARexx-Sender ** ** Version: 4 ** ** An exmple of how to send AN ARexx message from an AmigaOS4 ** C program using OS4's arexx.class. ** ** Date: 02-01-2012 09:19:17 ** ************************************************************ */ #include <string.h> #include <stdio.h> #include <classes/arexx.h> #include <proto/exec.h> #include <proto/intuition.h> #include <proto/dos.h> #include <proto/arexx.h> // Proto for the reply hook function STATIC VOID reply_callback(struct Hook *, Object *, struct RexxMsg *); // Declare our reply hook function static variable STATIC struct Hook reply_hook; // Starting program int main(void) { /* * DECLARE VARIABLES */ // Declare an Object variable for our ARexx object Object *arexx_obj; // Declare string variable to be used for the port message will be sent to char port_name[40]; // Declare string variable for command to sent in ARexx message char cmd[40]; // ARexx error code pointer uint32 errorCode; /* * INITIALIZE & OPEN AREXX CLASS */ // Check whether ARexx library has been opened // (opened automatically when compiled with the -lauto option) if (!ARexxBase) { printf(" ARexx not library opened\n"); return RETURN_FAIL; } printf(" ARexx library opened\n"); // open ARexx class object (creates port) - see autodocs ATTRIBUTES list arexx_obj = IIntuition->NewObject(NULL, "arexx.class", AREXX_HostName, "AREXX-SENDER", // you must set an ARexx port name for this app AREXX_NoSlot, TRUE, // set whether name should be incremented AREXX_ErrorCode, &errorCode, // set variable to receive error code, if any AREXX_ReplyHook, &reply_hook, // the hook structure defines how replies will be handled TAG_DONE); // if ARexx class was successfuly opened, continue if (arexx_obj) { // Define ReplyTo hook structure - to set which function gets called when a reply is received printf(" set up ReplyTo hook\n"); reply_hook.h_Entry = (HOOKFUNC)reply_callback; // pointer to function name to be called reply_hook.h_SubEntry = NULL; reply_hook.h_Data = NULL; /* * GET INPUT FROM USER */ // Ask user for name of port to send message to printf("Please enter the UPPERCASE name of an ARexx port to send a message to.\n"); fgets(port_name,sizeof(port_name),stdin); port_name[strlen(port_name)-1] = '\0'; // strip \n off end of string printf(" Got ARexx port name =>%s<\n",port_name); // Ask user for message to send - some command the host will accept printf("Please enter a message to send.\n"); fgets(cmd,sizeof(cmd),stdin); cmd[strlen(cmd)-1] = '\0'; // strip \n off end of string printf(" Got ARexx message =>%s<\n",cmd); /* * SEND AREXX MESSAGE */ // check whether target message port is found // (FindPort must be enclosed in a Forbid/Permit pair) IExec->Forbid(); if (IExec->FindPort(port_name)) { // target message port found, send message IExec->Permit(); printf(" sending ARexx message\n"); IIntuition->IDoMethod(arexx_obj, AM_EXECUTE,cmd,port_name, NULL, NULL, NULL, NULL); printf(" ARexx message sent, waiting for reply\n"); /* * WAIT FOR HOST TO REPLY TO MESSAGE */ // Obtain wait mask for arexx class ULONG rxsig = 0, signal; IIntuition->GetAttr(AREXX_SigMask, arexx_obj, &rxsig); // Call Wait command to wait for reply to our sent message signal = IExec->Wait(rxsig | SIGBREAKF_CTRL_C); printf(" Program received some message...\n"); /* * HANDLE RESPONSE */ // Did we receive an ARexx event ? if (signal & rxsig) { printf(" ARexx message received... handling...\n"); // Handling the incoming event will cause the reply_hook function to get called IIntuition->IDoMethod(arexx_obj, AM_HANDLEEVENT); printf(" ARexx message handled!\n"); } // Did the user pressed Control-C ? if (signal & SIGBREAKF_CTRL_C) { printf(" Received CTRL-C message.\n"); } } else { // target message port not found IExec->Permit(); printf(" Unable to send ARexx message, port name not found..\n"); } /* * DISPOSE OF AREXX CLASS OBJECT */ printf(" Disposing of ARexx object.\n"); IIntuition->DisposeObject(arexx_obj); } else { IDOS->Printf(" Could not create the ARexx object.\n"); if (errorCode == RXERR_PORT_ALREADY_EXISTS) IDOS->Printf(" Port already exists.\n"); } printf(" Quitting... GOODBYE!.\n"); return RETURN_OK; } /* reply_callback FUNCTION * * This function gets called whenever we get an ARexx reply and then call the "AM_HANDLEEVENT" * arexx class method. In this example, we will see a reply come back from the REXX host when * it has finished with the command we sent. */ STATIC VOID reply_callback(struct Hook *hook UNUSED, Object *o UNUSED, struct RexxMsg *rxm) { printf(" HOOK FUNCTION RUNNING...\n"); /* * DISPLAY CONTENTS OF REPLY MESSAGE */ // Show primary result code (integer) - 0 = success, no error printf(" Result1 =>%ld<\n",rxm->rm_Result1); // display secondary result - succesful or an error code if (rxm->rm_Result1 == 0) // if result code is 0, display any result string sent back printf(" Result2 =>%s<\n",(STRPTR)rxm->rm_Result2); else // if result code > 0, display error code number printf(" Result2 =>%ld<\n",rxm->rm_Result2); }
EXAMPLE TWO: Receiving ARexx Command Messages
The following is a demonstration of an application that can receive a number of ARexx command messages.
This program first declares a "Commands" array that spells out what ARexx commands it will receive, how they are formatted and what functions to call. The program next goes through the typical steps of initializing ARexx and opening an ARexx port called "AREXX-RECEIVER". Then it enters a loop - waiting for incoming messages until it receives a "QUIT" command. Finally, there are functions for each command that the application accepts.
The program from Example One can be used to send commands to this program.
/* ************************************************************ ** ** Created by: Paul Sadlik ** CodeBench 0.23 (17.09.2011) ** ** Project: ARexx-Receiver ** ** Version: 4 ** ** An example of how to receive ARexx messages with an AmigaOS4 ** C program using OS4's arexx.class. ** ** Date: 27-11-2011 ** ** Version: v2 ** ************************************************************ */ #include <string.h> #include <stdio.h> #include <classes/arexx.h> #include <utility/utility.h> #include <proto/exec.h> #include <proto/intuition.h> #include <proto/dos.h> #include <proto/utility.h> #include <proto/arexx.h> // Protos for the ARexx command functions to be called when our commands are received STATIC VOID rexx_Name (struct ARexxCmd *, struct RexxMsg *); STATIC VOID rexx_Version(struct ARexxCmd *, struct RexxMsg *); STATIC VOID rexx_Quit (struct ARexxCmd *, struct RexxMsg *); STATIC VOID rexx_Send (struct ARexxCmd *, struct RexxMsg *); STATIC VOID rexx_Date (struct ARexxCmd *, struct RexxMsg *); STATIC VOID rexx_Help (struct ARexxCmd *, struct RexxMsg *); /* * DECLARE COMMANDS RECOGNIZED BT THIS AREXX HOST */ const uint8 NoOfCmds = 6; //uint8 NoOfCmds; // Declare the array of structure that defines all the functions, parameters, etc. // of the commands that are valid for this program. (This array must neve // be const because arexx.class writes into it). See the "AREXX_Commands" // description in arexx_cl autodocs. STATIC struct ARexxCmd Commands[] = { {"NAME", 0, rexx_Name, NULL, 0, NULL, 0, 0, NULL}, {"VERSION", 1, rexx_Version, NULL, 0, NULL, 0, 0, NULL}, {"QUIT", 2, rexx_Quit, NULL, 0, NULL, 0, 0, NULL}, {"SEND", 3, rexx_Send, "TEXT/F", 0, NULL, 0, 0, NULL}, {"DATE", 4, rexx_Date, "SYSTEM/S", 0, NULL, 0, 0, NULL}, {"HELP", 5, rexx_Help, NULL, 0, NULL, 0, 0, NULL} }; // declare string buffer pointer to be used for creating HELP msg response STRPTR buffer = NULL; // Declare/define a global variable that we're running BOOL running = TRUE; // ARexx error code pointer uint32 errorCode; // Starting program int main(void) { // Declare an Object variable for our ARexx object Object *arexx_obj; /* * INITIALIZE & OPEN AREXX CLASS */ // Check whether ARexx library has been opened // (opened automatically when compiled with the -lauto option) if (!ARexxBase) { printf(" ARexx not library opened\n"); return RETURN_FAIL; } printf("ARexx library opened\n"); // open ARexx class object (creates port) - see autodocs ATTRIBUTES list arexx_obj = IIntuition->NewObject(NULL, "arexx.class", AREXX_HostName, "AREXX-RECEIVER", // set an ARexx port name for this app AREXX_NoSlot, TRUE, // set whether name should be incremented AREXX_ErrorCode, &errorCode, // set variable to receive error code, if any AREXX_Commands, Commands, // provide structure of commands recognized by app AREXX_NoSlot, TRUE, // set to not number port name (there can only be one) TAG_DONE); // if ARexx class was successfuly opened, continue if (arexx_obj) { printf("ARexx port opened\n"); // Determine the number of commands //NoOfCmds = (sizeof(Commands)/sizeof(ARexxCmd)) -1; // Obtain wait mask for arexx class ULONG rxsig = 0, signal; IIntuition->GetAttr(AREXX_SigMask, arexx_obj, &rxsig); /* * START EVENT LOOP */ // continue loop until "running" variable is FALSE do { // Wait for incoming messages from Intution signal = IExec->Wait(rxsig | SIGBREAKF_CTRL_C); printf("Program received some message...\n"); // Did we receive an ARexx event ? if (signal & rxsig) { /* * HANDLE INCOMING MESSAGE */ printf("ARexx message received... handling...\n"); // "Handling" the incoming event will cause command functions to get // called as defined in the "Commands" structure above. IIntuition->IDoMethod(arexx_obj, AM_HANDLEEVENT); printf("ARexx message handled!\n"); } // Did the user pressed Control-C (a.k.a., "Break") ? if (signal & SIGBREAKF_CTRL_C) { printf("Received CTRL-C message.\n"); // set "running" to quit event loop running = FALSE; } } while (running); /* * CLEAR OUT ANY AREXX MESSAGES */ IIntuition->IDoMethod(arexx_obj, AM_FLUSH); /* * DISPOSE OF AREXX CLASS OBJECT */ printf("Disposing of ARexx port.\n"); IIntuition->DisposeObject(arexx_obj); // free any memory used by HELP response string buffer if (buffer) IExec->FreeVec(buffer); } else { IDOS->Printf(" Could not create the ARexx object.\n"); if (errorCode == RXERR_PORT_ALREADY_EXISTS) IDOS->Printf(" Port already exists.\n"); } printf("Quitting... GOODBYE!.\n"); return RETURN_OK; } /* * rexx_Name FUNCTION * * This function gets called when the "NAME" command is received and the * "AM_HANDLEEVENT" arexx class method called. * * This function simply returns this program's name. */ STATIC VOID rexx_Name(struct ARexxCmd *ac, struct RexxMsg *rxm UNUSED) { printf(" Received NAME ARexx message.\n"); // return the program name to the message sender ac->ac_Result = (STRPTR) "ARexxReceiver"; } /* * rexx_Version FUNCTION * * This function gets called when the "VERSION" command is received and the * "AM_HANDLEEVENT" arexx class method called. * * This function simply returns this program's version number. */ STATIC VOID rexx_Version(struct ARexxCmd *ac, struct RexxMsg *rxm UNUSED) { printf(" Received VERSION ARexx message.\n"); // return the program version to the message sender ac->ac_Result = (STRPTR) "4.0"; } /* * rexx_Quit FUNCTION * * This function gets called when the "QUIT" command is received and the * "AM_HANDLEEVENT" arexx class method called. * * This function will cause this program to exit its event loop and quit. */ STATIC VOID rexx_Quit(struct ARexxCmd *ac, struct RexxMsg *rxm UNUSED) { printf(" Received QUIT message.\n"); // Respond that this app is quitting ac->ac_Result = (STRPTR) "ARexxReceiver quitting..."; // Set "running" to quit the main event loop running = FALSE; } /* * rexx_Send FUNCTION * * This function gets called when the "SEND" command is received (with text * to be printed) and the "AM_HANDLEEVENT" arexx class method called. * * This function will print the received text to the console. */ STATIC VOID rexx_Send(struct ARexxCmd *ac, struct RexxMsg *rxm UNUSED) { printf(" Received SEND ARexx message.\n"); printf(" with this text:\n"); // Print the text received from the message sender if (ac->ac_ArgList[0]) printf(" %s\n", (STRPTR)ac->ac_ArgList[0]); } /* * rexx_Date FUNCTION * * This function gets called when the "DATE" command is received and the * "AM_HANDLEEVENT" arexx class method called. * * Without any parameters this command will get a response of this program's * compilation date. With the "SYSTEM" parameter, the response will be the * current system date. */ STATIC VOID rexx_Date(struct ARexxCmd *ac, struct RexxMsg *rxm UNUSED) { printf(" Received DATE ARexx message.\n"); // is the SYSTEM switch specified with the received command message? if (!ac->ac_ArgList[0]) { // return the compilation date. printf(" with no parameters - returning compile date.\n"); ac->ac_Result = (STRPTR) "1-21-12"; } else { // compute system date and store in systemDate buffer printf(" with SYSTEM parameter - returning system date.\n"); // declare the needed variables STATIC TEXT systemDate[LEN_DATSTRING]; struct DateTime dt; // get the system date IDOS->DateStamp(&dt.dat_Stamp); // set datetime struct to format output dt.dat_Format = FORMAT_USA; dt.dat_Flags = 0; dt.dat_StrDay = NULL; dt.dat_StrDate = systemDate; dt.dat_StrTime = NULL; // get string with date IDOS->DateToStr(&dt); // return string with system date ac->ac_Result = systemDate; } } /* * rexx_Help FUNCTION * * This function gets called when the "HELP" command is received and the * "AM_HANDLEEVENT" arexx class method called. * * This function will build a list of all the commands this program is * defined to handle and will return a comma separated list to the message * sender. */ STATIC VOID rexx_Help(struct ARexxCmd *ac, struct RexxMsg *rxm UNUSED) { uint16 cmd_no = 0; printf(" Received HELP ARexx message.\n"); // loop though the commands array, accumulating commands list for(cmd_no = 0; cmd_no < NoOfCmds; ++cmd_no) { if (cmd_no == 0) { // try to add command name to empty string buffer buffer = IUtility->ASPrintf("%s",Commands[cmd_no].ac_Name); if (buffer==NULL) break; } else { // try to add command name to accumulating string buffer buffer = IUtility->ASPrintf("%s, %s",buffer,Commands[cmd_no].ac_Name); if (buffer==NULL) break; } } //done looping through commands - do we have a list? if (buffer==NULL) { printf(" unable to build commands list.\n"); // return list of accepted commands to ARexx message sender ac->ac_Result = (STRPTR) "Unable to build commands list"; } else { printf(" command list = >%s<\n",buffer); // return list of accepted commands to ARexx message sender ac->ac_Result = buffer; // NOTE: memory allocated for buffer will be freed at end of main func } }
EXAMPLE THREE: Calling ARexx Scripts
This application and ARexx script demonstrate a simple case where an application can run an ARexx script and receive results from it.
The Application
This program follows the pattern of Example One above except that it causes an ARexx script to be run instead of sending an ARexx message. Like Example one, it waits for a response and ARexx results are handled by a reply function.
/* ************************************************************ ** ** Created by: Paul Sadlik ** CodeBench 0.23 (17.09.2011) ** ** Project: ARexx-Macro ** ** Version: 2 ** ** An exmple of how to call an ARexx macro script from an ** AmigaOS4 C program using OS4's arexx.class. ** ** Date: 27-11-2011 ** ************************************************************ */ #include <stdio.h> #include <classes/arexx.h> #include <proto/exec.h> #include <proto/intuition.h> #include <proto/dos.h> #include <proto/arexx.h> // Proto for the reply hook function. STATIC VOID reply_callback(struct Hook *, Object *, struct RexxMsg *); // Declare our reply hook function static variable STATIC struct Hook reply_hook; // Starting program int main(void) { /* * DECLARE VARIABLES */ // Declare an Object variable for our ARexx object Object *arexx_obj; /* * INITIALIZE & OPEN AREXX CLASS */ // Check whether ARexx library has been opened // (opened automatically when compiled with the -lauto option) if (!ARexxBase) { printf(" ARexx not library opened\n"); return RETURN_FAIL; } // open ARexx class object (creates port) - see autodocs ATTRIBUTES list arexx_obj = IIntuition->NewObject(NULL, "arexx.class", AREXX_HostName, "AREXX-MACRO", // you must set ARexx port name for this app // AREXX_Commands, NULL, AREXX_NoSlot, TRUE, // set it so the port name is not enumerated AREXX_ReplyHook, &reply_hook, // the hook structure defines how replies will be handled TAG_DONE); // if ARexx class was successfuly opened, continue if (arexx_obj) { // Define ReplyTo hook structure - what function gets called when a reply is received reply_hook.h_Entry = (HOOKFUNC)reply_callback; // pointer to function name to be called reply_hook.h_SubEntry = NULL; reply_hook.h_Data = NULL; /* * RUN AREXX SCRIPT MACRO */ printf("Calling ARexx macro script...\n"); // Run ARexx script in current directory (or REXX: path). // Results will be returned to the "reply_callback" function. IIntuition->IDoMethod(arexx_obj, AM_EXECUTE, "rx_me_demo.rexx", NULL, NULL, NULL, NULL, NULL); printf("Calling macro done.\n"); /* * WAIT FOR RESPONSE */ // Obtain wait mask for arexx class ULONG rxsig = 0, signal; IIntuition->GetAttr(AREXX_SigMask, arexx_obj, &rxsig); // Wait for incoming messages from Intution signal = IExec->Wait(rxsig | SIGBREAKF_CTRL_C); printf("Program received some message...\n"); // Did we receive an ARexx event ? if (signal & rxsig) { printf("ARexx message received...\n"); // "Handling" the incoming event will cause the reply_hook function to get called IIntuition->IDoMethod(arexx_obj, AM_HANDLEEVENT); printf("ARexx message handled!\n"); } // Did the user enter a Control-C (a.k.a., "Break") ? if (signal & SIGBREAKF_CTRL_C) { printf("Received CTRL-C message.\n"); } /* * DISPOSE OF AREXX CLASS OBJECT */ printf("Disposing of ARexx port.\n"); IIntuition->DisposeObject(arexx_obj); } else IDOS->Printf("Could not create the ARexx host.\n"); printf("Quitting... GOODBYE!.\n"); return RETURN_OK; } /* reply_callback FUNCTION * * This function gets called whenever we get an ARexx reply and then call the "AM_HANDLEEVENT" * arexx class method. In this example, we will see a reply come back from the REXX host when * it has finished with the command we sent. */ STATIC VOID reply_callback(struct Hook *hook UNUSED, Object *o UNUSED, struct RexxMsg *rxm) { printf(" HOOK FUNCTION RUNNING...\n"); /* * DISPLAY CONTENTS OF REPLY MESSAGE */ // Show primary result code (integer) - 0 = success, no error printf(" Result1 =>%ld<\n",rxm->rm_Result1); // display secondary result - succesful or an error code if (rxm->rm_Result1 == 0) // if result code is 0, display any result string sent back printf(" Result2 =>%s<\n",(STRPTR)rxm->rm_Result2); else // if result code > 0, display error code number printf(" Result2 =>%ld<\n",rxm->rm_Result2); }
The Script
This is a simple ARexx script that addresses the AmigaOS command line ("COMMAND"), it uses "RequestString" to get a string from the user and then it sends that inpuyt back to the calling application with the "Return" function.
/* Small macro script for the small arexxclass demo */ options results address COMMAND cmdline = 'c:requeststring >t:DemoInput "Sample Macro Script" "This is the macro script running!*N*NPlease enter a response message*Nto send back to your app..."' (cmdline) if open(handle,"t:DemoInput",'r') then do input = readln(handle) call close(handle) address command 'c:delete T:DemoInput' end return input exit