Monday, October 15, 2012

Calling C++ from Java Using JNI

JNI is part of the Java SDK and we can use it to call native code (e.g. C++) from within our Java applications. This can be very useful when you have existing C++ code that contains business logic that you don't have the time or resources to migrate into your Java application. Let me start by saying that I don't think this should be your first course of action in this scenario. If possible, I think a cleaner approach would be to wrap the C++ implementation in a web service and then call the web service from within your Java application. However, if your Java app will be deployed to the same server as your native code and/or your native code is running on some legacy mainframe that makes it difficult to expose as a web service, JNI can be a very good choice.

Our first step is to code-up our Java application and compile it. Our Java class will look something like this.

public class TestApplication {
    public native int getNumber();

    public static void main(String args[]) {
        System.loadLibrary("TestApplication");
        TestApplication test = new TestApplication();

        System.out.println(test.getNumber());
    }
}

The native keyword in the method declaration simply tells the Java compiler that the getNumber() method is implemented in some native library outside of this Java class. The code inside the main method simply loads the external library (which we'll create in a later step) and calls the native getNumber() method that is implemented in our C++ code.

Next, we need to create a C++ header file that will define our native functions. This header file is not the same header file you may already have in your C++ code. This header file will be created against our TestApplication.class file. To generate this header file, run the following command at the command line from within the same directory as your TestApplication.class file.


>  javah TestApplication

The auto-generated .h file will look like this.

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class TestApplication */

#ifndef _Included_TestApplication
#define _Included_TestApplication
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     TestApplication
 * Method:    getNumber
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_TestApplication_getNumber
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif


The next step is to create the .cpp file that will contain the implementation of our native getNumber() method. Typcially, with JNI development, this C++ code is a wrapper around existing C++ code that we just expose using JNI. In other words, we're not editing the existing C++ library, but instead creating an adapter that will then call our legacy C++ code. Here's the .cpp source file in its entirety.

#include "TestApplication.h"

JNIEXPORT jint JNICALL Java_TestApplication_getNumber
 (JNIEnv *env, jobject obj) {
  return 10;
}


All I'm doing is including the header file that we automatically generated in the previous step and our native method is returning an integer value of 10. This is a very trivial example, but I want to keep it simple for this tutorial. In a later post, I'll talk about the details of JNIEnv, how to pass C++ object instances around, why the JNI methods are named the way they are, etc.. But that deserves a discussion of its own.

Next, we need to create the shared library that we referenced in our Java application. Depending on which C++ compiler you're using, this may vary a little, but the following command will automatically create the shared library if you're using the cl compiler on Windows.

Here are a few things to keep in mind:

  • The -I flag is just telling the C++ compiler to include those directories as include directories.You'll need to modify those to your Java install directory.
  • Make sure you have write permission to the directory where you're trying to create the DLL. If you don't, you'll get an error. Here, I'm just creating it in the local directory
  • Make sure that the C++ compiler you're using is the same architecture as your JVM (i.e. 32-bit or 64-bit).

cl -I"C:\jdk1.6.0_24\include" -I"C:\jdk1.6.0_24\include\win32" -LD TestApplication.cpp -FeTestApplication.dll


Now, if we run our Java program, we'll see the value from our native method being returned to our Java application and the value of 10 being printed to the console. In a later post, I'll talk more about the details of JNI and look at some examples that involve passing objects to our C++ application.

No comments:

Post a Comment