Copyright (c) Hyperion Entertainment and contributors.

AmiWest Lesson 8

From AmigaOS Documentation Wiki
Jump to navigation Jump to search

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").

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 AmigaOS 4, the requirements to open specific libraries has been further automated with the AmigaOS 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_END);

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_END);

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);


Examples

To download example code click on this link.

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 AmigaOS 4'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_END);
 
	// 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 AmigaOS 4'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_END);
 
	// 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 AmigaOS 4'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_END);
 
	// 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 input 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