Calling C from Rexx – overview

Rexx is a very flexible language. You can call other Rexx programs, you can call z/OS functions, or you can call functions written in C or assembler.

Programming a C function was not straight forward because it involved twisting and bending to get it to work – one of the downsides of being flexible.

See

What options are there?

You can use

  • call function(p1,p2,p3) This gets passed an array of parameters, and has to return data in and an EVALBLOCK. See here.
  • address link program “p1 p2 p3” where “p1 p2 p3” is a literal string which is passed to your program
  • address linkmvs program “p1 p2 p3” where p1, p2, p3 are variable names, whose values are substituted before being passed to the program. If the parameter is not a variable – the parameter is passed through as is. The program gets passed a list
    • length p1 value, p1 value
    • length p2 value, p2 value
    • length p3 value, p3 value
  • address linkpgm program “p1 p2 p3” where p1, p2, p3 is a variable names, whose values are substituted before being passed to the program. If the parameter is not a variable – the parameter is passed through as is. The program gets passed a list of (null terminated values)
  • p1 value
  • p2 value
  • p3 value

If you have code

a = "COLIN A"
address linkpgm CPQUERY "A B C"

The parameters received by the program are

  • “COLIN A”
  • “B”
  • “C”

If you pass a string with hex value in it – it may have a 0x00 – which would look like an end of string. This means the linkpgm is not a good interface for passing non character data.

Passing back values

With address link you can pass back a return code. You can also use Rexx services to get and set variables in the calling rexx programs.

With address linkmvs and address linkpgm you can update the values passed in (this is non trivial). You can set a return code and use Rexx services to get and set variables in the calling rexx programs.

Which should I use?

address link

The simplest, where you have greatest control is address link. Your program gets passed a string which it can process.

address linkmvs and address linkpgm

These are similar, and you need to be careful.

A = "COLINA" 
B = ""
P = "A B C"
(1) address linkmvs CPLINKM "A B C "
(2) address linkmvs CPLINKM "P"
(3) address linkmvs CPLINKM P

With the above, my program produces.

(1)
parm 0 >COLINA<
parm 1 is Null
parm 2 >C<

(2)
parm 0 >A B C<

(3)
parm 0 >COLINA<
parm 1 is Null
parm 2 >C<

If you are using linkpgm or linkmvs, the parameter specified will be substituted (if there is a variable with the same name).

my_stem. = "used for variables"
address linkmvs CPMVS "my_stem. "

If you want to specify a stem to your program so you can set my_stem.0, my_stem.1 etc you will not be using the value you specify, it will be used for variables.0 etc.

Using

address linkmvs CPLINKM "A 'B' C "

gives return code -2.

The return code set in RC may be -2, which indicates that processing of the variables was not successful. Variable processing may have been unsuccessful because the host command environment could not: Perform variable substitution before linking to or attaching the program.

Calling C from Rexx – writing the program.

You can write a Rexx external function in C, have it process the parameters, and return data.

I found writing a program to do this was non trivial, and used bits of C I was not familiar with.

See Calling C from Rexx – accessing variables.

If you use the address… environment, the C program does not pass parameters using the normal “main” interface;

int main( int argc, char **argv ){}

because the parameters are passed in via register 1, and you need a different technique.

Header files

There are header files in SYS1.SIEAHDR.H for the Rexx facilities.

#include <irxefpl.h> // REXX External Functions Parameter List 
#include <irxargtb.h> // REXX Argument Table control block mapping
#include <irxshvb.h> // REXX Shared Variable Request Block
#include <irxenvb.h> // REXX Environment Block
#include <irxexte.h> // REXX access to shared variables etc

Program for address link …

With this, the parameters are passed in one string. The program is given the address and the length of the string.

#pragma runopts(plist(os)) 
struct plistlink {
char ** pData;
int * len;
};
int main() {
struct plistlink * pLink = (struct plistlink *)__R1;
int k = * pLink -> len;
char * pc = *pLink -> pData;
printf("plistlink length %i %.*s\n",k,k,pc);
return 0;
}

The important things to note here are

  • #pragma runopts(plist(os)) this says that the parameters are in standard z/OS parameter format based off register 1.
  • int main() this defines the entry point, and sets up the C environment.
  • __R1 is a special #define which returns the value of register one on entry to the routine.
  • struct plistlink * pLink = (struct plistlink *)__R1; defines the input parameter list.

Program for address linkmvs…

Thanks to David Crayford for his assistance in this.

With address linkmvs the specified parameters are treated as variables and their values substituted. If a variable does not exist with the name, the value is passed as-is.

On entry register 1 points to a list of pointers to data. The last element in the list has the top bit on. Process the list until the top bit is on. For example for the parameter list with values “AAA BB C” (which are not substituted)

  • addr1 -> 0X0003″AAA”
  • addr2 -> 0X0002″BB”
  • *addr3 -> 0x001″C”

where * has the top bit on.

If there are no parameters the parameter list is

  • *addr -> 0x0000…
#pragma runopts(plist(os)) 
#define EOL(a) (((unsigned int)a) & 0x80000000)

struct plist {
short len;
char parm[0];
};
int main() {
for ( i = 0; ; i++ ) {
struct plist *p = __osplist[i];
if ( p->len ==0 ) printf("parm %i is Null\n",i);
if ( p->len > 0 )
printf( "parm %i >%.*s<\n",i, p->len, p->parm );
if ( EOL(__osplist[i]) ) break;
}

Where __osplist is a special #define for the parameters based off register 1.

Return code

In both cases you can use the C return … to return an integer value.

Calling C from Rexx – accessing variables.

Usually your program can access variables in the calling Rexx program. You can get, set or delete variables. It is sometimes more complex that the documentation implies.

If your program is called from ISPF you can also set and get ISPF variables, or use ISPF tables to pass data.

To use the Rexx variable interface the applications need access to the ENVironmentBlock (ENVB).

Getting to the ENVB

The Rexx documentation describes how this is passed in register 0, unfortunately a C program does not have access to register 0 (without writing some assembler glue code).

You can get the environment block by calling IRXINIT and passing the parameter “FINDENVB”.
I was unable to use fetch() to dynamically load IRXINIT. (It may work – I couldn’t get it to). Initially I user the binder to include the IRXINIT code, but this is not good practice as you should use the version from the system you are running on.

A better way based on code from David Crayford (thank you) is

#include <stdio.h>                     
#include <stdlib.h>
#include <string.h>
// specify how the irxinit routine is called
#pragma linkage(tsvt_rexxfunc_t,OS)

int main( int argc, char **argv ) {
struct envblock * pEnv;
int rc;
// the TSO anchor block
struct TSVT {
char dummy[140];
char * irxinit;
};

#pragma pack(1)
// this defines a function with parmlist(char *... )
// returning an int
typedef int tsvt_rexxfunc_t( char *, ... );

typedef struct tsvt TSVT;
// TSTV comes from ikjtstv - but no C header equivalent
// so fake one up.
struct tsvt {
int padding[35];
tsvt_rexxfunc_t *irxinit; // Address of IRXEXEC
};
#pragma pack(reset)

int rc2;
// now chain through the control blocks to find the rexx init
// cvtmap provided in SYS1.SIEAHDR.H(CVT)
#define CVTPTR 16L
struct cvtmap * cvt =*( (struct cvtmap ** ) CVTPTR);
// Comment: I think the *((xxx **)) is so unnatural, and always
// get it wrong.

// TSTV comes from ikjtstv - but no C header equivalent
TSVT * tsvt = cvt->cvttvt;
tsvt->irxinit("FINDENVB ",
0, //
0, // instor plist
0, // 4 user field
0, // 5 reserved
&pEnv , // 6 ->envblock
&rc2); // rexx parm 7 return code
printf(" rc2 %i\n",rc2);
}

Set a symbol

This took me a couple of hours to get right. The documentation is not clear in places.

char dummy; 
struct shvblock shv;
memset(&shv,0,sizeof(shv));
char * pSymbol = "COLINSSYMBOL";
char * pValue ="VALUECP";
shv. shvcode = 'S'; // SHVSTORE; // symbolic name set
shv. shvnama = pSymbol; // a symbolic name
shv. shvnaml = strlen(pSymbol); // Len symbolic name
shv. shvvala = pValue ;
shv. shvvall = strlen(pValue);
int rc3;
struct irxexte * pExte = (struct irxexte * ) pEnv-> envblock_irxexte;
tsvt_rexxfunc_t * fptr = (tsvt_rexxfunc_t *) pExte -> irxexcom;

rc = (fptr)("IRXEXCOM",
&dummy,
&dummy,
&shv,
&pEnv,
rc3);
rc2 = shv.shvret;
printf("post rc %i rc2 %i rc3 %i\n",rc,rc2,rc3);

Notes:

The Rexx header file provides

#define  SHVSTORE  "S"             /* Set variable from given value          */    

but you cannot use this because shv. shvcode expects a char ‘S’ , not “S”.

tsvt_rexxfunc_t is used to define the function at address fptr as a z/OS routine with parameter list in register 1, and the high end bit of the last parameter turned on.

After this executed, and the program returned, the Rexx “say COLINSSYMBOL” printed “VALUECP” so it was a success.

A slightly harder case of setting a value.

I put the above code into a subroutine so I was able to use

setSymbol('S',"COLINSSYMBOL","VALUECP");

You can use option upper case ‘S’ which takes the string you give it, and makes a Rexx variable, or you can use the lower case ‘s’ option, which says it does variables substitution.

Uppercase: (The Direct interface). No substitution or case translation takes place. Simple symbols must be valid REXX variable names (that is, in uppercase and not starting with a digit or a period), but in compound symbols any characters (including lowercase, blanks, and so on) are permitted following a valid REXX stem.

This is not entirely true.

With upper case ‘S’

With setSymbol(“MYKEY.aaa“,”VALUECP”), Rexx displayed “MYKEY.AAA” showing the variable did not exist, even though the call to defined it worked successfully.

With setSymbol(“MYKEY.AAA“,”VALUECP”), Rexx displayed “VALUECPA” showing the correct value.

If you are using ‘S’ then always specify the name in upper-case despite what the documentation says.

With lower case ‘s’

Both

setSymbol("MYKEY.aaa","VALUECPA"); 
setSymbol("mykey.bbb","VALUECPA");

worked.

But it gets more complex…

I had a small Rexx program:

/* REXX */
A = "COLINA"
address link CPLINK "A B C "
drop A
say value("MYKEY.A")
say value("MYKEY.B")
say value("MYKEY.COLINA")
say value("A")
say value("SMYKEY.A")
say value("SMYKEY.B")
say value("SMYKEY.COLINA")

If value(“MYKEY.A”) is “MYKEY.A” then there is no variable with that name.

and my program had

setSymbol('S',"MYKEY.A","BIGSA"); 
setSymbol('S',"MYKEY.B","BIGSB");
setSymbol('s',"SMYKEY.A","SMALLSA");
setSymbol('s',"SMYKEY.B","SMALLSB");

The output had

  1. say value(“MYKEY.A”) -> “BIGSA” from my program
  2. say value(“MYKEY.B”) -> “BIGSB” from my program
  3. say value(“MYKEY.COLINA”) -> “MYKEY.COLINA” not a variable
  4. say value(“SMYKEY.A”) -> “SMYKEY.A” not a variable
  5. say value(“SMYKEY.B”) -> “SMALLSB” set from my program
  6. say value(“SMYKEY.COLINA”) -> “SMALLSA” ‘.A’ was substituted with COLINA as part of the set call
  • Lines 1-3 show that there was no substitution of variables.
  • Lines 4 shows that variable SMKEY.A was not created; SMKEY.COLINA was substituted
  • Line 5 had no substitution and was like line 2
  • Line 6 this is the variable name used.

This means that if you specify a lower case ‘s’, the output may not be as you expect. I would suggest you use upper case ‘S’ unless you know what you are doing.