As part of playing with Python on z/OS I found you can call a z/OS Unix Services load module ( a .so object) from Python. It can also use a load module in a PDSE.
What sort is it?
A load module on z/OS can be used on one of two ways.
- Load it from steplib or PATH environment variable (which uses dllopen under the covers), call the function, and pass the parameters The parameters might be a byte string ( char * in C terms), a Unicode string, integer etc. You return one value, for example an integer return code.
- As part of a package where you use the Python “import package” statement. This gets loaded from PYTHONPATH, the current directory, and other directories (but not steplib). The parameters passed in are Python Objects, and you have to use Python functions to extract the value. You can return a complex Python object, for example a character string, return code and reason code.
This article is on the first case.
In both cases, the values passed in are in ASCII. If you use printf to display data, the printf treats your data as ASCII.
There is a good article on Python ctypes , a foreign function library for Python.
My initial program was
int add_it(int i, int j) { return i+j; }
I compiled it with a bash script
wc64=”-Wc,SO,LIST(lst64),XREF,LP64,DLL,SSCOM,EXPORT”
cc -c -o add.o ${wc64} add.c
cc -o add -V -Wl,DYNAM=DLL,LP64 add.o 1>ax 2>bx
This created a load module object “add”. Note: You need the EXPORT to make the entry point(s) visible to callers.
My python program was
import ctypes
from ctypes.util import find_library
zzmqe = ctypes.CDLL(“add”)
print(“mql”, zzmqe.add_it(2,5))
When this ran it produced
mql 7
As expected. To be consistent with Unix platforms, the load module should be called add.so, but “add” works.
Using strings is more complex
I changed the program (add2) to have strings as input
int add_one(char * a, char *b) { printf("a %s\n",a); printf("b %s\n",b); return 2 ; }
and a Python program, passing a byte string.
import ctypes
from ctypes.util import find_library
zzmqe = ctypes.CDLL("add2")
print("mql", zzmqe.add_one(b'abc',b'aaa'))
This ran and gave output
-@abc–@aaa-mql 2
This shows that Python has converted the printf output ASCII to EBCDIC, and so the “a” and “b” in the printf statements are converted to strange characters, and the \n (new line) is treated as hex rather than a new line.
When I compiled the program with ASCII (-Wc…ASCII), the output from the Python program was
a abc
b aaa
mql 2
Displaying the data as expected.
Using a load module in a PDSE.
The JCL
//COLINC3 JOB 1,MSGCLASS=H,COND=(4,LE)
//S1 JCLLIB ORDER=CBC.SCCNPRC
// SET LOADLIB=COLIN.C.REXX.LOAD
// SET LIBPRFX=CEE
//COMPILE EXEC PROC=EDCCB,
// LIBPRFX=&LIBPRFX,
// CPARM=’OPTFILE(DD:SYSOPTF),LSEARCH(/usr/include/),RENT’,
// BPARM=’SIZE=(900K,124K),RENT,LIST,XREF,RMODE=ANY,AMODE=64‘
//COMPILE.SYSOPTF DD DISP=SHR,DSN=COLIN.C.REXX(CPARMS)
// DD *
EXPORT,LP64
/*
//COMPILE.SYSIN DD *
int add_one(int i, int j)
{
return i+j;
}
//COMPILE.SYSLIB DD
// DD
// DD DISP=SHR,DSN=COLIN.C.REXX
//BIND.SYSLMOD DD DISP=SHR,DSN=&LOADLIB.
//BIND.SYSLIB DD DISP=SHR,DSN=CEE.SCEEBND2
// DD DISP=SHR,DSN=CEE.SCEELKED
// DD DISP=SHR,DSN=CEE.SCEELIB
//BIND.OBJLIB DD DISP=SHR,DSN=COLIN.C.REXX.OBJ
//BIND.SYSIN DD *
NAME ADD3(R)
/*
In Unix services
export STEPLIB=”COLIN.C.REXX.LOAD“:$STEPLIB
The python program
import ctypes
from ctypes.util import find_library
zzmqe = ctypes.CDLL(“ADD3”)
print(zzmqe)
print(dir(zzmqe))
print(“steplib”, zzmqe.add_one(2,5))
This gave
steplib 7
Byte string and character strings parameters
I set up a C program with a function COLIN
#pragma export(COLIN)
int COLIN(char * p1, int p2) {
printf(" p1 %4.4s\n",p1);
printf(" p2 %i\n",p2);
return 8;
}
This was compiled in Unix Services, and bound using JCL into a PDSE load library as member YYYYY.
I used a shell to invoke the Python script
export LIBPATH=/u/tmp/python/usr/lpp/IBM/cyp/v3r10/pyz/lib/:$LIBPATH export STEPLIB=COLIN.C.REXX.LOAD:$STEPLIB python3 dll.py
where the Python dll.py script was
import ctypes testlib = ctypes.CDLL("YYYYY") name =b'CSQ9' i = 7 zz = testlib.COLIN(name,i) print("return code",zz)
This displayed
p1 CSQ9
p2 7
return code 8
The name, a binary string CSQ9, was passed as a null terminated ASCII string (0x43535139 00).
When a string name = “CSQ9” was passed in, the data was in a Unicode string, hex values
00000043 00000053 00000051 00000039 00000000
You need to be sure to pass in the correct data (binary or Unicode), and be sure to handle the data in ASCII.
Is this how ‘import’ works?
This is different process to when a module is used via a Python import statement. If you passed in b’ABCD’ to a C extension which has been “imported” this would be passed as a Python Object, rather than the null terminated string 0x’4142434400′.