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.

C includes ” is not the same as <

The one line description: I found the difference between #include <a.h> and #include “a.h”.

I had problems finding compiling some sample C code which included

#include <__iew_api.h>

I was getting

WARNING CCN3296  #include file <__iew_api.h> not found.

There were a couple of problem. The length of the name __iew_api is more than 8 characters, so will not be in a PDS or PDSE. I found it in /usr/include.

In my JCL for comping C programs I had

LSEARCH(/usr/include/)

But still it was not found. After a cup of tea and walk down to the shops I wondered if it was the include that was causing the problems.

I changed it to

#include "__iew_api.h" 

and it found it. The documentation says

The file_path can be an absolute or relative path. If the double quotation marks are used, … the preprocessor adds the directory of the including file to the list of paths to be searched for the included file.
If the double angle brackets are used, … the preprocessor does not add the directory of the including file to the list of paths to be searched for the included file.

What’s C.E.E.1 and which function is this address in?

I had an abend in a module created from C programs, and wanted to know which function had the problem.

In the old days, each function had an eye-catcher and a compile date at the top of each function, so it was easy to scroll upwards towards the start of the dump until you came across the eye-catcher.

And then we had XPLINK… I think the XP link stands for eXtra Performance. The output of the C compiler changed to make it the code more efficient, especially with Java functions. For example:

  • before XPLINK, a call to a function would save all of the registers, update the save area chain – call the module… return and restore the registers afterwards. Some functions were as simple as set this value to 0 – and the overhead of the call was many times the cost of the instructions. The overhead was reduced by only saving what needed to be saved, and passing more parameters in registers.
  • by moving constant data, such as character strings, out of the mainline instructions, meant that sometimes fewer pages were needed for instructions – and so fewer pages were needed in the hardware instruction cache, and so may be faster. Moving the function name and compile time into the “data page” and so into the data cache, was part of this.

The function name etc is available – just not in an obvious place.

What does the code look like?

Meta data is stored in Program Prolog Areas (PPAs)

In the listing, at the start of each XPLINK function is “C.E.E.1”

00000E70    00C300C5 00C500F1 00000080 000000A0a *.C.E.E.1.....

At offset 00000080 from 00000E70 is a block of storage (PPA1) which identifies this function.

0006D8  02           =AL1(2)            Version 
0006D9 CE =AL1(206) CEL signature
...
0006F0 0003 AL2(3),C'pit'
0006F8 FFFFF9C0 =F'-1600' Offset to Entry Point Marker
  • At offset 0 is 0x02… so you know you are in the right control block
  • At offset 2 is 0xCE
  • At offset 10 is the code length
  • At offset 18 is 2 bytes of length, followed by the function name, possibly with up to 3 bytes of padding to align the next field on a 4 byte work boundary.
  • (Sometimes,) after this, the offset will vary because the name length is variable, is a field like 0XFF… such as FFFFFA60. This is – 0x5a0 (-1440). The address of this section (the 0x02) minus this value gets you to the C.E.E.1. This does not always seem to work!

The start of the function “pit” is at x6d8 – 1600 = x6d8 -x640 = x98

At this address in the listing was

                                *  void pit() 
000098 00C300C5 DC =F'12779717' XPLink entrypoint marker
00009C 00C500F1 DC =F'12910833'
0000A0 00000640 DC =F'1600'
0000A4 00000080 DC =F'128'
0000A8 pit DS 0D
* Start of executable code
0000A8 9049 4780 STM r4,r9,1920(r4) .....

Note the value’s 1600 and -1600 tie up.

So not too difficult to find the name of the function.

Using a 31 bit parameter list in a 64 bit C program.

You can use svc99() to create //DDNAME entries in a job. I used it to dynamically allocate

//COLIN DD SYSOUT=H

from within my C program.

You cannot use a 31 bit C program in a 64 bit C program

If you try to fetch and use a 31 bit C program in a 64 bit program you get

EDC5256S An AMODE64 application is attempting to fetch() an AMODE31 executable. (errno2=0xC4070068)

It is hard to make this to work, because of 64 bit parameters do not work in a 31 bit program. The save area’s are complex. Overall it is easier to just call a 64 bit program from a 64 bit program and do not try to use a 31 bit. You can do it, but you need some assembler glue code.

svc99 works in both 31 and 64 bit modes because at bind time

  • the 31 bit program includes a 31 bit version of svc99
  • the 64 bit program includes a 64 bit version of svc99.

The 64 bit program has come glue code to enable it to call the 31 bit program.

Using a 64 bit program with a 31 bit parameter list

Using svc99() was pretty easy from a 31 bit C program, but the documentation says The __S99parms structure must be in 31-bit addressable storage, which is a bit of a challenge.

For the 31 bit C program, the code is like

int main () { 
SVC99char1( sysoutClass, DALSYSOU,'A')
SVC99string(ddname,DALDDNAM,"COLIN")
struct __S99struc parmlist;
struct __S99rbx rbx =
{
.__S99EID = "S99RBX",
.__S99EOPTS =0xC4, // issue message before return, and use wto
.__S99EVER =0x01}; // version
memset(&parmlist, 0, sizeof(parmlist));
void *tp[3]= { /* array of text pointers */
&sysoutClass,
&ddname,
0};
int mask = 0x80000000;
memcpy(&p->tp[2],&mask,4); // make it a null address with high bit on
parmlist.__S99RBLN = sizeof(parmlist);
parmlist.__S99VERB = 01; /* verb for dsname allocation */
parmlist.__S99FLAG1 = 0x4000; /* do not use existing allocation */
parmlist.__S99TXTPP = tp; /* pointer to pointer to text units */
parmlist.__S99S99X =&rbx; // pointer to extension
int rc;
rc = svc99(&parmlist);

Where

  • SVC99char1(..) and SVC99string(…) define the parameters for SVC 99
  • struct __S99rbx rbx defines the request block extension which indicates errors to be reported on the job log
  • void tp[3] is the array of parameter for svc 99. The value can be null. The last must have the top bit on, so mask is copied in.
  • parmlist is the svc99 parameter list which points to the other data: the definition, and the request block extension.

Getting it to work in a 64 bit C program.

You have to build a parameter list where all the parameters are in 31 bit storage.

Generate the 31 bit structure

I defined a structure to contain the 31 bit elements, then used ___malloc31 to allocate 31 bit storage for it.

struct pl31 { 
struct __S99struc parmlist;
struct __S99rbx rbx;
// 31 bit equivilants of the SVC 99 parameters
struct sysoutClass sysoutClass;
struct ddname ddname ;
void * __ptr32 tp[3]; /* array of text pointers */
} pl31;

char * __ptr32 p31 =(char * __ptr32) __malloc31(sizeof(pl31));

The __ptr32 in char * __ptr32 p31= tells C this is a pointer to 31/32 bit storage.

Set up the parameter list

struct pl31 *  p ; 
p = (struct pl31 * ) p31; // set up 31 bit pointer to data
memcpy(&p->rbx,&rbx,sizeof(rbx)); // copy from static to 31 bit area
memset(&p->parmlist, 0, sizeof(p->parmlist)); // initialise to 0
p->parmlist.__S99RBLN = sizeof(p->parmlist);
p->parmlist.__S99VERB = 01; // verb for dsname allocation
p->parmlist.__S99VERB = 07; // display
p->parmlist.__S99FLAG1 = 0x4000; // do not use existing allocation
p->parmlist.__S99TXTPP =& p->tp ; // pointer to pointer to text units
p->parmlist.__S99S99X =& p->rbx ; // pointer to extension

Create the svc99 definitions in 31 bit storage

The macros SVC99string etc generate code like

struct ddname{ 
short key;
short count;
short length;
char value[sizeof("COLIN ")]; }
ddname = {0x0001,1,sizeof("COLIN ")-1,"COLIN "};

so within the 31 bit structure I could use

struct ddname ddname;

to allocate a structure the same shape as the original definition.

I then used a macro SVC99COPY(…); which copies the data from the original, static, definition into the 31 bit structure.

#define SVC99COPY(name) memcpy(&p->name,&name,sizeof(name));

Creating and initialising the rbx

Because the 31 bit storage is dynamically allocated, you cannot use structure initialisation like:

struct pl31 { 

struct __S99struc parmlist;
struct __S99rbx rbx =
{
.__S99EID = "S99RBX",
.__S99EOPTS =0xC4, // issue message before return, and use wto
.__S99EVER =0x01 // version
};

...
} pl31;

I defined rbx in the mainline code, and copied it into the pl31 structure once it had been allocated.

Set up the pointer to the definitions

p->tp[0]  = &p-> ddname      ; 
p->tp[1] = &p-> sysoutClass ;
int mask = 0x80000000;
memcpy(&p->tp[2],&mask,4);

The array of tp[] has to be initialised at run time, rather than at compile time because the 31 bit storage is dynamically allocated.

Invoke the svc99()

rc = svc99(&p->parmlist);

So overall – not too difficult.

Making the code bimodal

If you want to make a function which can be used from 31 or 64 bit programs. You need to provide two versions of the code. One compiled as a 64 bit program, the other as a 31 bit program. You could have

  • myprog() for 31 bit programs and
  • myprog64() for 64 bit programs.

or you could specify

#ifdef __LP64
#pragma map (myprog, "myprog64")
#else
#pragma map (myprog, "myprog31")
#endif
...
myprog()

At bind time you need to include the correct code, myprog64 or myprog31.

If could just use the one name, and have two object libraries, and specify the library in JCL, for example

//BIND.OBJLIB  DD DISP=SHR,DSN=COLIN.OBJLIB31 
//BIND.SYSIN DD *
INCLUDE OBJLIB(MYPROG)
NAME...

If you have used the wrong library at bind time, you may only find out a run time.

If you use the #pragma map to force it to use object names, you will find the problem at bind time, because myprog31 or myprog64 will not be found.