COBOL Textbook

Calling a C function from COBOL, and being called back

CALL BY VALUE, BY REFERENCE and RETURNING across the C ABI, then the other direction with cob_init and cob_resolve, with both listings and the build lines.

Published
Length
1,488 words, 7 min

GnuCOBOL compiles COBOL to C and then compiles that with your system C compiler. That single fact is why calling C from COBOL is easy, and it is also why the mistakes are so sharp when they happen: you are not crossing a language boundary with a translation layer to protect you, you are landing directly on the C ABI with COBOL's data types in your hands.

The GnuCOBOL FAQ is blunt about it. Under its list of known problems it says that "of special note are the edge cases surrounding foreign function interfacing and BY VALUE parameter handling with CALL and PROCEDURE DIVISION USING", because "COBOL has a very specific, and rigid, view of data types, and it is sometimes tricky mapping that to non-COBOL programs with different features allowed in call frame setup."

This page works through both directions: COBOL calling C, then C calling COBOL. Both listings ship, along with the build lines.

What CALL actually compiles into

Start with what the compiler does, because the whole interface follows from it. The manual gives the generated shape directly. This COBOL:

CALL "subr" USING X.

becomes, in the generated C:

int (*func)() = cob_resolve("subr");
if (func != NULL)
    func (X);

So a CALL with a literal program name is a run-time symbol lookup followed by an indirect call. There is no name mangling, no calling-convention negotiation, nothing between your COBOL and a C function with that exact name. Compile a C file with a function called c_add, link it in, and CALL "c_add" finds it.

The manual also documents the optimisation: with -fstatic-call the compiler emits subr(X); directly instead of resolving at run time. It notes the limit, which is worth remembering — the option "only takes effect when the called program name is in a literal (like CALL "subr"). With a data name (like CALL SUBR), the program is still called dynamically."

The three parameter modes

X in that generated snippet is passed as an address. That is COBOL's default and it is the first thing to get straight, because C functions mostly do not want addresses.

The FAQ states the working rule plainly: "Pass a REFERENCE or use RETURNING if C sets the value. Use VALUE when you want C to have its view of the pointer, not the REFERENCE address of the COBOL POINTER. So most inits are BY REFERENCE (or RETURNING) and most usage, including rundown of C ABI tools, is usually USING BY VALUE."

In practice that resolves to three cases:

Table 4.3-1
C signature COBOL call Why
int c_add(int a, int b) USING BY VALUE a BY VALUE b RETURNING sum C takes ints by value; COBOL must hand over the value, not its address
int c_fill(char *buf, int len) USING BY REFERENCE buf BY VALUE len C writes into the buffer, so it needs the address; the length is a scalar
const char *c_name(void) RETURNING ptr the return value is an address, so it lands in a USAGE POINTER

Get the mode wrong and nothing warns you. Pass a BINARY-LONG BY REFERENCE to a function expecting int and C reads the pointer as an integer; on a 64-bit machine that is a large, arbitrary, perfectly valid-looking number.

Matching the types

The other half of the mapping is the data description. COBOL's USAGE BINARY-* types are the ones with a defined width, and they are what you use at the boundary. The reserved-word list in the manual marks all of these as supported:

Table 4.3-2
COBOL Width C
BINARY-CHAR 8-bit signed char
BINARY-SHORT 16-bit short
BINARY-LONG (alias BINARY-INT) 32-bit int / int32_t
BINARY-DOUBLE (alias BINARY-LONG-LONG) 64-bit long long / int64_t
BINARY-C-LONG platform C's own long

BINARY-C-LONG earns its odd name: C's long is 32 bits on Windows and 64 bits on most Unix systems, so there is no fixed-width COBOL type that is always right for it. Use BINARY-C-LONG only when the C declaration really did say long, and a fixed-width type everywhere else.

Two absences matter. The list marks BOOLEAN as not supported, so a C bool parameter has to travel as a BINARY-CHAR. And there is no COBOL type for a null-terminated string at all, which is the subject of the next section.

A PIC 9(4) item is not an integer for these purposes. It is four ASCII digits in DISPLAY usage, and passing it to a C function expecting int hands over the bytes 0031 0037 0032 0035, not the number 1725.

Strings, which C does not have either

The FAQ makes the point precisely: "the term string is used loosely with C, C has no string type but instead has character arrays, most often terminated by a null byte of zero that determines the size at runtime." COBOL's PIC X(n) is the opposite — a fixed width, space-padded, with no terminator.

So every string crossing the boundary needs an explicit decision, and there are only three sensible ones.

Send a buffer and its length. COBOL owns the storage, C writes into it, and the length goes along BY VALUE so C can stay inside it. There is no bounds check on the COBOL side to catch an overrun, which is why the demo's c_fill clamps against len before it copies.

Send a null-terminated literal with BY CONTENT. The FAQ's own example does this: CALL "use-lib" USING BY VALUE c-handle BY CONTENT "Hello" & x"00". The & x"00" is the terminator, appended by hand, because nothing else is going to add it.

Receive a pointer and walk it. When C returns a char *, take it into a USAGE POINTER, declare a PIC X BASED item, and move the address along one byte at a time until you hit X"00":

01  WS-CPTR   USAGE POINTER.
01  WS-CCHAR  PIC X BASED.
...
CALL "c_name" RETURNING WS-CPTR
PERFORM UNTIL WS-I >= 32
    SET ADDRESS OF WS-CCHAR TO WS-CPTR
    IF WS-CCHAR = X"00"
        EXIT PERFORM
    END-IF
    ADD 1 TO WS-I
    MOVE WS-CCHAR TO WS-NAME(WS-I:1)
    SET WS-CPTR UP BY 1
END-PERFORM

SET ADDRESS OF ... TO is what makes a BASED item point at memory you did not declare, and the bound of 32 on the loop is not decoration: without it, a missing terminator walks off into whatever follows.

Building it

Compile the C, then hand the object file to cobc along with the COBOL:

cc -c cobol-call-c-function.c
cobc -x cobol-call-c-function.cob cobol-call-c-function.o
./cobol-call-c-function

-x builds an executable rather than a module. The manual notes that any number of functions can live in a single C file, and that the linked programs are called dynamically — the symbol is resolved at run time — unless you ask otherwise with -fstatic-call.

The other direction: C as the main program

Now turn it around. Section 2.3.1 of the manual gives the sequence: "Include libcob.h in your C program and call cob_init before using any COBOL module. Do a cleanup afterwards, either by calling cob_stop_run (if your program should terminate) or by calling cob_tidy (if your program should execute further on without any more COBOL calls)."

That last distinction is the one that catches people. cob_stop_run does not return. If your C program has work to do after the COBOL part, cob_tidy is the call you want, and the manual adds that "calling cob_init, one or several GnuCOBOL modules and then cob_tidy in this sequence can be done multiple times."

Finding the COBOL module is the same cob_resolve the compiler was generating earlier, only now you write it yourself:

cob_init(argc, argv);

addtwo = (int (*)()) cob_resolve("ADDTWO");
if (addtwo == NULL) {
    fprintf(stderr, "could not resolve ADDTWO\n");
    cob_tidy();
    return 1;
}

addtwo(&a, &b, &sum);
cob_tidy();

The addresses are not optional. A COBOL subprogram's LINKAGE SECTION items are the caller's storage, so PROCEDURE DIVISION USING LK-A LK-B LK-SUM expects three addresses. And the subprogram ends with GOBACK, not STOP RUN: STOP RUN ends the whole run unit, and the C driver would never reach its next line.

Build the COBOL as a loadable module with -m, and let the runtime find it:

cobc -m addtwo.cob
cc -DCOBOL_CALLBACK_MAIN cobol-call-c-function.c -o cdriver \
   $(cob-config --cflags) $(cob-config --libs)
COB_LIBRARY_PATH=. ./cdriver

What is not verified here

These listings have not been compiled. There is no cobc on the machine this was written on, so every line was desk-checked against the Programmer's Guide rather than run. The syntax, the flags and the runtime function names all come from the manual and the FAQ, cited above; the behaviour of your build is your own to confirm.

The FAQ's own caveats stand. It notes that user-defined functions accept only BY REFERENCE parameters, that BY VALUE and BY CONTENT are not supported for them, and that numeric literals passed to a FUNCTION-ID are currently handed over in string form — with intermediate working storage and a MOVE given as the reliable workaround. Those notes are dated within the FAQ; check them against the version you are running.

Nothing here is IBM COBOL. Enterprise COBOL on z/OS has its own conventions for calling C, and the cob_init and cob_resolve functions are GnuCOBOL runtime entry points that do not exist there.

The thing worth taking away is the first one: CALL "name" is a symbol lookup and an indirect call, nothing more. Once that is clear, every question at the boundary becomes a question about C — what does this function expect, by value or by address, how wide, who owns the memory — and COBOL's job is only to describe its data precisely enough to answer.

Index terms cobol, gnucobol, c interface, call, linkage section