Level: Introductory Sachin Agrawal (sachin_agrawal@in.ibm.com), Staff Software Engineer, IBM Software Labs, India Vijil Chenthamarakshan (vijil.e.c@in.ibm.com), Staff Software Engineer, IBM Software Labs, India
31 Jan 2006 When you need to use objects written in other languages, communications between a native event source and Java™ listeners can be tricky -- especially in multithreaded environments. This article helps you deal with traditional native libraries effectively by using a design pattern for transparently handling event communication from native code to the JVM.
In an object-oriented system, an object can fire a set of events. The Java programming language provides support for defining event listeners based on the Observer design pattern, but this isn't sufficient when you need to use objects written in other languages. Using the Java Native Interface (JNI) to communicate between a native event source and Java listeners can be tricky, especially in multithreaded environments. In this article, we describe a design pattern for transparently handling event communication from native code to the JVM. You can use this design to provide a Java interface to a legacy native application or to build native applications with Java listeners in mind.
The Observer design pattern
The Observer design pattern defines a many-to-one dependency between event listeners and an event creator. When the event creator fires an event, all its listeners are notified of the event. Because the event creator and the listener are decoupled, you can use and change each of them independently. This design pattern is central to event-driven programming and is widely used in GUI frameworks such as Swing and SWT.
Implementing the Observer design pattern is fairly straightforward when the entire application is written in the Java language. The class diagram in Figure 1 shows an example:
Figure 1. Observer design pattern
Java listeners for native applications
In some cases, though, you might want to support Java listeners for native applications. The bulk of the application (including the application entry point) might be written in native code, and the application generates events as a way of interacting with its user interface. In such a situation, the best way to support a Java-based UI is to let a Java class register itself as a listener for various events that the application generates. In short, you want to Java enable a native application by supporting listeners written in the Java language.
The class diagram in Figure 2 shows a sample scenario. The CEventSource class is written in C++. It lets listeners register and unregister for its "mouse down" events using the addMouseDownListener() and removeMouseDownListener() methods. It expects all the listeners to implement the IMouseDownListener interface.
Figure 2: Class diagram of a sample scenario
Note that the IMouseDownListener is a C++ abstract class. How can a Java class register for the event without introducing a compile-time binding between the Java listener and the CEventSource class? And after it has registered for the event, how can the CEventSource class invoke a method in the Java class? This is where Java Invocation API comes into picture.
The Invocation API
The Invocation API lets you load a JVM into a native application without explicitly linking with the JVM source (see Resources). You create a JVM by invoking a function in jvm.dll, which also attaches the current native thread to the JVM. Then, you can call all the Java methods in the JVM from your native thread.
However, the Invocation API does not solve the problem completely. You don't want the CEventSource class to have a compile-time dependency with the Java listeners. Also, the Java listeners should not be burdened with the responsibility of using JNI to register the listeners with CEventSource.
The Proxy design pattern
You can avoid these pitfalls by using the Proxy design pattern. A proxy in its general form is a placeholder for another object. The client objects can deal with the proxy object, while the proxy encapsulates all the details of using native methods. The details of the Proxy pattern are shown in Figure 3:
Figure 3. Proxy design pattern example
The EventSourceProxy is a proxy for the CEventSource class. To register itself as a listener, the client should implement the IJMouseDownListener interface. This interface is similar to IMouseDownListener but is written in Java code. When the first client invokes its addMouseDownListener() method, it uses the registerListener() native method to register itself as a listener with CEventSource. The registerListener() method is implemented in C++. It creates a JMouseDownListener object and registers it as a listener with CEventSource. The onMouseDownListener() method in JMouseDownListener uses the Invocation API to notify EventSourceProxy when its onMouseDown event is fired.
The EventSourceProxy also maintains a set of listeners that are registered with it. Whenever its onMouseDown is fired, it notifies all the listeners in its set. Note that even if multiple Java listeners exist for this event, only one instance of the proxy is registered with CEventSource. The proxy delegates onMouseDown events to all its listeners. This prevents unnecessary context switches between native and Java code.
Multithreading issues
Native methods receive the JNI interface pointer as an argument. However, a native listener that wants to delegate the event back to its associated Java proxy does not have a ready-made JNI interface pointer. The JNI interface pointer, once obtained, should be stored in memory for later use.
The JNI interface pointer is valid only in the current thread. A JVM implementing the JNI can allocate and store thread-local data in the area pointed to by the JNI interface pointer. This means is that you need to keep the JNI interface pointer in thread-local data as well.
The JNI interface pointer can be obtained in two ways:
- Once a thread creates the JVM using
JNI_CreateJavaVM, JNI puts the interface pointer value at a location specified by its second parameter. This value can then be saved in the thread-local area.
- If the JVM is already created by some other thread in the process, the current thread can call
AttachCurrentThread. JNI puts the interface pointer value at a location specified by its first parameter.
But all is not over yet. Remember that the programming is in C/C++, so automatic garbage collection is not available as it is in the Java language. Once the thread is done with JNI calls, it needs to release the interface pointer by calling DetachCurrentThread. If this call is not made and the thread exits, the process does not terminate gracefully. Instead, it waits forever for the now nonexistent thread to detach from the JVM in the DestroyJavaVM call.
In the sample code (see Download), all this is encapsulated in CJvm class at various locations. Also, multithreading is demonstrated by firing the event from a working thread separate from primary one.
Environment setup and sample code
Now you're ready to explore, build, and run the sample code (see Download).
Common interfaces
The IMouseDownListener and IEventSource interfaces are defined in common.h. IMouseDownListener has just one method: onMouseDown(). This method receives the screen location where the mouse was clicked. The IEventSource interface contains the addMouseDownListener() and removeMouseDownListener() methods for registering and unregistering the listeners.
Helper routines the for Java Invocation API
Seven frequently required utilities for simplified use of the Java Invocation API are defined in Jvm.h and implemented in Jvm.cpp:
CreateJavaObject() creates a Java object, given its class name and handle to native IEventSource. The created Java object would like to listen to events from this native handle. The handle is passed to the object through its constructor.
ReleaseJObject() invokes the Java object's release() method. This method is expected to unregister objects' listeners from the EventSourceProxy.
DetachThread() detaches the current thread from the JVM, if it is attached. This call is needed to release thread-specific resources that were allocated for JNI while the thread was being attached.
The rest are self-explanatory:
CreateJVM()
DestroyJVM()
GetJVM()
GetJNIEnv()
The CJvm class also maintains a JNI environment pointer at a thread-specific location. This is necessary because the JNI environment pointer is thread-dependent.
Native event source
CEventSource is a simple and straightforward implementation of the IEventSource interface in EventSource.h and EventSource.cpp.
Native event listener
CMouseDownListener is an implementation of IMouseDownListener interface in MouseDownListener.h and MouseDownListener.cpp. This native listener is written only for illustrative purposes.
Entry points
main.cpp contains main() and ThreadMain(). main() creates a native EventSource, a native listener, and a Java listener. It then creates the thread and lets it execute by sleeping for few seconds. Finally, it releases the Java listener and destroys the JVM.
ThreadMain() simply fires an event and then detaches itself from the JVM.
Java modules
IJMouseDownListener in IJMouseDownListener.java is simply a clone of the native interface to the Java platform.
MouseDownListener is a sample listener in Java implemented in MouseDownListener.java. It receives the native EventSource handle in its constructor. It defines a release() method, which unregisters its listeners with the EventSourceProxy.
EventSourceProxy is a placeholder or surrogate for EventSource from the native module. It is implemented in EventSourceProxy.java. It maintains a static hash table to map a proxy with the actual EventSource.
addMouseDownListener() and removeMouseDownListener() let you maintain the collection of Java listeners. A single native EventSource can have more than one Java listener, but the proxy registers/unregisters with the native EventSource only when required.
While forwarding the event from the native EventSource, a native implementation of the EventSourceProxy invokes fireMouseDownEvent(). This method iterates through the hash set of Java listeners and notifies them.
The native portion of EventSourceProxy also maintains a global reference to itself. This is required to invoke fireMouseDownEvent() at a later time.
Building and executing the sample code
All the Java classes in the sample code are built using normal procedures and require no special steps. For the native implementation of EventSourceProxy, you need to generate the header file using javah:
javah -classpath .\java\bin events.EventSourceProxy |
To build C++ modules for Win32 platform, we have provided Microsoft Developer Studio project files and the cpp.dsw workspace. You can open the workspace and simply build the main project. All projects in the workspace are associated with adequate dependencies. Make sure that your Developer Studio can locate JNI headers and compile-time JNI libraries. You can do this by selecting the Tools > Options > Directories menu items.
Once the build is successful, you need to take a few steps before you can execute the sample program.
First, because the JDK used to build Java classes and containing JNI headers and libraries might have runtime components for its Java Invocation API -- jvm.dll, for example -- you must set this up. The simplest approach is to update the PATH variable.
Second, the main program takes command-line arguments, which are simply JVM arguments. You need to pass at least two arguments to the JVM:
main.exe "-Djava.class.path=.\\java\\bin"
"-Djava.library.path=.\\cpp\\listener\\Debug"
|
The resulting console output is as follows:
In CMouseDownListener::onMouseDown
X = 50
Y = 100
In MouseDownListener.onMouseDown
X = 50
Y = 100 |
As you can see from the console output, the Java listener produces the same results as the native listener that was built for illustrative purposes.
Conclusion
This article has shown how you can register a Java class as a listener for events generated by native applications. By using the Observer design pattern, you've reduced the coupling between the event source and listeners. You've also hidden the event source's implementation details from the Java listener by using the Proxy design pattern. You can use this combination of design patterns to add a Java UI to an existing native application.
Download | Description | Name | Size | Download method |
|---|
| Source code illustrated in the article | j-jniobserver-source.zip | 259 KB | HTTP |
|---|
Resources -
Java Native Interface specification: JNI allows Java code that runs inside a JVM to interoperate with applications and libraries written in other programming languages.
-
The Invocation API: The Invocation API is part of the JNI specification.
-
"Java programming with JNI" (Scott Stricker, developerWorks, March 2002): This tutorial deals with the two most common applications of JNI: calling C/C++ code from Java programs, and calling Java code from C/C++ programs.
-
"e-BIT bytes: JNI pitfalls" (Mark Bluemel, developerWorks, June 2002): This tip shows some instances where JNI breaks down and offers some advice for when you absolutely need to use it.
-
Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides (Addison-Wesley, 1995): A classic text on object-oriented design patterns.
-
Head First Design Patterns by Elisabeth Freeman, Eric Freeman, Bert Bates, Kathy Sierra (O'Reilly Media, 2004): A book that teaches design patterns using a visually rich approach.
-
The Java technology zone: Hundreds of articles about every aspect of Java programming.
About the authors  | 
|  | Sachin Agrawal has worked extensively in C++ for several years. He does ongoing research into the C++ object models of various compilers. He works for IBM Software Labs, India.
|
 | |  | Vijil Chenthamarakshan works for IBM Software Labs, India. His interests include unstructured information management systems and paradigms that let developers work at higher levels of abstraction to cope with software complexity.
|
Rate this page
|