Embed Java code into your native apps

Integrate a Java user interface with legacy code on Unix

Like many developers, you want to incorporate new Java technologies into your projects. But currently your code is written in C or in another legacy language, and rewriting your entire application in Java is not a realistic option. In this article you will learn how to embed Java code into your native application and how to integrate GUIs of multiple worlds: Motif, the XToolKit, X Window and OpenGL for Unix, and Swing for Java.

After reading this article you will be able to:

  • Start a JVM from a legacy application
  • Create Java objects from a legacy application
  • Integrate a Swing GUI with the Motif window manager
  • Call back legacy code from Java using a thread-safe architecture

Where to start?

Consider a simple C program with a Motif user interface. By browsing through its menus, currently the user can create OpenGL windows that contain some simple 3D rendering. You have been asked to add a new menu to this application; you want to implement it using the Swing package from the JDK. The Java Native Interface (JNI) API from the Java Runtime Environment provides you with the tools you need to start a JVM from a C program and accomplish the task at hand. Using that JVM, JNI also allows you to create Java objects and call the objects' methods. Assuming that you have written the code needed for a Java class that implements the required menu (see SwingMenu.java for the full Java code, available for download from Resources), you now have to access the JNI from the C code to start the JVM and load your class. Listing 1 illustrates those last two tasks:

Listing 1: Start the JVM and load a class

/** File main.c **/
#include <stdlib.h>
#include <jni.h>
int main (int argc, char *argv[])
{
JNIEnv          *env;
JavaVM          *jvm;
JavaVMInitArgs  vm_args;
JavaVMOption    options[3];
int             nbOptions;
jint            res;
jclass          myJavaClass;
jobject         javaGUI;
jmethodID       constructorID;
char            *classPath, *libraryPath;
/** Code to be added here **/ 
/** Start the Java Virtual Machine **/
classPath = malloc(29);
strcpy(classPath, "-Djava.class.path=/myClassDir");   /* Path of the
SwingManu.class file */
libraryPath = malloc(29);
strcpy(libraryPath, "-Djava.library.path=/myLibDir");     /* Path of
the libNativeMethods.so library */
nbOptions=0;
options[0].optionString = classPath;   nbOptions++;
options[1].optionString = libraryPath; nbOptions++;
vm_args.version  = JNI_VERSION_1_2;                   /* Specifies the JNI
version used */
vm_args.options  = options;
vm_args.nOptions = nbOptions;
vm_args.ignoreUnrecognized = JNI_TRUE;                 /* JNI won't
complain about unrecognized options */
res = JNI_CreateJavaVM(&jvm,(void **)&env,&vm_args);
free(classPath);
free(libraryPath);
/*
 * Create an instance of the Class SwingMenu.
 * Before calling NewObject we must get the constructor
 * of the Java class. This constructor must de passed
 * as an argument to NewObject.
 */
myJavaClass   = (*env)->FindClass(env, "SwingMenu");
constructorID = (*env)->GetMethodID(env,myJavaClass,"<init>", 
"()V");
javaGUI       = (*env)->NewObject(env,myJavaClass,constructorID);
}

Job done! You are now able to start your application. With a few Java method invocations through JNI you can see your Swing menu on the screen. Below is the picture of your application at startup.

A Motif application using a Swing menu. The knight is drawn using OpenGL.

Allow Swing menu to call back legacy app

Now, what happens if you press the Swing buttons Red or Yellow in the figure above? Nothing yet. You would like the knight to change color, but for it to do so, you need to link your Java code to your legacy application. You must find a way to connect the legacy app with the Swing GUI. The java.awt.event package and JNI help complete that task. The AWT event model defines the ActionListener interface, which listens to the user interactions with the menu. That interface includes the actionPerformed() method, which is called when an action event occurs. The SwingMenu class must implement the ActionListener interface, and it must define the actionPerformed() method. In turn, actionPerformed() calls the changeColour() method that you have declared native in the SwingMenu class. Why native? Because changeColour() is actually implemented in C -- hence it can easily communicate with your legacy code. The C implementation is located in a dedicated dynamic library that you have named libNativeMethods.so, and that implementation finally calls your legacy function to change the knight's color.

Below is an example featuring a changeColour() method that ensures you can change the knight's color to red. The code below covers the declaration in the Java code, the actionPerformed() method implementation, and the prototyping in C:

Listing 2: Call a C function from Java

// File SwingMenu.java
// Implements the SwingMenu class
import javax.swing.*;
class SwingMenu extends JFrame implements ActionListener {
  JButton      redButton;
  // Declaring a method native
  private native void changeColour(int colour);
  static { system.loadLibrary("NativeMethods");  }
  // Method to create the Java GUI.
  public void initGUI(int mainWindow) {
     // Create the "Red" button.
     redButton = new JButton("Red");
     redButton.addActionListener(this);
  }
  // Method to handle button events.
  public void actionPerformed(ActionEvent e) {
      String string = e.getActionCommand();
      if (string.equals("Red")) { changeColour(1);  }
  }
}
/**  File nativeMethods.c **/
#include <jni.h>
#include <SwingMenu.h>
/*
 * Functions which is actually called when the changeColour() Java method
is called
 */
JNIEXPORT void JNICALL Java_SwingMenu_changeColour(JNIEnv env, jobject
obj, jint colour)
{
  legacyChangeColour((int)colour);   /* the legacyChangeColour() function
is defined in our legacy application */
  return;
}
jint JNI_Onload(JavaVM *vm, void *reserved)
{
   return (JNI_VERSION_1_2);
}

In Listing 2, the file nativeMethods.c implements the methods declared native. You can automatically generate the included file SwingMenu.h by using the javah tool on the SwingMenu class file. You should not modify that file; SwingMenu defines the prototypes of the C functions implementing the Java methods declared native. As you can see in Listing 2, the method name is slightly more complicated than changeColour().

Note that in the SwingMenu.java file, you must load the library libNativeMethods.so by calling System.loadLibrary(). Performing this call in a static block ensures that the library loads as soon as the Java class loads.

Finally, nativeMethods.c also implements a JNI_Onload() method, which the JVM calls when libNativeMethods.so loads. JNI_Onload() must return the JNI version used by this library. Use JNI 1.2 as specified when starting the JVM from the main program (see Listing 1).

Integrate with the window manager

The window manager is the application that controls the appearance of windows on the screen. This includes stacking order, focus behavior, and the appearance of minimized windows. A Motif application uses a shell widget to communicate with the window manager. That widget accepts the window manager dressing -- borders and buttons that enable the user to move or minimize windows. When a classic application uses many shells independent of one another and the user minimizes the main window, the other shells disappear from the screen. However, if you try this with your application, the Swing shell and its included menu stay on the screen.

To fix that situation, you need to notify the window manager that the shell widget used by the Swing menu is attached to your legacy application's shell. Since X11 window properties are the means of communication with the window manager, you need to change those properties on the underlying window associated with the Swing menu shell. This is classical X Window/Motif programming so I will not provide those details in this article (refer to the attachShell() method in nativeMethods.c available for download in Resources).

But, you may ask, how do I receive access to the underlying window of the Swing menu shell? Use a new Java 2 API: the AWT Native Interface (NI). This API allows you to access the drawing surface of an AWT Canvas. On Unix, this drawing surface is an X11 window. Once you have access to an X11 window, obtaining the shell widget window, which stands at the top of the window's hierarchy, is somewhat easy. So let's focus on how to obtain the drawing surface using the AWT NI.

Strictly speaking, the AWT NI is not a Java interface; C code calls it. In our case, calls to the AWT NI complete in the attachShell() method, which also handles the work needed to attach the Swing shell to the main shell. Since attachShell() is written in C and called from Java code, it is declared native in the SwingMenu class located in the nativeMethods.c file and built into the libNativeMethods.so library. As a result, on Solaris, you must build libNativeMethods.so with the jre/lib/libjawt.so library, which implements the AWT NI.

As mentioned earlier, the drawing surface is only available for an AWT Canvas, and you did not use AWT Canvas when building your Swing menu; only JButton components included in a JFrame container make up that menu. The solution: create a dummy Canvas and add it into the JFrame. As that Canvas shouldn't interest the user, give it a size of 1 pixel by 1 pixel.

Now let's return to your main purpose: Listing 3 includes the C code that implements the attachShell() method. The AWT Canvas passes as an argument from the Java code:

Listing 3: Obtain the underlying windowing information from an AWT Canvas

/** File nativeMethods.c **/
#include <X11/Xlib.h>
#include <jni.h>
#include "jawt_md.h"
#include "SwingMenu.h"
/* The Java object canvas must be  a java.awt.Canvas. */
JNIEXPORT void JNICALL Java_SwingMenu_attachShell(JNIEnv *env, jobject
obj, jobject canvas, jint shellWindow)
{
  JAWT                       awt;  /* defined in jawt.h which is included
in jawt_md.h */
  JAWT_DrawingSurface        *ds;
  JAWT_DrawingSurfaceInfo    *dsi;
  JAWT_X11DrawingSurfaceInfo *dsi_X11;
  jint    lock;
  Window  win;
  Display *dpy;
  awt.version = JAWT_VERSION_1_3;
  if (JAWT_GetAWT(env,&awt) == JNI_FALSE) {
     fprintf(stderr, "Can't get AWT ! Exiting.\n");
     exit(11);
  }
  ds = awt.GetDrawingSurface(env, canvas);
  if (ds == NULL) {
     fprintf(stderr, "Can't get drawind Surface ! Exiting.\n");
     exit(12);
  }
  lock = ds->Lock(ds);
  if ((lock & JAWT_LOCK_ERROR) != 0) {
    fprintf(stderr, "Can't lock drawing surface ! Exiting.\n");
    exit(13);
  }
  dsi = ds->GetDrawingSurfaceInfo(ds);
  if (dsi == NULL) {
    fprintf(stderr, "Can't get drawing Surface Information. Exiting !\n");
    exit(14);
  }
  dsi_X11 = (JAWT_X11DrawingSurfaceInfo*)dsi->platformInfo;
  dpy = dsi_X11->display;
  win = dsi_X11->drawable;
  ds->FreeDrawingSurfaceInfo(dsi);
  ds->Unlock(ds);
  awt.FreeDrawingSurface(ds);
}

The first thing to do is maintain a handle on an AWT structure that contains all the required information to complete the job. Then you can access a JAWT_DrawingSurface, which is the drawing surface. After two switches to a JAWT_DrawingSurfaceInfo and to a JAWT_X11DrawingSurfaceInfo, you obtain the X11 window and the X11 display. Note that before receiving the information, you must lock your drawing surface because Java is multithreaded, and the drawing surface's implementation might not be multithread-safe. In other words, if two separate threads access the drawing surface concurrently, your application may crash. The locking mechanism forces the threads to access the drawing surface one at a time.

Make the architecture more robust

Your Swing menu is now integrated with your legacy application. You have solved the window management issues, and you can call back your legacy application from the Java code. Now you want to create an OpenGL window by pressing one of the Swing buttons. To create such a window on Unix, you need to call OpenGL and glX functions (glX carefully integrates OpenGL with X11). Why can't you proceed like you did before? Simply implement a C function declared native in your Java code and called from the actionPerformed()method. The native function would handle the OpenGL window creation. Don't spend long hours on this approach, as it will most likely fail because of multithreading issues.

Java uses many threads -- one main thread, one for handling events, one for garbage collection, and so forth. That means different threads could concurrently access a function called from Java. Considering your application, for OpenGL and glX functions, the situation is even more complex as functions can also be called from the main thread of the legacy application.

You can assume that OpenGL and glX are multithread-safe -- i.e., they can actually support concurrent access from different threads. But that is nothing more than an assumption; the reality is implementation-dependent.

You might consider implementing the locking system that you used when accessing the drawing surface, but this time, neither OpenGL nor glX provide such a mechanism. You'll need to implement it yourself, which can prove quite complicated if you're unsure which calls need lock protection.

1 2 Page
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more