Sure, Java is a neat new toy. But it's nice to know you can mix in a bit of C code, from time to time, to beef up a Java program.
In recent months, one of the hottest technologies to hit the streets has been Java. It has taken the information systems industry by such a storm that most software vendors have jumped on the bandwagon to develop products for Java. Meanwhile, many companies still have large investments in applications written in other languages, such as C. My goal here is to show you how to interface to C from Java. I assume that you have a working knowledge of both Java and C.
Java provides a native method interface which can be used to invoke functions written in other languages. Currently only an interface to C is supported. (Of course, C++ source code can be accessed via C functions). There are approximately six steps required for connecting a Java program to C. The two main steps include writing the Java program and C functions. The remaining steps are required for gluing the two together.
Here is a simple example to illustrate each of these six steps. The example consists of a Java program that calls a C function to print the message "Hello Java'' (a twist on the typical "Hello World'' message). To keep the first example simple, no parameters are passed to the C function and no values are returned from it. Figure 1 provides a visual representation of the various components created from the following six steps required to make the Java and C connection:
1) Write a Java program.
2) Compile it.
3) Generate a header file for the C function.
4) Generate a stub file for the C function.
5) Write the C function.
6) Build a dynamic link library with the C function.Let's look at each of these steps in more detail.
Step 1
The following is a simple Java program that defines a native method SayHi. Its implementation is provided in step 5:
public class HelloJava { public native void SayHi(); public static void main(String args[]) { System.loadLibrary("hello"); new HelloJava().SayHi(); } }Native methods are declared much the same way as regular Java methods, with two differences. The declaration must contain the native keyword, and there is no body for the method in Java. The body is provided in the C function. The loadLibrary method (part of the Java System class) must be called prior to using the native method so that the dynamic link library containing the C function can be loaded. The new expression must be used to instantiate a native method.
What I have just shown here is one way of instantiating a native method. Alternatively, the native method could be declared in another Java class. In that case, we instantiate that Java class using a new expression, then simply call the native method off of the class. This style is used in Listing 3 and described below.
Step 2
Compile the Java class using a Java compiler, such as javac utility provided with the Java Development Kit (JDK). Running the following command will generate a Java byte code file, HelloJava.class:
javac HelloJava.javaStep 3
Generate a header file for the C function using the javah utility. The generated header file (HelloJava.h) basically provides a prototype for the C function, which is covered in step 5.
javah HelloJavaStep 4
Generate a stub function, using the javah utility, to connect the Java class to the C function. The following command will generate a stub file named HelloJava.c:
javah -stubs HelloJavaStep 5
Provide an implementation for the C function, SayHi:
/* HelloJavaImp.c: Implementation of Java native method, SayHi(). */ #include "HelloJava.h" #include <stdio.h> void HelloJava_SayHi(struct HHelloJava *javaObj) { printf("Hello Java!\n"); }Three things in the above source code deserve mention. First, the header file generated in step 3, HelloJava.h, must be included in the C module. Second, notice the name of the C function, HelloJava_SayHi. It contains the name of the Java class and the native method separated by an underscore. You will come across this naming convention (class_method) whenever you are interfacing with native methods in Java.
Last, an automatic parameter is always passed as the first parameter to a native method. It is essentially a handle to the Java class invoking the native method. You can think of this parameter as the this keyword in C++. I discuss how to use this parameter below.
Step 6
Build a dynamic link library. Here is a simple makefile which contains targets for building a dynamic link library on Windows 95, using the Microsoft C/C++ compiler, and a shared library on Solaris. The environment variable JAVAHOME points to the root directory of the JDK:
all: @echo Please specify a target: Win95 or Sun. Win95: cl HelloJavaImp.c HelloJava.c \ -I$(JAVAHOME)\include -I$(JAVAHOME)\include\win32 \ -Fehello.dll -MD -LD $(JAVAHOME)\lib\javai.lib Sun: cc -G -I$(JAVAHOME)/include -I$(JAVAHOME)/include/solaris \ HelloJavaImp.c HelloJava.c -o libhello.so
Once you have completed the above six steps successfully, you are ready to run the sample Java and C application. Here is how you would test the application and the output you should receive:C:\ANIL\Doc\Articles\Java-C\Example1> java HelloJava Hello Java!Passing Parameters and Returning Values
Now that I have shown you the basic steps required to connect Java programs to C functions, it is time to get a little fancier by passing parameters to a C function and returning values from it. Since steps 2, 3, 4 and 6 are similar for all cases, I concentrate on steps 1 and 5 which deal with a Java program and C function(s), respectively. But before plunging into the specifics of these examples, I provide a quick overview of Java data types and how they correspond to C data types.
The following table provides a list of Java native data types and their machine sizes:
Type Size byte 8-bit char 16-bit
short 16-bit int 32-bit float 32-bit long 64-bit double 64-bit
As you might notice, some Java data types are larger than their equivalent C data types. For example, a char is 16 bits in Java and (typically) eight bits in C. The numeric data types are similar to those you find on UNIX systems, but they are typically larger than ones on MS-DOS based systems. An int is usually two bytes on MS-DOS, for example, but it is four bytes in Java. The sizes for Java data types are the same across all supported platforms, which makes the language more portable.Listings 1 and 2 show a sample Java program and a sample C function (tested on Windows 95 and Solaris), respectively. The purpose of this sample application is simple. The Java class calls a C function with certain parameters. The C function prints the values in these parameters to stdout using printf and returns the number of characters printed to the Java class, which in turn prints a message indicating how many characters were printed.
Let's review some of the significant points in these examples. In the Java program, notice how I placed the native method in a separate class (PrintInC) from the "main'' class (ThePrinter). I did this to show you an alternative way of invoking native methods. Also notice the static block in the PrintInC class. This is a good place to automatically perform tasks when the class is loaded, which in my case happened to be loading a dynamic library:
static { System.loadLibrary("print"); }
Now, let's consider the C implementation. First notice that I include a StubPreamble.h header file. This is a JDK include file which contains necessary structures (such as HArrayOfInt), functions prototypes (such as makeCString), and macros (such as unhand). Note also the mapping of data types used in Java versus what I had to use in C:public native int doPrint(long l, int i[], int count, double d, String s); long PrintInC_doPrint(struct HPrintInC *this, int64_t l, HArrayOfInt *ai, long iCount, double d, struct Hjava_lang_String *s)Notice how each int has been mapped to long. An int may be only two bytes on MS-DOS but is always four bytes in Java. Similarly, a Java long argument has been mapped to the type int64_t in C. The other two notable parameters in these examples are a Java array (int[]) and a Java object (String). The Java int[] array gets mapped to the C structure HArrayOfInt.
HArrayOfInt is one example of a standard naming convention used in the StubPreamble.h header file. Other examples include HArrayOfLong, HArrayOfByte, and so on. Java objects also use standard naming conventions such as H for handle, the name of the language (e.g. java), the package (class library) containing the class (lang), and the name of the object (String). So a structure for the Java String object, which is part of the lang package, is named Hjava_lang_String.
Finally, let me point out a C macro (unhand) and function (makeCString) used in this example. The unhand macro provides access to the variables in Java's C Structures, such as the HArrayOfInt, as shown here:
i = unhand(ai)->body;Another example of unhand is shown in the next section.
The makeCString function takes a Java String as an argument and returns a corresponding char pointer. There are two other functions related to conversions between Java strings and C character arrays, makeJavaString and java2Cstring. The former is covered in the next section along with an example. The latter is similar to makeCString, except that it works more like strncpy -- it copies a Java String object into an existing char array as shown in the following example:
char OutFile[127+1]; javaString2CString(pOutFile, OutFile, sizeof(OutFile));Accessing Java Objects
So far I have shown you how to call C functions from Java. Now I show you how to go the other way, accessing Java objects from C. Java provides the ability for C functions to access data members in Java objects, create instances of Java classes, and invoke dynamic and static methods in the Java class. Listings 3 and 4 demonstrate how to accomplish most of these tasks.
Listing 3 contains mostly the same type of Java source code as my previous examples, but one thing is done slightly differently and worth mentioning. The JavaAccessor class illustrates how a Java class containing multiple native methods can be instantiated and then individual methods in that class can be invoked, as shown here:
JavaAccessor ja = new JavaAccessor(); .. System.out.println("PATH=" + ja.getEnv("PATH")); ja.deleteFile("dummy.txt");
Listing 4 requires a little more explanation, as there are several new things I have not covered yet. First, the getEnv native method returns a Java object (String), unlike the other examples I have shown so far which return native data types. Notice how you have to use makeJavaString, the opposite of makeCString, to return a Java String object:return makeJavaString(value, strlen(value));
You can access data members from the "automatic'' parameter that is passed to every native method, in this case the pointer to HJavaAccessor. You simply reference a data member in a Java class by using the unhand macro to dereference the Java object:long JavaAccessor_deleteFile(struct HJavaAccessor *javaObj, struct Hjava_lang_String *fileName) { ... unhand(javaObj)->delRC=rc;Finally, you can instantiate Java classes and invoke methods within them. Three C functions accomplish this -- execute_java_constructor, execute_java_dynamic_method, and execute_java_static_method.
execute_java_constructor (shown below) expects at least four parameters. It can require more, depending on the number of parameters a constructor requires. The first parameter indicates the language/execution environment, which for our purposes is always zero for Java. The second parameter is the name of the Java class. The third is currently reserved for future use. The fourth is the signature of the constructor, and the remaining arguments are any parameters to pass to the constructor.
The signature specified in the fourth parameter is made up of a set of parentheses which contain symbols for a method's parameters. For example, since JavaAccessor's constructor expects no parameters, it simply has the signature "()":
hJavaAccessor=(HJavaAccessor *) execute_java_constructor(0, "JavaAccessor", 0, "()");
Similarly, execute_java_dynamic_method (shown below) also expects at least four parameters. It can require more, depending on the number of parameters the Java method requires. The first parameter indicates the language environment, which again is zero for Java. The second parameter is a reference to the Java object created via the execute_java_constructor function. The third parameter is the name of the method to invoke. The fourth is the method's signature, and the remaining arguments are any parameters to pass to the method.As shown in this example, the Java method "printRC" expects a single argument of type int (I) and does not return any values, so it has a V (for void) to indicate this at the right side of the closing parenthesis:
if (hJavaAccessor) execute_java_dynamic_method(0, (HObject *)hJavaAccessor, "printRC", "(I)V", rc);
One good way to find out what letters to use for data types in the signature is to define native methods in a dummy java class and then generate a stub file using the javah utility. You can then inspect the generated .c file, which will have comments indicating the signature types. For example, Listing 5 shows a stub file generated from the following Java class:class test { public native long LONG(); public native float FLOAT(); public native boolean BOOLEAN(); public native void MIX_PARMS( int i, long l, String s, byte b, float f, double d, char c, boolean bool, short si, int[] ai); }Exceptions and Thread Synchronization
C functions called by Java classes can throw exceptions, which can be caught by Java methods. This is accomplished by the SignalError C function provided in the Java library. Consider thisline taken from Listing 4:
SignalError(0, "java/lang/NullPointerException",
0);
Java is multi-threaded language, hence C functions used in Java could potentially be accessed concurrently by various threads in a given Java program. Java provides a data-locking mechanism which permits thread-safe operations in Java and native methods. For C functions written for Java, thread-safe operations can be accomplished by using the synchronized keyword in the Java method declaration (as shown below) and three C functions provided in the JDK: monitorWait, monitorNotify, and monitorNotifyAll.public synchronized native String getEnv(String var);Conclusion
Java is a feature-rich language that comes bundled with packages for everything from local file I/O, to socket communications, to GUI programming, to classes for the World Wide Web. Still, there might be times where you need to drop down to C or C++ to accomplish specific tasks, or perhaps to leverage existing C routines or a C API library. For these times, it is nice to know that Java provides the ability to do so. However, one thing to keep in mind is that, whenever possible, any C functions written for Java should be kept as portable and ANSI compliant as possible. Portability is a big benefit of Java, and it would be nice if you could have the C code be portable as well.
If you do not yet own a book on Java, try downloading the free documentation from Sun Microsystems' FTP site (ftp.javasoft.com). The Java Tutorial, by Mary Campione and Kathy Walrath, is an excellent source of information for learning Java. It is available on the Web site in two formats, HTML and Poscript. o
Anil Hemrajani currently provides software engineering and training consulting services to a Fortune 500 corporation in McLean, VA. He can be contacted at anil@patriot.net or via http://www.patriot.net/users/anil/.