- You can have Python programs which are pure Python.
- You can call C programs that act like Python programs, using Python constructs within the C program
- You can call a C program from Python, and it processes parameters like a normal C program.
- You can pass simple data types such as char, integers and strings.
- You can pass structures. See Python calling C functions – passing structures
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)