24 min read

> "No language is an island. The moment you accept that COBOL doesn't have to do everything — that it can collaborate with C for performance, Java for web interfaces, and assembler for low-level system access — you unlock a modernization path that...

Chapter 26: Inter-Language Communication

"No language is an island. The moment you accept that COBOL doesn't have to do everything — that it can collaborate with C for performance, Java for web interfaces, and assembler for low-level system access — you unlock a modernization path that doesn't require rewriting a single line of working COBOL." — Priya Kapoor, systems architect, GlobalBank

Derek Washington had been at GlobalBank for six months when he encountered the date conversion problem. The core banking system stored dates in a packed decimal Julian format inherited from a 1987 design. The new web portal — written in Java — expected ISO 8601 strings. The existing COBOL date conversion routine worked flawlessly, but it was COBOL. The web portal was Java. How do you bridge that gap?

"You could rewrite the date logic in Java," Maria Chen said, "and hope you get every edge case right on the first try. Or you could call the COBOL routine that has been tested in production for thirty years."

Derek blinked. "You can call COBOL from Java?"

"You can call almost anything from almost anything," Priya Kapoor said, "if you understand the interface conventions. That is what this chapter is about."

Inter-language communication — sometimes called mixed-language programming or language interoperability — is one of the most practical skills in mainframe development. Modern enterprise systems are polyglot by nature: COBOL for business logic, C for utility libraries, Java for web tiers, assembler for performance-critical routines. Understanding how these languages talk to each other is essential for any COBOL developer working in a contemporary environment.


26.1 Why Inter-Language Communication Matters

Before diving into syntax, let us understand why you would want COBOL to communicate with other languages.

The Pragmatic Case

Each language brings strengths that others lack:

Language Strength in Enterprise Context
COBOL Business logic, decimal arithmetic, batch processing, readability
C System utilities, string manipulation, performance-critical algorithms, portable libraries
Java Web interfaces, REST/SOAP services, modern frameworks, developer availability
Assembler Hardware-level optimization, system services, interrupt handling, maximum performance

Rather than rewriting proven COBOL logic in Java (risky, expensive) or writing new web services in COBOL (possible but awkward), inter-language communication lets each language do what it does best.

Real-World Scenarios

Scenario 1: COBOL calling C. Your COBOL program needs to generate a UUID, compress data, or parse XML. A C library already does this. Call it from COBOL rather than writing the logic in COBOL.

Scenario 2: Java calling COBOL. A new web application needs access to business rules encoded in COBOL programs. Rather than extracting and reimplementing the rules, the Java tier calls the COBOL program directly.

Scenario 3: COBOL calling assembler. A performance-critical loop processes millions of records. An assembler routine handles the inner loop at machine-code speed, called from COBOL for each batch.

Scenario 4: COBOL as a web service. An existing COBOL program is exposed as a REST API through a wrapper layer, allowing mobile apps and partner systems to access mainframe functionality.

⚖️ The Modernization Spectrum: Inter-language communication sits at the pragmatic center of the modernization spectrum. It is more modern than maintaining everything in COBOL, but less disruptive than a full rewrite. It acknowledges that the existing COBOL code has value — decades of tested business logic — while embracing modern technologies for new capabilities.


26.2 Calling C from COBOL

The most common inter-language interface in COBOL is calling C functions. The z/OS Language Environment (LE) provides a standardized calling convention that makes COBOL-C interoperability relatively straightforward.

The Basic Pattern

       IDENTIFICATION DIVISION.
       PROGRAM-ID. COBOL-CALLS-C.

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-INPUT-STRING         PIC X(100).
       01  WS-STRING-LENGTH        PIC S9(9) COMP.
       01  WS-RESULT               PIC S9(9) COMP.

       PROCEDURE DIVISION.
           MOVE "Hello from COBOL" TO WS-INPUT-STRING
           MOVE FUNCTION LENGTH(
               FUNCTION TRIM(WS-INPUT-STRING TRAILING))
               TO WS-STRING-LENGTH

           CALL "c_string_length"
               USING BY REFERENCE WS-INPUT-STRING
                     BY VALUE WS-STRING-LENGTH
               RETURNING WS-RESULT

           DISPLAY "C says length is: " WS-RESULT
           STOP RUN.

And the corresponding C function:

/* c_string_length.c */
#include <string.h>

int c_string_length(char *str, int max_len) {
    int i;
    /* Find actual length (COBOL pads with spaces) */
    for (i = max_len - 1; i >= 0; i--) {
        if (str[i] != ' ') break;
    }
    return i + 1;
}

Parameter Passing Conventions

This is where most inter-language bugs originate. COBOL and C have fundamentally different default parameter-passing conventions:

Convention COBOL Default C Default
Passing mechanism BY REFERENCE (passes address) By value (passes copy)
String termination Space-padded, fixed length Null-terminated
Numeric representation Display (EBCDIC digits), packed decimal, or binary Binary (int, long, double)
Byte order Big-endian (z/OS) Platform-dependent

BY REFERENCE vs. BY VALUE: This is the single most important thing to understand. COBOL defaults to BY REFERENCE — it passes the address of the data item. C defaults to pass-by-value for scalars. When calling C from COBOL, you must be explicit:

      *> Pass address of string (C receives a pointer)
       CALL "c_function"
           USING BY REFERENCE WS-STRING-DATA

      *> Pass value of integer (C receives int, not int*)
       CALL "c_function"
           USING BY VALUE WS-INTEGER-VALUE

      *> Pass address of structure (C receives struct pointer)
       CALL "c_function"
           USING BY REFERENCE WS-RECORD-AREA

⚠️ Critical Warning: Passing a COBOL numeric item BY REFERENCE when the C function expects a value (or vice versa) is the number one cause of inter-language bugs. The program will not crash gracefully — it will interpret an address as a number (or a number as an address), producing garbage results or ABENDs. Always verify the C function's parameter expectations.

Data Type Mapping: COBOL to C

Here is the essential data type mapping table:

COBOL Data Type C Data Type Notes
PIC S9(9) COMP or BINARY int (32-bit) Signed binary
PIC S9(18) COMP long long (64-bit) Signed binary
PIC S9(4) COMP short (16-bit) Signed binary
PIC X(n) char[n] or char * No null terminator!
POINTER void * Address
PIC S9(9)V9(2) COMP-3 No direct equivalent Convert first
PIC 9(n) DISPLAY No direct equivalent EBCDIC digits

💡 Key Insight: COMP (or BINARY or COMP-4) items map cleanly to C integer types. DISPLAY and COMP-3 (packed decimal) items have no direct C equivalent and must be converted before passing. This is why C interface routines in COBOL typically use COMP data items exclusively.

String Handling Between COBOL and C

Strings are the most common source of inter-language problems. COBOL strings are fixed-length, space-padded, and not null-terminated. C strings are variable-length, null-terminated, and may contain any bytes.

       WORKING-STORAGE SECTION.
       01  WS-COBOL-STRING         PIC X(50).
       01  WS-C-STRING             PIC X(51).
       01  WS-NULL-CHAR            PIC X VALUE X'00'.

       PROCEDURE DIVISION.
      *> Prepare a null-terminated string for C
           MOVE "Hello World" TO WS-COBOL-STRING
           STRING WS-COBOL-STRING DELIMITED BY "  "
                  WS-NULL-CHAR DELIMITED BY SIZE
                  INTO WS-C-STRING
           END-STRING

           CALL "c_process_string"
               USING BY REFERENCE WS-C-STRING

A cleaner approach uses a helper paragraph:

       MAKE-C-STRING.
      *> Convert COBOL string to C string (null-terminated)
           INSPECT WS-COBOL-STRING
               TALLYING WS-TALLY FOR TRAILING SPACES
           COMPUTE WS-ACTUAL-LENGTH =
               FUNCTION LENGTH(WS-COBOL-STRING) - WS-TALLY
           MOVE WS-COBOL-STRING(1:WS-ACTUAL-LENGTH)
               TO WS-C-STRING(1:WS-ACTUAL-LENGTH)
           MOVE X'00'
               TO WS-C-STRING(WS-ACTUAL-LENGTH + 1:1).

Complete Example: COBOL Calling a C Date Library

This is the scenario Derek Washington faced at GlobalBank. Here is the complete solution:

The C library (date_utils.c):

/* date_utils.c — Date utility library for COBOL interface */
#include <stdio.h>
#include <string.h>
#include <time.h>

/* Convert YYYYMMDD integer to day-of-week (0=Sun, 6=Sat) */
int get_day_of_week(int date_int) {
    struct tm t = {0};
    t.tm_year = (date_int / 10000) - 1900;
    t.tm_mon  = ((date_int % 10000) / 100) - 1;
    t.tm_mday = date_int % 100;
    mktime(&t);
    return t.tm_wday;
}

/* Calculate days between two YYYYMMDD dates */
int days_between(int date1, int date2) {
    struct tm t1 = {0}, t2 = {0};
    time_t time1, time2;

    t1.tm_year = (date1 / 10000) - 1900;
    t1.tm_mon  = ((date1 % 10000) / 100) - 1;
    t1.tm_mday = date1 % 100;

    t2.tm_year = (date2 / 10000) - 1900;
    t2.tm_mon  = ((date2 % 10000) / 100) - 1;
    t2.tm_mday = date2 % 100;

    time1 = mktime(&t1);
    time2 = mktime(&t2);

    return (int)((time2 - time1) / 86400);
}

/* Format date as ISO 8601 string: "2025-03-15" */
void format_iso_date(int date_int, char *output, int out_len) {
    char buffer[11];
    snprintf(buffer, sizeof(buffer), "%04d-%02d-%02d",
             date_int / 10000,
             (date_int % 10000) / 100,
             date_int % 100);

    /* Pad with spaces for COBOL */
    memset(output, ' ', out_len);
    memcpy(output, buffer, 10);
}

/* Validate a date — returns 1 if valid, 0 if not */
int validate_date(int date_int) {
    int year  = date_int / 10000;
    int month = (date_int % 10000) / 100;
    int day   = date_int % 100;
    int days_in_month[] =
        {31,28,31,30,31,30,31,31,30,31,30,31};

    if (year < 1900 || year > 2099) return 0;
    if (month < 1 || month > 12)    return 0;
    if (day < 1)                    return 0;

    /* Leap year check */
    if (month == 2 &&
        (year % 4 == 0 &&
         (year % 100 != 0 || year % 400 == 0)))
        days_in_month[1] = 29;

    if (day > days_in_month[month - 1]) return 0;
    return 1;
}

The COBOL program calling the C library:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. DATE-UTIL-DEMO.

       ENVIRONMENT DIVISION.

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-DATE-1              PIC 9(8) VALUE 20250315.
       01  WS-DATE-2              PIC 9(8) VALUE 20251231.
       01  WS-DATE-INT-1          PIC S9(9) COMP.
       01  WS-DATE-INT-2          PIC S9(9) COMP.
       01  WS-DAY-OF-WEEK         PIC S9(9) COMP.
       01  WS-DAYS-BETWEEN        PIC S9(9) COMP.
       01  WS-IS-VALID            PIC S9(9) COMP.
       01  WS-ISO-DATE            PIC X(20).
       01  WS-ISO-LEN             PIC S9(9) COMP VALUE 20.
       01  WS-DAY-NAMES.
           05  FILLER PIC X(9) VALUE "Sunday   ".
           05  FILLER PIC X(9) VALUE "Monday   ".
           05  FILLER PIC X(9) VALUE "Tuesday  ".
           05  FILLER PIC X(9) VALUE "Wednesday".
           05  FILLER PIC X(9) VALUE "Thursday ".
           05  FILLER PIC X(9) VALUE "Friday   ".
           05  FILLER PIC X(9) VALUE "Saturday ".
       01  WS-DAY-TABLE REDEFINES WS-DAY-NAMES.
           05  WS-DAY-NAME        PIC X(9) OCCURS 7.

       PROCEDURE DIVISION.
       MAIN-LOGIC.
           DISPLAY "=== DATE UTILITY DEMO ==="
           DISPLAY "Using C library from COBOL"
           DISPLAY " "

      *> Convert COBOL display numeric to binary for C
           MOVE WS-DATE-1 TO WS-DATE-INT-1
           MOVE WS-DATE-2 TO WS-DATE-INT-2

      *> Validate the date
           CALL "validate_date"
               USING BY VALUE WS-DATE-INT-1
               RETURNING WS-IS-VALID
           IF WS-IS-VALID = 1
               DISPLAY "Date " WS-DATE-1 " is valid"
           ELSE
               DISPLAY "Date " WS-DATE-1 " is INVALID"
               STOP RUN
           END-IF

      *> Get day of week
           CALL "get_day_of_week"
               USING BY VALUE WS-DATE-INT-1
               RETURNING WS-DAY-OF-WEEK
           ADD 1 TO WS-DAY-OF-WEEK
           DISPLAY WS-DATE-1 " is a "
                   WS-DAY-NAME(WS-DAY-OF-WEEK)

      *> Calculate days between
           CALL "days_between"
               USING BY VALUE WS-DATE-INT-1
                     BY VALUE WS-DATE-INT-2
               RETURNING WS-DAYS-BETWEEN
           DISPLAY "Days between " WS-DATE-1
                   " and " WS-DATE-2 ": "
                   WS-DAYS-BETWEEN

      *> Format as ISO date
           CALL "format_iso_date"
               USING BY VALUE WS-DATE-INT-1
                     BY REFERENCE WS-ISO-DATE
                     BY VALUE WS-ISO-LEN
           DISPLAY "ISO format: " WS-ISO-DATE

           DISPLAY " "
           DISPLAY "=== DEMO COMPLETE ==="
           STOP RUN.

📊 Compilation Notes: On z/OS, compile the C code with the XL C compiler, then link it with the COBOL program using the Language Environment binder. On Micro Focus, compile C with GCC or Visual C++ and link as a shared library. The exact JCL or build commands vary by environment — consult your installation guide.


26.3 Calling COBOL from C

The reverse direction — C calling COBOL — is equally important, particularly when C or Java programs need access to COBOL business logic.

Setting Up the COBOL Program for External Calling

For a COBOL program to be callable from C, it must follow certain conventions:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. CALC-INTEREST.

       DATA DIVISION.
       LINKAGE SECTION.
       01  LS-PRINCIPAL           PIC S9(11)V99 COMP-3.
       01  LS-RATE                PIC S9(3)V9(6) COMP-3.
       01  LS-PERIODS             PIC S9(4) COMP.
       01  LS-RESULT              PIC S9(11)V99 COMP-3.
       01  LS-RETURN-CODE         PIC S9(4) COMP.

       PROCEDURE DIVISION USING LS-PRINCIPAL
                                LS-RATE
                                LS-PERIODS
                                LS-RESULT
                                LS-RETURN-CODE.
       MAIN-LOGIC.
           IF LS-PRINCIPAL <= ZEROS
            OR LS-RATE <= ZEROS
            OR LS-PERIODS <= ZEROS
               MOVE -1 TO LS-RETURN-CODE
               GOBACK
           END-IF

           COMPUTE LS-RESULT ROUNDED =
               LS-PRINCIPAL *
               (1 + LS-RATE) ** LS-PERIODS

           MOVE 0 TO LS-RETURN-CODE
           GOBACK.

The calling C program:

/* call_cobol.c — Calling COBOL interest calculator */
#include <stdio.h>

/* Declare the COBOL program as external */
extern void CALC_INTEREST(
    void *principal,    /* COMP-3 decimal */
    void *rate,         /* COMP-3 decimal */
    short *periods,     /* COMP (binary) */
    void *result,       /* COMP-3 decimal */
    short *return_code  /* COMP (binary) */
);

int main() {
    /* Note: COMP-3 manipulation from C requires
       special handling — packed decimal encoding.
       In practice, you'd use a conversion library
       or BINARY/COMP parameters for C interfaces. */

    short periods = 12;
    short return_code = 0;

    /* Simplified — real code would use proper
       packed decimal handling */
    printf("Calling COBOL CALC-INTEREST...\n");
    /* CALC_INTEREST(&principal, &rate, &periods,
                     &result, &return_code); */
    printf("Return code: %d\n", return_code);

    return 0;
}

⚠️ Critical Warning: Calling COBOL from C with COMP-3 (packed decimal) parameters is notoriously difficult because C has no native packed decimal type. For C-callable COBOL programs, strongly prefer COMP (binary) parameters. If you must use COMP-3, consider writing a thin COBOL wrapper that accepts COMP parameters and converts internally.

Better Approach: Binary Interface

Design COBOL programs intended for C callers with COMP (binary) parameters:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. CALC-INT-BIN.
      *> Designed for C/Java callers — binary interface
      *> -----------------------------------------------

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-PRINCIPAL-DEC       PIC S9(11)V99.
       01  WS-RATE-DEC            PIC SV9(6).
       01  WS-RESULT-DEC          PIC S9(11)V99.

       LINKAGE SECTION.
      *> All parameters are binary for C compatibility
       01  LS-PRINCIPAL-CENTS     PIC S9(18) COMP.
       01  LS-RATE-MILLIONTHS     PIC S9(9) COMP.
       01  LS-PERIODS             PIC S9(9) COMP.
       01  LS-RESULT-CENTS        PIC S9(18) COMP.
       01  LS-RETURN-CODE         PIC S9(9) COMP.

       PROCEDURE DIVISION USING LS-PRINCIPAL-CENTS
                                LS-RATE-MILLIONTHS
                                LS-PERIODS
                                LS-RESULT-CENTS
                                LS-RETURN-CODE.
       MAIN-LOGIC.
      *> Convert from binary cents to COBOL decimal
           COMPUTE WS-PRINCIPAL-DEC =
               LS-PRINCIPAL-CENTS / 100
           COMPUTE WS-RATE-DEC =
               LS-RATE-MILLIONTHS / 1000000

      *> Validate
           IF WS-PRINCIPAL-DEC <= ZEROS
            OR WS-RATE-DEC <= ZEROS
            OR LS-PERIODS <= ZEROS
               MOVE -1 TO LS-RETURN-CODE
               GOBACK
           END-IF

      *> Calculate using COBOL's decimal arithmetic
           COMPUTE WS-RESULT-DEC ROUNDED =
               WS-PRINCIPAL-DEC *
               (1 + WS-RATE-DEC) ** LS-PERIODS

      *> Convert back to binary cents for C
           COMPUTE LS-RESULT-CENTS =
               WS-RESULT-DEC * 100

           MOVE 0 TO LS-RETURN-CODE
           GOBACK.

Now the C caller is straightforward:

extern void CALC_INT_BIN(
    long long *principal_cents,
    int *rate_millionths,
    int *periods,
    long long *result_cents,
    int *return_code
);

int main() {
    long long principal = 10000000LL; /* $100,000.00 */
    int rate = 50000;                 /* 5% = 0.050000 */
    int periods = 360;               /* 30-year mortgage */
    long long result = 0;
    int rc = 0;

    CALC_INT_BIN(&principal, &rate, &periods,
                 &result, &rc);

    if (rc == 0) {
        printf("Future value: $%lld.%02lld\n",
               result / 100, result % 100);
    }
    return rc;
}

💡 Key Insight: The "binary interface" pattern — where COBOL programs present COMP parameters for external callers and handle decimal conversion internally — is the industry standard practice. It isolates C callers from COBOL's decimal data types while preserving COBOL's precise decimal arithmetic inside the business logic.


26.4 Java Interop — COBOL and Java Together

Java-COBOL interoperability is increasingly important as web frontends, microservices, and API layers are built in Java while core business logic remains in COBOL.

Under z/OS Language Environment

On z/OS, both COBOL and Java run under the Language Environment (LE), which provides a common runtime foundation. The Java Native Interface (JNI) is the bridge:

┌─────────────────────────────────────────┐
│           Java Application              │
│  ┌────────────────────────────────────┐ │
│  │    JNI (Java Native Interface)     │ │
│  └──────────────┬─────────────────────┘ │
│                 │                        │
│  ┌──────────────▼─────────────────────┐ │
│  │   Language Environment (LE)        │ │
│  │  ┌─────────┐  ┌────────────────┐   │ │
│  │  │  COBOL  │  │  C wrapper     │   │ │
│  │  │ program │←─│  (JNI native)  │   │ │
│  │  └─────────┘  └────────────────┘   │ │
│  └────────────────────────────────────┘ │
└─────────────────────────────────────────┘

The typical pattern involves three layers:

  1. Java class with native method declarations
  2. C "glue" code implementing the JNI native methods
  3. COBOL program containing the business logic

Java Layer

// AccountService.java
public class AccountService {
    // Load the native library
    static {
        System.loadLibrary("acctservice");
    }

    // Native method declarations
    public native int getBalance(String accountNumber);
    public native int deposit(String accountNumber,
                             long amountCents);
    public native int withdraw(String accountNumber,
                              long amountCents);
    public native String getAccountStatus(
                             String accountNumber);

    // Java wrapper for cleaner API
    public double getBalanceDollars(String acctNum) {
        return getBalance(acctNum) / 100.0;
    }

    public boolean depositDollars(String acctNum,
                                 double amount) {
        long cents = Math.round(amount * 100);
        return deposit(acctNum, cents) == 0;
    }
}

C JNI Glue Layer

/* acctservice_jni.c — JNI bridge to COBOL */
#include <jni.h>
#include <string.h>
#include "AccountService.h"  /* Generated by javah */

/* Declare COBOL programs */
extern void ACCT_BAL(char *acct_num, int *balance_cents,
                     int *return_code);
extern void ACCT_DEP(char *acct_num, long long *amount_cents,
                     int *return_code);

JNIEXPORT jint JNICALL
Java_AccountService_getBalance(
    JNIEnv *env, jobject obj, jstring acctNum) {

    const char *acct = (*env)->GetStringUTFChars(
                           env, acctNum, NULL);
    char cobol_acct[11];
    int balance = 0;
    int rc = 0;

    /* Prepare COBOL-format account number */
    /* (space-padded, 10 chars, no null terminator) */
    memset(cobol_acct, ' ', 10);
    cobol_acct[10] = '\0';
    strncpy(cobol_acct, acct, strlen(acct));

    /* Call COBOL */
    ACCT_BAL(cobol_acct, &balance, &rc);

    (*env)->ReleaseStringUTFChars(env, acctNum, acct);

    return (rc == 0) ? balance : -1;
}

JNIEXPORT jint JNICALL
Java_AccountService_deposit(
    JNIEnv *env, jobject obj, jstring acctNum,
    jlong amountCents) {

    const char *acct = (*env)->GetStringUTFChars(
                           env, acctNum, NULL);
    char cobol_acct[11];
    long long amount = (long long)amountCents;
    int rc = 0;

    memset(cobol_acct, ' ', 10);
    cobol_acct[10] = '\0';
    strncpy(cobol_acct, acct, strlen(acct));

    ACCT_DEP(cobol_acct, &amount, &rc);

    (*env)->ReleaseStringUTFChars(env, acctNum, acct);

    return rc;
}

COBOL Layer

       IDENTIFICATION DIVISION.
       PROGRAM-ID. ACCT-BAL.
      *> Binary interface for Java/C callers
      *> Returns account balance in cents

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-ACCT-REC.
           COPY ACCT-COPY.
       01  WS-FILE-STATUS          PIC XX.
       01  WS-BALANCE-DEC          PIC S9(11)V99.

       LINKAGE SECTION.
       01  LS-ACCT-NUMBER          PIC X(10).
       01  LS-BALANCE-CENTS        PIC S9(9) COMP.
       01  LS-RETURN-CODE          PIC S9(9) COMP.

       PROCEDURE DIVISION USING LS-ACCT-NUMBER
                                LS-BALANCE-CENTS
                                LS-RETURN-CODE.
       MAIN-LOGIC.
           MOVE 0 TO LS-RETURN-CODE
           MOVE ZEROS TO LS-BALANCE-CENTS

      *> Look up account (simplified — real code
      *> would read from VSAM or DB2)
           PERFORM READ-ACCOUNT

           IF WS-FILE-STATUS = '00'
               COMPUTE LS-BALANCE-CENTS =
                   WS-BALANCE-DEC * 100
           ELSE
               MOVE -1 TO LS-RETURN-CODE
           END-IF

           GOBACK.

       READ-ACCOUNT.
      *> Simulated account read
           IF LS-ACCT-NUMBER(1:4) = "1000"
               MOVE 1542067 TO LS-BALANCE-CENTS
               MOVE '00' TO WS-FILE-STATUS
           ELSE
               MOVE '23' TO WS-FILE-STATUS
           END-IF.

🔗 Cross-Reference: The ACCT-COPY copybook referenced here is the same one used in the GlobalBank core banking system (see Chapter 12 for the full copybook layout). Reusing existing copybooks in inter-language interfaces ensures consistency with the core system.


26.5 SPECIAL-NAMES and CALL-CONVENTION

The SPECIAL-NAMES paragraph in the ENVIRONMENT DIVISION provides several features critical to inter-language communication.

CALL-CONVENTION

The CALL-CONVENTION clause specifies how parameters are passed when calling external routines:

       ENVIRONMENT DIVISION.
       CONFIGURATION SECTION.
       SPECIAL-NAMES.
           CALL-CONVENTION 0 IS COBOL-CONVENTION
           CALL-CONVENTION 8 IS C-CONVENTION
           CALL-CONVENTION 16 IS PASCAL-CONVENTION.

Then use the convention in your CALL statements:

      *> Call C function with C calling convention
       CALL C-CONVENTION "c_function"
           USING BY VALUE WS-PARAM-1
                 BY REFERENCE WS-PARAM-2

      *> Call COBOL subprogram with COBOL convention
       CALL COBOL-CONVENTION "COBOL-SUB"
           USING WS-PARAM-1
                 WS-PARAM-2

The calling convention numbers are compiler-specific. Here are the common conventions for Micro Focus COBOL:

Value Convention Description
0 COBOL Standard COBOL calling convention (BY REFERENCE default)
1 stdcall Windows API convention
8 C/cdecl C language convention
16 Pascal Pascal convention (rarely used)
74 WINAPI Thread-safe Windows API

ALPHABET Clause

The ALPHABET clause in SPECIAL-NAMES is crucial when exchanging data between EBCDIC (mainframe) and ASCII (distributed) systems:

       SPECIAL-NAMES.
           ALPHABET ASCII-SET IS STANDARD-1
           ALPHABET EBCDIC-SET IS NATIVE
           ALPHABET UTF8-SET IS "UTF-8".

This becomes important when COBOL programs exchange string data with C or Java programs running on different platforms.

CURRENCY SIGN

When processing international financial data received from external systems:

       SPECIAL-NAMES.
           CURRENCY SIGN IS "EUR" WITH PICTURE SYMBOL "E".

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-EUR-AMOUNT          PIC EEE,EEE,EE9.99.

26.6 Assembler Calls — High-Performance Routines

Calling assembler from COBOL is a time-honored mainframe technique for performance-critical operations. While less common today (modern compilers optimize well), assembler calls remain relevant for system-level services.

Why Assembler?

Assembler routines are used when: - Maximum performance is required (inner loops processing millions of records) - Direct system service calls (SVCs) are needed - Hardware-specific instructions must be used - The operation has no COBOL equivalent (bit manipulation, specific I/O operations)

Calling Convention

z/OS assembler routines follow the standard linkage convention: - Register 1 points to a parameter list (list of addresses) - Register 13 points to a save area - Register 14 contains the return address - Register 15 contains the entry point address (and return code on exit)

From COBOL, this is transparent — the compiler generates the proper linkage code:

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-INPUT-DATA          PIC X(1000).
       01  WS-OUTPUT-DATA         PIC X(1000).
       01  WS-DATA-LENGTH         PIC S9(9) COMP.
       01  WS-RETURN-CODE         PIC S9(9) COMP.

       PROCEDURE DIVISION.
           MOVE 1000 TO WS-DATA-LENGTH

           CALL 'ASMUTIL'
               USING WS-INPUT-DATA
                     WS-OUTPUT-DATA
                     WS-DATA-LENGTH
                     WS-RETURN-CODE

           EVALUATE WS-RETURN-CODE
               WHEN 0
                   DISPLAY "Success"
               WHEN 4
                   DISPLAY "Warning"
               WHEN 8
                   DISPLAY "Error"
               WHEN OTHER
                   DISPLAY "Unexpected RC: "
                           WS-RETURN-CODE
           END-EVALUATE.

Example: High-Performance String Scan

An assembler routine that scans a buffer for a specific byte pattern — something that can be done in COBOL with INSPECT but is significantly faster in assembler for large buffers:

ASMUTIL  CSECT
         SAVE  (14,12)          Save registers
         BALR  12,0             Establish base
         USING *,12
         LM    2,5,0(1)         Load parameter addresses
*        R2 = address of input data
*        R3 = address of output data
*        R4 = address of data length
*        R5 = address of return code
*
         LH    6,0(4)           Load data length
         XR    15,15            Clear return code
*        ... (processing logic) ...
         ST    15,0(5)          Store return code
         RETURN (14,12),RC=0
         END

⚠️ Important: Assembler programming is beyond the scope of this textbook. The point here is to understand the interface — how COBOL calls assembler and what parameter conventions apply. If your shop uses assembler routines, they were likely written decades ago and are called exactly like any other subprogram.


26.7 Mixed-Language Debugging

Debugging across language boundaries is one of the most challenging aspects of inter-language programming. Here are the key techniques:

The Debugging Challenge

When a bug occurs in a mixed-language program, the difficulty is identifying which layer caused the problem:

COBOL program → C function → COBOL subprogram → ABEND

Was the error in the COBOL caller's parameter setup? In the C function's logic? In the COBOL subprogram? The stack trace will show all three, but the root cause could be in any of them.

Strategy 1: Trace Logging at Boundaries

Add logging at every language boundary:

      *> Before calling C
           DISPLAY "TRACE: Calling c_validate, param1="
                   WS-PARAM-1 " param2=" WS-PARAM-2

           CALL "c_validate"
               USING BY VALUE WS-PARAM-1
                     BY REFERENCE WS-PARAM-2
               RETURNING WS-RESULT

      *> After calling C
           DISPLAY "TRACE: Returned from c_validate, RC="
                   WS-RESULT

Strategy 2: Parameter Verification

Before calling across language boundaries, verify that parameters are in the expected format:

       VERIFY-C-PARAMS.
      *> Verify binary parameter is in expected range
           IF WS-DATE-INT < 19000101
            OR WS-DATE-INT > 20991231
               DISPLAY "ERROR: Invalid date for C: "
                       WS-DATE-INT
               MOVE -1 TO WS-RETURN-CODE
               GOBACK
           END-IF

      *> Verify string parameter is not all spaces
           IF WS-ACCT-NUMBER = SPACES
               DISPLAY "ERROR: Blank account number"
               MOVE -2 TO WS-RETURN-CODE
               GOBACK
           END-IF.

Strategy 3: Using Language Environment Debugging

On z/OS, the Language Environment provides tools that work across all LE-conforming languages:

      *> Enable LE condition handling
       CALL 'CEEHDLR'
           USING WS-TOKEN
                 WS-HANDLER-ADDR
                 WS-FEEDBACK-CODE

      *> Check LE feedback codes after inter-language calls
       IF WS-FEEDBACK-CODE NOT = SPACES
           DISPLAY "LE error: " WS-FEEDBACK-CODE
       END-IF

💡 Key Insight: The best debugging strategy for mixed-language programs is prevention. Clearly define and document the interface contract — parameter types, sizes, valid ranges, null handling, error codes. Most inter-language bugs are interface mismatch bugs, and they are far easier to prevent than to debug.


26.8 Web Service Integration

Modern COBOL programs increasingly need to communicate with web services — both as clients (calling REST APIs) and as servers (exposing COBOL logic via HTTP).

COBOL Calling REST Services

COBOL cannot natively make HTTP calls, but several approaches exist:

Approach 1: Through a C/Java middleware layer

       CALL "http_get"
           USING BY REFERENCE WS-URL
                 BY REFERENCE WS-RESPONSE-BUFFER
                 BY VALUE WS-BUFFER-SIZE
                 BY REFERENCE WS-HTTP-STATUS

Where http_get is a C function wrapping libcurl:

/* http_wrapper.c */
#include <curl/curl.h>
#include <string.h>

struct MemoryStruct {
    char *memory;
    size_t size;
};

static size_t write_callback(void *contents,
    size_t size, size_t nmemb, void *userp) {
    struct MemoryStruct *mem =
        (struct MemoryStruct *)userp;
    size_t realsize = size * nmemb;
    memcpy(&(mem->memory[mem->size]),
           contents, realsize);
    mem->size += realsize;
    return realsize;
}

void http_get(char *url, char *response,
              int buf_size, int *status) {
    CURL *curl;
    CURLcode res;
    struct MemoryStruct chunk;
    char c_url[2048];

    chunk.memory = response;
    chunk.size = 0;

    /* Convert COBOL space-padded URL to C string */
    strncpy(c_url, url, 2048);
    /* Trim trailing spaces */
    int len = strlen(c_url);
    while (len > 0 && c_url[len-1] == ' ')
        c_url[--len] = '\0';

    curl = curl_easy_init();
    if (curl) {
        curl_easy_setopt(curl, CURLOPT_URL, c_url);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
                        write_callback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA,
                        (void *)&chunk);
        res = curl_easy_perform(curl);

        if (res == CURLE_OK) {
            long http_code = 0;
            curl_easy_getinfo(curl,
                CURLINFO_RESPONSE_CODE, &http_code);
            *status = (int)http_code;
        } else {
            *status = -1;
        }

        /* Pad remainder with spaces for COBOL */
        if (chunk.size < buf_size) {
            memset(response + chunk.size, ' ',
                   buf_size - chunk.size);
        }

        curl_easy_cleanup(curl);
    } else {
        *status = -2;
    }
}

Approach 2: IBM CICS Web Services

On z/OS with CICS, you can use CICS's built-in web service support:

       EXEC CICS WEB OPEN
           HOST(WS-HOST-NAME)
           HOSTLENGTH(WS-HOST-LEN)
           PORTNUMBER(WS-PORT)
           SCHEME(HTTPS)
           SESSTOKEN(WS-SESSION-TOKEN)
       END-EXEC

       EXEC CICS WEB SEND
           FROM(WS-REQUEST-DATA)
           FROMLENGTH(WS-REQ-LENGTH)
           MEDIATYPE(WS-CONTENT-TYPE)
           SESSTOKEN(WS-SESSION-TOKEN)
           METHOD(WS-HTTP-METHOD)
       END-EXEC

       EXEC CICS WEB RECEIVE
           INTO(WS-RESPONSE-DATA)
           LENGTH(WS-RESP-LENGTH)
           SESSTOKEN(WS-SESSION-TOKEN)
       END-EXEC

🔗 Cross-Reference: CICS web services are covered in detail in Chapters 29-32. This section provides a preview of the inter-language aspects.

Exposing COBOL as a REST API

The reverse — making COBOL programs accessible via REST — typically uses IBM's z/OS Connect or Micro Focus Enterprise Server:

HTTP Request → z/OS Connect → CICS Transaction → COBOL Program
                              ↑
                          JSON ↔ COBOL
                          mapping

The COBOL program itself does not change. z/OS Connect generates the mapping between JSON request/response payloads and COBOL data structures automatically from copybooks.


26.9 GlobalBank Case Study: The Integration Layer

At GlobalBank, Priya Kapoor designed an integration architecture that demonstrates every inter-language technique in this chapter:

┌─────────────────────────────────────────────────────┐
│                  Web Portal (Java)                   │
│           REST API / AccountService.java             │
└──────────────────────┬──────────────────────────────┘
                       │ JNI
┌──────────────────────▼──────────────────────────────┐
│                C Bridge Layer                        │
│         acctservice_jni.c / date_utils.c            │
└──────────┬───────────────────────────┬──────────────┘
           │ CALL                      │ CALL
┌──────────▼──────────┐  ┌────────────▼──────────────┐
│   COBOL Business    │  │   COBOL Data Access       │
│   ACCT-MAINT        │  │   ACCT-READ / ACCT-WRITE  │
│   TXN-PROC          │  │   TXN-READ / TXN-WRITE    │
│   BAL-CALC          │  │   (DB2 via embedded SQL)   │
└──────────┬──────────┘  └────────────┬──────────────┘
           │                          │
           │       ┌──────────────────▼──────────┐
           └──────►│  Assembler Utilities         │
                   │  DATECONV / STRUTIL          │
                   │  (legacy high-perf routines) │
                   └──────────────────────────────┘

Derek Washington's observations after implementing this:

"The layering felt excessive at first — Java calls C calls COBOL calls assembler. But each layer has a clear purpose. Java handles HTTP and JSON. C handles the JNI ceremony and type conversion. COBOL handles business logic with its decimal arithmetic and file I/O. And those assembler date conversion routines? They have been running without a bug for twenty-five years. Why would we rewrite them?"

Maria Chen added: "The key was the binary interface pattern. Every cross-language boundary uses COMP data types. No COMP-3 crosses a boundary, no DISPLAY numeric crosses a boundary. COBOL converts internally. This eliminated an entire category of bugs."

🔴 The Human Factor: The integration layer at GlobalBank required three different skill sets: Java developers, C/systems programmers, and COBOL developers. The biggest challenge was not technical — it was communication. Each group had to understand enough of the other languages to write correct interface specifications. Monthly cross-team code reviews, where each group explained their side of the interface to the others, proved essential.


26.10 MedClaim Case Study: External Validation

At MedClaim, Tomás Rivera integrated an external claims validation library written in C. The library, provided by a healthcare industry consortium, validated diagnosis codes (ICD-10) and procedure codes (CPT) against the current standard.

The Problem

MedClaim's COBOL programs validated diagnosis codes against an internal table — a copybook with 70,000 entries that required quarterly manual updates. The consortium offered a C library that was updated automatically via API.

The Solution

       IDENTIFICATION DIVISION.
       PROGRAM-ID. CLM-VALIDATE.
      *> Claim validation with external C library

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-DIAG-CODE            PIC X(7).
       01  WS-PROC-CODE            PIC X(5).
       01  WS-C-DIAG-CODE          PIC X(8).
       01  WS-C-PROC-CODE          PIC X(6).
       01  WS-DIAG-VALID           PIC S9(9) COMP.
       01  WS-PROC-VALID           PIC S9(9) COMP.
       01  WS-DIAG-DESC            PIC X(256).
       01  WS-PROC-DESC            PIC X(256).
       01  WS-NULL-BYTE            PIC X VALUE X'00'.
       01  WS-ACTUAL-LEN           PIC 9(4).
       01  WS-TALLY                PIC 9(4).

       LINKAGE SECTION.
       01  LS-CLAIM-RECORD.
           COPY CLM-COPY.
       01  LS-VALIDATION-RESULT    PIC X(2).
       01  LS-VALIDATION-MSG       PIC X(200).

       PROCEDURE DIVISION USING LS-CLAIM-RECORD
                                LS-VALIDATION-RESULT
                                LS-VALIDATION-MSG.
       MAIN-LOGIC.
           MOVE CLM-DIAGNOSIS-CODE TO WS-DIAG-CODE
           MOVE CLM-PROCEDURE-CODE TO WS-PROC-CODE

      *> Prepare null-terminated strings for C library
           PERFORM PREPARE-DIAG-STRING
           PERFORM PREPARE-PROC-STRING

      *> Call C validation library
           CALL "icd10_validate"
               USING BY REFERENCE WS-C-DIAG-CODE
                     BY REFERENCE WS-DIAG-VALID
                     BY REFERENCE WS-DIAG-DESC

           CALL "cpt_validate"
               USING BY REFERENCE WS-C-PROC-CODE
                     BY REFERENCE WS-PROC-VALID
                     BY REFERENCE WS-PROC-DESC

      *> Interpret results
           EVALUATE TRUE
               WHEN WS-DIAG-VALID = 0
                AND WS-PROC-VALID = 0
                   MOVE 'OK' TO LS-VALIDATION-RESULT
                   MOVE "Both codes valid"
                       TO LS-VALIDATION-MSG
               WHEN WS-DIAG-VALID NOT = 0
                AND WS-PROC-VALID = 0
                   MOVE 'DX' TO LS-VALIDATION-RESULT
                   STRING "Invalid diagnosis: "
                          DELIMITED SIZE
                          WS-DIAG-CODE
                          DELIMITED SPACES
                          INTO LS-VALIDATION-MSG
                   END-STRING
               WHEN WS-DIAG-VALID = 0
                AND WS-PROC-VALID NOT = 0
                   MOVE 'PX' TO LS-VALIDATION-RESULT
                   STRING "Invalid procedure: "
                          DELIMITED SIZE
                          WS-PROC-CODE
                          DELIMITED SPACES
                          INTO LS-VALIDATION-MSG
                   END-STRING
               WHEN OTHER
                   MOVE 'XX' TO LS-VALIDATION-RESULT
                   MOVE "Both codes invalid"
                       TO LS-VALIDATION-MSG
           END-EVALUATE

           GOBACK.

       PREPARE-DIAG-STRING.
           MOVE ZEROS TO WS-TALLY
           INSPECT WS-DIAG-CODE
               TALLYING WS-TALLY FOR TRAILING SPACES
           COMPUTE WS-ACTUAL-LEN =
               FUNCTION LENGTH(WS-DIAG-CODE) - WS-TALLY
           MOVE SPACES TO WS-C-DIAG-CODE
           MOVE WS-DIAG-CODE(1:WS-ACTUAL-LEN)
               TO WS-C-DIAG-CODE(1:WS-ACTUAL-LEN)
           MOVE WS-NULL-BYTE
               TO WS-C-DIAG-CODE(WS-ACTUAL-LEN + 1:1).

       PREPARE-PROC-STRING.
           MOVE ZEROS TO WS-TALLY
           INSPECT WS-PROC-CODE
               TALLYING WS-TALLY FOR TRAILING SPACES
           COMPUTE WS-ACTUAL-LEN =
               FUNCTION LENGTH(WS-PROC-CODE) - WS-TALLY
           MOVE SPACES TO WS-C-PROC-CODE
           MOVE WS-PROC-CODE(1:WS-ACTUAL-LEN)
               TO WS-C-PROC-CODE(1:WS-ACTUAL-LEN)
           MOVE WS-NULL-BYTE
               TO WS-C-PROC-CODE(WS-ACTUAL-LEN + 1:1).

James Okafor noted: "The null-termination dance is ugly, but it runs a quarter of a million times a day without issues. We wrote it once, tested it exhaustively, and wrapped it in a reusable paragraph. Every COBOL program that calls the C library uses the same PREPARE-*-STRING pattern."


26.11 Data Type Mapping Reference

This section provides a comprehensive reference for data type mapping across the four languages covered in this chapter.

COBOL to C Mapping

COBOL Size C Notes
PIC X(n) n bytes char[n] No null terminator
PIC 9(n) DISPLAY n bytes char[n] EBCDIC digits
PIC S9(4) COMP 2 bytes short / int16_t Signed
PIC S9(9) COMP 4 bytes int / int32_t Signed
PIC S9(18) COMP 8 bytes long long / int64_t Signed
PIC S9(n)V9(m) COMP-3 varies No equivalent Convert in COBOL
POINTER 4/8 bytes void * Address
INDEX 4 bytes int Binary integer

COBOL to Java Mapping (via JNI)

COBOL JNI Type Java Type
PIC X(n) jstring String
PIC S9(4) COMP jshort short
PIC S9(9) COMP jint int
PIC S9(18) COMP jlong long
COMP-1 jfloat float
COMP-2 jdouble double

EBCDIC vs. ASCII Considerations

On z/OS, COBOL data is in EBCDIC encoding. C and Java on distributed platforms use ASCII (or UTF-8). When data crosses platform boundaries:

      *> EBCDIC 'A' = X'C1'
      *> ASCII  'A' = X'41'
      *> These are NOT the same byte!

Conversion options: 1. The Language Environment handles conversion automatically for LE-conforming programs on z/OS 2. For cross-platform communication, use explicit conversion functions 3. z/OS Connect and CICS Web Services handle encoding conversion for web service scenarios


26.12 Best Practices for Inter-Language Programming

Drawing from GlobalBank's and MedClaim's experience, here are essential best practices:

1. Establish a Binary Interface Standard

Decree that all cross-language parameters use COMP (binary) data types. No COMP-3, no DISPLAY numeric, no packed decimal crosses a language boundary.

      *> GOOD: Binary interface
       01  LS-AMOUNT-CENTS        PIC S9(18) COMP.

      *> BAD: Packed decimal across boundary
       01  LS-AMOUNT              PIC S9(7)V99 COMP-3.

2. Centralize String Conversion

Create a reusable string conversion utility:

       IDENTIFICATION DIVISION.
       PROGRAM-ID. STRING-CONV.

       DATA DIVISION.
       LINKAGE SECTION.
       01  LS-COBOL-STRING        PIC X(256).
       01  LS-COBOL-LENGTH        PIC S9(4) COMP.
       01  LS-C-STRING            PIC X(257).
       01  LS-DIRECTION            PIC X.
           88 TO-C-STRING         VALUE 'C'.
           88 TO-COBOL-STRING     VALUE 'B'.

       PROCEDURE DIVISION USING LS-COBOL-STRING
                                LS-COBOL-LENGTH
                                LS-C-STRING
                                LS-DIRECTION.
       MAIN-LOGIC.
           EVALUATE TRUE
               WHEN TO-C-STRING
                   PERFORM CONVERT-TO-C
               WHEN TO-COBOL-STRING
                   PERFORM CONVERT-TO-COBOL
           END-EVALUATE
           GOBACK.

       CONVERT-TO-C.
           MOVE ZEROS TO WS-TALLY
           INSPECT LS-COBOL-STRING(1:LS-COBOL-LENGTH)
               TALLYING WS-TALLY FOR TRAILING SPACES
           COMPUTE WS-ACTUAL =
               LS-COBOL-LENGTH - WS-TALLY
           MOVE LS-COBOL-STRING(1:WS-ACTUAL)
               TO LS-C-STRING(1:WS-ACTUAL)
           MOVE X'00'
               TO LS-C-STRING(WS-ACTUAL + 1:1).

       CONVERT-TO-COBOL.
           MOVE SPACES TO LS-COBOL-STRING
           INSPECT LS-C-STRING
               TALLYING WS-ACTUAL FOR CHARACTERS
               BEFORE INITIAL X'00'
           IF WS-ACTUAL > LS-COBOL-LENGTH
               MOVE LS-COBOL-LENGTH TO WS-ACTUAL
           END-IF
           MOVE LS-C-STRING(1:WS-ACTUAL)
               TO LS-COBOL-STRING(1:WS-ACTUAL).

3. Document Every Interface

Create a standard interface documentation format:

      *> ========================================================
      *> INTERFACE SPECIFICATION: CLM-VALIDATE
      *> ========================================================
      *> Purpose: Validate claim codes against C library
      *> Called by: CLM-INTAKE, CLM-ADJUD (COBOL)
      *> Calls: icd10_validate, cpt_validate (C library)
      *>
      *> C Library: libmedcodes.so version 4.2.1
      *> C Compiler: GCC 11.2 / XL C 16.1.1
      *>
      *> Parameters IN:
      *>   LS-CLAIM-RECORD    — Standard claim copybook
      *> Parameters OUT:
      *>   LS-VALIDATION-RESULT — 'OK','DX','PX','XX'
      *>   LS-VALIDATION-MSG    — Human-readable message
      *>
      *> Error codes: See CLM-VALIDATE-ERRORS copybook
      *> Last modified: 2025-03-15 by J.Okafor
      *> ========================================================

4. Test at the Boundary

Write tests that specifically exercise the language boundary with edge cases:

       TEST-BOUNDARY-CONDITIONS.
      *> Test 1: Empty string
           MOVE SPACES TO WS-TEST-STRING
           PERFORM CALL-C-FUNCTION
           IF WS-RESULT NOT = EXPECTED-EMPTY
               DISPLAY "FAIL: Empty string test"
           END-IF

      *> Test 2: Maximum length string
           MOVE ALL 'X' TO WS-TEST-STRING
           PERFORM CALL-C-FUNCTION
           IF WS-RESULT NOT = EXPECTED-FULL
               DISPLAY "FAIL: Max length test"
           END-IF

      *> Test 3: Special characters
           MOVE "O'Brien-Smith" TO WS-TEST-STRING
           PERFORM CALL-C-FUNCTION

      *> Test 4: Maximum numeric value
           MOVE 999999999 TO WS-TEST-NUMBER
           PERFORM CALL-C-NUMERIC
           IF WS-RESULT NOT = EXPECTED-MAX
               DISPLAY "FAIL: Max numeric test"
           END-IF

      *> Test 5: Negative values
           MOVE -1 TO WS-TEST-NUMBER
           PERFORM CALL-C-NUMERIC.

5. Handle Errors Across Boundaries

Define a consistent error-handling contract:

      *> Standard return code convention across all languages:
      *>   0     = Success
      *>   1-99  = Business logic error (documented)
      *>  -1     = Invalid parameter
      *>  -2     = Resource not found
      *>  -3     = System error (check LE messages)
      *>  -9     = Unknown error

26.13 The Student Mainframe Lab: Trying Inter-Language Calls

Using GnuCOBOL with C

If you are using GnuCOBOL on Linux, Mac, or Windows, you can experiment with COBOL-C interoperability. GnuCOBOL compiles COBOL to C, making the interface particularly natural.

🧪 Try It Yourself: COBOL Calling C with GnuCOBOL

Step 1: Write the C function (save as mathutil.c):

#include <math.h>

int compute_compound(int principal_cents,
                     int rate_bp,
                     int periods,
                     long long *result_cents) {
    double p = principal_cents / 100.0;
    double r = rate_bp / 10000.0;
    double result;

    if (p <= 0 || r <= 0 || periods <= 0) {
        *result_cents = 0;
        return -1;
    }

    result = p * pow(1.0 + r, periods);
    *result_cents = (long long)(result * 100);
    return 0;
}

Step 2: Write the COBOL program (save as callmath.cbl):

       IDENTIFICATION DIVISION.
       PROGRAM-ID. CALLMATH.

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-PRINCIPAL           PIC S9(9) COMP-5.
       01  WS-RATE-BP             PIC S9(9) COMP-5.
       01  WS-PERIODS             PIC S9(9) COMP-5.
       01  WS-RESULT-CENTS        PIC S9(18) COMP-5.
       01  WS-RC                  PIC S9(9) COMP-5.
       01  WS-DISPLAY-PRINCIPAL   PIC $$$,$$$,MATH1$,$$$,$$$,$$9.99.

       PROCEDURE DIVISION.
       MAIN-LOGIC.
           MOVE 10000000 TO WS-PRINCIPAL
           MOVE 500 TO WS-RATE-BP
           MOVE 360 TO WS-PERIODS

           COMPUTE WS-DISPLAY-PRINCIPAL =
               WS-PRINCIPAL / 100
           DISPLAY "Principal: " WS-DISPLAY-PRINCIPAL
           DISPLAY "Rate: 5.00% annual"
           DISPLAY "Periods: " WS-PERIODS " months"

           CALL "compute_compound"
               USING BY VALUE WS-PRINCIPAL
                     BY VALUE WS-RATE-BP
                     BY VALUE WS-PERIODS
                     BY REFERENCE WS-RESULT-CENTS
               RETURNING WS-RC

           IF WS-RC = 0
               COMPUTE WS-DISPLAY-RESULT =
                   WS-RESULT-CENTS / 100
               DISPLAY "Future value: "
                       WS-DISPLAY-RESULT
           ELSE
               DISPLAY "Error: RC=" WS-RC
           END-IF

           STOP RUN.

Step 3: Compile and link:

# Compile C code to object file
gcc -c -o mathutil.o mathutil.c -lm

# Compile COBOL and link with C object
cobc -x -o callmath callmath.cbl mathutil.o -lm

Step 4: Run:

./callmath

📊 Note on COMP-5: In GnuCOBOL, COMP-5 specifies native binary format, which maps directly to C int types. This is the preferred numeric type for C interoperability in GnuCOBOL, as opposed to COMP which may have COBOL-specific binary formatting.


26.14 Advanced Topic: COBOL and .NET Interoperability

While less common on z/OS, Micro Focus COBOL on Windows and Linux supports direct interoperability with .NET languages (C#, VB.NET). This is relevant for organizations that run COBOL on distributed platforms.

Managed COBOL

Micro Focus Visual COBOL can compile COBOL to .NET managed code, allowing COBOL classes to be used directly from C# and vice versa:

      *> COBOL class compiled to .NET assembly
       CLASS-ID. AccountValidator PUBLIC.

       OBJECT.
         PROCEDURE DIVISION.

         METHOD-ID. Validate PUBLIC.
           DATA DIVISION.
           LINKAGE SECTION.
           01  LS-ACCT-NUM        PIC X(10).
           01  LS-IS-VALID        PIC X.
           PROCEDURE DIVISION USING LS-ACCT-NUM
                              RETURNING LS-IS-VALID.
               IF LS-ACCT-NUM(1:2) = "10"
                   MOVE 'Y' TO LS-IS-VALID
               ELSE
                   MOVE 'N' TO LS-IS-VALID
               END-IF.
         END METHOD Validate.

       END OBJECT.

       END CLASS AccountValidator.

The C# caller:

// C# calling managed COBOL
var validator = new AccountValidator();
string result = validator.Validate("1000000001");
Console.WriteLine($"Valid: {result}");

This approach eliminates the parameter-mapping complexity of native interop because both languages run on the same .NET runtime. String types, numeric types, and object references are managed by the .NET framework.

📊 When to Consider .NET Interop: This approach is most relevant for organizations migrating COBOL from mainframes to distributed platforms using Micro Focus. It offers the smoothest interop experience — no JNI, no C glue code, no parameter-mapping tables. However, it requires Micro Focus Visual COBOL and a .NET runtime.


26.15 Security Considerations in Inter-Language Communication

Inter-language boundaries introduce security considerations that single-language systems do not face.

Buffer Overflow Risks

When COBOL passes a PIC X(50) buffer to a C function, the C function can write beyond the 50-byte boundary if not carefully coded. COBOL's fixed-length data items do not prevent C from overwriting adjacent memory:

      *> COBOL declares a 50-byte buffer
       01  WS-BUFFER              PIC X(50).
       01  WS-SENSITIVE-DATA      PIC X(100).

      *> If C function writes more than 50 bytes to WS-BUFFER,
      *> it overwrites WS-SENSITIVE-DATA!
       CALL "c_fill_buffer"
           USING BY REFERENCE WS-BUFFER
                 BY VALUE 50

Mitigation: Always pass the buffer size as an explicit parameter and verify in the C function that writes do not exceed the specified size. Consider adding a guard field:

       01  WS-BUFFER              PIC X(50).
       01  WS-GUARD               PIC X(8) VALUE 'GUARDIAN'.

After the C call, verify that WS-GUARD still contains 'GUARDIAN'. If it has been overwritten, a buffer overflow occurred.

Encoding Injection Attacks

When COBOL receives string data from external systems (through C or Java wrappers) and uses it in SQL queries, ensure that input validation occurs before embedding the data in SQL:

      *> BAD: Passing unvalidated external input to SQL
       CALL "get_user_input"
           USING BY REFERENCE WS-ACCT-NUMBER

       EXEC SQL
           SELECT ... WHERE ACCT_NUMBER = :WS-ACCT-NUMBER
       END-EXEC

      *> GOOD: Validate before SQL
       CALL "get_user_input"
           USING BY REFERENCE WS-ACCT-NUMBER

       INSPECT WS-ACCT-NUMBER
           REPLACING ALL "'" BY SPACE
       IF WS-ACCT-NUMBER IS NUMERIC
           EXEC SQL
               SELECT ... WHERE ACCT_NUMBER = :WS-ACCT-NUMBER
           END-EXEC
       ELSE
           DISPLAY "Invalid account number format"
       END-IF

💡 Key Insight: Embedded SQL with host variables (the colon notation) is inherently resistant to SQL injection because DB2 treats host variable values as data, not as SQL code. This is one advantage of embedded SQL over dynamically constructed SQL strings. However, if you build SQL strings by concatenating user input — even in a COBOL program — you can still create injection vulnerabilities.

Authentication at Language Boundaries

When Java or C programs call COBOL, ensure that authentication and authorization are enforced at the boundary, not assumed to be inherited:

       METHOD-ID. ProcessSecureTransaction.
           DATA DIVISION.
           LINKAGE SECTION.
           01  LS-USER-TOKEN       PIC X(64).
           01  LS-TXN-DATA         PIC X(200).
           01  LS-RESULT           PIC 9(2).
           PROCEDURE DIVISION USING LS-USER-TOKEN
                                    LS-TXN-DATA
                              RETURNING LS-RESULT.
      *> Validate authentication token first
               CALL "validate_auth_token"
                   USING BY REFERENCE LS-USER-TOKEN
                   RETURNING WS-AUTH-VALID

               IF WS-AUTH-VALID NOT = 1
                   MOVE 99 TO LS-RESULT
                   DISPLAY "AUTH FAILURE: Invalid token"
                   GOBACK
               END-IF

      *> Token valid — proceed with business logic
               ...

26.16 Performance Considerations

Inter-language calls have overhead that does not exist in same-language calls. Understanding this overhead helps you make informed design decisions.

Call Overhead

Each cross-language call incurs: - Parameter marshaling: Converting data between language representations - Context switching: Moving between language runtime environments - Stack management: Setting up and tearing down stack frames for the target language

On z/OS under the Language Environment, a COBOL-to-C call typically adds 5-15 microseconds of overhead compared to a COBOL-to-COBOL call. For a single call, this is negligible. For a million calls in a batch loop, it adds 5-15 seconds.

Design for Chunky, Not Chatty, Interfaces

The key performance principle is to minimize the number of cross-language calls, even if each call does more work:

      *> BAD: Chatty interface — 3 cross-language calls
       CALL "c_validate_name" USING WS-NAME WS-RESULT-1
       CALL "c_validate_date" USING WS-DATE WS-RESULT-2
       CALL "c_validate_code" USING WS-CODE WS-RESULT-3

      *> GOOD: Chunky interface — 1 cross-language call
       CALL "c_validate_all"
           USING WS-NAME WS-DATE WS-CODE WS-RESULTS

The "chunky" approach passes more data in a single call, reducing the total marshaling and context-switching overhead.

When to Cross Language Boundaries

Use this decision matrix:

Criterion Stay in COBOL Cross to C/Java
Called once per transaction Marginal benefit Acceptable overhead
Called millions of times Avoid boundary Avoid boundary (move logic to one side)
Complex algorithm Consider C Good fit for C
Simple data lookup Stay in COBOL Unnecessary overhead
Existing tested library Rewrite cost Use the library

26.17 The Student Mainframe Lab: Getting Started with Inter-Language Calls

Environment Setup for GnuCOBOL and C

If you are using GnuCOBOL on Linux, macOS, or Windows (via WSL or MinGW), the COBOL-C interface is the most accessible inter-language exercise. GnuCOBOL compiles COBOL to C internally, making the interface particularly natural.

Prerequisites: - GnuCOBOL 3.1+ installed (cobc --version to check) - GCC installed (gcc --version to check) - Both accessible from the same shell

Verify setup:

# Check GnuCOBOL
cobc --version
# Should show GnuCOBOL 3.x

# Check GCC
gcc --version
# Should show GCC version

# Simple test — compile a COBOL hello world
echo '       IDENTIFICATION DIVISION.
       PROGRAM-ID. HELLO.
       PROCEDURE DIVISION.
           DISPLAY "Hello from COBOL".
           STOP RUN.' > hello.cbl
cobc -x -o hello hello.cbl
./hello

Walkthrough: Your First COBOL-C Program

Let us build a complete, working example from scratch.

Step 1: Create the C function (save as strutil.c):

#include <ctype.h>
#include <string.h>

/* Count vowels in a string (spaces are not vowels) */
int count_vowels(char *str, int length) {
    int count = 0;
    int i;
    for (i = 0; i < length; i++) {
        char c = toupper(str[i]);
        if (c == 'A' || c == 'E' || c == 'I' ||
            c == 'O' || c == 'U') {
            count++;
        }
    }
    return count;
}

/* Reverse a string in place */
void reverse_string(char *str, int length) {
    int i, j;
    char temp;
    /* Find actual end (skip trailing spaces) */
    j = length - 1;
    while (j >= 0 && str[j] == ' ') j--;
    /* Reverse from start to actual end */
    for (i = 0; i < j; i++, j--) {
        temp = str[i];
        str[i] = str[j];
        str[j] = temp;
    }
}

/* Convert all lowercase to uppercase */
void to_upper(char *str, int length) {
    int i;
    for (i = 0; i < length; i++) {
        str[i] = toupper(str[i]);
    }
}

Step 2: Create the COBOL program (save as teststr.cbl):

       IDENTIFICATION DIVISION.
       PROGRAM-ID. TESTSTR.

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-TEST-STRING         PIC X(30)
                                  VALUE "Hello COBOL World".
       01  WS-STRING-LEN          PIC S9(9) COMP-5.
       01  WS-VOWEL-COUNT         PIC S9(9) COMP-5.

       PROCEDURE DIVISION.
       MAIN-LOGIC.
           DISPLAY "=== COBOL-C String Utils ==="
           DISPLAY "Original: [" WS-TEST-STRING "]"
           MOVE FUNCTION LENGTH(WS-TEST-STRING)
               TO WS-STRING-LEN

      *> Count vowels via C function
           CALL "count_vowels"
               USING BY REFERENCE WS-TEST-STRING
                     BY VALUE WS-STRING-LEN
               RETURNING WS-VOWEL-COUNT
           DISPLAY "Vowels:   " WS-VOWEL-COUNT

      *> Convert to uppercase via C function
           CALL "to_upper"
               USING BY REFERENCE WS-TEST-STRING
                     BY VALUE WS-STRING-LEN
           DISPLAY "Upper:    [" WS-TEST-STRING "]"

      *> Reverse the string via C function
           CALL "reverse_string"
               USING BY REFERENCE WS-TEST-STRING
                     BY VALUE WS-STRING-LEN
           DISPLAY "Reversed: [" WS-TEST-STRING "]"

           DISPLAY "=== Done ==="
           STOP RUN.

Step 3: Compile and run:

# Compile C code to object file
gcc -c -o strutil.o strutil.c

# Compile COBOL and link with C object
cobc -x -o teststr teststr.cbl strutil.o

# Run
./teststr

Expected output:

=== COBOL-C String Utils ===
Original: [Hello COBOL World             ]
Vowels:   5
Upper:    [HELLO COBOL WORLD             ]
Reversed: [DLROW LOBOC OLLEH             ]
=== Done ===

Common Pitfalls in the Lab

Pitfall 1: Forgetting BY VALUE. If you pass WS-STRING-LEN BY REFERENCE instead of BY VALUE, the C function receives a pointer to the integer, not the integer itself. The "length" it sees will be an address (like 140735291562788), not 30. The function will try to process millions of bytes, likely causing a segfault.

Pitfall 2: COMP vs. COMP-5. In GnuCOBOL, COMP-5 specifies native binary format (identical to C's int), while COMP may use COBOL's binary format with truncation rules. For C interop in GnuCOBOL, always use COMP-5.

Pitfall 3: String length. COBOL's WS-TEST-STRING is 30 bytes, space-padded. The C function receives all 30 bytes, including the trailing spaces. Functions like reverse_string above must account for this by trimming trailing spaces first.

Pitfall 4: Return values. In GnuCOBOL, the RETURNING clause maps to the C function's return value. Ensure the COBOL return variable matches the C return type (COMP-5 for int).


26.18 Chapter Review Questions

  1. Explain the difference between passing a parameter BY REFERENCE and BY VALUE. Give a scenario where using the wrong convention would produce incorrect results without causing an ABEND (making it hard to detect).

  2. Why does this chapter recommend COMP (binary) data types for all cross-language parameters? What would happen if you passed a PIC S9(7)V99 COMP-3 item to a C function that expects a 4-byte integer?

  3. Design a binary interface for a COBOL program that calculates sales tax. The program should accept an item price and a state code, and return the tax amount and the total price. Specify all parameter types on both the COBOL and C sides.

  4. Evaluate this statement: "If we need Java integration, we should rewrite the COBOL in Java rather than building a three-layer bridge." Under what circumstances is this true? Under what circumstances is it false?


26.19 Common Mistakes and How to Avoid Them

This section catalogs the most common inter-language programming errors, drawn from real production incidents at organizations like GlobalBank and MedClaim. Studying these mistakes will help you avoid repeating them.

Mistake 1: Passing DISPLAY Numeric to C

      *> WRONG: PIC 9(8) DISPLAY is EBCDIC characters, not binary
       01  WS-DATE               PIC 9(8) VALUE 20250315.

       CALL "c_process_date"
           USING BY VALUE WS-DATE

The C function receives the EBCDIC byte representation of the characters "20250315" — which is not the integer 20250315. On z/OS, the EBCDIC encoding of "2" is X'F2', not X'02'.

Fix: Convert to COMP before passing:

       01  WS-DATE-DISPLAY       PIC 9(8) VALUE 20250315.
       01  WS-DATE-BINARY        PIC S9(9) COMP.

       MOVE WS-DATE-DISPLAY TO WS-DATE-BINARY
       CALL "c_process_date"
           USING BY VALUE WS-DATE-BINARY

Mistake 2: Forgetting to Null-Terminate Strings

      *> WRONG: C expects null-terminated string
       CALL "c_print_string"
           USING BY REFERENCE WS-COBOL-STRING

The C function's strlen() will search past the end of the COBOL string looking for a null byte, reading whatever follows in memory — potentially causing a segfault or exposing sensitive data.

Fix: Always null-terminate before calling C, or always pass the length as a separate parameter.

Mistake 3: Returning from a Callable COBOL Program with STOP RUN

      *> WRONG: STOP RUN terminates the entire runtime
       PROGRAM-ID. CALC-INTEREST.
       ...
       PROCEDURE DIVISION USING ...
           ...
           STOP RUN.

If this program is called from Java via JNI, STOP RUN terminates the entire JVM. The Java application crashes without warning.

Fix: Use GOBACK for callable programs:

           GOBACK.

Mistake 4: Assuming Same Endianness

On z/OS, integers are big-endian (most significant byte first). On x86 Linux/Windows, integers are little-endian (least significant byte first). If COBOL on z/OS writes a binary integer to a file and C on Linux reads it, the value will be wrong.

Fix: Either convert endianness explicitly, or use character representations (which are byte-order independent) for data exchange files.

Mistake 5: Ignoring Return Codes from C Functions

      *> WRONG: Not checking return code
       CALL "c_validate" USING WS-DATA
       PERFORM PROCESS-DATA

If c_validate returns an error code, the program proceeds to PROCESS-DATA with potentially invalid data.

Fix: Always capture and check the return code:

       CALL "c_validate" USING WS-DATA
           RETURNING WS-RC
       IF WS-RC NOT = 0
           DISPLAY "Validation failed: " WS-RC
           PERFORM ERROR-HANDLER
       END-IF
       PERFORM PROCESS-DATA

Mistake 6: Not Using GOBACK in Nested Calls

When COBOL program A calls C function B, which calls COBOL program C, program C must use GOBACK to return to B. If C uses STOP RUN, it terminates the entire call chain — A, B, and C all terminate. If C uses EXIT PROGRAM, it may not properly return to the C function depending on the runtime.

      *> Program C — called from C function B
       PROCEDURE DIVISION USING ...
           ...
           GOBACK.    *> Correctly returns to C function B
      *>   STOP RUN.  *> WRONG — kills everything

Mistake 7: Passing Group Items to C

      *> RISKY: Group item layout may include filler/padding
       01  WS-RECORD.
           05  WS-NAME            PIC X(20).
           05  WS-AGE             PIC S9(4) COMP.
           05  WS-SALARY          PIC S9(7)V99 COMP-3.

       CALL "c_process_record"
           USING BY REFERENCE WS-RECORD

The C function receives a pointer to the raw bytes of WS-RECORD. The layout depends on COBOL's storage rules, which may include padding bytes between fields. The C struct must match exactly — including any alignment padding.

Fix: Define the corresponding C struct with #pragma pack(1) to eliminate padding, and verify the total size matches FUNCTION LENGTH(WS-RECORD).


26.20 Putting It All Together: Integration Decision Framework

When faced with an integration requirement, use this decision framework to choose the right approach:

Step 1: Define the Requirement

Ask these questions: - Direction: Does COBOL need to call out, or does an external system need to call in? - Volume: How many calls per second/minute/hour? - Latency: What is the acceptable response time? - Data complexity: Simple scalars, strings, or complex structures?

Step 2: Choose the Integration Pattern

Requirement Recommended Pattern
COBOL needs a utility function (math, string, date) COBOL calls C
Web app needs COBOL business logic Java → JNI/C → COBOL
COBOL needs to call a REST API COBOL → C (libcurl) or CICS Web Services
High-frequency inner loop Consider assembler, or move logic to one language
New subsystem replacing COBOL Write in Java/C#, expose via API
Gradual modernization Wrapper pattern with binary interface

Step 3: Implement with Standards

Regardless of which pattern you choose: 1. Use the binary interface pattern (COMP parameters only) 2. Document the interface specification 3. Write boundary tests for edge cases 4. Add trace logging at every boundary 5. Plan for monitoring and alerting

Step 4: Plan for Maintenance

The integration layer is the most fragile part of a mixed-language system. Plan for: - Versioning: What happens when the C library is updated? - Testing: How do you test the boundary when you can only change one side? - Documentation: Who maintains the interface spec as both sides evolve? - Skills: Who can debug issues that span multiple languages?


26.21 Summary

Inter-language communication transforms COBOL from an isolated language into a collaborative partner in polyglot enterprise architectures. The key techniques covered in this chapter include:

COBOL calling C: Use COMP (binary) parameters, pass scalars BY VALUE and strings/structures BY REFERENCE, handle string null-termination explicitly, and verify parameter formats before the call.

C/Java calling COBOL: Design COBOL programs with binary interfaces, use GOBACK (not STOP RUN) for callable programs, and handle packed decimal conversion internally in COBOL.

Java interop: Use a three-layer architecture (Java → JNI/C glue → COBOL), convert between Java types and COBOL binary types in the C glue layer, and manage string encoding explicitly.

Assembler calls: Follow standard z/OS linkage conventions, use CALL with standard parameters, and rely on the Language Environment for register management.

Web services: Use CICS web services, z/OS Connect, or C wrapper libraries with libcurl to connect COBOL to REST/SOAP endpoints.

The overarching principle is the binary interface pattern: every cross-language boundary uses COMP data types for numeric values and explicitly manages string format conversion. This single convention prevents the majority of inter-language bugs.

⚖️ The Modernization Spectrum: Inter-language communication is perhaps the most pragmatic modernization strategy. It preserves decades of tested COBOL business logic while enabling integration with modern technologies. The approach accepts complexity at the interface boundary in exchange for stability in the business logic core — a tradeoff that most shops find favorable.

🔵 The Human Factor: The real challenge in inter-language programming is not syntax — it is communication between development teams. COBOL developers, Java developers, and C programmers each bring different assumptions, different debugging tools, and different mental models. Successful inter-language architectures invest as much in cross-team collaboration as in cross-language code.

🔗 Looking Ahead: Chapter 27 introduces embedded SQL — COBOL's primary interface to DB2 databases. Where this chapter covered calling other programs, Chapter 27 covers calling a database. The themes are similar: parameter mapping, error handling, and the importance of understanding both sides of the interface.