Even though IBM Open Enterprise SDK for Python is a 64-bit application, it’s possible to directly call Python from 31-bit COBOL and vice versa. Python excels at specific domains, and it may be easier accomplish a task in Python instead. One example would be AI & Machine Learning, for which there is a package repository for containing hundreds of Python packages called the Python AI Toolkit for IBM z/OS. This blog is going to show you how you can take your 31-bit COBOL application and call Python directly so that you can take advantage of what Python has to offer on z/OS without having to rewrite your COBOL application in Python.
Prerequisites
You will need both of the following products installed & setup:
- IBM Open Enterprise SDK for Python
- For Python 3.12, PTF UI98924 is required
- No prerequisites for Python 3.13
- IBM Enterprise COBOL for z/OS
- One of the following C/C++ Compilers:
Python calling 31-bit COBOL
Since Python on z/OS is a 64-bit application, we cannot directly call 31-bit COBOL functions. Python does have support for calling packages written in C however, and there is a C Language Environment service that allows us to call a 31-bit DLL from a 64-bit DLL called CEL4RO31. So, we can write a Python package in C, which can then call the 31-bit COBOL using CEL4RO31. You can imagine that calling a 31-bit COBOL DLL from Python will then look like this:
Note that because we are introducing a C component, you will need a to setup a C compiler on your system.
There are two parts that are required:
- A COBOL DLL to be called
- A Python package written in C, which calls CEL4RO31
Python Package
To create the Python package, we’re going to be using the package setuptools, which is the standard way of creating Python packages. The directory structure will look as follows:
.
├── cobtest.cbl
└── callcobol/
├── setup.py
└── src/
└── callcobol.c
cobtest.cbl – This is the 31-bit COBOL application that we will compile and call from Python.
callcobol – This is the name of our package.
setup.py – This is a file that’s used by setuptools to define what is contained within our Python package. For example, what is the name of the package, what is its version, who can you contact for help, and what files are required to be built to create the package.
callcobol.c – This is our C file. It will contain the function required by Python and will be the place where we will use CEL4RO31 to call a COBOL DLL.
Let’s look at each of these files. If you prefer, they are also available on Github.
cobtest.cbl:
IDENTIFICATION DIVISION.
PROGRAM-ID. 'COBTEST'.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
DATA DIVISION.
FILE SECTION.
WORKING-STORAGE SECTION.
LINKAGE SECTION.
01 PARAM1 PIC S9(9) USAGE IS BINARY.
01 PARAM2 PIC S9(9) USAGE IS BINARY.
01 RETCODE PIC S9(9) USAGE IS BINARY.
PROCEDURE DIVISION USING BY VALUE PARAM1
BY VALUE PARAM2
RETURNING RETCODE.
ADD PARAM1 to RETCODE
ADD PARAM2 to RETCODE
GOBACK.
This is a very simple COBOL program. All it does is takes in 2 parameters, param1 and param2, and returns the addition of them. The program itself is named COBTEST. This is the program that we will be calling from Python, and can use the following to compile it as a shared library:
cob2 -q"dll" -q"pgmname(longmixed)" -bdll -qexportall cobtest.cbl -o cobtest.so
setup.py:
import os
from setuptools import setup, Extension
from setuptools.command.build_ext import build_ext
from setuptools import setup
def main():
setup(
name="callcobol",
version="1.0.0",
ext_modules=[
Extension(
"callcobol",
sources=[“src/callcobol.c”],
)
],
)
if __name__ == "__main__":
main()
This setup.py simply declares a few things – the name of the modules, version, and where it can find the C source required to build the shared library. The C source will do the actual call to our 31-bit COBOL module.
callcobol.c:
#include <Python.h>
#include <stdlib.h>
#include <stdio.h>
…
int call_cobtest_31bit(int n1, int n2) {
RO31_cb* RO31_info;
RO31_args* RO31_args_p;
RO31_module* RO31_module_p;
RO31_function* RO31_func_p;
…
CEL4RO31((void*)RO31_info);
return ret;
}
static PyObject *call_cobtest(PyObject *self, PyObject *args) {
int n1, n2;
if(!PyArg_ParseTuple(args, "ii", &n1, &n2)){
return NULL;
}
int ret = call_cobtest_31bit(n1, n2);
return Py_BuildValue("i", ret);
}
static PyMethodDef methods[] = {
{"call_cobtest", call_cobtest, METH_VARARGS, "returns sum of 2 inputs"},
{NULL, NULL, 0, NULL}
};
static struct PyModuleDef callcobol_exension_module = {
PyModuleDef_HEAD_INIT,
"callcobol",
NULL,
-1,
methods
};
PyMODINIT_FUNC PyInit_callcobol(void) {
return PyModule_Create(&callcobol_exension_module);
}
This sample has been shrunk down to the important parts due to being quite long. The full source code can be found here, which is based off the CEL4RO31 sample here.
There are 3 key portions of this sample:
PyInit_callcobol – The function that is called when Python loads the module. This creates a Python module using a PyModuleDef struct (callcobol_exension_module) which describes the module - its name and what methods it has.
call_cobtest – This is an intermediate function that Python calls. Its use is to verify that the arguments being passed into the function are correct.
call_cobtest_31bit – The function that uses CEL4RO31 to call our COBOL function.
To use this, we can install this package:
pip3 install ./callcobol
Then run it with the following:
python3 -c "import callcobol; print(callcobol.call_cobtest(5,6))"
This calls our COBOL function with the arguments “5,6”. Since our COBOL function simply returns the addition of its parameters, our Python program will print out 11.
31-bit COBOL calling 64-bit Python
Python has a C API which can be used to embed the Python interpreter inside your COBOL program. The IBM Open Enterprise SDK for Python includes the side deck for these functions, so you can directly call them from your COBOL program if Python’s libpython is contained in your LIBPATH. However, since the AMODE is different, we can use COBOL’s AMODE3164 to swap AMODE’s between programs so that we can call Pythons C API. This graph illustrates how we’ll be doing this:
To do this, we need 2 COBOL programs. The first will be our 31-bit program, and the second will be our 64-bit COBOL program that embeds Pythons C API calls. The one restriction is that to use the Language Environment 31-bit to 64-bit interoperability functionality, our 64-bit COBOL program needs to reside in a dataset.
cobtest.cbl:
IDENTIFICATION DIVISION.
PROGRAM-ID. "COBTEST".
DATA DIVISION.
WORKING-STORAGE SECTION.
77 PGM-NAME PICTURE X(13).
LINKAGE SECTION.
PROCEDURE DIVISION.
*Dynamically call our 64-bit COBOL program
MOVE "DYCALLEE" to PGM-NAME.
CALL PGM-NAME.
STOP RUN.
This is our 31-bit COBOL application that does a dynamic call to a second 64-bit COBOL program named DYCALLEE:
dycallee.cbl:
IDENTIFICATION DIVISION.
PROGRAM-ID. "DYCALLEE".
DATA DIVISION.
WORKING-STORAGE SECTION.
01 pyrun PIC u(80) VALUE z'print("hello world")'.
LINKAGE SECTION.
PROCEDURE DIVISION.
CALL "Py_Initialize"
CALL "PyRun_SimpleString" USING
BY REFERENCE pyrun
END-CALL
CALL "Py_Finalize"
GOBACK.
This is the 64-bit COBOL program which embeds and calls Pythons C API. There are a few steps for this:
Py_Initialize – Initializes the Python interpreter and must be run before using any Python functionality.
PyRun_SimpleString – Runs some directly embedded Python code. This is the easiest way to embed simple programs, but Pythons more direct C API can be used instead, such as importing a given package with PyImport_ImportModule and using that result. You can see examples of this on our Github here, which uses and runs the Pandas package.
Py_Finalize – Shuts down & cleans up all resources that the Python interpreter had created.
After compiling and running this sample, Python should print out “Hello, world”.
To compile & run all of these, you can run the following:
# Compile COBOL 31bit program
cob2 cobtest.cbl -o cobtest
# Compile COBOL 64bit shared lib with Pythons sidedeck
cob2 -q64 -q"pgmname(longmixed)" -bdll dycallee.cbl -o dycallee ${PYTHON_PATH}/lib/libpython3.13.x
# Create a data set to hold our 64-bit COBOL application
YOUR_DS=$USER.DATA.FILE
tso "alloc DSN('${YOUR_DS}') NEW SPACE(10,10) DIR(10) UNIT(SYSDA) RECFM(U) BLKSIZE(4096) DSNTYPE(LIBRARY)"
cp dycallee "//'$YOUR_DS(DYCALLEE)'"
# Run
export STEPLIB=${YOUR_DS}:$STEPLIB
export _IGZ_RUNOPTS="AMODE3164"
./cobtest
The full example can be seen in Github.
That’s all that’s required to do interoperability between 31-bit COBOL and Python. There are also some additional related blogs that you may be interested in:
Python & 64-bit COBOL Interoperability
Using Python with JCL & REXX
Easily calling HLASM from Python