Java Tip 17: Integrating Java with C++

Learn how to use C++ code from within a Java application and how to call from C++ to a Java object

Page 2 of 2

To safely keep references to Java objects, we can simply store the references in a Java vector. If we make this vector part of a singleton object that's globally available, the list will never get garbage collected, and we'll be able to get to the list from C++. Within a C++ object, the "reference" to the Java object can actually be an index into the singleton Vector. In my example, I've called the class that maintains this vector JavaObjectHolder. The structure of our entire system is shown below:

JavaObjectHolder is a straightforward Java class. It simply has methods to add an object (returning its index), remove an object, and get an object. For simplicity, I made these methods static. The declarations of the methods look like this:

    class JavaObjectHolder {
        public static int addObject(Object o)  {
            ...        }
        public static void removeObject(int handle) {
            ...
        }
        public static Object getObject(int handle) {
            ...
        }
    }

(The complete source of this class, including exception specifications, can be found at the end of this article.)

Now that we have a way of maintaining references to Java objects, we're in a position to actually implement C++::JavaObservableProxy. The header is straightforward:

#if !defined(JavaObservableProxy_h) #define JavaObservableProxy_h #include "Observer.h" #include "Observable.h" class JavaObservableProxy : public Observer { public: JavaObservableProxy(struct HObservable* javaObj, Observable* obs); ~JavaObservableProxy(); void update(); private: int javaObjectId_; Observable* observedOne_; }; #endif

Within the implementation file (JavaObservableProxy.cc), we first define a convenience function to get us the class descriptor of the JavaObjectHolder class:

    static ClassClass* javaObjectHolder()
        // Give a pointer to the class descriptor for JavaObjectHolder
    {
        static ClassClass* result = 0;
        if (result == 0)  {
            result = FindClass(0, "JavaObjectHolder", TRUE);
            assert(result != 0);
        }
        return result;
    }

This is faster than calling the Java library function FindClass() each time.

Next, we need to write the constructor. The constructor simply calls JavaObjectHolder.addObject to convert the handle into an integer that's safe to store on the heap:

    JavaObservableProxy::JavaObservableProxy(
            struct HObservable* javaObj,
            Observable* obs)
    {
        javaObjectId_ = execute_java_static_method(
                            0, javaObjectHolder(), "addObject",
                            "(Ljava/lang/Object;)I",
                            javaObj);
        observedOne_ = obs;
        observedOne_->addObserver(this);
    }

When the C++::JavaObservableProxy is destroyed, we'll need to have the matching call to javaObjectHolder.removeObject()...

    JavaObservableProxy::~JavaObservableProxy()
    {
        observedOne_->deleteObserver(this);
        execute_java_static_method(
            0, javaObjectHolder(), "removeObject", 
"(I)V", javaObjectId_);
        javaObjectId_ = -1;
    }

Finally, we have the infrastructure we need. All that's left is to implement C++::JavaObservableProxy::update()...

    void JavaObservableProxy::update()
    {
        HObject* javaObj = (HObject*)
            execute_java_static_method(
                0, javaObjectHolder(), "getObject", 
"(I)Ljava/lang/Object;",
                javaObjectId_);
            // If an exception occurred, get back to the Java runtime, because
            // invoking another method would clear the exception flag.
        if (exceptionOccurred(EE()))
            return;
        execute_java_dynamic_method(0, javaObj, "setChanged", 
"()V");
        if (exceptionOccurred(EE()))
            return;
        execute_java_dynamic_method(0, javaObj, "notifyObservers", 
"()V");
    }

This method gets a handle to the Java proxy (by calling JavaObjectHolder.getObject()), then executes setChanged() and notifyObservers() on the Java object.

Garbage collection revisited

In this discussion, we've created a number of instances, but we haven't said anything about how to clean them up. Remember, this is C++, and we need to manually dispose of the objects we create!

An obvious place to dispose of the C++ objects we create would be within the finalize() method of NumberListProxy. Unfortunately, this won't work because there is a circular reference: NumberListProxy maintains a reference to C++::JavaObservableProxy, and C++::JavaObservableProxy maintains a reference to NumberListProxy (by going through the static Vector inside JavaObjectHolder). There is no way for Java's garbage collector to detect this circular reference, so NumberListProxy instance will never be collected.

To get around this, we must resort to manual memory management. We add a method called "detach()" to NumberListProxy. When the Java side is done with a NumberListProxy instance, it must call NumberListProxy.detach(). This method can free all of the C++ instances that are created. (Some languages support a concept called "weak references" that can solve problems of this nature in an automatic fashion. Weak references are not a part of Java, and a discussion of them would be beyond the scope of this article.)

Putting it all together

To demonstrate the system we've just developed, I created a simple application to exercise it. This application creates a number list, establishes an observer, and adds a few numbers to the list. Whenever a number is added, the observer is notified, and it prints a message to stdout. The Java observer is quite simple:

import java.util.*; class NumberListObserver implements Observer { NumberListObserver(NumberListProxy subject) { subject_ = subject; subject.addObserver(this); } /** * Called when the subject changed * @param o not used * @param arg not used **/ public void update(Observable o, Object arg) { synchronized (subject_) { // Don't want size() to change under us! int sz = subject_.size(); System.out.print(" The list now has: "); for (int i = 0; i < sz; i++) { if (i > 0) System.out.print(", "); System.out.print(subject_.getNumber(i)); } } System.out.println(""); } private NumberListProxy subject_; // Thing being observed }

The main program looks like this:

    import java.util.*;
    class TestNumberList {
        public static void main(String args[]) {
            NumberListProxy model = new NumberListProxy();
            NumberListObserver obs = new NumberListObserver(model);
            System.out.println("Adding 3 to the list...");
            model.addNumber(3);
            System.out.println("Adding 42 to the list...");
            model.addNumber(42);
            System.out.println("Adding 666 to the list...");
            model.addNumber(666);
            System.out.println("Adding 7 to the list...");
            model.addNumber(7);
            model.deleteObserver(obs);
            model.detach();
        }
    }

Running the program yields this output:

    billf@pluto:~/javaC++Article/src$ java TestNumberList
    Adding 3 to the list...
        The list now has:  3
    Adding 42 to the list...
        The list now has:  3, 42
    Adding 666 to the list...
        The list now has:  3, 42, 666
    Adding 7 to the list...
        The list now has:  3, 42, 666, 7

JDK 1.1 and beyond

In their recent announcement, JavaSoft informed us that JDK 1.1 will have a "new Java native method interface" (see Resources). Hopefully, this new interface will provide a mechanism for getting a global reference to a Java object.

Netscape has comprehensive documentation on their JRI native method interface (see Resources). JRI ships with Netscape 3.0, and it provides everything we need in the way of registering global references. Unfortunately, it's only implemented by Netscape. Hopefully, JavaSoft will implement something similar, if not exactly the same. (Dare I hope that the JDK 1.1 will have something along the lines of Netscape's JRI?)

ILOG has announced a project called TwinPeaks (being developed with JavaSoft -- see Resources), which promises to "deliver Internet-ready C++ business application components to developers and customers." I expect this means that it will automate the writing of some of the glue code that we wrote by hand in this article. It will probably include other useful tools -- perhaps a debugger that can step from Java into C++.

Getting the source code

If you'd like the complete source code for the program developed in this article (complete with Solaris Makefile), click for javaAndC++.tar.gz, or for javacpp.zip. If you'd just like to browse, here are the source files:

Conclusions

Integrating C++ classes into a Java application is fairly straightforward, although somewhat messy. We expect the standard native method APIs to improve in the near future. Once this happens, the general approach outlined in this article will continue to work, but the implementation will be easier (and performance will be better).

Accessing C++ from Java can be worth the inconvenience. It opens up large bodies of "legacy" C++ code to potential reuse within Java applications. It also lets us exploit the power of C++, where C++ has an advantage -- that is, in performance (at least for now), in directly accessing hardware, in doing other low-level activities, and so on. With this power comes danger, however. We lose the pointer safety that Java provides us, opening ourselves up to memory-corruption bugs. In other words, integrating C++ with Java can be a powerful technique but one to be used with care!

When not planning his next trip to France or raising beagles with foreign names, Bill Foote likes to program in Java, Eiffel, CLOS, Smalltalk and, yes, C++. Current projects include a strange brew of Java, C++, and ANTLR to be used in semiconductor test equipment.

Learn more about this topic

| 1 2 Page 2