Introduction | Using Other Object Domains |
This chapter describes how you can access legacy procedural COBOL programs from Java without using Object COBOL domain support.
You can call procedural COBOL programs from Java. This is an alternative to the Object COBOL Java domain support described in the chapters Calling Java from COBOL and Calling Object COBOL from Java. The procedural support described in this chapter is more suitable for use with existing COBOL programs and does not require any knowledge of Object COBOL. However, access to Java objects is only available through either the Java Native Interface (JNI), or by use of the Object COBOL Java domain.
The support is provided through a Java class called mfcobol.runtime. This 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 mfcobol.runtime unpack the array and pass the data to your COBOL program in a form it can use. The diagram below shows how this works a Java program calling a COBOL program and passing it two parameters.
Figure 2-1: A Java program calling a COBOL program through mfcobol.runtime
You need to have at least a basic knowledge of the Java language to be able to use this technology effectively. Sun's Java site is a good starting place.
See the section Setting Up Java/COBOL Support in chapter Calling Object COBOL from Java for instructions on how to set up your COBOL and Java environments.
A Java program calls a COBOL entry-point by using the functions in runtime.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 mfcobol.*;
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 going to be 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.
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() function in runtime.class. For example, to load the programs inside mycbl.dll (on Windows) or mycbl.so (on some Unix systems).:
{ if (runtime.cobload("mycbl", null) != 0) System.out.println("Could not load library\n") ; else System.out.println("Library loaded successfully\n") ; }
However, if any programs within mycbl have entry-points which you wish to expose for use within Java, you need to load each of those programs in turn. For example, if mycbl contained two COBOL programs, calcint and calctax, each of which had entry-points you wanted to call from Java, you would need to code the following:
{ if (runtime.cobload("mycbl", "calcint") != 0) System.out.println("Could not load calcint\n"} ; else { if (runtime.cobload("mycbl", "calctax") != 0) System.out.println("Could not load calctax\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_ functions, The cobcall_ functions are all static functions of runtime.class, so you do not have to instantiate runtime.class before starting. Each cobcall_ function takes two or three parameters (the third parameter is optional):
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_ functions, 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 function. 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 here to declare COBOL data items that are going to be used for passing parameters between Java and COBOL. Using these datatypes helps keep your code portable between different COBOL platforms.
See the Java Run-time Class Library for the full list of cobcall_ functions - this is documented as part of the Class Library Reference. Click Help Topics on the Net Express Help menu, and look up Class library in the index. Select Class Library Reference from the Topics Found dialog box.
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 mfcobol.* ; class SimpleCall { public static void main(String argv[]) throws Exception { Object theParams[] = {new Integer (4), new Integer(7), new Byte((byte)'a')} ; int i = runtime.cobcall_int("legacy", theParams) ; System.out.println(i) ; theParams[2] = new Character ('s') ; i = runtime.cobcall_int("legacy", theParams) ; System.out.println(i) ; } }
The Java class SimpleCall assumes that legacy.cbl is built into a library file called legacy.ext. However, if this subroutine were built into a different library file, you would need to use the runtime.cobload() function to load the library file before making calls to legacy.
import mfcobol.* ; class SimpleCall { static { // load the library from the static initializer for the class runtime.cobload("cobolapps", null) ; } public static void main(String argv[]) throws Exception { Object theParams[] = {new Integer (4), new Integer(7), new Character ('a')} ; int i = runtime.cobcall_int("legacy", theParams) ; System.out.println(i) ; theParams[2] = new Byte((byte)'s') ; i = runtime.cobcall_int("legacy", theParams) ; 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() function used does not return a value, but you can pass in an object as the first parameter, and return the same object type from your COBOL program. SimpleCall2 passes the first parameter by reference, the second by value, and the third by content.
import mfcobol.* ; class SimpleCall2 { public static void main(String argv[]) throws Exception { // Set up an array containing the parameters Object theParams[] = { new Integer(1), new Integer(2), new Integer(3) }; // Set up an array containing the usage information int theUsage[] = {runtime.BY_REFERENCE, runtime.BY_VALUE, runtime.BY_CONTENT} ; runtime.cobcall(null, "usages", theParams, theUsage) ; } }
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 runtime.cobload() function to load the library file before making calls to legacy.
import mfcobol.* ; 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 = runtime.cobcall_int("legacy2", theParams) ; System.out.println("After call\n"+firstDemo) ; } }
To prevent memory leaks you should cancel any COBOL programs you have loaded from Java before the owning Java object is garbage collected. Use the following call from the "finalize" method of the Java object:
runtime.cobcancel("program")
where program is the name of a COBOL program loaded using a runtime.cobload() call. The following is an example of a "finalize" method in a Java program:
protected void finalize() { try { runtime.cobcancel("demoFinalizers"); System.out.println("demoFinalizers - finalize'ed"); } catch(Exception e) { System.out.println("Error during finalize : "+e.getMessage()); } }
You can pass strings between Java and COBOL programs. You can pass a string to COBOL either using the Java String or StringBuffer classes. 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 ptrt2tring 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} ; runtime.cobcall_int("stringadd", theParams) ; System.out.println(sb1) ; } }
The Java Native Interface (JNI) enables non-Java programs to access Java objects and classes. One of the variants of the cobcall() function provided in the mfcobol.runtime 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.
Note:
The other way to access Java from COBOL is to use the domain support, as documented in the chapter Calling Java from 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 mfcobol.*; class testexcept { public static void main(String argv[]) throws Exception { try { /* Last parameter is true for passing in JNIEnv.... */ runtime.cobcall(null,"throwex",null,null,true); } catch(Exception e) { System.out.println("PASS - exception caught ("+ e + ")"); } } }
Net Express includes some demonstration programs that illustrate different aspects of calling procedural COBOL from Java. These demonstration programs are contained in directories beneath \Program Files\MERANT\Net Express\Base\Demo\Javademo\Cobol. 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 |
|
pi |
|
primtypes |
|
Copyright © 2000 MERANT International Limited. All rights reserved.
This document and the proprietary marks and names
used herein are protected by international law.
Introduction | Using Other Object Domains |