Disclaimer: This blog was originally posted by Visda Vokhshoori in 2015 and migrated to the IBM Z and LinuxONE community.
z/OS V2R3 XL C/C++, IBM Mainframe flagship compiler for IBM Z, was announced this week; with features targetting the latest release of IBM Z, z14, as well as features targetting general application development. A complete list of these features is put together in this compiler's data sheet, and can be found here:
http://www-01.ibm.com/support/docview.wss?uid=swg27050023&aid=1
Amongst the zOS V2R3 XL C/C++ new capabilities is the support for a new keyword, __fdptr. This keyword is available for Metal C applications only, and its purpose is to make it possible to call a function as well as make the load module's associated extern and static data available to the caller.
Declaration of such a pointer to, for example, a function that takes two parameters of type int, and returns an int looks like, below:
int (* __fdptr func)(int, int);
To see the significance of the __fdptr keyword, let's take a step back to zOS V1R12 XL C/C++ release of the compiler when support for RENT option was introduced for Metal C. For a system programmer introduction of this feature meant constructing re-enterancy for the user application code. With RENT option the compiler constructed an area of storage, Writeable Storage Area, WSA, and initialized it with extern and static variables, and made the address of this storage area available to all source files in the application code.
A simple example to demonstrate this is:
a.c uses an extern variable and function that is defined in b.c. Using the RENT option the Metal C application constructs and initializes the WSA and makes its address available to all routines in a.c and b.c. The application can now access the variables and calls the function where and when necessary.
>cat a.c
extern int x;
extern int f(void);
int main()
{
if (x != 7) return 66;
if ((x = f()) != 9) return 67;
else return 55;
}
>cat b.c
int x = 7;
int f(void) { x = 9; return x; }
>cat build.sh #in Unix System Services
xlc -qMETAL -S -qRENT a.c
xlc -qMETAL -S -qRENT b.c
/bin/as -mgoff a.s
/bin/as -mgoff b.s
export _LD_SYSLIB="//'CBC.SCCNOBJ'"; /bin/ld -o a.out a.o b.o
./a.out
If an application wants to access the extern variables, and/or function entry points built in a separate module, e.g. a library, or wants to gain access to the function and its associated data through out the life of the application, without having to keep reloading the WSA address, applciation can use the function descriptor capability added to Metal C in z/OS V2R3 XL C/C++.
The z/OS V2.3 Metal C Programming Guide, as well as z/OS V2.3 Language Referenc provide information on this new Metal C feature. Here, I try to build an example based on the description provided in these two books.
A function pointer that is declared with __fdptr keyowrd point to a Metal C function descriptor, which is an internal control block and holds two pieces of information:
-the address of the function to be called when the function pointer is dereferenced,
-the address of the data location or function environment
Using this function descriptor application code can point/call DLL like functions.
Qualifying a pointer with __fdptr keyword tells the compiler that the code intends to build a funciton descriptor, and call a function usin it. The compiler, however, expects to find code that initializes one field to a function address, which could be a function defined in the same or another source file; and the other field to the address of the data location. These should be done in the user code.
We had a declaration of a function descriptor that pointed to a function. It took two parameters of type int and returned an int. We use the same in this blog, and define its two fields.
Here in func_dsc.c I have the main entry point. In main I define the Metal C function descriptor via a call to func_dsc_initializer, which is defined in func_dsc_init.c.
>cat func_dsc.c
void* func_dsc_initializer();
typedef int (* __fdptr metalc_func_dsc)(int, int);
int main() {
metalc_func_dsc func_dsc = (metalc_func_dsc) func_dsc_initializer();
return 0;
}
In func_dsc_init.c I define a structure that holds two fields of type pointer with the intention of having one point to the function address and the other to the function environment, and create a function called func_dsc_initialzer() where the assignment to these two fields are done. func_dsc_initializer() returns the address to the structure back to the main routine where this function is called.
>cat func_dsc_init.c
static struct function_descriptor_t {
void* function_address;
void* function_envirnmt;
} function_descriptor;
int doAdd(int a, int b) {
return a + b;
}
void* func_dsc_initializer() {
function_descriptor.function_address = (void*)&doAdd;
function_descriptor.function_envirnmt = (void*)0;
return &function_descriptor;
}
I compile each source file with METAL compile option, in addition to allow variable and function names of greater than 8 characters, I use the LONGNAME compile option as well. I assemble, link and run, and verify that the return code is 0 as expected.
>cat build.sh
xlc -qMETAL -S -c -qLONGNAME ./func_dsc_init.c
xlc -qMETAL -S -c -qLONGNAME ./func_dsc.c
as -mgoff func_dsc_init.o
as -mgoff func_dsc.o
ld -o func_dsc.out func_dsc.o func_dsc_init.o
./func_dsc.out
echo $?
0
In this blog I clarified how to set up/define the Metal C descriptor.