1. 准备工作
-
在Ubuntu上安装OpenJDK
sudo apt install default—jdk
设置JAVA_HOME环境变量:找到OpenJDK的安装路径(e.g. /usr/lib/jvm/java—11—openjdk—amd64)
sudo vim /etc/profile
在文件末尾添加
export JAVA_HOME=/usr/lib/jvm/java—11—openjdk—amd64 export PATH=$PATH:$JAVA_HOME/bin
检查是否配置成功
echo $JAVA_HOME echo $PATH
2. JNI简介
全称Java Native Interface,主要用于在Java代码中调用非Java代码,以此绕过Java的内存管理和解决Java的性能问题。
3. Getting Started
3.1 JNI with C
先看一下Java调用C的简单示例:
public class HelloJNI { // Save as HelloJNI.java
static {
System.loadLibrary("hello"); // Load native library hello.dll (Windows) or libhello.so (Unixes)
// at runtime
// This library contains a native method called sayHello()
}
// Declare an instance native method sayHello() which receives no parameter and returns void
private native void sayHello();
// Test Driver
public static void main(String[] args) {
new HelloJNI().sayHello(); // Create an instance and invoke the native method
}
}
关键在于System.loadLibrary("hello");这行代码,Java通过这行代码调用动态链接库(Windows中的后缀名是.dll,Unix系OS中是.so),在运行时Java文件时可以通过设置虚拟机参数—Djava.library.path=/path/to/lib将本地库引入。
通过下面的代码来编译Java代码并生成对应的头文件:
javac —h . HelloJNI.java
生成的头文件代码如下:
/* DO NOT EDIT THIS FILE — it is machine generated */
#include <jni.h>
/* Header—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
头文件中定义了一个函数:
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);
依照惯例函数的命名规则是Java_{package_and_classname}_{function_name}(JNI_arguments)。
函数的两个参数的含义分别是:
-
JNIEnv*
: reference to JNI environment, which lets you access all the JNI functions. -
jobject
: reference to "this
" Java object.
extern "C"只被C++编译器识别,用于提示编译器这些函数应当使用C的函数命名协议来编译,C和C++的函数命名协议不同,C++支持重载。
下面的代码是要被调用的C文件:
// Save as "HelloJNI.c"
#include <jni.h> // JNI header provided by JDK
#include <stdio.h> // C Standard IO Header
#include "HelloJNI.h" // Generated
// Implementation of the native method sayHello()
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject thisObj) {
printf("Hello World!\n");
return;
}
jni.h是包含在JDK中的,不同OS平台的存放位置不同。
下面来编译和运行:
$ gcc —fPIC —I"$JAVA_HOME/include" —I"$JAVA_HOME/include/linux" —shared —o libhello.so HelloJNI.c
$ java —Djava.library.path=. HelloJNI
3.2 JNI with C++
将上一个模块中c文件替换成cpp文件:
// Save as "HelloJNI.cpp"
#include <jni.h> // JNI header provided by JDK
#include <iostream> // C++ standard IO header
#include "HelloJNI.h" // Generated
using namespace std;
// Implementation of the native method sayHello()
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject thisObj) {
cout << "Hello World from C++!" << endl;
return;
}
编译:
$ g++ —fPIC —I"$JAVA_HOME/include" —I"$JAVA_HOME/include/linux" —shared —o libhello.so HelloJNI.cpp
运行:
java —Djava.library.path=. HelloJNI
4. JNI 基础
JNI中定义了一下两类数据类型和Java中的类型来对应:
-
基础类型:
jint—int
jbyte—byte
jshort—short
jlong—long
jfloat—float
jdouble—double
jchar—char
jboolean—boolean -
引用类型:
jobject—java.lang.Object
jclass—java.lang.Class
jstring
—java.lang.String
jthrowable
—java.lang.Throwable
jarray
—Java arrayjarray
包括八种基础类型(jintArray,jbyteArray,jshortArray,jlongArray,jfloatArray,jdoubleArray,jcharArrayand
jbooleanArray)和一种引用类型(jobjectArray)
native程序要做的事是:
- 通过上面的JNI类型获取函数参数,这些参数都是从Java层传递过来的。
- 同样,JNI类型和native类型也有对应关系,例如:jstring对应c的string类型,jintArray对应c的int[]类型,jni中的基础类型像jint这种不需要转换,可以直接操作。
- 执行操作。
- 创建一个jni类型的返回对象,然后把返回值复制到这个对象中。
- 返回。
最困难的一步就是jni类型和native类型的转换,jni为此提供了很多的转换接口。
jni本质上是一个c的接口,它并不会真正传递对象。
5. 在Java和native之间传递参数和返回结果
5.1 传递基础类型
jni和java基础类型不需要转换,八种jni基础类型和java基础类型一一对应。
jni和c基础类型需要转换,在"jni.h
" 和"win32\jni_mh.h
"中可以查看类型定义的声明:
// In "win\jni_mh.h" - machine header which is machine dependent
typedef long jint;
typedef __int64 jlong;
typedef signed char jbyte;
// In "jni.h"
typedef unsigned char jboolean;
typedef unsigned short jchar;
typedef short jshort;
typedef float jfloat;
typedef double jdouble;
typedef jint jsize;
需要注意的是:jint对应着c中的long(至少32位),而不是int(可以为16位)。
TestJNIPrimitive.java:
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));
}
}
TestJNIPrimitive.c:
#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;
}
TestJNIPrimitive.cpp:
#include <jni.h>
#include <iostream>
#include "TestJNIPrimitive.h"
using namespace std;
JNIEXPORT jdouble JNICALL Java_TestJNIPrimitive_average
(JNIEnv *env, jobject obj, jint n1, jint n2) {
jdouble result;
cout << "In C++, the numbers are " << n1 << " and " << n2 << endl;
result = ((jdouble)n1 + n2) / 2.0;
// jint is mapped to int, jdouble is mapped to double
return result;
}
5.2 传递String类型
头文件中的函数定义:
JNIEXPORT jstring JNICALL Java_TestJNIString_sayHello(JNIEnv *, jobject, jstring);
在jni和c之间传递string类型比基本类型更加复杂,需要在jni类型jstring和c类型char*之间做转换。
针对这种转换,jni提供了两个函数:
-
jstring—>char*
const char* GetStringUTFChars(JNIEnv*, jstring, jboolean*)
-
char*—>jstring
jstring NewStringUTF(JNIEnv*, char*)
示例代码:
TestJNIString.c
#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 == inCStr) 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);
}
jni支持Unicode (16-bit characters) 和 UTF-8 (encoded in 1-3 bytes) 两种类型的string,UTF-8类似于c中的char array(从本质而言,C语言把字符串字面量作为字符数组来处理。当C语言编译器在程序中遇到长度为n的字符串字面量时,它会为字符串字面量分配长度为n+1的内存空间。这块内存空间将用来存储字符串字面量中的字符,以及一个用来标志字符串末尾的额外字符(空字符)。空字符是一个所有位都为0的字节,因此用转义序列\0来表示,空字符的码值为0),用于C/C++。
// UTF-8 String (encoded to 1-3 byte, backward compatible with 7-bit ASCII)
// Can be mapped to null-terminated char-array C-string
const char * GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy);
// Returns a pointer to an array of bytes representing the string in modified UTF-8 encoding.
void ReleaseStringUTFChars(JNIEnv *env, jstring string, const char *utf);
// Informs the VM that the native code no longer needs access to utf.
jstring NewStringUTF(JNIEnv *env, const char *bytes);
// Constructs a new java.lang.String object from an array of characters in modified UTF-8 encoding.
jsize GetStringUTFLength(JNIEnv *env, jstring string);
// Returns the length in bytes of the modified UTF-8 representation of a string.
void GetStringUTFRegion(JNIEnv *env, jstring str, jsize start, jsize length, char *buf);
// Translates len number of Unicode characters beginning at offset start into modified UTF-8 encoding
// and place the result in the given buffer buf.
// Unicode Strings (16-bit character)
const jchar * GetStringChars(JNIEnv *env, jstring string, jboolean *isCopy);
// Returns a pointer to the array of Unicode characters
void ReleaseStringChars(JNIEnv *env, jstring string, const jchar *chars);
// Informs the VM that the native code no longer needs access to chars.
jstring NewString(JNIEnv *env, const jchar *unicodeChars, jsize length);
// Constructs a new java.lang.String object from an array of Unicode characters.
jsize GetStringLength(JNIEnv *env, jstring string);
// Returns the length (the count of Unicode characters) of a Java string.
void GetStringRegion(JNIEnv *env, jstring str, jsize start, jsize length, jchar *buf);
// Copies len number of Unicode characters beginning at offset start to the given buffer buf
函数GetStringUTFChars()用于从一个java的jstring类型生成c的char*类型:
// Returns a pointer to an array of bytes representing the string in modified UTF-8 encoding.
const char * GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy);
其中的第三个参数(类型为jboolean*), isCopy的值为JNI_TRUE的时候说明函数的返回值是原始的String实例的复制,为JNI_FALSE的时候则是指向原始实例的指针(这种情况下native层不可以修改字符串的内容)。一般来说jni优先返回一个指针,其次是返回一个复制实例。但是,我们很少修改基础字符串,所以第三个参数经常传递NULL指针。
ReleaseStringUTFChars()和GetStringUTFChars()必须成对出现,用来释放内存和引用,使对象可以被GC回收。
函数NewStringUTF()通过c的string创建一个新的jstring。
JDK1.2中引入了GetStringUTFRegion()可以用来替代GetStringUTFChars(),isCopy不再需要,因为这个函数直接将jstring复制到了c的char array中。
JDK 1.2中还引入了函数Get/ReleaseStringCritical(),这两个函数成对出现,且中间不能被阻塞。它的功能和GetStringUTFChars()类似,一般返回指针,如果失败的话,会返回一个复制实例。
示例代码:
TestJNIString.cpp
#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());
}
注意c++和c的string函数语法不通,在c++中,我们可以用 "env->
"代替"(env*)->
",此外,在c++函数中我们可以省略JNIEnv*
参数。c++支持string类,也支持c中的char array。
5.3 传递基础类型数组
TestJNIPrimitiveArray.java
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]);
}
}
生成的头文件中的函数定义如下:
JNIEXPORT jdoubleArray JNICALL Java_TestJNIPrimitiveArray_sumAndAverage (JNIEnv *, jobject, jintArray);
在java中,数组是引用类型。有九种数组类型,包括8个基础类型和一个引用类型。jni分别对应地定义了8种基础类型数组,jintArray,
jbyteArray,
jshortArray,
jlongArray,
jfloatArray,
jdoubleArray,
jcharArray,
jbooleanArray,对应java中的8种基础类型:int,
byte,
short,
long,
float,
double,
char,
boolean。jobjectArray对应引用类型数组。
和前面介绍的基础类型类似,需要在java类型和jni类型之间进行转换:
jni的jintArray—>c的jint[]
jint* GetIntArrayElements(JNIEnv *env, jintArray a, jboolean *iscopy)
c的jint[]—>jni的jintArray
Step1 : 分配内存
jintArray NewIntArray(JNIEnv *env, jsize len)
Step2 : 从 jint[]
复制内容到 jintArray
void SetIntArrayRegion(JNIEnv *env, jintArray a, jsize start, jsize len, const jint *buf)
上述的函数有八种,对应8个基础类型。
native代码需要做的事:
- 把jni数组转换成c的数组
- 执行操作
- 把c的数组转换成jni的数组,然后返回
TestJNIPrimitiveArray.c
#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;
}
jni基础类型数组函数
// ArrayType: jintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray, jbooleanArray
// PrimitiveType: int, byte, short, long, float, double, char, boolean
// NativeType: jint, jbyte, jshort, jlong, jfloat, jdouble, jchar, jboolean
NativeType * Get<PrimitiveType>ArrayElements(JNIEnv *env, ArrayType array, jboolean *isCopy);
void Release<PrimitiveType>ArrayElements(JNIEnv *env, ArrayType array, NativeType *elems, jint mode);
void Get<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize length, NativeType *buffer);
void Set<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize length, const NativeType *buffer);
ArrayType New<PrimitiveType>Array(JNIEnv *env, jsize length);
void * GetPrimitiveArrayCritical(JNIEnv *env, jarray array, jboolean *isCopy);
void ReleasePrimitiveArrayCritical(JNIEnv *env, jarray array, void *carray, jint mode);
GET|Release<*PrimitiveType*>ArrayElements()
用于从java的jxxxArray
创建c的array jxxx[]
。
GET|Set<*PrimitiveType*>ArrayRegion()
用于将一个jxxxArray
复制到预分配的c的 jxxx[]
,或者从c的 jxxx[]
复制到jxxxArray
中。
The New<PrimitiveType>Array()
用于给 jxxxArray
分配一个给定的大小,然后可以用Set<*PrimitiveType*>ArrayRegion()
从一个jxxx[]
中获取内容填充 jxxxArray
。
Get|ReleasePrimitiveArrayCritical()
成对出现,中间不允许被阻塞。
6. 获取java类的成员变量/方法和类静态变量/方法
6.1 获取类的成员变量
通过
GetObjectClass()
获取对象的类的引用。-
获取成员ID
GetFieldID()
。需要提供变量名和成员描述。格式如下:"L<fully-qualified-name>;
"。用/
代替.
。例如:String
- "Ljava/lang/String;
"。对于基础类型:
"I"
forint
,"B"
forbyte
,"S"
forshort
,"J"
forlong
,"F"
forfloat
,"D"
fordouble
,"C"
forchar
,"Z"
forboolean
对于数组:
要加一个前缀
"["
,例如Object
array-"[Ljava/lang/Object;
",int
array-"[I"
。 基于 Field ID, 通过
GetObjectField()
orGet<primitive-type>Field()
获取成员变量。使用
SetObjectField()
orSet<primitive-type>Field()
修改成员变量。
示例代码:
TestJNIInstanceVariable.java
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);
}
}
TestJNIInstanceVariable.c
#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);
}
获取成员变量的jni方法:
jclass GetObjectClass(JNIEnv *env, jobject obj);
// Returns the class of an object.
jfieldID GetFieldID(JNIEnv *env, jclass cls, const char *name, const char *sig);
// Returns the field ID for an instance variable of a class.
NativeType Get<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID);
void Set<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID, NativeType value);
// Get/Set the value of an instance variable of an object
// <type> includes each of the eight primitive types plus Object.
6.2 获取类静态变量
获取静态变量和实例变量类似,只是在调用的函数上有所差别,GetStaticFieldID(),
Get|SetStaticObjectField(),
Get|SetStatic<Primitive-type>Field()。
TestJNIStaticVariable.java
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);
}
}
TestJNIStaticVariable.c
#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);
}
获取静态变量的jni函数:
jfieldID GetStaticFieldID(JNIEnv *env, jclass cls, const char *name, const char *sig);
// Returns the field ID for a static variable of a class.
NativeType GetStatic<type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID);
void SetStatic<type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID, NativeType value);
// Get/Set the value of a static variable of a class.
// <type> includes each of the eight primitive types plus Object.
6.3. 调用Java类的成员方法和类静态方法
调用成员方法的步骤:
step1:通过 GetObjectClass()
获取对象的类的引用。
step2:GetMethodID()获取Method ID,提供方法名和签名,签名的格式是 "(parameters)return-type"。可通过javap
utility (Class File Disassembler) with -s
(print signature) and -p
(show private members)这个命令来列出java类的方法签名:
> 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;
.......
step3:基于Method ID,调用Call<Primitive-type>Method()or
CallVoidMethod()or
CallObjectMethod(),返回值类型分别是<Primitive-type>, void and Object,对于有参数的方法要提供参数。
示例代码:
TestJNICallBackMethod.java
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();
}
}
TestJNICallBackMethod.c
#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);
}
如果要调用静态方法,使用GetStaticMethodID(), CallStatic<Primitive-type>Method(),CallStaticVoidMethod() or CallStaticObjectMethod()。
调用成员和静态方法的jni函数:
jmethodID GetMethodID(JNIEnv *env, jclass cls, const char *name, const char *sig);
// Returns the method ID for an instance method of a class or interface.
NativeType Call<type>Method(JNIEnv *env, jobject obj, jmethodID methodID, ...);
NativeType Call<type>MethodA(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args);
NativeType Call<type>MethodV(JNIEnv *env, jobject obj, jmethodID methodID, va_list args);
// Invoke an instance method of the object.
// The <type> includes each of the eight primitive and Object.
jmethodID GetStaticMethodID(JNIEnv *env, jclass cls, const char *name, const char *sig);
// Returns the method ID for an instance method of a class or interface.
NativeType CallStatic<type>Method(JNIEnv *env, jclass clazz, jmethodID methodID, ...);
NativeType CallStatic<type>MethodA(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args);
NativeType CallStatic<type>MethodV(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args);
// Invoke an instance method of the object.
// The <type> includes each of the eight primitive and Object.
6.4. 调用父类的实例方法
CallNonvirtual<Type>Method(),类似于java中的super.methodName()。
具体步骤:
step1:GetMethodID()
step2:CallNonvirtual<Type>Method()
jni函数如下:
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);
7. 创建java对象和对象数组
在native中调用NewObject()and
newObjectArray(),构建jobjectand
jobjectArray,然后返回到java中。
7.1 在native中调用java的构造方法创建java对象
调用构造方法和调用实例方法类似:
step1: 获取Method ID method name:"<init>
" return-type:"V
"
step2: 调用NewObject()创建java对象
示例代码:
TestJavaConstructor.java
public class TestJNIConstructor {
static {
System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
}
// Native method that calls back the constructor and return the constructed object.
// Return an Integer object with the given int.
private native Integer getIntegerObject(int number);
public static void main(String args[]) {
TestJNIConstructor obj = new TestJNIConstructor();
System.out.println("In Java, the number is :" + obj.getIntegerObject(9999));
}
}
TestJavaConstructor.c
#include <jni.h>
#include <stdio.h>
#include "TestJNIConstructor.h"
JNIEXPORT jobject JNICALL Java_TestJNIConstructor_getIntegerObject
(JNIEnv *env, jobject thisObj, jint number) {
// Get a class reference for java.lang.Integer
jclass cls = (*env)->FindClass(env, "java/lang/Integer");
// Get the Method ID of the constructor which takes an int
jmethodID midInit = (*env)->GetMethodID(env, cls, "<init>", "(I)V");
if (NULL == midInit) return NULL;
// Call back constructor to allocate a new instance, with an int argument
jobject newObj = (*env)->NewObject(env, cls, midInit, number);
// Try running the toString() on this newly create object
jmethodID midToString = (*env)->GetMethodID(env, cls, "toString", "()Ljava/lang/String;");
if (NULL == midToString) return NULL;
jstring resultStr = (*env)->CallObjectMethod(env, newObj, midToString);
const char *resultCStr = (*env)->GetStringUTFChars(env, resultStr, NULL);
printf("In C: the number is %s\n", resultCStr);
//May need to call releaseStringUTFChars() before return
return newObj;
}
用于创建对象的jni函数:
jclass FindClass(JNIEnv *env, const char *name);
jobject NewObject(JNIEnv *env, jclass cls, jmethodID methodID, ...);
jobject NewObjectA(JNIEnv *env, jclass cls, jmethodID methodID, const jvalue *args);
jobject NewObjectV(JNIEnv *env, jclass cls, jmethodID methodID, va_list args);
// Constructs a new Java object. The method ID indicates which constructor method to invoke
jobject AllocObject(JNIEnv *env, jclass cls);
// Allocates a new Java object without invoking any of the constructors for the object.
7.2 对象数组
不同于基础类型的数组可以批量处理所有元素,对象数组需要使用Get|SetObjectArrayElement()处理每个元素。
示例代码:创建一个Integer数组,计算总和和平均值,返回Double数组。
TestJNIObjectArray.java
import java.util.ArrayList;
public class TestJNIObjectArray {
static {
System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
}
// Native method that receives an Integer[] and
// returns a Double[2] with [0] as sum and [1] as average
private native Double[] sumAndAverage(Integer[] numbers);
public static void main(String args[]) {
Integer[] numbers = {11, 22, 32}; // auto-box
Double[] results = new TestJNIObjectArray().sumAndAverage(numbers);
System.out.println("In Java, the sum is " + results[0]); // auto-unbox
System.out.println("In Java, the average is " + results[1]);
}
}
TestJNIObjectArray.c
#include <jni.h>
#include <stdio.h>
#include "TestJNIObjectArray.h"
JNIEXPORT jobjectArray JNICALL Java_TestJNIObjectArray_sumAndAverage
(JNIEnv *env, jobject thisObj, jobjectArray inJNIArray) {
// Get a class reference for java.lang.Integer
jclass classInteger = (*env)->FindClass(env, "java/lang/Integer");
// Use Integer.intValue() to retrieve the int
jmethodID midIntValue = (*env)->GetMethodID(env, classInteger, "intValue", "()I");
if (NULL == midIntValue) return NULL;
// Get the value of each Integer object in the array
jsize length = (*env)->GetArrayLength(env, inJNIArray);
jint sum = 0;
int i;
for (i = 0; i < length; i++) {
jobject objInteger = (*env)->GetObjectArrayElement(env, inJNIArray, i);
if (NULL == objInteger) return NULL;
jint value = (*env)->CallIntMethod(env, objInteger, midIntValue);
sum += value;
}
double average = (double)sum / length;
printf("In C, the sum is %d\n", sum);
printf("In C, the average is %f\n", average);
// Get a class reference for java.lang.Double
jclass classDouble = (*env)->FindClass(env, "java/lang/Double");
// Allocate a jobjectArray of 2 java.lang.Double
jobjectArray outJNIArray = (*env)->NewObjectArray(env, 2, classDouble, NULL);
// Construct 2 Double objects by calling the constructor
jmethodID midDoubleInit = (*env)->GetMethodID(env, classDouble, "<init>", "(D)V");
if (NULL == midDoubleInit) return NULL;
jobject objSum = (*env)->NewObject(env, classDouble, midDoubleInit, (double)sum);
jobject objAve = (*env)->NewObject(env, classDouble, midDoubleInit, average);
// Set to the jobjectArray
(*env)->SetObjectArrayElement(env, outJNIArray, 0, objSum);
(*env)->SetObjectArrayElement(env, outJNIArray, 1, objAve);
return outJNIArray;
}
jni中用于创建和操作对象数组的函数:
jobjectArray NewObjectArray(JNIEnv *env, jsize length, jclass elementClass, jobject initialElement);
// Constructs a new array holding objects in class elementClass.
// All elements are initially set to initialElement.
jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index);
// Returns an element of an Object array.
void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject value);
// Sets an element of an Object array.
8. jni中的局部引用和全局引用
对于追求效率的程序而言,管理引用非常关键,有助于节省开销,提高可重用性。
jni把对象引用(对于jobject的引用)分为两种:局部和全局引用。
- 局部引用在native方法内创建,一旦方法结束就被释放。它的生命周期和方法相同。你也可以调用DeleteLocalRef()主动释放,使对象可以被回收。对象都是作为局部引用被传递给native方法的。所有的jni函数返回的对象都是局部引用,通过CallStaticObjectMethod()获取的jobject也是局部引用。
- 全局引用会一直存在知道通过DeleteGlobalRef()被主动释放,可以用一个局部引用通过NewGlobalRef()来创建全局引用。
示例代码:在java中定义两个native方法,都用于创建Integer对象。
public class TestJNIReference {
static {
System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
}
// A native method that returns a java.lang.Integer with the given int.
private native Integer getIntegerObject(int number);
// Another native method that also returns a java.lang.Integer with the given int.
private native Integer anotherGetIntegerObject(int number);
public static void main(String args[]) {
TestJNIReference test = new TestJNIReference();
System.out.println(test.getIntegerObject(1));
System.out.println(test.getIntegerObject(2));
System.out.println(test.anotherGetIntegerObject(11));
System.out.println(test.anotherGetIntegerObject(12));
System.out.println(test.getIntegerObject(3));
System.out.println(test.anotherGetIntegerObject(13));
}
}
在c代码中我们要通过FindClass()获取class java.lang.Integer的引用,然后获取构造方法的method ID。但是我们想复用class ref 和method ID。
下面看一个错误的使用情况:
#include <jni.h>
#include <stdio.h>
#include "TestJNIReference.h"
// Global Reference to the Java class "java.lang.Integer"
static jclass classInteger;
static jmethodID midIntegerInit;
jobject getInteger(JNIEnv *env, jobject thisObj, jint number) {
// Get a class reference for java.lang.Integer if missing
if (NULL == classInteger) {
printf("Find java.lang.Integer\n");
classInteger = (*env)->FindClass(env, "java/lang/Integer");
}
if (NULL == classInteger) return NULL;
// Get the Method ID of the Integer's constructor if missing
if (NULL == midIntegerInit) {
printf("Get Method ID for java.lang.Integer's constructor\n");
midIntegerInit = (*env)->GetMethodID(env, classInteger, "<init>", "(I)V");
}
if (NULL == midIntegerInit) return NULL;
// Call back constructor to allocate a new instance, with an int argument
jobject newObj = (*env)->NewObject(env, classInteger, midIntegerInit, number);
printf("In C, constructed java.lang.Integer with number %d\n", number);
return newObj;
}
JNIEXPORT jobject JNICALL Java_TestJNIReference_getIntegerObject
(JNIEnv *env, jobject thisObj, jint number) {
return getInteger(env, thisObj, number);
}
JNIEXPORT jobject JNICALL Java_TestJNIReference_anotherGetIntegerObject
(JNIEnv *env, jobject thisObj, jint number) {
return getInteger(env, thisObj, number);
}
在第二次调用时,java.lang.Integer的引用失效了(并且不是NULL),这是因为FindClass()返回的是一个局部引用,方法一旦结束就失效了。
为了解决这个问题,我们需要创建一个全局引用:
// Get a class reference for java.lang.Integer if missing
if (NULL == classInteger) {
printf("Find java.lang.Integer\n");
// FindClass returns a local reference
jclass classIntegerLocal = (*env)->FindClass(env, "java/lang/Integer");
// Create a global reference from the local reference
classInteger = (*env)->NewGlobalRef(env, classIntegerLocal);
// No longer need the local reference, free it!
(*env)->DeleteLocalRef(env, classIntegerLocal);
}
需要注意的是jmethodID和jfieldID不是jobject,无法创建全局引用。
9. jni的常见错误
错误信息:SEVERE: java.lang.UnsatisfiedLinkError: no xxx in java.library.path
原因:使用了第三方的本地库
结局方法:将本地库的路径添加到"java.library.path",使用如下命令(以JOGL为例):
> java -Djava.library.path=d:\bin\jogl2.0\lib myjoglapp
源码地址:关注下面的公众号回复“JNI一文详解”可获得源码地址。
微信公众号 长夜西风