That’s strange – the compile worked.

I was setting up a script to compile some C code in Unix Services, and it worked – when I expected the bind to fail because I had not specified where to find a stub file.

How to compile the source

I used a shell script to compile and bind the source. I was surprised to see that it worked, because it needed some Linkedit stubs from CSSLIB. I thought I needed

export _C89_LSYSLIB=”CEE.SCEELKEX:CEE.SCEELKED:CBC.SCCNOBJ:SYS1.CSSLIB”

but it worked without it.

The script

name=irrseq 

export _C89_CCMODE=1
# export _C89_LSYSLIB="CEE.SCEELKEX:CEE.SCEELKED:CBC.SCCNOBJ:SYS1.CSSLIB"
p1="-Wc,arch(8),target(zOSV2R3),list,source,ilp32,gonum,asm,float(ieee)"
p5=" -I. "
p8="-Wc,LIST(c.lst),SOURCE,NOWARN64,XREF,SHOWINC "

xlc $p1 $p5 $p8 -c $name.c -o $name.o
# now bind it
l1="-Wl,LIST,MAP,XREF "
/bin/xlc $name.o -o irrseq -V $l1 1>binder.out

The binder output had

XL_CONFIG=/bin/../usr/lpp/cbclib/xlc/etc/xlc.cfg:xlc 
-v -Wl,LIST,MAP,XREF irrseq.o -o./irrseq
STEPLIB=NONE
_C89_ACCEPTABLE_RC=4
_C89_PVERSION=0x42040000
_C89_PSYSIX=
_C89_PSYSLIB=CEE.SCEEOBJ:CEE.SCEECPP
_C89_LSYSLIB=CEE.SCEELKEX:CEE.SCEELKED:CBC.SCCNOBJ:SYS1.CSSLIB

Where did these come from? – I was interested in SYS1.CSSLIB. It came from xlc config file below.

xlc config file

By default the compile command uses a configuration file /usr/lpp/cbclib/xlc/etc/xlc.cfg .

The key parts of this file are

* FUNCTION: z/OS V2.4 XL C/C++ Compiler Configuration file
*
* Licensed Materials - Property of IBM
* 5650-ZOS Copyright IBM Corp. 2004, 2018.
* US Government Users Restricted Rights - Use, duplication or
* disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
*

* C compiler, extended mode
xlc: use = DEFLT
...* common definitions
DEFLT: cppcomp = /usr/lpp/cbclib/xlc/exe/ccndrvr
ccomp = /usr/lpp/cbclib/xlc/exe/ccndrvr
ipacomp = /usr/lpp/cbclib/xlc/exe/ccndrvr
ipa = /bin/c89
as = /bin/c89
ld_c = /bin/c89
ld_cpp = /bin/cxx
xlC = /usr/lpp/cbclib/xlc/bin/xlc
xlCcopt = -D_XOPEN_SOURCE
sysobj = cee.sceeobj:cee.sceecpp
syslib = cee.sceelkex:cee.sceelked:cbc.sccnobj:sys1.csslib
syslib_x = cee.sceebnd2:cbc.sccnobj:sys1.csslib
exportlist_c = NONE
exportlist_cpp = cee.sceelib(c128n):cbc.sclbsid(iostream,complex)
exportlist_c_x = cee.sceelib(celhs003,celhs001)
exportlist_cpp_x = ...
exportlist_c_64 = cee.sceelib(celqs003)
exportlist_cpp_64 = ...
steplib = NONE

Where the _x entries are for xplink.

It is easy to find the answer when you know the solution.

Note:

Without the export _C89_CCMODE=1

I got

IEW2763S DE07 FILE ASSOCIATED WITH DDNAME /0000002 CANNOT BE OPENED
BECAUSE THE FILE DOES NOT EXIST OR CANNOT BE CREATED.
IEW2302E 1031 THE DATA SET SPECIFIED BY DDNAME /0000002 COULD NOT BE
FOUND, AND THUS HAS NOT BEEN INCLUDED.
FSUM3065 The LINKEDIT step ended with return code 8.

Compiling in 64 bit

It was simple to change the script to compile it in 64 bit mode, but overall this didn’t work.

p1="-Wc,arch(8),target(zOSV2R3),list,source,lp64,gonum,asm,float(ieee)" 
...
l1="-Wl,LIST,MAP,XREF -q64 "

When I compiled in 64 bit mode, and tried to bind in 31/32 bit mode (omitting the -q64 option) I got messages like

 IEW2469E 9907 THE ATTRIBUTES OF A REFERENCE TO isprint FROM SECTION irrseq#C DO
NOT MATCH THE ATTRIBUTES OF THE TARGET SYMBOL. REASON 2
...
IEW2469E 9907 THE ATTRIBUTES OF A REFERENCE TO IRRSEQ00 FROM SECTION irrseq#C
DO NOT MATCH THE ATTRIBUTES OF THE TARGET SYMBOL. REASON 3

IEW2456E 9207 SYMBOL CELQSG03 UNRESOLVED. MEMBER COULD NOT BE INCLUDED FROM
THE DESIGNATED CALL LIBRARY.
...
IEW2470E 9511 ORDERED SECTION CEESTART NOT FOUND IN MODULE.
IEW2648E 5111 ENTRY CEESTART IS NOT A CSECT OR AN EXTERNAL NAME IN THE MODULE.

IEW2469E THE ATTRIBUTES OF A REFERENCE TO … FROM SECTION … DO
NOT MATCH THE ATTRIBUTES OF THE TARGET SYMBOL. REASON x

  • Reason 2 The xplink attributes of the reference and target do not match.
  • Reason 3 Either the reference or the target is in amode 64 and the amodes do not match. The IRRSEQ00 stub is only available in 31 bit mode, my program was 64 bit amode.

IEW2456E SYMBOL CELQINPL UNRESOLVED. MEMBER COULD NOT BE INCLUDED
FROM THE DESIGNATED CALL LIBRARY.

  • The compile in 64 bit mode generates an “include…” of the 64 bit stuff needed by C. Because the binder was in 31 bit, it used the 31 bit libraries – which did not have the specified include file. When you compile in 64 bit mode you need to bind with the 64 bit libraries. The compile command sorts all this out depending on the options.
  • The libraries used when binding in 64 bit mode are syslib_x = cee.sceebnd2:cbc.sccnobj:sys1.csslib. See the xlc config file above.

IEW2470E 9511 ORDERED SECTION CEESTART NOT FOUND IN MODULE.
IEW2648E 5111 ENTRY CEESTART IS NOT A CSECT OR AN EXTERNAL NAME IN THE MODULE.

  • Compiling in 64 bit mode, generates an entry point of CELQSTRT instead of CEESTART, so the binder instructions for 31 bit programs specifying the entry point of CEESTART will fail.

Overall

Because IRRSEQ00 only comes in a 31 bit flavour, and not a 64 bit flavour, I could not call it directly from a 64 bit program, and I had to use a 32 bit compile and bind.

Python calling C functions

  1. You can have Python programs which are pure Python.
  2. You can call C programs that act like Python programs, using Python constructs within the C program
  3. You can call a C program from Python, and it processes parameters like a normal C program.

This blog post is about the third, calling a C program from Python, passing simple data types such as char, integers and strings.

I have based a lot of this on the well written pyzfile package by @daveyc.

The glue that makes it work is the ctypes package a “foreign function library” package.

Before you start

The blog post is called “Python calling C functions”. I tried using a z/OS stub code directly. This is not written in C, and I got.

CEE3595S DLL ... does not contain a CELQSTRT CSECT.

Which shows you must supply a C program.

The C program that I wrote, calls z/OS services. These must be defined like (or default to)

#pragma linkage(...,OS64_NOSTACK)     

Getting started

My C program has several functions including

int query() {  
return 0;
}

The compile instructions said exportall – so all functions are visible from outside of the load module.

You access this from Python using code like

lib_file = pathlib.Path(__file__).parent / "pySMFRealTime.so"
self.lib = ctypes.CDLL(str(lib_file))
...
result = self.lib.query()

Where

  • lib_file = pathlib.Path(__file__).parent / “pySMFRealTime.so” says get the file path of the .so module in the same directory as the current Python file.
  • self.lib = ctypes.CDLL(str(lib_file)) load the file and extract information.
  • result = self.lib.query() execute the query function, passing no parameters, and store any return code in the variable result

Passing simple parameters

A more realistic program, passing parameters in, and getting data back in the parameters is

int conn(const char* resource_name,  // input:  what we want to connect to
char * pOut, // output: where we return the handle
int * rc, // output: return code
int * rs, // output: reason code
int * debug) // input: pass in debug information
{
int lName = strlen(resource_name);
if (*debug >= 1)
{
printf("===resource_namen");
printHex(stdout,pFn,20);
}
...
return 0;
}

The Python code has

lib_file = pathlib.Path(__file__).parent / "pySMFRealTime.so"
self.lib = ctypes.CDLL(str(lib_file))
self.lib.conn.argtypes = [c_char_p, # the name of stream
c_char_p, # the returned buffer
ctypes.POINTER(ctypes.c_int), # rc
ctypes.POINTER(ctypes.c_int), # rs
ctypes.POINTER(ctypes.c_int), # debug
]
self.lib.conn.restype = c_int

The code to do the connection is

def conn(self,name: str,):
token = ctypes.create_string_buffer(16) # 16 byte handle
rc = ctypes.c_int(0)
rs = ctypes.c_int(0)
debug = ctypes.c_int(self.debug)
self.token = None
retcode = self.lib.conn(name.encode("cp500"),
token,
rc,
rs,
debug)
if retcode != 0:
print("returned rc",rc, "reason",rs)
print(">>>>>>>>>>>>>>>>> connect error ")
return None
print("returned rc",rc, "reason",rs)
self.token = token
return rc

The code does

  • def conn(self,name: str,): define the conn function and pass in the variable name which is a string
  • token = ctypes.create_string_buffer(16) # 16 byte handle create a 16 byte buffer and wrap it in ctypes stuff.
  • rc = ctypes.c_int(0), rs = ctypes.c_int(0), debug = ctypes.c_int(self.debug) create 3 integer variables.
  • self.token = None preset this
  • retcode = self.lib.conn( invoke the conn function
    • name.encode(“cp500”), convert the name from ASCII (all Python printable strings are in ASCII) to code page 500.
    • token, the 16 byte token defined above
    • rc, rs, debug) the three integer variables
  • if retcode != 0: print out error messages
  • print(“returned rc”,rc, “reason”,rs) print the return and reason code
  • self.token = token save the token for the next operation
  • return rc return to caller, with the return code.

Once I got my head round the ctypes… it was easy.

The C program

There are some things you need to be aware of.

  • Python is compiled with the -qascii compiler option, so all strings etc are in ASCII. The code name.encode(“cp500”), converts it from ASCII to EBCDIC. The called C program sees the data as a valid EBCDIC string (null terminated).
  • If a character string is returned, with printable text. Either your program coverts it to ASCII, or your Python calling code needs to convert it.
  • Your C program can be compiled with -qascii – or as EBCDIC(no -qascii)
    • Because Python is compiled in ASCII, the printf routines are configured to print ASCII. If your program is compiled as ASCII, printf(“ABCD”) will print as ABCD. If your program is compiled as EBCDIC printf(“ABCD”) will print garbage – because the hex values for EBCDIC ABCD are not printable as ASCII characters.
    • If your program is compiled as ASCII you can define EBCDIC constants.
      • #pragma convert(“IBM-1047”)
      • char * pEyeCatcher = “EYEC”; // EBCDIC eye catcher for control block
      • #pragma convert(pop)

Python calling C functions – passing structures

I’ve written how you can pass simple data from Python to a C function, see Python calling C functions.

This article explains how you can pass structures and point to buffers in the Python program. it extends Python calling C functions. It allows you to move logic from the C program to a Python program.

Using complex arguments

The examples in Python calling C functions were for using simple elements, such as Integers or strings.

I have a C structure I need to pass to a C function. The example below passes in an eye catcher, some lengths, and a buffer for the C function to use.

The C structure

typedef struct querycb {                                                         
char Eyecatcher[4]; /* Eye catcher offset 0 */
uint16_t Length; /* Length of the block 4 */
char Rsvd1[1]; /* Reserved 6 */
uint8_t Version; /* Version number 7 */
char Flags[2]; /* Flags 8 */
uint16_t Reserved8; // 10
uint32_t Count; // number returned 12
uint32_t lBuffer; // length of buffer 16
uint32_t Reservedx ; // 20
void *pBuffer; // 24
} querycb;

The Python code

# create the variables
eyec = "EYEC".encode("cp500") # char[4] eye catcher
l = 32 # uint16_t
res1 = 0 # char[1]
version = 1 # uint8_t -same as a char
flags = 0 # char[2]
res2 = 0 # uint16_t
count = 0 # uint32_t
lBuffer = 4000 # uint32_t
res3 = 0 # uint32_t
# pBuffer # void *
# allocate a buffer for the C program to use and put some data
# into it
pBuffer = ctypes.create_string_buffer(b'abcdefg',size=lBuffer)
# cast the pBuffer so it is a void *
pB = ctypes.cast(pBuffer, ctypes.c_void_p)
# use the struct.pack function. See @4shbbhhiiiP below
# @4 is 4 bytes, the eye catcher
# h half word
# bb two char fields res1, and version
# hh two half word s flags and res2
# iii three integer fields. count lBuffer and res3
# P void * pointer
# Note pB is a ctype, we need the value of it, so pB.value
p = pack("@4shbbhhiiiP", eyec,l,res1,version,flags,
res2,count,lBuffer,res3,pB.value)

#create first parm
p1 = ctypes.c_int(3) # pass in the integer 3 as an example
# create second parm
p2 = ctypes.cast(p, ctypes.c_void_p)

# invoke the function

retcode = lib.conn(p1,p2)

The C program

int conn(int * p1, char * p2) 
// int conn(int max,...)
{
typedef struct querycb {
char Eyecatcher[4]; /* Eye catcher 0 */
uint16_t Length; /* Length of the block 4 */
char Rsvd1[1]; /* Reserved 6 */
uint8_t Version; /* Version number 7 */
char Flags[2]; /* Flags 8 */
uint16_t Reserved8; // 10
uint32_t Count; // number returned 12
uint32_t lBuffer; // length of buffer 16
uint32_t Reservedx ; // 20
void *pBuffer; // 24
} querycb;

querycb * pcb = (querycb * ) p2;

printf("P1 %i\n",*p1);
printHex(stdout,p2,32);
printf("Now the structure\n")
printHex(stdout,pcb -> pBuffer,32);
return 0 ;
}

The output

P1 3
00000000 : D8D9D7C2 00200001 00000000 00000000 ..... .......... EYEC............
00000010 : 00000FA0 00000000 00000050 0901BCB0 ...........P.... ...........&....
Now the structure
00000000 : 61626364 65666700 00000000 00000000 abcdefg......... /...............
00000010 : 00000000 00000000 00000000 00000000 ................ ................

Where

  • EYEC is the passed in eye catcher
  • 00000FA0 is the length of 4000
  • 00000050 0901BCB0 is the 64 address of the structure
  • abcdefg is the data used to initialise the buffer

Observations

It took me a couple of hours to get this to work. I found it hard to get the cast, and the ctype…. functions to work successfully. There may be a better way of coding it, if so please tell me. The code works, which is the objective – but there may be better more correct ways of doing it.

Benefits

By using this technique I was able to move code from my C program to set up the structure needed by the z/OS service into C. My C program was just parse input parameters, set up the linkage for the z/OS service, and invoke the service.

If course I did not have the constants available from the C header file for the service, but that’s a different problem.

C calling an “assembler” function, setting the high order bit on, and passing parameters.

Since days of old when knights were bold, the standard parameter list to call an assembler function was to pass the addresses of the parameters, and set on the top bit of the address for the last address.
This way the called function knows how many parameters have been passed, and you do not need to pass a parameter count.

Setting the high order bit on, for the last parameter

I had to ask for help to remind me how to do it from C, so I could call “Assembler” functions.

You can get C to do this using

#pragma linkage(IRRSPK00 ,OS)

Example

The syntax of the routine from the RACF callable services documentation is

CALL IRRSPK00 (Work_area,
ALET, SAF_return_code,
ALET, RACF_return_code,
ALET, RACF_reason_code,
ALET, Function_code,
Option_word,
Ticket_area,
Ticket_options,
Ticket_principal_userid,
Application_Id
)

Here is part of my C program.

#pragma linkage(IRRSPK00 ,OS)
...
long SAF_RC,RACF_RC,RACF_RS;
SAF_RC=0 ;
long ALET = 0;
// ticket options needs special treatment, see below
int Ticket_options = 1;
int * pTO = & Ticket_options;

rc=IRRSPK00(
&work_area,
&ALET , &SAF_RC,
&ALET , &RACF_RC,
&ALET , &RACF_RS,
&ALET ,&Function_code,
&Option_word,
&ticket, // length followed by area
&pTO,
&userid,
&appl
);

If you use #pragma linkage(IRRSPK00 ,OS) it sets on the high order bit. You pass the address of the parameters. I just used &variable, there are other ways.

Passing variables

Most of the parameters are passed by address for example &ALET inserts the address of the variable, conforming to the z/OS standards.

There is a field Ticket_principal_userid which is the name of a 10-byte area that consists of a 2-byte length field followed by the userid id for whom a PassTicket operation is to be performed followed by an 8-byte PassTicket field.

I defined a structures for each variable like

struct {
short length;
char value[8];
} ticket;

In the program I used &ticket.

Ticket option

The documentation says

Ticket_options: The name of a fullword containing the address of a binary bit string that identifies the ticket-specific processing to be performed.

It took me a while to understand what this meant. I had to use

int Ticket_options = 1; 
int * pTO = & Ticket_options;

and use it

int Ticket_options = 1; 
int * pTO = & Ticket_options;
...
&ticket, // length followed by area
&pTO,

Whoops R_GenSec (IRRSGS00 or IRRSGS64): Generic security API interface

I had great problems getting this to work. The documentation said

The address double words from 31 bit callers should have the first word filled with
zeros and the second word filled with the 31 bit address. Sub-parameter addresses will be in the format of the AMODE of the caller.

I do not know what this means. When I coded it as expected I got

CEE3250C The system or user abend S0E0 R=00000029

Which means invalid ALET supplied.

I converted the program to 64 bit and it still failed!

Getting into supervisor mode and other hard things in C, is easy.

Some functions in z/OS need a user to be in a privileged state such as key zero or supervisor state. Writing the code in assembler has been pretty easy, but writing it in a C program has been hard.

For example you could write a function in assembler, and call it. This has the cross language challenges.

I recently found an easy way – just reuse some code from Zowe. This is open source, so you need to follow the license

This program and the accompanying materials are made available under the terms of the Eclipse Public License v2.0 which accompanies this distribution, and is available at https://www.eclipse.org/legal/epl-v20.html

SPDX-License-Identifier: EPL-2.0

Copyright Contributors to the Zowe Project.

The code uses __asm() Inline assembly statements (IBM extension).

The functions are

  • ddnameExists
  • wtoMessage
  • wtoPrintf3
  • atomicIncrement compare and swap
  • testauth
  • extractPSW
  • supervisorMode or back to problem state
  • setKey
  • getExternalSecurityManager
  • getCVT
  • getATCVT
  • getIEACSTBL
  • getCVTPrefix
  • getECVT
  • getTCB
  • getSTCB
  • getOTCB
  • getASCB
  • getASXB
  • getASSB
  • getJSAB
  • getCurrentACEE
  • getFirstChildTCB
  • getParentTCB
  • getNextSiblingTCB
  • resolveSymbolBySyscall Input: A symbol starting with & and not ending with .
  • resolveSymbol Input: A symbol starting with & and not ending with .
  • lots of saf functions see here
  • loadByName
  • getDSAB Data Set Association Block
  • isCallerLocked
  • isCallerCrossMemory

Some of these need to be APF protected, so although it is easy to use the above code, you may still need to get the load library APF authorised, and the code approved.


For example

Get into supervisor state

The code here.

int supervisorMode(int enable){
// Use MODESET macro for requests
int currentPSW = extractPSW();
if (enable){
if (currentPSW & PROBLEM_STATE){
__asm(ASM_PREFIX
" MODESET MODE=SUP \n"
:
:
:"r0","r1","r15");
return TRUE;
} else{
return FALSE; /* do nothing, tell caller no restore needed */
}
} else{
if (currentPSW & PROBLEM_STATE){
return TRUE; /* do nothing, tell user was in problem state */
} else{
__asm(ASM_PREFIX
" MODESET MODE=PROB \n"
:
:
:"r0","r1","r15");
return FALSE;
}
}
}

To compile it I used

// SET LOADLIB=COLIN.LOAD 
//DOCLG EXEC PROC=EDCCB,INFILE='ADCD.C.SOURCE(C)',
// CPARM='OPTF(DD:COPTS)'
//* CPARM='LIST,SSCOMM,SOURCE,LANGLVL(EXTENDED)'
//COMPILE.ASMLIB DD DISP=SHR,DSN=SYS1.MACLIB
//COMPILE.COPTS DD *
LIST,SOURCE
aggregate(offsethex) xref
SEARCH(//'ADCD.C.H',//'SYS1.SIEAHDR.H')
TEST
ASM
RENT ILP32 LO
OE
NOMARGINS EXPMAC SHOWINC XREF
LANGLVL(EXTENDED) sscom dll
DEFINE(_ALL_SOURCE)
DEBUG
/*
...

You need to specify

  • //COMPILE.ASMLIB for the the assembler macro libraries.
  • and the compiler option ASM which enables inlined assembly code inside C/C++ programs.

I was all so easy, once I had been told about it.

How do I create a header file from an assembler macro?

You can easily do this using standard JCL

//COLINZP  JOB 1,MSGCLASS=H 
// JCLLIB ORDER=CBC.SCCNPRC
//DSECT EXEC PROC=EDCDSECT,
// INFILE=COLIN.C.SOURCE(ASMMAC2),
// OUTFILE=COLIN.C.H.VB(TESTASM),
// DPARM='EQU(BIT)'
//ASSEMBLE.SYSLIB DD
// DD DISP=SHR,DSN=HLA.SASMMAC1
//ASSEMBLE.SYSIN DD *
DUMMY DSECT
ABC DS 10F
END
//DSECT.EDCDSECT DD SYSOUT=*
/*

This produced in //DSECT.EDCDSECT

#pragma pack(packed)                 

struct dummy {
int abc[10];
};

#pragma pack(reset)

You can point //DSECT.EDCDSECT to where you want your header file stored.

The syntax of the DPARM is defined here.

The comment option

By default comments are produced. One comment was

short int adplasid; /* Address space identifier @P2C */

which went past column 75.
With NOCOMMENT it produced

short int adplasid;

and all the lines were less than 72 characters long.

With COMMENT(@) it ignores anything beyond the specified string

short int adplasid; /* Address space identifier */

But some lines were still longer than 75 characters.

Using the default COMMENT

This worked first time – ish. When I tried to use the generated header file, it failed with a syntax error, because comments had wrapped over line boundary. I had to edit the output and remove the comments causing the problem.

Having fixed the obvious comment wraps, I then found comments extended into columns 72-80.

This meant, as far as C was concerned, the comment was still open, so the field on the next line was taken as part of the comment, and I was getting messages like

CCN3022 "adplserv" is not a member of "struct abdpl". 

I edited the file and fixed up these comments.

Plan b) was to specify the option NOCOMMENT and no comments are produced at all.

It could not find a macro…

The DSECT I was trying to generate referenced BIT0. My challenge was to find where this was defined!

  • If you use ISPF 3.4 on SYS1.MACLIB you can use the command SRCHFOR BIT0 and it searches all the members for the specified value. Tab to the “Prompt” heading, and press enter. The members with the value will be at top of the list.
  • You can use ISPF 2, and specify ‘SYS1.MACLIB(IEZ*)’ to display only a subset of the members – beginning with IEZ. Tab to the “prompt” heading, as above
  • You can use ISPF 3.15 Extended Search-For Utility. Specify DS name ‘SYS1.MACLIB’, and * for all members. Specify your search argument and press enter. You get one file with every occurrence it found and the member name. This was the easiest way for looking for what wanted.

The definition I wanted was not in SYS1.MACLIB, but I found it in SYS1.AMODGEN.

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.