- Special Edition Using Java, 2nd Edition -

Chapter 41

Extending Java with Other Languages


by Anil Hemrajani

Java is a feature-rich language that comes bundled with packages (class libraries) for everything from local file I/O to socket communications, to GUI programming, to classes for the World Wide Web. However, there might be times when you need to accomplish specific tasks not supported by Java.

Java provides a native method interface that can be used to invoke functions written in other languages—currently only an interface to C is supported (of course, C++ source code could be accessed via C functions). This is a nice feature mainly because it provides the flexibility of extending the Java language; there are reasons you might not want to go this route, though.

Pros and Cons

The following are some pros associated with using the Java native method interface:

The following are some cons associated with using the Java native method interface:

Based on these pros and cons, one somewhat safe bet might be to only use native methods for Java applications and not Java applets. This is mainly due to the fact that Java bytecode files must be distributed to users (or placed on a accessible network directory) who want to run Java applications, so distributing DLLs along with the bytecode files is not entirely a bad implementation of a Java application.

Accessing C Functions from Java

There are approximately six steps required for connecting Java programs to C. The two main steps include writing the Java program and C functions, the remaining steps are required for “gluing” the two together. In the following lines, I use a simple example to describe each of these six steps. The example shown here consists of a Java program that calls a C function to print the message Hello Java (a twist from 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 41.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 DLL with the C function.


FIG. 41.1

Components created in the HelloJava example.

These six steps are described in more detail in the following sections.

Step 1: Write a Java Program

Listing 41.1 shows a simple Java program that defines a native method, SayHi, the implementation for which is provided in step 5:

Listing 41.1 HelloJava.java.

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 except for two differences: the declaration must contain the native keyword, and there is no body for the method in Java because it is provided in the C function.

There are three things to note in the listing 41.1:

Listing 41.1 shows one way of instantiating a native method. Alternatively, the native method could be declared in another Java class in which case, you would instantiate that Java class using the new keyword; then simply call the native method off of the class.

Step 2: Compile the Java Class

Compile the Java class using the javac compiler provided with the Java Development Kit (JDK). Running the following command generates a Java bytecode file, HelloJava.class:

javac HelloJava.java

Step 3: Generate a Header File

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 covered in step 5.

javah HelloJava

Step 4: Generate a Stub Function

Generate a stub function using the javah utility, to connect the Java class to the C function. The following command generates a stub file named HelloJava.c:

javah -stubs HelloJava

Step 5: Provide an Implementation

Provide an implementation for the C function, SayHi, as shown in listing 41.2.

Listing 41.2 HelloJavaImp.c—Implementation for 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");
}

There are three things in listing 41.2 which deserve mention:

Step 6: Build a Dynamic Link Library

Listing 41.3 shows a simple makefile that contains targets for building a dynamic link library on Windows 95 (using the Microsoft C/C++ compiler) and a shared library on SunOS/Solaris (Note, the environment variable JAVAHOME points to the root directory of the JDK).

Listing 41.3 makefile—Simple makefile for HelloJava.

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 -nologo $(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 these six steps successfully, you are ready to run the sample Java and C application as shown in figure 41.2.


FIG. 41.2

Compile and run the HelloJava program.

Passing Parameters and Returning Values

Now that you have seen 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. Because steps 2, 3, 4 and 6 are similar for all cases, you 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, it might help to get 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/Format
byte 8-bit signed two’s complement
char 16-bit Unicode characters
short 16-bit signed two’s complement
int 32-bit signed two’s complement
long 64-bit signed two’s complement
float 32-bit 32-bit IEEE 754
double 64-bit 32-bit IEEE 754

As you might notice, some Java data types are larger than their equivalent C data types. For example, a char is 16-bit in Java and 8-bit in C. The numeric data types are similar to those you find on UNIX systems; however, they are larger than ones on MS-DOS based systems such as an int which is two bytes on MS-DOS but four bytes in Java. The sizes for Java data types are the same across all supported platforms which makes the language more portable.

Listings 41.4 and 41.5 show a sample Java program and a sample C function, 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.

Listing 41.4 ThePrinter.java—Java source file for the PrintInC example.

// ThePrinter: Call native methods to print stuff
public class ThePrinter
{
public static void main(String args[])
{
int count=4;
int i[] = new int[count];
i[0] = 10;
i[1] = 75;
i[2] = 95;
i[3] = 115;
int printed = new PrintInC().doPrint(
25,
i, count,
100.33,
"Hello C");
System.out.println("Java: " +
printed +
" chars printed");
}
}
class PrintInC
{
public native int doPrint(long l,
int i[],
int count,
double d,
String s);
static
{ System.loadLibrary("print"); }
}

Listing 41.5 PrintInCImp.c—Implementation for PrintInC native methods.

/* PrintInCImp.c: C functions for Java class */
#include <stdio.h>
#include <StubPreamble.h>
#include "PrintInC.h"
/*** Java to C demo: Print data values ***/
long PrintInC_doPrint(struct HPrintInC *this,
int64_t l,
HArrayOfInt *ai, long iCount,
double d,
struct Hjava_lang_String *s)
{
int charsPrinted=0, idx=0;
long *i;
/* Print value of "long" (Java int) */
charsPrinted += printf("C: l = %ld\n", l);
/* Print array of longs */
i = unhand(ai)->body;
for (idx=0; idx < iCount; idx++)
charsPrinted += printf("C: i[%d] = %d\n", idx, i[idx]);
/* Print value of "double */
charsPrinted += printf("C: d = %f\n", d);
/* Print Java string */
charsPrinted += printf("C: s = %s\n", makeCString(s));
/* Return total characters printed */
return charsPrinted;
}

In the Java program, notice how the native method is placed in a separate class (PrintInC) from the main class (ThePrinter). This was done 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 this case happened to be loading a dynamic library:

static { System.loadLibrary("print"); }

Now, let’s go over some of the significant items in the C implementation. First of all, notice the inclusion of a StubPreamble.h header file; this is a JDK include file which contains necessary structures (for example, HArrayOfInt), functions prototypes (such as makeCString) and macros (such as unhand). Secondly, notice the mapping of data types used in Java versus what was used 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 the ints have been mapped to longs. This is because an int is only two bytes on MS-DOS and four bytes in Java. Similarly, a Java long argument has been mapped to a int64_t in C.

The other two notable parameters in these examples is the use of a Java array (int[]) and a Java object (String). Note how the Java int[] array gets mapped to a C structure, HArrayOfInt. The 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 (for example, java), the package 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.

unhand and makeCString

Lastly, take a look at 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 (another example of unhand is shown in the next section):

i = unhand(ai)->body;

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 makeCString. The makeJavaString is covered in the next section along with an example. The makeCString is similar to makeCString, except it works more like strncpy; that is, 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));

So far you have seen how to call C functions from Java. Next you learn how to go the other way—accessing Java objects from C.

Accessing Java Objects from C

Java provides the ability for C functions to access data members in Java objects and also create instances of Java classes and invoke dynamic and static methods in the Java class. Listings 41.6 and 41.7 demonstrate how to accomplish most of these tasks.

Listing 41.6 contains mostly the same type of Java source code as the previous examples. One aspect done slightly differently and worth mentioning is the JavaAccessor class which 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 41.6 JavaAccessor.java—Demo java object access.

// JavaAccessor: Demo Java Object Access
public class JavaAccessor
{
public synchronized native String getEnv(String var)
throws java.lang.NullPointerException;
public native int deleteFile(String fileName);
public int delRC=0;
public static void main(String args[])
{
System.loadLibrary("ja");
JavaAccessor ja = new JavaAccessor();
try
{
System.out.println("PATH=" +
ja.getEnv("PATH"));
{
catch(java.lang.NullPointerException e)
{
System.out.println("getEnv returned NULL!");
}
ja.deleteFile("dummy.txt");
System.out.println("Return code from"+
" deleteFile() = "+
ja.delRC);
}
public void printRC(int RC)
{
System.out.println("Java: RC=" + RC);
}
}

Listing 41.7 JavaAccessorImp.c—Demo Java object access in C.

#include <stdio.h>
#include <string.h>
#include "JavaAccessor.h"
/*** Get environment variable ***/
struct Hjava_lang_String
*JavaAccessor_getEnv(struct HJavaAccessor
*javaObj,
struct Hjava_lang_String
*varName)
{
char *var=makeCString(varName), *value;
value=getenv(var);
if (!value)
{
SignalError(0,
"java/lang/NullPointerException",
"No such environment variable");
return NULL;
}
return makeJavaString(value,
strlen(value));
}
/*** Delete a local file ***/
long JavaAccessor_deleteFile(struct HJavaAccessor
*javaObj,
struct Hjava_lang_String
*fileName)
{
HJavaAccessor *hJavaAccessor;
int rc=unlink(makeCString(fileName));
unhand(javaObj)->delRC=rc;
hJavaAccessor=(HJavaAccessor *)
execute_java_constructor(0,
"JavaAccessor",
0,
"()");
if (hJavaAccessor)
execute_java_dynamic_method(0,
(HObject *)hJavaAccessor,
"printRC",
"(I)V",
rc);
else
printf("Unable to create hJavaAccessor\n");
return rc;
}

First, the getEnv native method returns a Java object (String), unlike the other examples you have seen so far which return native data types. Notice how you have to use the makeJavaString, opposite of makeCString, to return a Java String object:

return makeJavaString(value, strlen(value));

Next, look at how you can access data members from the automatic parameter that is passed to every native method, in this case it is struct HJavaAccessor *. Notice how you can simply reference a data member in a Java class by using the unhand macro to de-reference the Java object:

long JavaAccessor_deleteFile(struct HJavaAccessor *javaObj,
struct Hjava_lang_String *fileName)
{
...
unhand(javaObj)->delRC=rc;

Finally, look at how you can instantiate Java classes and invoke methods off of it. There are three C functions to accomplish this:

Invoking Methods off of Java Classes

The execute_java_constructor expects at least four parameters but it could be more depending on the number of parameters a constructor requires:

The remaining arguments are any parameters to pass to the constructor. The signature specified in the fourth parameter accepts the form “(Arguments)ReturnValue” which is made up of a set of parentheses containing symbols for a method’s parameters and return values following the closing parenthesis. For example, because JavaAccessor’s constructor expects no parameters, you can simply use a signature of () (the next paragraph contains an example with parameters).

hJavaAccessor=(HJavaAccessor *)
execute_java_constructor(0, "JavaAccessor", 0,
"()");

The execute_java_dynamic_method expects at least four parameters but it could be more depending on the number of parameters the Java method requires:

The remaining arguments are any parameters to pass to the method. As shown in this example, the printRC Java method expects a single argument of type int (I) and does not return any values, so we use a “V” (for void) to indicate this at the right side of the closing parenthesis (the next section provides a list of letters for the various data types):

if (hJavaAccessor)
execute_java_dynamic_method(0, (HObject *)hJavaAccessor,
"printRC", "(I)V", rc);

Corresponding Letters for Data Types

The following is a list of letters to use for the various data types as defined in the signature.h include file provided with the JDK:

Letter Data Type
B byte
C char
D double
F float
I int
J long
S short
V void
Z boolean
“[“ + array type array
“L” + class name + “;” class

Exceptions

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 which contains the following signature:

void SignalError(struct execenv *executionEnvironment,
char *exceptionClass,
char *exceptionMessage)

The following example, taken from Listing 41.7, illustrates how to throw a NullPointerException:

SignalError(0, "java/lang/NullPointerException",
"No such environment variable");

Thread Synchronization

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 and three C functions provided in the JDK:

These functions are equivalent to the wait(), notify(), and notifyAll() functions provided in the java.lang.Object class:

public synchronized native String getEnv(String var);

Interfacing with C++

C++ classes are not directly supported in Java, but they can be invoked via C functions which serve as connection functions. In other words, functions which accept parameters from Java hand them off to C++ objects, and pass any data or return values from the C++ objects back to Java. In fact, this concept is similar to how Java objects are instantiated from C using execute_java_constructor; their instance methods are invoked using execute_java_dynamic_method and static methods using execute_java_static_method. Similar generic functions could be implemented for C++ objects.

On the other hand, custom C functions can also be implemented. For example, if you wanted to instantiate the C++ fstream class and invoke its methods, you could have C functions such as instantiate_fstream(), fstream_open(), fstream_close(), and so on.

One thing to ensure when using a C++ compiler to compile your C connection functions is that these functions be placed inside an extern “C” block so their names do not get mangled. The javah utility in JDK 1.0.2 automatically does this for you, but for prior versions you have to add the extern statements yourself.

Portions of this chapter first appeared in C/C++ Users Journal, Volume 14, issue 9, and are reprinted here by permission of Miller Freeman, Inc.


Previous Page TOC Next Page

| Previous Chapter | Next Chapter |

| Search | Table of Contents | Book Home Page | Buy This Book |

| Que Home Page | Digital Bookshelf | Disclaimer |


To order books from QUE, call us at 800-716-0044 or 317-361-5400.

For comments or technical support for our books and software, select Talk to Us.

© 1996, QUE Corporation, an imprint of Macmillan Publishing USA, a Simon and Schuster Company