Learn how to exchange data with REXX programs via the REXX external data queue.
During the years in which Simon Coulter posted on the midrange.com forums, he taught the IBM i community a lot about the REstructured eXtended eXecutor (REXX) language. For example, in the following post in the RPG400-L mailing list, Simon demonstrated the simplicity and ease of use of this powerful scripting language vividly via a couple of interesting example Rexx programs.
What is Rexx? (Was: Outputting a very specific character)
* Subject: What is Rexx? (Was: Outputting a very specific character)
* From: "Simon Coulter" <shc@xxxxxxxxxxxxxxxxx>;
* Date: Fri, 28 Jul 00 10:09:51 +1000
Hello Rob,
You wrote:
>What member type is this, REXX?
Rexx is the SAA Procedure Language. It has been available on the AS/400 since at least VRM130 -- a looooong time. It is singularly underrated and ignored by most AS/400 programmers. It has better string handling than CL, better loop control, supports subroutines, and is available on all IBM platforms and there are even versions of it for Microsquib's so-called operating systems. The PC versions also have an object-oriented dialect (ObjectRexx) and there is a version that works with the Web (NetRexx).
You can imbed CL commands, run SQL, and slice and wrap cheese (OK, I lied about the last part). Rexx is well worth a look. Trivial examples follow:
<-------------- example 1 ----------------->
parse source system .
arg user
say system
select
when system = 'OS/400' then 'dspusrprf usrprf('user')'
otherwise say 'don''t know the system'
end
<------------- example 2 ------------------->
/* Rexx the wonder dog */
tally = 0;
number = 0;
say ' ';
say date('w') center("System Calculator", 54) date();
say ' ';
say "Type an arithmetical expression, RESET, or press Enter to end.";
say ' ';
do n = 1 to 400;
pull expression;
if expression = '' then exit(0);
interpret 'tally='expression;
if expression = 'RESET' | ,
expression = 'R' | ,
expression = 'r' then tally = 0;
say expression " = " tally;
end
Regards,
Simon Coulter.
The sources of these examples are available as hosttype.rexx and wonder-dog.rexx, respectively. To run the example REXX programs, upload them to your QSYS.LIB file system and run them via the Start REXX Procedure (STRREXPRC) command. For example:
STRREXPRC SRCMBR(HOSTTYPE) SRCFILE(MYSRCLIB/MYSRCFILE) PARM(A_USER)
In the first example, the PARSE SOURCE instruction (one of the variants of the PARSE instruction) is used to retrieve the name of the system on which the program is running. Please refer to Chapter 5, Parsing, of the REXX/400 Reference. In the second example, Simon demonstrated a calculator program composed of only 16 lines of REXX code. The central task of the calculator program, evaluating a string expression at runtime and executing the evaluated string as REXX instructions, is achieved by the INTERPRET instruction (see Chapter 3, Keyword Instructions, of the REXX/400 Reference).
In a 2002 post, Re: Rexx/400 and CL was Encryption packages and Encryption Algorithm, Simon mentioned the REXX external data queue as an effective and easy-to-use method of receiving data from REXX programs.
Re: Rexx/400 and CL was Encryption packages and Encryption Algorithm
* Subject: Re: Rexx/400 and CL was Encryption packages and Encryption Algorithm
* From: "Simon Coulter" <shc@xxxxxxxxxxxxxxxxx>;
* Date: Thu, 17 Jan 02 11:17:49 +1100
Hello Howard,
You wrote:
All of the examples in the books show me how to get variables into Rexx, but either I am blind or brain-dead, but I can not get Rexx to pass a variable back into the CL, here is what I am doing, a bit jumbled at the moment out of frustration. How do I get the result back into the CL?
Since you just need a random number you can invoke the C-runtime srand() and rand() functions, or the ILE CEERAN0 function. These functions have been discussed here before (and in the RPG list) so search the archives for details. If you really want to learn something about Rexx then read on . . .
Rexx is intended to be used as the job control language. It has support for calling other programs and for invoking CL commands and retrieving values from CL via RTNVAL(*YES) parameters. The Rexx Programmer's Guide discusses returning data from C to Rexx. That method should work with any ILE language. It also discusses using the Rexx queue to return data from non-ILE languages. In all these cases Rexx is the driver.
You are using CL as the driver and invoking Rexx via STRREXPRC. The PARM keyword maps directly to the Rexx ARG list which implicitly means it is input only (think of it as pass by VALUE) so no changes in Rexx will be reflected in CL variables specified on PARM.
When using a non-Rexx program as the driver I believe there are two ways of getting the data.
1/ If RETURN is used in a Rexx procedure that was NOT invoked via Rexx CALL or as a Rexx function then the RETURN value will be sent in message CPF7CFF. However that restricts the return value to a number between -32768 and 32767.
You are not using a seed value but you are setting a maximum limit on the range of random numbers. Your limit is a six-digit number based on time (you might want to rethink that because I don't think that's what you intended). Any RETURN value is likely to be too big for the range allowed by CPF7CFF.
2/ Put the return value on the Rexx queue (using PUSH) and retrieve it using the QREXQ API.
Since Rexx can be the CPP for a CL command it would be nice if we could use RTNVAL(*YES) for the parameter and code something like:
CMD PROMPT('Test Rexx Command')
PARM KWD(RANDOM) TYPE(*CHAR) LEN(6) RTNVAL(*YES)
however RTNVAL(*YES) is not supported when Rexx is used as the CPP.
Regards,
Simon Coulter.
The Rexx External Data Queue
The following paragraphs quoted from Chapter 9, Using the Rexx External Data Queue, of the REXX/400 Programmer's Guide give a brief introduction to the REXX external data queue.
The REXX external data queue provides a way to temporarily hold data which REXX, and any suitably tailored application programs, can use. The data on the queue is accessible by and visible to users as lines or as buffers. A buffer is a sub-grouping of lines within a queue, and lines are character strings of arbitrary lengths. Each line can contain up to 32,767 characters. [1] The individual characters have no special meaning or effect to REXX. The external data queue can be used to replace user input.
The data on the queue can be used by REXX programs and user-written programs in an arbitrary manner. Thus, the REXX queue services can be used as a way of exchanging data between programs, providing a device for inter-program communication.
A REXX external data queue comes into existence when a job is started and persists until the job is ended. All programs that run under the same job have access to that external data queue. [2]
The following operations can be performed on the REXX external data queue:
- A line can be placed at the end of the current queue buffer.
- A line can be placed at the front of the current queue buffer.
- A line can be retrieved from the front of the queue.
- The number of lines on the queue can be queried.
- A new queue buffer can be created.
- A queue buffer can be removed.
- The entire queue can be cleared.
These operations are made available directly to a REXX program through REXX instructions and CL commands [3]. The same operations can be performed within other programming languages through the queue services application programming interface (QREXQ). For more information on the QREXQ API, see the REXX/400 Reference. [4]
Notes
[1] Also note that, according to the REXX/400 Reference, lines that are longer than 32,767 bytes will be truncated with no error indication. And the maximum size of all the data contained in the queue is 15.5MB.
[2] In IBM i, the REXX external data queue is a temporary, automatically extensible MI space object named QREXXDATAQ. An MI space pointer addressing the start of the associated space of QREXXDATAQ is placed into the Process Communication Object (PCO) at offset hex 0100 (256 bytes into the PCO) at the beginning of an IBM i job. Therefore, you can find the QREXXDATAQ object easily via a DMPSYSOBJ *PCS command. The QREXXDATAQ space object of an IBM i job is destroyed at the end of the job. (At least at VRM540, a QREXXDATAQ space is not reserved for reusing by new jobs.)
[3] There is no REXX instruction or built-in function to directly operate REXX queue buffers. To operate queue buffers, a REXX program needs to issue the Add REXX Buffer (ADDREXBUF) command and/or the Remove REXX Buffer (RMVREXBUF) command.
[4] The QREXQ API can be invoked from programs written in any language to achieve any type of REXX queue operation. It is documented in details in the Queuing Interfaces section of Chapter 9, AS/400 System Interfaces, of the REXX/400 Reference.
The QREXQ API is a UDSS (User Domain/System State) OPM program that takes five parameters. The RPG prototype of QREXQ is the following:
/** * RPG prototype of the QREXQ API. */ d qrexq pr extpgm('QREXQ') * Requested function code d func * Data buffer d buf * Length / Number d len_num 10u 0 * Operation flag d op_flg 5u 0 * Return code d rc 5u 0 |
The type of a requested REXX queue operation is specified by QREXQ's first parameter, requested function code (func) and optionally its fourth parameter, operation flag (op_flg). The following table maps each queue-related REXX instruction (or built-in function) to the corresponding invocation to the QREXQ API with proper func parameter (and optionally op_flg parameter).
Rexx Instruction (or Built-In) |
Parameter func (and op_flg) of QREXQ |
Function Description |
QUEUE |
'A',0 |
Place a given line at the end of the current queue buffer. |
PUSH |
'A',1 |
Place a given line at the front of the queue. |
PULL PARSE UPPER PULL |
P |
Retrieve a line from the front of queue or read from STDIN. [1] |
QUEUED() [2] |
Q |
Query the number of lines in the queue. |
address command 'ADDREXBUF &bufnum' |
N |
Create a new queue buffer. |
address command 'RMVREXBUF 'bufnum address command 'RMVREXBUF *ALL' |
R |
Remove a specific buffer or all buffers from the queue. |
Notes
[1] If the REXX queue is currently empty, the QREXQ API will not read from STDIN (the REXX input stream). Instead, it will set the return code (@var rc) parameter to value 2 to indicate the queue is empty.
[2] QUEUED() is a REXX built-in function.
Examples of Using the QREXQ API
This section shows you some trivial RPG and CL examples of using the QREXQ API:
- 1.Program rexq01.rpgle (which is also the Command Processor Program (CPP) of the RTVREXQLN command) returns the number of lines in the REXX queue.
- 2.Program rexq02.rpgle pulls a line from the front of the REXX queue.
- 3.The OPM CL program rexq04.clp pulls all lines from the REXX queue by calling the REXQ02 program. REXQ04 uses the RTVREXQLN command to retrieve the current number of lines in the REXX queue.
/** * @file rexq01.rpgle * * Query the number of lines in the Rexx queue. */
/** * RPG prototype of the QREXQ API. */ d qrexq pr extpgm('QREXQ') * Requested function code d func * Data buffer d buf * Length / Number d len_num 10u 0 * Operation flag d op_flg 5u 0 * Return code d rc 5u 0
/** * Prototype of this program */ d rexq01 pr extpgm('REXQ01') d 11p 0
d func s d num s 10u 0 d op_flg s 5u 0 d rc s 5u 0
d rexq01 pi d queued 11p 0
/free qrexq( func : *inlr : num : op_flg : rc );
queued = num; *inlr = *on; /end-free |
/** * @file rexq01.rpgle * * PUSH one line from the QREXXDATAQ. */ h dftactgrp(*no)
/** * RPG prototype of the QREXQ API. */ d qrexq pr extpgm('QREXQ') * Requested function code d func * Data buffer d buf * Length / Number d len_num 10u 0 * Operation flag d op_flg 5u 0 * Return code d rc 5u 0
/** * Prototype of this program * * Parameters: * - msg. Data of the line pulled from the REXX data queue (INTPUT). * - buflen. Length of the data buffer (INPUT). * - msglen. On return, it is set to the length of the pulled line * data, or -1 if the REXX data queue is empty (OUTPUT). */ d rexq02 pr extpgm('REXQ02') d msg d buflen 11p 0 d msglen 11p 0
* System built-in _memcpy d memcpy pr * extproc('__memcpy') d target * value d source * value d length 10u 0 value
d func s d msg2 s d len s 10u 0 d op_flg s 5u 0 d rc s 5u 0
d rexq02 pi d msg d buflen 11p 0 d msglen 11p 0
/free len = buflen; qrexq( func : msg : len : op_flg : rc ); if rc = 5; // Data buffer too small msg@ = %alloc(len); qrexq(func : msg2 : len : op_flg : rc); memcpy(%addr(msg) : msg@ : buflen); dealloc msg@; endif; msglen = len;
if rc = 2; msglen = -1; endif;
*inlr = *on; /end-free |
dcl &line *char 16 dcl &len *dec len(11 0) value(16) dcl &msglen *dec len(11 0) dcl &num *dec len(11 0)
rtvrexqln &num pull: if cond(&num *eq 0) then(goto end) call REXQ02 (&line &len &msglen) sndpgmmsg &line chgvar &line ' ' rtvrexqln &num goto pull
end: endpgm |
It is handy to wrap a CL command, say RTVREXQLN, into REXQ01 to return the number of lines in the REXX queue. The command definition of RTVREXQLN is the following:
CMD PROMPT('Get Number of Rexx Q Lines') PARM KWD(NUM) TYPE(*DEC) LEN(11 0) RTNVAL(*YES) |
Compile the RTVREXQLN command and specify REXQ01 as its CPP. For example:
CRTCMD CMD(RTVREXQLN) PGM(REXQ01) SRCFILE(SRCLIB/SRCFILE) ALLOW(*BPGM *IPGM *BREXX *IREXX *BMOD *IMOD)
Now let's add a few lines of data to the REXX external data queue in FIFO order via a simple REXX program:
queue date() queue time() queue address() |
Run the above REXX program via the Start REXX Procedure (STRREXPRC) command. Then call REXQ04 to pull all lines from the REXX queue. The output of REXQ04 might look like the following:
4 > call rexq04 13 Dec 2013 11:01:57 COMMAND |
LATEST COMMENTS
MC Press Online