This chapter describes how you can access legacy procedural COBOL programs from Java using the support provided in the com.microfocus.cobol.RuntimeSystem class.
You can call procedural COBOL programs from Java or from EJBs written in any language, not necessarily COBOL. There are a number of ways of doing this, which are summarized in the chapter Using Java and COBOL Together chapter.
This chapter describes how to use the support provided in the com.microfocus.cobol.RuntimeSystem class and the CobolBean class, which is actually an extension to the RuntimeSystem class and uses RuntimeSystem.cobcall*() methods. Use this technique if you do not want to use OO COBOL and do not want to use Enterprise Server.
Access to Java objects is available either through the Java Native Interface (JNI), or by use of the OO COBOL Java domain.
The Java support in the com.microfocus.cobol.RuntimeSystem class provides functions which enable you to load, call and cancel COBOL programs. It also enables you to pass parameters to a COBOL program using a Java array. The functions in com.microfocus.cobol.RuntimeSystem class unpack the array and pass the data to your COBOL program in a form it can use. Figure 0-1 shows a Java program calling a COBOL program and passing it two parameters.
Figure 1: Calling COBOL from Java
You need to have at least a basic knowledge of the Java language to be able to use this technology effectively. Sun's Java Web site is a good starting place.
See the section Setting Up the Environment for Java and COBOL in the chapter Using Java and COBOL Together for instructions on how to set up the environments.
A Java program calls a COBOL entry point by using the functions in RuntimeSystem.class, supplied in mfcobol.jar. This class contains a set of functions named cobcall_returntype(), where returntype is the data type returned by the COBOL program or entry point you want to call. For example, use cobcall_int() to call a COBOL program which returns an integer.
To make COBOL support available to your Java program, include the following statement at the start of your Java source file:
import com.microfocus.cobol.*;
Java run-time systems are multi-threaded, so any COBOL program you are going to use with Java must be linked with the COBOL multi-threaded run-time system whether or not the Java program calling it uses multi-threading. If your COBOL program is called from a multi-threaded Java program, you need to take care that COBOL data accessed from one thread is not corrupted by another thread.
There are several ways you can deal with this:
SERIAL causes the COBOL run-time system to serialize access to your COBOL code between different threads. Only one thread can access your program at a time. This is the safest option, although it has potentially the highest overhead for execution speed. It is suitable when the COBOL program is providing access to a shared resource (for example, a printer), or when the COBOL program called from Java is in turn calling other COBOL programs which have not been enabled for multi-threading.
REENTRANT"2" causes the COBOL run-time system to allocate separate user data and FD file areas to each different thread. This prevents any conflicts or data corruption within the program. But REENTRANT"2" can't guarantee thread safety if it calls other non-threaded programs, or accesses other shared resources - in these sorts of cases, SERIAL is a safer option. The REENTRANT"2" directive can provide better performance than SERIAL as one thread is not kept waiting for the next thread to finish.
Thread-local storage is allocated per thread, so there is no possibility of one thread corrupting the data used by another thread. This is fairly efficient, but might not be always be an option with legacy code.
This can be very efficient as you control which data is thread-local and which data is shared between threads. You could use this option with legacy COBOL code if you are ready to write COBOL driver programs to sit between the Java run-time environment and your legacy programs. Your driver program would be responsible for controlling access to the legacy programs, and would have to use semaphores or some similar mechanism to prevent two threads from accessing the same code at the same time.
You need to be very careful when canceling a COBOL program in a threaded environment where the program is shared between threads. This applies whether you are using CANCEL or the com.microfocus.cobol.RuntimeSystem.cobcancel() method calls. If possible avoid canceling altogether.
Alternatively, consider using the CobolBean interface, which associates a COBOL programs working storage with a instance of a CobolBean class.
If your COBOL programs are linked into a library file, or if you want to expose entry-points inside a COBOL program, you need to load the library file or programs before making any calls. You can do this using the runtime.cobload() method in runtime.class. For example, to load the programs inside mycbl:
{ if (RuntimeSystem.cobload("mycbl") != 0) System.out.println("Could not load library\n") ; else System.out.println("Library loaded successfully\n") ; }
Once you have loaded the libraries or programs required by your Java application (see the previous section for details) you can make calls to COBOL using the cobcall_ methods. The cobcall_ methods are all static methods of RuntimeSystem.class, so you do not have to instantiate RuntimeSystem.class before starting. Each cobcall_ method takes two or three parameters (the third parameter is optional):
Style 1:
Style 2:
Parameters are converted between Java and COBOL data types as described in the chapter Java Data Types. As explained in the section Coding Your Java Program there are different cobcall_ methods, each named according to the Java equivalent of the data type returned by the COBOL program or entry being called. For example, a COBOL program that returns a signed integer (such as a pic s9(9) comp-5) is returning the Java data type int. So you would call the COBOL program using the cobcall_int method. By default all parameters are passed by reference.
The copyfile javatypes.cpy also provides a set of COBOL type definitions for Java data types. We advise you to use the data types defined in this file to declare COBOL data items that are going to be used for passing parameters between Java and COBOL. Using these data types helps keep your code portable between different COBOL platforms.
See the Java Classes for Run-time Support for the full list of cobcall_ methods - this is documented as part of the Java Run-time Class Library Reference, which is in docs/mfcobol.docs.zip.
This section shows you two short examples of calling a COBOL program from Java. The first example shows the following features:
This is a simple COBOL subroutine, named legacy.cbl:
working-storage section. copy "javatypes.cpy". 01 wsResult jint. linkage section. 01 wsOperand1 jint. *> type defined in javatypes.cpy 01 wsOperand2 jint. 01 wsOperation pic x. procedure division using wsOperand1 wsOperand2 wsOperation. evaluate wsOperation when "a" add wsOperand1 to wsOperand2 giving wsResult when "s" subtract wsOperand1 from wsOperand2 giving wsResult end-evaluate exit program returning wsResult.
This is a Java program which calls the subroutine:
import com.microfocus.cobol.* ; class SimpleCall { public static void main(String argv[]) throws Exception { int i = RuntimeSystem.cobcall_int("legacy", new ParameterList() .add((int)4 .add((int)7 .add((byte)'a')); System.out.println(i) ; } }
The second example shows you how to pass data to a COBOL program with the equivalent of different usage clauses. The cobcall() method used does not return a value, but it takes an object as the first parameter, and returns the same object type from the COBOL program. SimpleCall2 passes the first parameter by reference, the second by value, and the third by content.
import com.microfocus.cobol.* ; class SimpleCall2 { public static void main(String argv[]) throws Exception { RuntimeSystem.cobcall(null, "usages", new ParameterList() .add((int)1, RuntimeSystem.BY_REFERENCE) .add((int)2, RuntimeSystem.BY_VALUE) .add((int)3, RuntimeSystem.BY_CONTENT)); } }
The example in this section is similar to the example in the previous section, except that the result from the COBOL program is also used to change the value of one of the data members in the Java object.
thread-local-storage section. copy "javatypes.cpy". linkage section. 01 wsOperand1 jint. 01 wsOperand2 jint. 01 wsOperation pic x. 01 wsResult jint. procedure division using wsOperand1 wsOperand2 wsOperation wsResult. evaluate wsOperation when "a" add wsOperand1 to wsOperand2 when "s" subtract wsOperand1 from wsOperand2 end-evaluate exit program returning wsResult
The Java class SimpleCall2 assumes that legacy2.cbl is built into a library file called legacy2.ext. However, if this subroutine were built into a different library file, you would need to use the RuntimeSystem.cobload() method to load the library file before making calls to the legacy program.
import com.microfocus.cobol.* ; class SimpleCall2 { Integer simpleInteger1; Integer simpleInteger2; Integer simpleResult; public SimpleCall2(int a, int b) { simpleInteger1 = new Integer(a); simpleInteger2 = new Integer(b); simpleResult = new Integer(0); } public String toString() { return new String( "simple1Integer1 = "+simpleInteger1+"\n" + "simple1Integer2 = "+simpleInteger2+"\n" + "simpleResult = "+simpleResult); } public static void main(String argv[]) throws Exception { SimpleCall2 firstDemo = new SimpleCall2(4,7); Object theParams[] = { firstDemo.simpleInteger1, firstDemo.simpleInteger2, new Byte((byte) 'a'), firstDemo.simpleResult } System.out.println("Before call\n"+firstDemo) ; int i = RuntimeSystem.cobcall_int("legacy2",theParams); System.out.println("After call\n"+firstDemo) ; } }
The com.microfocus.cobol.CobolBean class enables you to associate an instance of CobolBean to an instance of a COBOL program's storage section without having to recode the COBOL application to either be thread-safe or take the parameters from the Java application.
For example, the following COBOL program shares the same data when it is called using cobcall() by more than one Java class.
$set intlevel"4" data-context working-storage section. 01 address-rec pic x(30). linkage section. 01 lnk-address-rec pic x(30). procedure division. goback. entry "setAddressBook" using lnk-address-rec. move lnk-address-rec to address-rec exit program returning 0. entry "getAddressBook" using lnk-address-rec. move address-rec to lnk-address-rec exit program returning 0.
You can create a Java class that extends from com.microfocus.cobol.CobolBean, and this makes an instance (data-context) version of cobcall() available. You need to compile any COBOL programs called from CobolBean.cobcall() with the Compiler directive DATA-CONTEXT rather than using any threading directive such as RE-ENTRANT(1/2), SERIAL. The Compiler directive DATA-CONTEXT informs the COBOL run-time system to allocate a new COBOL Working-Storage Section whenever it is being used from CobolBean.cobcall().
In the following example, if the COBOL program shared data, bean1.getAddress() would be the same as bean2.getAddress(), causing problems. However because the Java program uses the cobcall() from CobolBean, the COBOL Working-Storage Section is associated with the bean.
import com.microfocus.cobol.*; import com.microfocus.cobol.lang.*; public class MyBean extends com.microfocus.cobol.CobolBean { private StringBuffer address = new StringBuffer(30); public MyBean() throws Exception { super(); super.cobload("addbook"); } public String getAddress() throws Exception { Pointer addressPointer = new Pointer(this.address.toString(),30); super.cobcall("getAddressBook", new ParameterList().add(addressPointer)); this.address.setLength(0); this.address.append(addressPointer.toString()); return address.toString(); } public void setAddress(String address) throws Exception { super.cobcall("setAddressBook", new ParameterList().add(new Pointer(address,30))); } public static void main(String[] args) throws Exception { MyBean bean1 =new MyBean(); bean1.setAddress("Mr A"); MyBean bean2 =new MyBean(); bean2.setAddress("Mr B"); System.out.println("bean1.getAddress="+ bean1.getAddress()); System.out.println("bean2.getAddress="+ bean2.getAddress()); } }
To prevent memory leaks you should cancel any COBOL programs you have loaded from Java before the owning Java object is subjected to garbage collection. Use the following call from the Finalize method of the Java object:
RuntimeSystem.cobcancel("program")
where program is the name of a COBOL program loaded using a RuntimeSystem.cobload() call. The following is an example of a Finalize method in a Java program:
private void Finalize() { try { RuntimeSystem.cobcancel("demoFinalizers"); System.out.println("demoFinalizers - finalize'ed"); } catch(Exception e) { System.out.println( "Error during finalize : "+e.getMessage()); } }
Warning:
You pass strings to COBOL using either the String or StringBuffer class. You can use any the following mechanisms to do this:
An intrinsic problem with passing Java strings to COBOL is that Java strings are variable length whereas COBOL strings are fixed length. For COBOL to modify a predefined fixed-length field, you need to wrap the instance in a new instance of the Pointer class. The Pointer class is in the com.microfocus.cobol.lang package.
For example, here is a COBOL program that defines a PIC X(20) data item:
showbytes.cbl: working-storage section. linkage section. 01 lnk-string pic x(20). procedure division using lnk-string. display "lnk-string = " lnk-string. move "Hello from COBOL - 1" to lnk-string. exit program returning 0.
The corresponding Java code would be:
showbytes.java: import com.microfocus.cobol.RuntimeSystem; import com.microfocus.cobol.lang.Pointer; import com.microfocus.cobol.lang.ParameterList; public class showbytes { public static void main(String[] args) throws Exception { String myString = new String("Hello to COBOL"); Pointer ptr2String = new Pointer(myString, 20); RuntimeSystem.cobcall("showbytes", new ParameterList().add(ptr2String)); myString = ptr2String.toString(); System.out.println(myString = ["+myString+"]"); } }
A COBOL program cannot modify the contents of a String passed to it, but it can change a StringBuffer. To make handling strings easier, javatypes.cpy defines the type MF-JSTRING. This data type has the following structure:
01 mf-jstring is typedef. 03 len jint. 03 capacity jint. 03 ptr2string pointer.
The len field defines the length of the string. The capacity field defines the size of the buffer - this field is always 0 for a String. The ptr2string field is a pointer to the start of the actual string. In the case of a StringBuffer, you can either modify the existing buffer, or allocate a new one and set ptr2string to the start of the new buffer.
The COBOL program below concatenates a String and StringBuffer from Java, and returns the result in the StringBuffer.
program-id. StringAdd. thread-local-storage section. copy "javatypes.cpy". 01 lsNewPtr pointer. 01 lsSize jint. *> type defined in javatypes.cpy 01 i jint. 01 lsStatus jint. 01 lsBigBuffer pic x(1024). linkage section. 01 lnkJString mf-jstring. 01 lnkJStringBuffer mf-jstring. 01 lnkString pic x(256). 01 lnkStringbuffer pic x(256). procedure division using lnkJString lnkJStringBuffer. set address of lnkStringBuffer to ptr2string of lnkJStringBuffer set address of lnkString to ptr2string of lnkJString *> Check that lnkJStringBuffer is a Java StringBuffer if capacity of lnkJStringBuffer > 0 add len of lnkJString to len of lnkJStringBuffer giving lsSize *> Check we don't overflow concatenation buffer if lsSize < 1024 move len of lnkJString to i move lnkString(1:i) to lsBigBuffer add 1 to i move lnkStringBuffer to lsBigBuffer(i:lsSize) set ptr2string of lnkJStringBuffer to address of lsBigBuffer move lsSize to len of lnkJStringBuffer end-if end-if
The Java class StringsToCobol passes "fred" and "ginger" to StringAdd and then displays the result.
import mfcobol.*; public class StringsToCobol { public static void main(String[] args) throws Exception { StringBuffer sb1 = new StringBuffer("ginger") ; Object theParams[] = {"fred" , sb1} ; RuntimeSystem.cobcall_int("stringadd", theParams) ; System.out.println(sb1) ; } }
The CobolNational Java class enables you to create UTF-16 strings that can be passed to a COBOL PIC N(...) usage is national field.
For example, the following COBOL program, hellonat, receives the string "Hello From Java" into a data item defined as PIC N(40) USAGE IS NATIONAL:
$set unicode(portable) working-storage section. linkage section. 01 lnk-natstring pic n(40) usage is national. procedure division using lnk-natstring. display "Java said to COBOL [" lnk-natstring "]" move "Hello From Java" to lnk-natstring exit program returning 0.
The corresponding Java code passes the string "Hello From Java" to the COBOL program, hellonat:
CobolNational cobnat = new CobolNational("Hello From Java", 40); RuntimeSystem.cobcall("hellonat", new ParameterList().add(cobnat)); System.out.println(cobnat.toString());
The CustomRecord interface in com.microfocus.cobol.lang enables you to pass group items to a legacy COBOL program, when using cobCall() and cobrcall().
The CustomRecord interface is:
package com.microfocus.cobol.lang; public interface CustomRecord { public Object[] getParameters(); public void setParameters(Object[] parms); }
In the demo $COBDIR/demo/javademo/cobol/record/RecordDemo, the data item customerDetails is defined as follows:
01 customerDetails. 03 customerName pic x(30). 03 customerAddress pic x(30). 03 customerRef pic 9(6).
A Java implementation could be:
import com.microfocus.cobol.lang.*; import java.text.*; public class RecordData implements com.microfocus.cobol.lang.CustomRecord { private String customerName; private StringBuffer customerAddress; private int customerRef; RecordData(String name, String address, int ref) { customerName = name; customerAddress = new StringBuffer(address); customerRef = ref; } public String getCustomerName() { return this.customerName; } public String getCustomerAddress() { return this.customerAddress.toString(); } public int getCustomerRef() { return this.customerRef; } public Object[] getParameters() { String strCustomerRef = Integer.toString(this.customerRef); while(strCustomerRef.length() < 6) { strCustomerRef = "0"+strCustomerRef; } customerAddress.setLength(30); /* must ensure length is right! */ customerAddress.ensureCapacity(30); return new ParameterList() .add(new Pointer(this.customerName,30)) .add(this.customerAddress) .add(strCustomerRef.getBytes()) .getArguments(); } public void setParameters(Object[] parms) { Pointer ptr = (Pointer)parms[0]; this.customerName = ptr.toString(); this.customerAddress = (StringBuffer)parms[1]; byte[] byteCustomerRef = (byte[])parms[2]; this.customerRef = Integer.parseInt(new String(byteCustomerRef)); } public String toString() { return "Customer Name : "+this.customerName+"\n"+ "Customer Address : "+this.customerAddress+"\n"+ "Customer Ref : "+this.customerRef; } }
The Java program could call the COBOL program as follows:
cobcall("RecordDemo",new ParameterList().add( new RecordData(myname, myaddress, myref)));
See also Adding Parameters Using ParameterList() in the chapter Java Data Types.
The Java Native Interface (JNI) enables non-Java programs to access Java objects and classes. One of the variants of the cobcall() method provided in the com.microfocus.cobol.RuntimeSystem class passes through a JNI pointer to the COBOL program being called. The JNI pointer is a pointer to a table of Java functions which enable non-Java code to access the Java run-time system.
For ease of use with COBOL, javatypes.cpy defines data type JNINativeInterface. JNINativeInterface is a group data item consisting of a set of procedure pointers to the JNI Java functions. One use of JNI with COBOL is to throw a Java exception from a COBOL program.
The other way to access Java from COBOL is to use the domain support, as documented in the chapter Calling Java from OO COBOL.
The following short COBOL program throws an exception using a JNI function.
identification division. program-id. "except". special-names. ********************************************** * The call convention used for JNI calls needs * to be defined here. For Win32 systems, it is * 74 (which corresponds to the stdcall calling * convention). ********************************************** $if UNIX defined call-convention 0 is javaapi. $else call-convention 74 is javaapi. $end local-storage section. copy "javatypes.cpy". 01 JavaException pointer. 01 ExceptionClass pointer. linkage section. 01 JEnv pointer. 01 jobject pointer. 01 lnk-JNINativeInterface JNINativeInterface. * The first parameter (JEnv) is a pointer to the JNI * function table. It must be passed (by reference) as * the first parameter on all JNI function calls. procedure division using by reference JEnv. * Map the pointer passed in JEnv to the * JNINativeInterface structure so that we * can call JNI functions. set address of lnk-JNINativeInterface to JEnv * Get a reference to the exception class call javaapi FindClass using by reference JEnv by reference z"java/lang/IllegalArgumentException" returning ExceptionClass end-call * Unable to find the exception class, so simply exit if ExceptionClass = NULL exit program end-if * Throw the new exception call javaapi ThrowNew using by reference JEnv by value ExceptionClass by reference z"Thrown from COBOL code" end-call exit program .
This is the Java program which calls it:
import com.microfocus.cobol.*; class testexcept { public static void main(String argv[]) throws Exception { try { /* Last parameter is true for passing in JNIEnv... */ RuntimeSystem.cobcall(null,"throwex",null,null,true); } catch(Exception e) { System.out.println( "PASS - exception caught ("+ e + ")"); } } }
Your COBOL development system includes some demonstration programs that illustrate different aspects of calling procedural COBOL from Java. These demonstration programs are contained in the $COBDIR/demo/javademo/cobol directory in your product installation. Each directory includes all relevant program files, as well as a readme.txt file to explain the programs in more detail.
The following table shows the directories containing the demonstration programs and gives a brief description of the purpose of the demonstration:
Directory | Illustrates |
---|---|
arrays | Reading a Java array from COBOL. Updating a Java array from COBOL. |
pi | Calculating pi. Passing numbers back to Java via Strings. |
primtypes | Passing primitive Java types to COBOL. Updating primitive Java types from COBOL. |
record | Writing a simple COBOL Java object. Receiving structures in a COBOL program from Java objects that implement the DataType interface. Passing parameters by reference. |