IBM Z DACH - Group home

How to call existing COBOL modules from Python

  

Python is a popular language and with the availability of IBM Open Enterprise SDK for Python on z/OS and customers exploiting it, there might be the need to integrate existing application modules such as COBOL.

Python is written in C and compiled as a 64bit executable. Existing COBOL modules are usually AMODE 31 executables compiled with NODLL option. There are certain restrictions in Language Environment (LE) like direct dynamic calls between 64bit and 31bit modules not being possible or DLL compiled modules cannot dynamically call NODLL compiled modules. To allow calling COBOL means a C compiler is required to do the bridging, which is a prerequisite for installing python modules too, that have parts written in C and don't deliver binary images for z/OS. The setup of the C compiler is not covered in this blog post.

But z/OS 2.4 has introduced AMODE64 and AMODE31 program interoperability which makes it possible to cross the AMODE boundaries by using specific services, in case of 64bit Python calling 31bit COBOL CEL4RO31 needs to be used.

Now having talked about some prerequisites, lets start.

Python itself has the ctypes library which allows Python to interoperate with C. As a side note to the IBM Open Enterprise SDK for Python zIIP enablement feature it should be mentioned, that going the route to call C means that all of the called code is not zIIP eligible. But here is sample Python code how to call a C library:

import ctypes                           
from ctypes.util import find_library    
mydll = ctypes.CDLL("call_cobol.so")    
print("output: ", mydll.call_cobol(2,5))

In order to be able to call a C DLL, the C DLL has to be created. This is done by running the following two commands:

cc -c -o call_cobol.o -Wc,’SO,LIST(lst64.lst),XREF,LP64,DLL,SSCOM,EXPORT,ASCII’ call_cobol.c
cc -o call_cobol.so -V -Wl,DYNAM=DLL,LP64 call_cobol.o 1>ax 2>bx

The ASCII option is required to be able to see the print outputs from C mixed with the output from python, if not used, it will be unreadable on the command line output. This in turn however requires the DLL module and function name in the C source to be specified in EBCDIC which can be done by using the #pragma convert compiler directive.

The redirects to ax and bx files in the second command are just for convenience to redirect the output, because otherwise the linker output will scroll over the Unix session which is unpleasantly large especially if used from ISPF.

In the required C source code, the interface for calling the COBOL DLL using CEL4RO31 service needs to be implemented:

#include <stdlib.h>                                           
#include <stdio.h>                                            
#include <__le_cwi.h>                                         
                                                              
#define MLENGTH    8   /*length of module name string */      
#define FLENGTH    7   /*length of function name string */    
#define ALENGTH    8   /*length of target program argument */ 
                                                              
/* Fixed length structure RO31_CB */                          
typedef struct RO31_cb {                                       
    unsigned int version;                                     
    unsigned int length;                                      
    unsigned int flags;                                       
    unsigned int off_module;                                  
    unsigned int off_func;                                    
    unsigned int off_args;                                    
    unsigned int dll_handle;                                  
    unsigned int func_desc;                                   
    unsigned int ret_gr_buffer[5];                            
    unsigned int retcode;                                     
} RO31_cb;                                                     
                                                              
/* Module name to load */                                       
typedef struct RO31_module{                                     
    int length;                                                 
    char module_name[MLENGTH];                                  
}RO31_module;                                                   
                                                                
/* Function name to query */                                    
typedef struct RO31_function{                                   
    int length;                                                 
    char function_name[FLENGTH];                                
}RO31_function;                                                 
                                                                
/* Arguments of the target program, R1 will point to content */ 
/* when calling target program                               */ 
typedef struct RO31_args{                                       
    int length;                                                 
    int input;                                                  
    int input2;                                                 
}RO31_args;                                                     
                                                                
int call_cobol(int x, int j)                                    
{                                                               
    int i;                                                      
    RO31_cb*           RO31_info;                               
    RO31_args*         RO31_args_p;                             
    RO31_module*       RO31_module_p;                                  
    RO31_function*     RO31_func_p;                                    
    unsigned int       ret_value;                                      
    unsigned int       buf_len;                                        
                                                                       
    buf_len =  sizeof(RO31_cb) + MLENGTH + FLENGTH + ALENGTH + 12+i*99;
    /* Get below the bar storage */                                    
    RO31_info = NULL;                                                  
    CELQGIPB(&buf_len,(void *)&RO31_info,&ret_value);                  
                                                                       
    /* Init the RO31_INFO */                                           
    (*RO31_info).version = 1;                                          
    (*RO31_info).flags = 0xE0000000;                                   
    (*RO31_info).off_module = sizeof(RO31_cb);                         
    (*RO31_info).off_func = (*RO31_info).off_module + MLENGTH + 4;     
    (*RO31_info).off_args = (*RO31_info).off_func + FLENGTH + 4;       
    (*RO31_info).length = sizeof(RO31_cb) + MLENGTH + FLENGTH + ALENGTH + 12;                                                                  
                                                                       
    RO31_args_p = (RO31_args*)(((int)RO31_info)+(*RO31_info).off_args);
    RO31_module_p =                                                    
(RO31_module*)(((int)RO31_info)+(*RO31_info).off_module);              
    RO31_func_p =                                                      
(RO31_function*)(((int)RO31_info)+(*RO31_info).off_func);              
                                                                       
    RO31_args_p->length     = ALENGTH;                                  
    RO31_module_p->length   = MLENGTH;                                  
    RO31_func_p->length     = FLENGTH;                                  
                                                                        
    RO31_args_p -> input = 14;                                          
    RO31_args_p -> input2 = 1234;                                       
                                                                        
    /* Module name is CALLEE31                             */           
    /* Prototype of target program is "int CALLEE(int);"   */           
#pragma convert("IBM-037")                                              
    memcpy((*RO31_module_p).module_name,"COBDLL  ",8);                  
    memcpy((*RO31_func_p).function_name,"COBFUNC ",8);                  
#pragma convert(pop)                                                    
                                                                        
    printf("Call cel4ro31 to run target program in AMODE 31\n");        
                                                                        
    /* Call CEL4RO31()      */                                          
    CEL4RO31((void*)RO31_info);                                         
                                                                        
    if((*RO31_info).retcode == 0){                                      
        /* Set return value     */                                      
        ret_value = RO31_info->ret_gr_buffer[0];                        
        printf("Back to 64bit program, return value is %d\n",ret_value);
    }else{                                                              
        printf("Error in CEL4RO31, retcode is %d\n",                    
        (*RO31_info).retcode);
    }                         
                              
    return x+j;               
}                             

Since the COBOL compiler is capable of creating DLLs from COBOL source, there is no need to have another 31bit C DLL, rather a very simple COBOL DLL can be created and compiled with module name COBDLL into a PDSE:

       CBL DLL,EXPORTALL,RENT                  
       IDENTIFICATION DIVISION.                                   
       PROGRAM-ID. 'COBFUNC'.                                      
       ENVIRONMENT DIVISION.                                      
       CONFIGURATION SECTION.                                     
       INPUT-OUTPUT SECTION.                                      
       FILE-CONTROL.                                              
       DATA DIVISION.                                             
       FILE SECTION.                                              
       WORKING-STORAGE SECTION.                                   
       01 MODULE                       PIC X(8) VALUE 'COBNODLL'.
       LINKAGE SECTION.                                           
       01 SOMEINT                      PIC S9(9) USAGE IS BINARY. 
       01 SOMEINT2                     PIC S9(9) USAGE IS BINARY. 
       01 RETCODE                      PIC S9(9) USAGE IS BINARY. 
       PROCEDURE DIVISION using by value SOMEINT                 
                          by value SOMEINT2                      
                          returning RETCODE.                     
           CALL 'WRAPPER' USING MODULE by reference SOMEINT      
                          by reference SOMEINT2 returning RETCODE
           GOBACK                                                
           .                                                     

In order to do the bridge between DLL compiled COBOL and NODLL compiled COBOL, a wrapper module is required. It simply passes the pointers as a parameter and does dynamic call to the target subroutine. Please note that the number of parameters need to match for the DLL caller, the wrapper and the NODLL target. Plus the parameter types need to match for the DLL caller and the NODLL target.

       CBL NODLL                  
       IDENTIFICATION DIVISION.                                   
       PROGRAM-ID. 'WRAPPER'.                                      
       ENVIRONMENT DIVISION.                                      
       CONFIGURATION SECTION.                                     
       INPUT-OUTPUT SECTION.                                      
       FILE-CONTROL.                                              
       DATA DIVISION.                                             
       FILE SECTION.                                              
       WORKING-STORAGE SECTION.                                   
       LINKAGE SECTION.                                           
       01 MODULE                          PIC X(8).
       01 PARM1                           USAGE IS POINTER. 
       01 PARM2                           USAGE IS POINTER. 
       01 RETCODE                      PIC S9(9) USAGE IS BINARY. 
       PROCEDURE DIVISION using MODULE, PARM1, PARM2
                          returning RETCODE.                      
           CALL MODULE USING PARM1, PARM2 returning RETCODE
           GOBACK                                                 
           .                                                      

And as the actual target of the python call, the COBOL NODLL module.

       CBL NODLL                  
       IDENTIFICATION DIVISION.                                   
       PROGRAM-ID. 'COBNODLL'.                                      
       ENVIRONMENT DIVISION.                                      
       CONFIGURATION SECTION.                                     
       INPUT-OUTPUT SECTION.                                      
       FILE-CONTROL.                                              
       DATA DIVISION.                                             
       FILE SECTION.                                              
       WORKING-STORAGE SECTION.                                   
       LINKAGE SECTION.                                           
       01 SOMEINT                      PIC S9(9) USAGE IS BINARY. 
       01 SOMEINT2                     PIC S9(9) USAGE IS BINARY. 
       01 RETCODE                      PIC S9(9) USAGE IS BINARY. 
       PROCEDURE DIVISION USING BY REFERENCE SOMEINT              
                          BY REFERENCE SOMEINT2                   
                          RETURNING RETCODE.                      
           DISPLAY 'COBNODLL: INT IS ' SOMEINT ' AND ' SOMEINT2   
           MOVE 0 TO RETCODE                                      
           GOBACK                                                 
           .                                                      

To run the sample, it is required to add the PDSE with the COBOL DLL to the STEPLIB environment variable on z/OS.

export STEPLIB=$STEPLIB:GAEBLER.LOAD.PDSE

And execution can be done like this:

/usr/lpp/cyp/v3r11/pyz/bin/python3 call_cobol.py

Successful execution will look like this:

Call cel4ro31 to run target program in AMODE 31
COBNODLL: Int is 000000014 and 000001234         
Back to 64bit program, return value is 0       
output:  7                                     

If the module cannot be found because the module cannot be found in the STEPLIB environment variables PDSEs, the output looks like this:

Call cel4ro31 to run target program in AMODE 31
Error in CEL4RO31, retcode is 4                
output:  7                                     

To sum it up, the simplified calling sequence is like this:

Python with ctypes library -> 64bit C DLL implementing call to CEL4RO31 service -> 31bit COBOL DLL calling statically linked NODLL wrapper -> 31bit COBOL NODLL module

PS: This blog post will be updated later to include the sample code and JCL in a github repository, passing a more complex structure then primitive data types and I am looking into enabling Db2 SQL calls in the called COBOL routine and what is needed to enable it.