Easy Peasy: Go Calling COBOL Calling Go on z/OS
Go is a modern, advanced, but easy to learn language that is compiled to efficient native code, and is fully supported on z/OS. At this writing, the latest version is 1.24. It is widely known that Go can call C on virtually every platform, including z/OS. On z/OS with IBM Open Enterprise SDK for Go, we go a little further and allow Go to call other enterprise languages with as much ease as it can call C. In this blog we will show how Go can call COBOL, and then we’ll go one step further and use COBOL to call back into Go.
Go Calling COBOL
We’ll start with the simplest of COBOL programs (called ccc.cbl)
IDENTIFICATION DIVISION.
PROGRAM-ID. MYPROG.
DATA DIVISION.
WORKING-STORAGE SECTION.
PROCEDURE DIVISION.
DISPLAY "In MYPROG".
GOBACK.
This is a pretty simple COBOL program. It only has one executable statement – to print “In MYPROG”. Nonetheless it illustrates how a COBOL program can be called from Go as a function. Note that the program ends with GOBACK, because it is being called like a function, so it needs the equivalent of “return” statement.
Here is the Go program (main.go) that calls it:
package main
/*
#cgo LDFLAGS: XDDLL.x
int MYPROG(void);
*/
import "C"
import (
"fmt"
"runtime"
"github.com/ibmruntimes/go-recordio/v2/utils"
)
func main() {
runtime.LockOSThread() // lock thread
utils.ThreadEbcdicMode() // COBOL must run in ebcdic
ret := C.MYPROG()
utils.ThreadAsciiMode() // restore
runtime.UnlockOSThread() // unlock thread
fmt.Printf("Call COBOL MYPROG returns %d\n", ret)
}
Here are the points to note
- The COBOL program is called in the same way that a C program would be called, with a CGO comment ending with ‘import “C”’.
- The CGO comment includes an LDFLAGS directive which names the XDDLL.x sidefile associated with the dll our COBOL has been compiled into.
- The CGO comment also includes a function prototype for the COBOL subprogram we’ll be calling: int MYPROG(void); The prototype declares an int return, but we don’t actually return anything so that value will be zero by default.
- We lock the Go thread during the calling of the COBOL program in order to retain EBCDIC mode. This is because other goroutines may otherwise be scheduled to this thread, and they wouldn’t work properly in EBCDIC mode. Thread locking is accomplished with a routine imported from the runtime package.
- We switch to EBCDIC mode before calling COBOL. This is accomplished with a utility imported from go-recordio.
- We call MYPROG via the CGO syntax C.MYPROG() – this allows us to use z/OS standard XPLINK mechanics for calling other languages.
- We set the thread back to ASCII mode and unlock it
- Finally we print the return value, which is 0 because the COBOL program didn’t actually return any value, so we get the default value for the ret variable.
These programs can be built with the following commands:
cob2 -q64 -vV -comprc_ok=8 -q"list,LP(64)" -qexportall -c ccc.cbl
cob2 -Wl,dll,lp64 -o XDDLL ccc.o
go build
COBOL Calling Other Programs
Before we look at COBOL calling back to Go, let’s look at how COBOL calls another COBOL program with arguments. Later, we’ll use the same basic techniques when calling back to Go. Consider these two COBOL progams (ccc1.cbl and ccc2.cbl):
IDENTIFICATION DIVISION.
PROGRAM-ID. MYPROG.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 ASTRING.
05 FILLER PIC X(5) VALUE 'BILLO'.
05 FILLER PIC X VALUE X'00'.
01 ULTIMATEANSWER PIC S9(8) COMP VALUE 42.
01 MYCOUNT PIC S9(8) COMP VALUE 777.
01 RESULT PIC S9(8) COMP VALUE 0.
PROCEDURE DIVISION.
DISPLAY "in MYPROG".
CALL "SUBPROG" USING ASTRING ULTIMATEANSWER MYCOUNT.
GOBACK.
IDENTIFICATION DIVISION.
PROGRAM-ID. SUBPROG.
DATA DIVISION.
LINKAGE SECTION.
01 ASTRING.
05 FILLER PIC X(5).
05 FILLER PIC X.
01 ULTIMATEANSWER PIC S9(8) COMP.
01 MYCOUNT PIC S9(8) COMP.
PROCEDURE DIVISION USING ASTRING ULTIMATEANSWER MYCOUNT.
DISPLAY "in PROCEDURE division"
DISPLAY "ASTRING " ASTRING.
DISPLAY "ANSWER TO THE ULTIMATE QUESTION " ULTIMATEANSWER.
DISPLAY "MYCOUNT " MYCOUNT.
GOBACK.
Here, the first program – MYPROG – is invoked by Go, just as we did before, however MYPROG then calls the program SUBPROG – also written in COBOL. Notice how the CALL statement simply lists the arguments to be provided to the second program (ASTRING ULTIMATEANSWER MYCOUNT), which are then available to the second program through a USING verb. The output of running this program is:
in MYPROG
in PROCEDURE division
ASTRING BILLO
ANSWER TO THE ULTIMATE QUESTION 00000042
MYCOUNT 00000777
Call COBOL MYPROG returns 0
Where the last line was printed by the Go program that started things off.
Calling Back to Go
Now lets change this around so that instead of calling a COBOL program, the first program calls back into Go using those same arguments, except this time we’ll add some flavor to the mix by applying the preposition BY to the arguments. Specifically, we’ll use BY CONTENT, BY REFERENCE, and BY VALUE, then we’ll see how those are reflected in the Go code. Here is COBOL program (ccc.cbl):
IDENTIFICATION DIVISION.
PROGRAM-ID. SUBPROG.
DATA DIVISION.
WORKING-STORAGE SECTION.
* int TESTAPI(const char *astring,
* int *intp, int mycount)
01 ASTRING.
05 FILLER PIC X(5) VALUE 'BILLO'.
05 FILLER PIC X VALUE X'00'.
01 ULTIMATEANSWER PIC S9(8) COMP VALUE 42.
01 MYCOUNT PIC S9(8) COMP VALUE 777.
01 RESULT PIC S9(8) COMP VALUE 0.
LINKAGE SECTION.
01 FP USAGE IS FUNCTION-POINTER.
PROCEDURE DIVISION USING BY VALUE FP.
DISPLAY "in PROCEDURE division".
DISPLAY "about to call function pointer"
CALL FP USING
BY CONTENT ASTRING,
BY REFERENCE ULTIMATEANSWER ,
BY VALUE MYCOUNT
RETURNING RESULT.
DISPLAY "RESULT " RESULT.
GOBACK.
END PROGRAM 'SUBPROG'.
Instead of calling a second COBOL program, we are now receiving a function pointer from the Go calling program as seen in the LINKAGE section: FP is being used as a FUNCTION-POINTER. We proceed to call FP using all the arguments as before, but with the modifications as mentioned above. By the way, let’s remember for later that the string ASTRING is given a value that ends with 05 FILLER PIC X VALUE X’00’.
Let’s now look at the Go program (main.go):
package main
/*
#cgo LDFLAGS: XDDLL.x
typedef int (*callback_t)(const char *astring, int *intp, int mycount);
int SUBPROG(callback_t cb);
extern int TESTAPI(char *astring, int *intp, int mycount);
*/
import "C"
import (
"fmt"
"runtime"
"github.com/ibmruntimes/go-recordio/v2/utils"
"github.com/indece-official/go-ebcdic"
)
//export TESTAPI
func TESTAPI(astring *C.char, intp *C.int, mycount C.int) C.int {
utils.ThreadAsciiMode()
str, _ := ebcdic.Decode([]byte(C.GoString(astring)), ebcdic.EBCDIC037)
fmt.Println("A STRING: ", str)
fmt.Println("AN INT: ", *intp)
fmt.Println("MYCOUNT: ", mycount)
utils.ThreadEbcdicMode()
return C.int(400)
}
func main() {
runtime.LockOSThread() // lock thread
utils.ThreadEbcdicMode() // COBOL must run in ebcdic
ret := C.SUBPROG((C.callback_t)(C.TESTAPI))
utils.ThreadAsciiMode() // restore
runtime.UnlockOSThread() // unlock thread
fmt.Printf("Call COBOL SUBPROG returns %d\n", ret)
}
As you can see in TESTAPI, the string passed by CONTENT materializes in the Go program as a null-terminated C-like string. C strings need to be null terminated, which is why in the COBOL program we have that 05 FILLER PIC X VALUE X’00’ previously mentioned. The string is in EBCDIC – so we have to transliterate it into ASCII, which we do with an imported package. The ULTIMATEANSWER signed number passed by REFERENCE materializes as a pointer to an int, and the simple VALUE MYCOUNT materializes as an integer. Something to note in TESTAPI: there is no call to runtime.LockOSThread(). This is because the thread we’re calling back into has already been locked before the original call from main().
There are a few more interesting entries in the CGO comment at the top of the program. As before we have an LDFLAGS directive to link with a side file, and as before we have a function prototype for the function we’ll be calling: int SUBPROG(callback_t cb). The prototype argument there deserves a second look. It’s type is callback_t which is also defined in the CGO comment as
typedef int (*callback_t)(const char *astring, int *intp, int mycount);
Using C syntax this defines a type called callback_t which is a function pointer, where the function pointed-to takes three arguments: a null terminated string, a pointer to an integer, and an integer. So when we call SUBPROG we are passing it a function pointer. In this case, we want to pass it an XPLINK-style function pointer, so we use Go’s syntax C.TESTAPI, then we convert it into the function pointer type defined in the CGO comment.
So, we are passing the COBOL program a pointer to the Go function TESTAPI, which has arguments of the right types ‘C’ types that match what COBOL is providing. We have put a prototype for this function in the CGO comment at the top, and we have also preceded TESTAPI’s definition with the Go compiler directive //export TESTAPI which is necessary to set-up the XPLINK linkage conventions that will be used. (Naming TESTAPI explicitly isn’t technically necessary, but it reinforces that the linkname is going to be TESTAPI).
In the COBOL program, the LINKAGE section contains
01 FP USAGE IS FUNCTION-POINTER.
Which tells the COBOL compiler that this program will be receiving a function pointer as an argument. Later we call that function pointer with these lines:
CALL FP USING
BY CONTENT ASTRING,
BY REFERENCE ULTIMATEANSWER ,
BY VALUE MYCOUNT
RETURNING RESULT.
This time the result really has a value (400), which we can print. The full output of the program is:
in PROCEDURE division
about to call function pointer
A STRING: BILLO
AN INT: 42
MYCOUNT: 777
RESULT 00000400
Call COBOL SUBPROG returns 0
This program is built using the same script as shown for the simplest program above. And there you have it – that’s all the basics of calling COBOL from Go, and calling back into Go. Easy Peasy!
Examples from this blog can be found in https://github.com/ibmruntimes/go-examples.