Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

JVM Languages

GCJ & the Cygnus Native Interface


July, 2004: GCJ & the Cygnus Native Interface

Coding native methods in C++

Gene works at TimeSys where he focuses on embedded Linux. He can be contacted at gene.sallyverizon.net.


The GNU Compiler for the Java Programming Language (GCJ) is a GCC front-end for Java that has been a part of the GCC distribution since Version 2.95 (http://www.gnu.org/software/gcc/java/). With GCJ, you can compile Java code into machine language for the target GCC machine. This lets you take advantage of Java's benefits without the runtime overhead—both in terms of memory and performance—usually associated with the VM used to execute Java code.

Like C/C++, Java is more like an environment than a language. While the language itself provides a great deal of power, the libraries included with these languages provide the functionality necessary to create useful applications without substantial additional work. GCJ provides the library functionality of JDK 1.2 as part of the normal distribution.

Most full installations of GCC include the GCJ compiler. The easiest way to check if you have GCJ installed on your system is by using the which command:

$ which gcj
/usr/bin/gcj

If you get a path, you have the binary on your system's $PATH. In the unlikely event you have the binary on your system but not on your path, you can search using:

$ find / -name gcj
/usr/bin/gcj

Once you locate GCJ and make sure it's on your path, you can begin using the tool for the examples in this article.

If it's not installed, the easiest way to get GCJ is by locating the compiled binaries and installing them on your system. If you're running Linux, you can use the RPM or apt-get tools to get the most recent version of GCC (currently 3.2).

For more information on the mechanics of getting GCC compiled and installed, see books such as The Definitive Guide to GCC by Kurt Wall and William Von Hagen, or GCC: The Complete Reference by Arthur Griffith.

Native Interfaces

The Java language designers built into the specification the capability to use code compiled specifically for the machine where the program is running. This language feature bristles Java purists, as it runs against the write-once/run-anywhere philosophy of the language. With the native interface, users can access libraries or system services—graphics services, device drivers, or other system-specific services—not accessible via Java's standard class libraries. Depending on the environment and requirements of the application, certain parts of the system may be written in a lower level language to garner performance advantages.

At this point, you may be wondering who cares about write-once/run-anywhere because this concept is broken by GCJ itself, as it produces machine code for a specific platform. However, Java has more to offer than just write-once/run-anywhere. Java's clean class/interface/inheritance model is a great language feature. Implementers benefit from the smartly implemented garbage collection system, too. Furthermore, the standard class library offers a great starting point for application development.

Java Native Interface

Understanding that users would want or need to implement in lower level languages from within Java, the designers incorporated the Java Native Interface (JNI) into the language. JNI offers an effective way for users to map lower level languages into a Java class by letting users mark certain methods in a class as native.

Telling the compiler that a method was native meant that it would not allow a body, as this would be implemented in a platform-specific way. Java uses the information to create function declarations that the user would then implement. JNI also supplied an API for invoking methods and inspecting and changing properties.

Creating code for JNI is a multistep process:

  1. Write Java class code, storing it, in this case, in the file CSimpleJNI.java; see Example 1(a).
  2. Compile the source Java code into object code (bytecode); see Example 1(b).
  3. Extract native method prototypes; see Example 1(c).
  4. Write the C code; see Example 1(d).

As you can see, JNI expects you to code in C. While there's nothing wrong with C, most Java engineers have become accustomed to the object-oriented features of the language, and C is a bit of a mental shift.

GCJ's CNI

Recognizing that C++ may offer a better match for Java engineers, the GCJ developers provide the capability to code native methods in C++ instead of C. With the Cygnus Native Interface (CNI), you can take advantage of the language features of C/C++ when creating native methods.

CNI (http://gcc.gnu.org/java/papers/cni/t1.html) contains well thought out constructs for method invocation, property access, exception handling, and synchronization. Using these constructs instead of their JNI procedural equivalents means you can get the same work done using less code. With CNI and C++, the interface between C++ and Java is much smoother and natural.

If you want to use CNI, you'd still follow pretty much the same steps as JNI to create the method prototypes; however, the step that extracts the native method prototypes emits a header file containing a class definition rather than function prototypes. For instance, using gcjh to create a CNI header file for the previous example yields the C++ code in Example 2(a). The code necessary to implement nativeMethod looks like Example 2(b).

Compared to JNI

As you can see, CNI produces an interface into Java with C++ code. So instead of generating a group of functions containing the class and method name, CNI generates C++ code declaring a class with the native methods left empty for you to implement. However, more than just the language has changed, as the philosophy behind how you interact with Java from a native perspective is much different, in a good, geeky kind of way.

The first thing to get your head around is the notion that the binding between the CNI class and the Java class is much stronger. Because JNI was implemented for a language that did not support any object-oriented concepts, handling the differences between the languages required extra code. When working in CNI, you have the feeling of working in situ with the Java class; gone is the messy code necessary to bridge the two languages.

Using CNI

To invoke a method, users must look up the class identifier, find the method ID, and then invoke the method, usually via one of the Call<type>Method(...) helper functions. Listing One is replaced by simply calling getValue(), the method defined in the class. This alone should bring smiles to JNI users, as the JNI semantics of method invocation defers all checking to runtime. So changing a method's name or signature without the same change in the C code results in a successful compilation, but failures when running the program. Although all good engineers check the error conditions for method and report errors at runtime, it's still better to catch changes like this sooner rather than later.

Exception handling occurs using the native exception handling in the C++ runtime, both for throwing and catching exceptions. For example, throwing an exception with JNI means looking up the class of the exception, and using a JNI-supplied helper function to create and immediately throw the exception. In Listing Two, for instance, the brittleness in the code appears right after the ThrowNew, where users must remember to return before doing anything else, if the exception is to be caught in the enclosing Java code. While experienced JNI programmers know this in their sleep, newer engineers may make the mistake of writing code with subtle defects.

For CNI users, throwing an exception means just doing: throw new CThrowMe(); and some combination of the C++ runtime and CNI does the rest.

Catching an exception is just as easy; exceptions thrown by classes percolate through as regular C++ exceptions. Consequently, you can catch them just by wrapping the code that may throw an exception in a try/catch block in your CNI code; see Listing Three. If the native code does not catch the exception, the call stack unwinds past the CNI code and the exception is caught in Java code or results in an unhandled exception being emitted by the Java runtime.

Contrast this with the JNI mechanism where you must poll the VM to determine if an exception has been raised (see Listing Four).

There are two potential problems to keep in mind: First, if you don't check to see if the exception has been raised, you'll never know if one was raised in the first place. Second, if you forget to call ExceptionClear, the exception may get processed twice if the Java code contains a try/catch block for the exception. Using IsInstanceOf to determine the type of exception could yield some unwieldy code, although most engineers create a helper function that hides this implementation detail. Aside from the mechanical issues of handing the exception, the author and maintainer must know enough about the code to check for exceptions in a timely manner, so the system is not placed in an inconsistent state.

Java implements synchronization through monitors attached to each object. The same principle applies for objects in CNI. To achieve the effect of a Java synchronize block, you need to write something like JvSynchronize sync (this);, which does the underlying mechanics to acquire the lock on the monitor when its constructor runs and the destructor releases the lock.

Emulating a synchronized block using JNI requires very little work—just call a function with the current object pointer as the parameter and use an unlock when done. You create a critical section with CNI by instantiating a JvSynchronize object or using the JvMonitorEnter/JvMonitorExit function calls.

Caveats and Limitations

While CNI provides great functionality, it does have some limitations and other rough edges:

  • You can't perform a call by interface reference. Within a native method, you can't use a reference to an interface to call a method on an object implementing that interface. This is a nice construct in the Java language, but in CNI, this hasn't been implemented.
  • All parents of an object must be CNI. When you create the C++ header file from the class definition, the parent of the class appears in the file as the parent class in C++ and the generated code includes a #include to get that class's definition. This means that the entire ancestry of a class must have been run through the processor that creates the header files. For newer projects, this is not that difficult to work into the project's make files, but for existing projects with deep hierarchies, this constraint can translate into considerable work.
  • Class initialization. Class initialization occurs automatically in Java. In CNI, however, you are responsible for making sure that a class is initialized by calling the JvInitClass method in a class's static functions. This method performs the initialization for the current class and all parents.
  • Java lets you declare methods as synchronized, meaning that the entire method is wrapped in an implicit synchronized block. When used from JNI, the lock is acquired before calling the native code. With CNI, this semantic is ignored and you must write the synchronization code inside of the native method.

Conclusion

CNI offers a great way to program in native code when working with the GCJ compiler front end. The way CNI maps your Java classes into C++ classes relieves you from the drudgery typically associated with JNI-coded interfaces and lets you use the constructs offered by C++ in concert with your Java code. If you have the chance, give this technology a try. You won't be disappointed.

DDJ



Listing One

(*env)->GetObjectClass(env, thisObj);
mthdID = (*env)->GetMethodID(env, clsID, "getValue", "()I");
jiValue = (*env)->CallIntMethod(env, thisObj, mthdID);
Back to article


Listing Two
jclass rteClass;
jthrowable rteInstance;
rteClass = (*env)->FindClass(env, "java/lang/RuntimeException");
(*env)->ThrowNew(env, rteClass, "An exception thrown from JNI");
/* party members in good standing should return here */
Back to article


Listing Three
try {
  // stuff
}
catch (CThrowMe e) {
  // do something !
}
Back to article


Listing Four
  if ((rteException = (*env)->ExceptionOccurred(env)) != NULL) {
    if ((*env)->IsInstanceOf(env, rteException, rteClass) == JNI_TRUE) {
      printf("Caught the exception in native code\n");
      (*env)->ExceptionClear(env);
    }
  }
Back to article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.