> "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...
In This Chapter
- 26.1 Why Inter-Language Communication Matters
- 26.2 Calling C from COBOL
- 26.3 Calling COBOL from C
- 26.4 Java Interop — COBOL and Java Together
- 26.5 SPECIAL-NAMES and CALL-CONVENTION
- 26.6 Assembler Calls — High-Performance Routines
- 26.7 Mixed-Language Debugging
- 26.8 Web Service Integration
- 26.9 GlobalBank Case Study: The Integration Layer
- 26.10 MedClaim Case Study: External Validation
- 26.11 Data Type Mapping Reference
- 26.12 Best Practices for Inter-Language Programming
- 26.13 The Student Mainframe Lab: Trying Inter-Language Calls
- 26.14 Advanced Topic: COBOL and .NET Interoperability
- 26.15 Security Considerations in Inter-Language Communication
- 26.16 Performance Considerations
- 26.17 The Student Mainframe Lab: Getting Started with Inter-Language Calls
- 26.18 Chapter Review Questions
- 26.19 Common Mistakes and How to Avoid Them
- 26.20 Putting It All Together: Integration Decision Framework
- 26.21 Summary
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:
- Java class with native method declarations
- C "glue" code implementing the JNI native methods
- 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
-
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).
-
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?
-
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.
-
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.