Mixing it up with C and Assembler on z/OS…
Calling an assembler program from a C program.
Parameter passing conventions
On z/OS (or MVS if you are old enough), programs written in ‘traditional’ mainframe languages such as assembler, Cobol and PL/1 typically pass parameters to each other using the following convention:
Basically register 1 points to a list of addresses and each address in the list points to the actual data for that parameter. The end of the address list is usually (but does not have to be) indicated by the high order bit being on in the last entry. If the last entry is not indicated then only a fixed number of parameters can be passed since the called program has no way of knowing how long a variable length list is.
Since the last entry is indicated, it is very easy to pass a variable number of parameters to a called program.
There is of course NO type checking of the data between the calling and called program. If you pass the wrong type or length of data in a parameter or if you do not pass enough parameters and the called program does not check the end of the list then it is highly likely that things are going to go wrong!
C on the other hand has all sorts of type checking for the parameters being passed between the calling and called program. Typically (and I am sure you already know this) in C, the type and form of the parameters to be passed to another called program are defined in a header (H) file. Something like this:
int mysubrtn(int pntr2Parm1,int Parm2);
Which says the module called ‘mysubrtn’ accepts two parameters, both integers (fullwords on z/OS) and returns an integer.
If you try to call ‘mysubrtn’ with any other types of parameters or with a different number of parameters the code will not compile.
You can tell C that the called program accepts a variable number of parameters by including three periods after the fixed number of parameters (one in this case):
int mysubrtn(int pntr2Parm1,...);
You do however then lose the type checking for the variable parameters.
The main issue when it comes to calling an assembler program is that C does it’s own thing in the way it passes parameters between programs. Basically the problem boils down to the fact that by default, C and assembler do it differently. So the first step in calling an assembler program from a C program is telling the C compiler to generate code that is compatible with the operating systems way of doing things!
The #pragma linkage statement
The #pragma linkage statement tells the C compiler that when the named program is called, it should use the standard OS convention to pass the parameters to the called program. The statement looks like this:
Typically the named program is the main entry point (usually the CSECT for an assembler program) for the target program but it does not have to be. In fact there is nothing to stop a program, especially an assembler program, from having multiple entry points declared with each taking different parameters and/or performing different functions. so long as you code a #pragma linkage entry for each entry point, it will work fine.
On it’s own, this is enough to be able to call an assembler program from your C code. In the absence of anything else the default is that the C code expects the assembler code to return an int (fullword), typically a return code. And of course, there is no parameter checking since the C code currently knows nothing about the parameters the assembler code requires.
So you could do something like this:
int rc; rc=myasmrtn();
int rc; int parm1; parm1=99; rc=myasmrtn(parm1);
However you could also call it like this:
The difference between the last two examples is that the first one creates a data structure like this:
While the second one creates a data structure like this:
Adding parameter checking for the assembler program to your C code
You do this in exactly the same way that you would do it for a C program, that is by defining a model call statement, either inline or in a header (H) file. For example:
int myasmrtn(int parm1);
Since you have declared the number and type of parameters the the assembler code expects, the following will no longer compile:
int rc; int parm1; rc=myasmrtn();
int rc; int parm1; rc=myasmrtn(&parm1);
int rc; char parm1="some string"; rc=myasmrtn(parm1);
The cools part though is that since we are now defining what parameters the program will accept, we can also define what it will return. For example, instead of returning a simple return code in an int, it could return the address of a work area:
Lets assume that you have an assembler program that can accept a variable number of parameters with the first parameter indicating the call type which in turn dictates what the following parameters will be. Now we want to call that program from our C code and just to be safe we’d like to use some form of parameter checking. Just to make things easier, we have the source of the assembler program so we can modify it to help us achieve this goal.
In assembler the executable code typically starts with the CSECT (or RSECT) statement and this also defines the main entry point for a program. You can however define other labels within the code as alternative entry points like this:
ENTRY ALTENTRY MYASMRTN CSECT Some code ALTENTRY DS 0H Some other code
Or even this:
ENTRY ALTENTRY MYASMRTN CSECT ALTENTRY DS 0H Some common code
Notice that in the second example, we have two entry points that both invoke the SAME code. As far as the executable assembler code is concerned this is the same entry point, the actual code would not care. But from a C point of view we can now do something like this:
#pragma linkage(myasmrtn,OS) #pragma linkage(altentry,OS) int myasmrtn(int calltype, int parm1, int parm2); int* altentry(int calltype, char *parm2, int parm2)
When calling ‘myasmrtn’ the C compiler will check that I am passing three integers (fullwords). When calling ‘altentry’, the C compiler will check that I am passing an int, a char string (actually the address of the string) and another int. In addition, when I call ‘myasmrtn’ I expect an integer back (such as a return code). When I call ‘altentry’ I expect the address of an int (although it could actually by anything, even a getmained area) back.
From the assembler perspective, it does not care about all that but from the C perspective, we now have some basic parameter checking at compile time.