参考网址: Java Native Interface (JNI)

一、 JNI环境搭建:

可以参考Android NDK开发详解Android 5.0 开发环境搭建进行如下操作:

二、 JNI简单实例:

public class HelloJNI {
   static {
      System.loadLibrary("hello"); // Load native library at runtime
                                   // hello.dll (Windows) or libhello.so (Unixes)
   }
 
   // Declare a native method sayHello() that receives nothing and returns void
   private native void sayHello();
 
   // Test Driver
   public static void main(String[] args) {
      new HelloJNI().sayHello();  // invoke the native method
   }
}

Compile the "HelloJNI.java" into "HelloJNI.class".

> javac HelloJNI.java

Run javah utility on the class file to create a header file for C/C++ programs:

  > javah HelloJNI

The output is HelloJNI.h as follows:

  /* DO NOT EDIT THIS FILE - it is machine generated */
  #include <jni.h>
  /* Header for class HelloJNI */
   
  #ifndef _Included_HelloJNI
  #define _Included_HelloJNI
  #ifdef __cplusplus
  extern "C" {
  #endif
  /*
   * Class:     HelloJNI
   * Method:    sayHello
   * Signature: ()V
   */
  JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);
   
  #ifdef __cplusplus
  }
  #endif
  #endif

The arguments:

  • JNIEnv*: reference to JNI environment, which lets you access all the JNI fucntions.
  • jobject: reference to "this" Java object.

Save the C program as "HelloJNI.c".

#include <jni.h>
#include <stdio.h>
#include "HelloJNI.h"
 
// Implementation of native method sayHello() of HelloJNI class
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject thisObj) {
   printf("Hello World!\n");
   return;
}

Compile the C program - this depends on the C compiler you used.

For MinGW GCC in Windows

> set JAVA_HOME=C:\Program Files\Java\jdk1.7.0_{xx}
      // Define and Set environment variable JAVA_HOME to JDK installed directory
      // I recommend that you set JAVA_HOME permanently, via "Control Panel" ⇒ "System" ⇒ "Environment Variables"
> echo %JAVA_HOME%
      // In Windows, you can refer a environment variable by adding % prefix and suffix 
> gcc -Wl,--add-stdcall-alias -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -shared -o hello.dll HelloJNI.c
      // Compile HellJNI.c into shared library hello.dll

You can also compile and link in two steps:

// Compile-only with -c flag. Output is HElloJNI.o
> gcc -c -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" HelloJNI.c
 
// Link into shared library "hello.dll"
> gcc -Wl,--add-stdcall-alias -shared -o hello.dll HelloJNI.o

For Cygwin GCC in Windows

For gcc-3, include option -mno-cygwin to build DLL files which are not dependent upon the Cygwin DLL.

> gcc-3 -D __int64="long long" -mno-cygwin -Wl,--add-stdcall-alias 
  -I"<JAVA_HOME>\include" -I"<JAVA_HOME>\include\win32" -shared -o hello.dll HelloJNI.c

For gcc-4: I still cannot find the correct compiler option (-mno-cygwin is not supported). The Java program hangs!

> java HelloJNI
or
> java -Djava.library.path=. HelloJNI

三、 JNI Basics

The native functions receives argument in the above JNI types and returns a value in the JNI type (such as jstring, jintArray). However, native functions operate on their own native types (such as C-string, C's int[]). Hence, there is a need to convert (or transform) between JNI types and the native types.

四、 Passing Arguments and Result between Java & Native Programs

4.1 Passing Primitives

Passing Java primitives is straight forward. A jxxx type is defined in the native system, i.e,. jint, jbyte, jshort, jlong, jfloat, jdouble, jchar and jboolean for each of the Java's primitives int, byte, short, long, float, double, char and boolean, respectively.

public class TestJNIPrimitive {
   static {
      System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
   }
 
   // Declare a native method average() that receives two ints and return a double containing the average
   private native double average(int n1, int n2);
 
   // Test Driver
   public static void main(String args[]) {
      System.out.println("In Java, the average is " + new TestJNIPrimitive().average(3, 2));
   }
}
#include <jni.h>
#include <stdio.h>
#include "TestJNIPrimitive.h"
 
JNIEXPORT jdouble JNICALL Java_TestJNIPrimitive_average
          (JNIEnv *env, jobject thisObj, jint n1, jint n2) {
   jdouble result;
   printf("In C, the numbers are %d and %d\n", n1, n2);
   result = ((jdouble)n1 + n2) / 2.0;
   // jint is mapped to int, jdouble is mapped to double
   return result;
}

4.2 Passing Strings

public class TestJNIString {
   static {
      System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
   }
   // Native method that receives a Java String and return a Java String
   private native String sayHello(String msg);
 
   public static void main(String args[]) {
      String result = new TestJNIString().sayHello("Hello from Java");
      System.out.println("In Java, the returned string is: " + result);
   }
}
#include <jni.h>
#include <stdio.h>
#include "TestJNIString.h"
 
JNIEXPORT jstring JNICALL Java_TestJNIString_sayHello(JNIEnv *env, jobject thisObj, jstring inJNIStr) {
   // Step 1: Convert the JNI String (jstring) into C-String (char*)
   const char *inCStr = (*env)->GetStringUTFChars(env, inJNIStr, NULL);
   if (NULL == inCSt) return NULL;
 
   // Step 2: Perform its intended operations
   printf("In C, the received string is: %s\n", inCStr);
   (*env)->ReleaseStringUTFChars(env, inJNIStr, inCStr);  // release resources
 
   // Prompt user for a C-string
   char outCStr[128];
   printf("Enter a String: ");
   scanf("%s", outCStr);    // not more than 127 characters
 
   // Step 3: Convert the C-string (char*) into JNI String (jstring) and return
   return (*env)->NewStringUTF(env, outCStr);
}
#include <jni.h>
#include <iostream>
#include <string>
#include "TestJNIString.h"
using namespace std;
 
JNIEXPORT jstring JNICALL Java_TestJNIString_sayHello(JNIEnv *env, jobject thisObj, jstring inJNIStr) {
   // Step 1: Convert the JNI String (jstring) into C-String (char*)
   const char *inCStr = env->GetStringUTFChars(inJNIStr, NULL);
   if (NULL == inCStr) return NULL;
 
   // Step 2: Perform its intended operations
   cout << "In C++, the received string is: " << inCStr << endl;
   env->ReleaseStringUTFChars(inJNIStr, inCStr);  // release resources
 
   // Prompt user for a C++ string
   string outCppStr;
   cout << "Enter a String: ";
   cin >> outCppStr;
 
   // Step 3: Convert the C++ string to C-string, then to JNI String (jstring) and return
   return env->NewStringUTF(outCppStr.c_str());
}

4.3 Passing Array of Primitives

public class TestJNIPrimitiveArray {
   static {
      System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
   }
 
   // Declare a native method sumAndAverage() that receives an int[] and
   //  return a double[2] array with [0] as sum and [1] as average
   private native double[] sumAndAverage(int[] numbers);
 
   // Test Driver
   public static void main(String args[]) {
      int[] numbers = {22, 33, 33};
      double[] results = new TestJNIPrimitiveArray().sumAndAverage(numbers);
      System.out.println("In Java, the sum is " + results[0]);
      System.out.println("In Java, the average is " + results[1]);
   }
}
#include <jni.h>
#include <stdio.h>
#include "TestJNIPrimitiveArray.h"
 
JNIEXPORT jdoubleArray JNICALL Java_TestJNIPrimitiveArray_sumAndAverage
          (JNIEnv *env, jobject thisObj, jintArray inJNIArray) {
   // Step 1: Convert the incoming JNI jintarray to C's jint[]
   jint *inCArray = (*env)->GetIntArrayElements(env, inJNIArray, NULL);
   if (NULL == inCArray) return NULL;
   jsize length = (*env)->GetArrayLength(env, inJNIArray);
 
   // Step 2: Perform its intended operations
   jint sum = 0;
   int i;
   for (i = 0; i < length; i++) {
      sum += inCArray[i];
   }
   jdouble average = (jdouble)sum / length;
   (*env)->ReleaseIntArrayElements(env, inJNIArray, inCArray, 0); // release resources
 
   jdouble outCArray[] = {sum, average};
 
   // Step 3: Convert the C's Native jdouble[] to JNI jdoublearray, and return
   jdoubleArray outJNIArray = (*env)->NewDoubleArray(env, 2);  // allocate
   if (NULL == outJNIArray) return NULL;
   (*env)->SetDoubleArrayRegion(env, outJNIArray, 0 , 2, outCArray);  // copy
   return outJNIArray;
}

五、 Accessing Object's Variables and Calling Back Methods

5.1 Accessing Object's Instance Variables

public class TestJNIInstanceVariable {
   static {
      System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
   }
 
   // Instance variables
   private int number = 88;
   private String message = "Hello from Java";
 
   // Declare a native method that modifies the instance variables
   private native void modifyInstanceVariable();
 
   // Test Driver   
   public static void main(String args[]) {
      TestJNIInstanceVariable test = new TestJNIInstanceVariable();
      test.modifyInstanceVariable();
      System.out.println("In Java, int is " + test.number);
      System.out.println("In Java, String is " + test.message);
   }
}
#include <jni.h>
#include <stdio.h>
#include "TestJNIInstanceVariable.h"
 
JNIEXPORT void JNICALL Java_TestJNIInstanceVariable_modifyInstanceVariable
          (JNIEnv *env, jobject thisObj) {
   // Get a reference to this object's class
   jclass thisClass = (*env)->GetObjectClass(env, thisObj);
 
   // int
   // Get the Field ID of the instance variables "number"
   jfieldID fidNumber = (*env)->GetFieldID(env, thisClass, "number", "I");
   if (NULL == fidNumber) return;
 
   // Get the int given the Field ID
   jint number = (*env)->GetIntField(env, thisObj, fidNumber);
   printf("In C, the int is %d\n", number);
 
   // Change the variable
   number = 99;
   (*env)->SetIntField(env, thisObj, fidNumber, number);
 
   // Get the Field ID of the instance variables "message"
   jfieldID fidMessage = (*env)->GetFieldID(env, thisClass, "message", "Ljava/lang/String;");
   if (NULL == fidMessage) return;
 
   // String
   // Get the object given the Field ID
   jstring message = (*env)->GetObjectField(env, thisObj, fidMessage);
 
   // Create a C-string with the JNI String
   const char *cStr = (*env)->GetStringUTFChars(env, message, NULL);
   if (NULL == cStr) return;
 
   printf("In C, the string is %s\n", cStr);
   (*env)->ReleaseStringUTFChars(env, message, cStr);
 
   // Create a new C-string and assign to the JNI string
   message = (*env)->NewStringUTF(env, "Hello from C");
   if (NULL == message) return;
 
   // modify the instance variables
   (*env)->SetObjectField(env, thisObj, fidMessage, message);
}

Get the Field ID of the instance variable to be accessed via GetFieldID() from the class reference. You need to provide the variable name and its field descriptor (or signature). For a Java class, the field descriptor is in the form of "L<fully-qualified-name>;", with dot replaced by forward slash (/), e.g.,, the class descriptor for String is "Ljava/lang/String;". For primitives, use "I" for int, "B" for byte, "S" for short, "J" for long, "F" for float, "D" for double, "C" for char, and "Z" for boolean. For arrays, include a prefix "[", e.g., "[Ljava/lang/Object;" for an array of Object; "[I" for an array of int.

5.2 Accessing Class' Static Variables

public class TestJNIStaticVariable {
   static {
      System.loadLibrary("myjni"); // nyjni.dll (Windows) or libmyjni.so (Unixes)
   }
 
   // Static variables
   private static double number = 55.66;
 
   // Declare a native method that modifies the static variable
   private native void modifyStaticVariable();
 
   // Test Driver
   public static void main(String args[]) {
      TestJNIStaticVariable test = new TestJNIStaticVariable();
      test.modifyStaticVariable();
      System.out.println("In Java, the double is " + number);
   }
}
#include <jni.h>
#include <stdio.h>
#include "TestJNIStaticVariable.h"
 
JNIEXPORT void JNICALL Java_TestJNIStaticVariable_modifyStaticVariable
          (JNIEnv *env, jobject thisObj) {
   // Get a reference to this object's class
   jclass cls = (*env)->GetObjectClass(env, thisObj);
 
   // Read the int static variable and modify its value
   jfieldID fidNumber = (*env)->GetStaticFieldID(env, cls, "number", "D");
   if (NULL == fidNumber) return;
   jdouble number = (*env)->GetStaticDoubleField(env, cls, fidNumber);
   printf("In C, the double is %f\n", number);
   number = 77.88;
   (*env)->SetStaticDoubleField(env, cls, fidNumber, number);
}

5.3 Callback Instance Methods and Static Methods

public class TestJNICallBackMethod {
   static {
      System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
   }
 
   // Declare a native method that calls back the Java methods below
   private native void nativeMethod();
 
   // To be called back by the native code
   private void callback() {
      System.out.println("In Java");
   }
 
   private void callback(String message) {
      System.out.println("In Java with " + message);
   }
 
   private double callbackAverage(int n1, int n2) {
      return ((double)n1 + n2) / 2.0;
   }
 
   // Static method to be called back
   private static String callbackStatic() {
      return "From static Java method";
   }

   // Test Driver 
   public static void main(String args[]) {
      new TestJNICallBackMethod().nativeMethod();
   }
}
#include <jni.h>
#include <stdio.h>
#include "TestJNICallBackMethod.h"
 
JNIEXPORT void JNICALL Java_TestJNICallBackMethod_nativeMethod
          (JNIEnv *env, jobject thisObj) {
 
   // Get a class reference for this object
   jclass thisClass = (*env)->GetObjectClass(env, thisObj);
 
   // Get the Method ID for method "callback", which takes no arg and return void
   jmethodID midCallBack = (*env)->GetMethodID(env, thisClass, "callback", "()V");
   if (NULL == midCallBack) return;
   printf("In C, call back Java's callback()\n");
   // Call back the method (which returns void), baed on the Method ID
   (*env)->CallVoidMethod(env, thisObj, midCallBack);
 
   jmethodID midCallBackStr = (*env)->GetMethodID(env, thisClass,
                               "callback", "(Ljava/lang/String;)V");
   if (NULL == midCallBackStr) return;
   printf("In C, call back Java's called(String)\n");
   jstring message = (*env)->NewStringUTF(env, "Hello from C");
   (*env)->CallVoidMethod(env, thisObj, midCallBackStr, message);
 
   jmethodID midCallBackAverage = (*env)->GetMethodID(env, thisClass,
                                  "callbackAverage", "(II)D");
   if (NULL == midCallBackAverage) return;
   jdouble average = (*env)->CallDoubleMethod(env, thisObj, midCallBackAverage, 2, 3);
   printf("In C, the average is %f\n", average);
 
   jmethodID midCallBackStatic = (*env)->GetStaticMethodID(env, thisClass,
                                 "callbackStatic", "()Ljava/lang/String;");
   if (NULL == midCallBackStatic) return;
   jstring resultJNIStr = (*env)->CallStaticObjectMethod(env, thisClass, midCallBackStatic);
   const char *resultCStr = (*env)->GetStringUTFChars(env, resultJNIStr, NULL);
   if (NULL == resultCStr) return;
   printf("In C, the returned string is %s\n", resultCStr);
   (*env)->ReleaseStringUTFChars(env, resultJNIStr, resultCStr);
}

You can list the method signature for a Java program via javap utility (Class File Disassembler) with -s (print signature) and -p (show private members):

> javap --help
> javap -s -p TestJNICallBackMethod
  .......
  private void callback();
    Signature: ()V
 
  private void callback(java.lang.String);
    Signature: (Ljava/lang/String;)V
 
  private double callbackAverage(int, int);
    Signature: (II)D
 
  private static java.lang.String callbackStatic();
    Signature: ()Ljava/lang/String;
  .......

5.4 Callback Overridden Superclass' Instance Method

The JNI function for calling the overridden superclass' instance method are:

NativeType CallNonvirtual<type>Method(JNIEnv *env, jobject obj, jclass cls, jmethodID methodID, ...);
NativeType CallNonvirtual<type>MethodA(JNIEnv *env, jobject obj, jclass cls, jmethodID methodID, const jvalue *args);
NativeType CallNonvirtual<type>MethodV(JNIEnv *env, jobject obj, jclass cls, jmethodID methodID, va_list args);

六、 Creating Objects and Object Arrays

see the website "Java Native Interface (JNI)" for more details.

七、 Local and Global References

util.h

#include <jni.h>

extern JavaVM* android_jvm;

#ifdef __cplusplus

// For cpp.
#define ATTACH_JVM(env) \
    JNIEnv *e;\
    int __env = android_jvm->GetEnv((void **)&e, JNI_VERSION_1_6); \
    android_jvm->AttachCurrentThread(&env,NULL);

#define DETACH_JVM(env) \
    if( __env == JNI_EDETACHED) {\
        android_jvm->DetachCurrentThread();\
    }

#else

// For c
#define ATTACH_JVM(env) \
    JNIEnv *e;\
    int __env = (*android_jvm)->GetEnv(android_jvm, (void **)&e, JNI_VERSION_1_6);\
    (*android_jvm)->AttachCurrentThread(android_jvm, &env, NULL);

#define DETACH_JVM() \
    if (__env == JNI_EDETACHED) {\
        (*android_jvm)->DetachCurrentThread(android_jvm);\
    }

#endif

util.cpp

JavaVM* android_jvm;
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    android_jvm = vm;
    return JNI_VERSION_1_6;
}

.cpp

JNIEnv* env;
ATTACH_JVM(env);
env->CallVoidMethod(obj, jmethodID); // see note
DETACH_JVM();

Note:

obj should be global references.

八、 How to access arrays within an object with JNI?

参考网址how-to-access-arrays-within-an-object-with-jni

// Get the class
jclass mvclass = env->GetObjectClass( *cls );
// Get method ID for method getSomeDoubleArray that returns a double array
jmethodID mid = env->GetMethodID(mvclass, "getSomeDoubleArray", "()[D");
// Call the method, returns JObject (because Array is instance of Object)
jobject mvdata = env->CallObjectMethod(*base, mid);
// Cast it to a jdoublearray
jdoubleArray * arr = reinterpret_cast<jdoubleArray*>(&mvdata)
// Get the elements (you probably have to fetch the length of the array as well
double * data = env->GetDoubleArrayElements(*arr, NULL);
// Don't forget to release it 
env->ReleaseDoubleArrayElements(*arr, data, 0);

九、 How to access an object defined in java with JNI?

参考网址: Android NDK实现 C 代码中操作 Java 代码的类对象

java code:

package com.example.utility;

public class Information {
	public int data;
}

jni code:

jclass infoClass = env->FindClass("com/example/utility/Information");

jmethodID infoConsMethod = env->GetMethodID(infoClass, "<init>", "()V"); // Information construction
jobject infoObj = env->NewObject(infoClass, infoConsMethod);

jfieldID field = env->GetFieldID(infoClass, "data", "I");
env->SetIntField(infoObj, field, 10);

十、 How to access java.util.List within an object defined in java with JNI?

参考网址:

java code:

public class Information {
	public List<xxx> xxxlist = new ArrayList<xxx>();
}

jni code:

jclass class = env->GetObjectClass(obj); //obj type: Information

jfieldID field = env->GetFieldID(class, "xxxlist", "Ljava/util/List;");
jobject listObj = env->GetObjectField(obj, field);

jclass listClass = env->FindClass("java/util/List");
jmethodID listAdd = env->GetMethodID(listClass, "add", "(Ljava/lang/Object;)Z"); // boolean add(java.lang.Object);

jclass elemClass = env->FindClass("xxx/xxx/xxx");
jmethodID elemConsMethod = env->GetMethodID(elemClass, "<init>", "()V"); // xxx.xxx.xxx construction

for (int i = 0; i < 10; i++) {
	jobject elemObj = env->NewObject(elemClass, elemConsMethod);
	env->CallBooleanMethod(listObj, listAdd, elemObj);
}

===================================================================

相关网址

Java Native Interface Wiki