1. 为啥使用 jni
jni 能够允许 Java 代码与 c/c++ 编写的应用程序和库进行交互,是将 Java 层(上层) 与 c/c++层(底层)的有机联系起来的桥梁
- 运行速度快
- 硬件控制,硬件代码通常使用 c 语言编写
- 复用现有的优秀的 c/c++ 代码 (如 opencv ,ffmpeg)
2. 在 Java 中调用 c/c++ 库函数
- 第一步: 编写 Java 代码 (native本地方法)
- 第二步: 编译 Java 代码 (javac命令)
- 第三步: 生成 c/c++ 语言头文件 (javah命令)
- 第四步: 编写 c/c++ 代码(实现c/c++ 语言头文件的方法)
- 第五步: 生成 c/c++ 共享库 (编译生成 .so)
- 第六步: 运行 Java 代码 (java命令)
- 编写 Java 代码
public class HelloJNI {
static {
System.loadLibrary("HelloJNI"); // Load native library at runtime
}
// 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
}
}
- 编译 Java 代码 生成 HelloJNI.class 文件
javac HelloJNI.java
- 编译生成的头文件 生成 HelloJNI.h 文件
javah HelloJNI
生成的头文件
/* 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
- 实现 HelloJNI.h 文件的方法 HelloJNI.cpp
#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;
}
- 编译生成动态库
- HelloJNI.dll (Windows)
- libHelloJNI.jnilib(Mac)
- libHelloJNI .so (Unixes)
gcc -shared -I /Library/Java/JavaVirtualMachines/jdk1.8.0_102.jdk/Contents/Home/include/ -fPIC HelloJNI.cpp -o libHelloJNI.jnilib
6. 运行Java代码
java HelloJNI
3. c/c++ 库函数调用 Java 代码
- 编写 Java 代码
JniTest.java
public class JniTest {
private int intField;
public JniTest(int num) {
intField = num;
System.out.println("[java] 调用 JniTest 对象的构造方法: intField = " + intField);
}
public int callByNative(int num) {
System.out.println("[Java] JniTest 对象的 callByNative(" + num + ") 调用");
return num;
}
public void callTest() {
System.out.println("[java] JniTest 对象的 callTest 方法调用 : intField = " + intField);
}
}
JniFuncMain.java
public class JniFuncMain {
private static int staticIntField = 300;
static {
System.loadLibrary("jnifunc");
}
public static native JniTest createJniObject();
public static void main(String argv[]) {
System.out.println("[java] createJniObject() 调用本地方法");
JniTest jniObj = createJniObject();
jniObj.callTest();
}
}
- 编译 Java 代码 生成 JniTest.class JniFuncMain.class 文件
javac JniTest.java JniFuncMain.java
3. 编译生成的头文件 生成 JniFuncMain.h 文件
javah JniFuncMain
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class JniFuncMain */
#ifndef _Included_JniFuncMain
#define _Included_JniFuncMain
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: JniFuncMain
* Method: createJniObject
* Signature: ()LJniTest;
*/
JNIEXPORT jobject JNICALL Java_JniFuncMain_createJniObject
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
- 实现 JniFuncMain.h 文件的方法 JniFuncMain.cpp
#include <jni.h>
#include <stdio.h>
#include <JniFuncMain.h>
JNIEXPORT jobject JNICALL Java_JniFuncMain_createJniObject
(JNIEnv * env, jclass clazz){
jclass targetClass;
jmethodID mid;
jobject newObject;
jstring helloStr;
jfieldID fid;
jint staticIntField;
jint result;
// 获取 JniFuncMain 类的 staticIntFiled 变量值
fid = env->GetStaticFieldID(clazz,"staticIntField","I");
staticIntField = env->GetStaticIntField(clazz,fid);
printf("[CPP]获取 JniFuncMain 类的 staticIntFiled 值\n");
printf("JniFuncMain.staticIntField = %d\n",staticIntField);
// 查找生成对象的类
targetClass = env->FindClass("JniTest");
// 查找构造函数
mid = env->GetMethodID(targetClass,"<init>","(I)V");
// 生成 JniTest 对象(返回对象的引用)
printf("[CPP] JniTest 对象生成\n");
newObject = env->NewObject(targetClass,mid,100);
// 调用对象的方法
mid = env->GetMethodID(targetClass,"callByNative","(I)I");
result = env->CallIntMethod(newObject,mid,200);
// 设置 JniObject 对象的 intField 值
fid = env->GetFieldID(targetClass,"intField","I");
printf("[CPP] 设置 JniTest 对象的 intField 值为 200 \n");
env->SetIntField(newObject,fid,result);
// 返回对象引用
return newObject;
}
- 编译生成动态库 libjnifunc.jnilib
gcc -shared -I /Library/Java/JavaVirtualMachines/jdk1.8.0_102.jdk/Contents/Home/include/ -fPIC JniFuncMain.cpp -o libjnifunc.jnilib
6. 运行Java代码
java JniFuncMain
4. jni 的基本概念
- Java 中使用 System.loadLibrary("...") 把动态库加载进去,不同系统动态库的命名不一样
- c/c++ 头文件分析
#define JNIEXPORT __attribute__ ((visibility ("default")))
#define JNICALL
typedef _JNIEnv JNIEnv;
struct _JNIEnv {...}
-
JNIEXPORT , 用于定义与平台相关的宏,是用来做本地函数出现在编译的二进制(* .so文件)的动态表。它们可以(在这里更多信息 )设置为“隐藏”或“默认”。给编译器用的,一般不用管.
-
JNICALL 定义为空 编译器用,也不用管
-
JNIEnv 为 c 结构体,实际上代表了Java环境,通过这个JNIEnv* 指针,就可以对Java端的代码进行操作,例如 创建Java类中的对象,调用Java对象的方法,获取Java对象中的属性等等。JNIEnv的指针会被 JNI 传入到本地方法的实现函数中来对Java端的代码进行操作。
-
jobject 代表调用 native方法sayHello 方法的 Java 对象 (非静态方法会被传入)
-
jclass 代表调用调用该 native 方法 createJniObject 的类 (静态方法会被传入)
-
方法名 java native方法对应 c/c++方法名为 Java_类名_方法名
- 类名: 包含包名
- 方法名: Java中的定义的方法名
-
jfieldID : java 类当中属性的的标识,获取或者赋值java 对象的属性 ,必须先获取 jfieldID
-
jmethodID : Java 类当中方法的标识,调用java方法 ,必须先获取 jmethodID
获取 jfieldID,jmethodID 必须提供在 Java 类当中的名字(属性名,方法名)和签名,签名是为了更好的找到 Java 当中的字段和方法,如果只有方法名的话不行,可能有方法重载
5. 各种数据类型和函数的签名格式
-
基本数据类型
Signature格式 Java Native 占用内存大小(字节) B byte jbyte 1 C char jchar 2 D double jdouble 8 F float jfloat 4 I int jint 4 S short jshort 2 J long jlong 8 Z boolean jboolean 1 V void void -
数组数据类型
数组简称则是在前面添加: [
| Signature格式 | Java | Native |
|---|---|---|
| [B | byte[] | jbyteArray |
| [C | char[] | jcharArray |
| [D | double[] | jdoubleArray |
| [F | float[] | jfloatArray |
| [I | int[] | jintArray |
| [S | short[] | jshortArray |
| [J | long[] | jlongArray |
| [Z | boolean[] | jbooleanArray |
- 复杂数据类型 对象类型简称:L+classname+;
| Signature格式 | Java | Native |
|---|---|---|
| Ljava/lang/String; | String | jstring |
| L+classname +; | 所有对象 | jobject |
| [L+classname +; | Object[] | jobjectArray |
| Ljava.lang.Class; | Class | jclass |
| Ljava.lang.Throwable; | Throwable | jthrowable |
- 函数签名
函数签名格式 : (参数签名)返回类型签名
| Java函数 | 对应签名 |
|---|---|
| void foo() | ()V |
| float foo(int i) | (I)F |
| long foo(int[] i) | ([I)J |
| double foo(Class c) | (Ljava/lang/Class;)D |
| boolean foo(int[] i,String s) | ([ILjava/lang/String;)Z |
| String foo(int i) | (I)Ljava/lang/String; |
5.可使用javap命令获取签名
如果不确定Java 类中字段或者函数的签名格式,可以使用 javap 命令
javap -s -p 类名
// -s表示输出Java签名
// -p输出所有的类属性和方法成员
// -c输出为指令模式
属性和方法的下面的 descriptor 就显示了该签名
6. 常用 jni 函数的使用
所有对 Java 代码的操作: 都是通过 JNIEnv * env 指针完成的
- java 属性操作
- 首先要获取 jfieldID
根据是否静态变量调用不同的方法,但是参数都是类似的
参数:
- clazz: Java 类型
- name : Java 中的属性名
- sig : 属性签名
// 获取对象属性 jfieldID
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig);
// 获取静态属性 jfieldID
jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig);
- 获取java对象属性的值
根据 java 对象属性的类型的不同需要调用不同的方法,但是参数都是类似的
参数:
- obj :要获取那个属性值的对象
- fieldID : java 类当中属性的的标识
jobject GetObjectField(jobject obj, jfieldID fieldID);
jboolean GetBooleanField(jobject obj, jfieldID fieldID);
jbyte GetByteField(jobject obj, jfieldID fieldID);
jchar GetCharField(jobject obj, jfieldID fieldID);
jshort GetShortField(jobject obj, jfieldID fieldID);
jint GetIntField(jobject obj, jfieldID fieldID);
jlong GetLongField(jobject obj, jfieldID fieldID);
jfloat GetFloatField(jobject obj, jfieldID fieldID);
jdouble GetDoubleField(jobject obj, jfieldID fieldID);
- 设置java对象属性的值
根据 java 对象属性的类型的不同需要调用不同的方法,但是参数都是类似的
参数:
- obj : 要设置属性值的对象
- fieldID : java 类当中属性的的标识
- value : 设置的值
void SetObjectField(jobject obj, jfieldID fieldID, jobject value);
void SetBooleanField(jobject obj, jfieldID fieldID, jboolean value);
void SetByteField(jobject obj, jfieldID fieldID, jbyte value);
void SetCharField(jobject obj, jfieldID fieldID, jchar value);
void SetShortField(jobject obj, jfieldID fieldID, jshort value);
void SetIntField(jobject obj, jfieldID fieldID, jint value);
void SetLongField(jobject obj, jfieldID fieldID, jlong value);
void SetFloatField(jobject obj, jfieldID fieldID, jfloat value);
void SetDoubleField(jobject obj, jfieldID fieldID, jdouble value);
- 获取java静态变量的值
根据 java 静态变量的类型的不同需要调用不同的方法,但是参数都是类似的
参数:
- clazz :要获取那个静态变量的类
- fieldID : java 类当中静态变量的的标识
jobject GetStaticObjectField(jclass clazz, jfieldID fieldID);
jboolean GetStaticBooleanField(jclass clazz, jfieldID fieldID);
jbyte GetStaticByteField(jclass clazz, jfieldID fieldID);
jchar GetStaticCharField(jclass clazz, jfieldID fieldID);
jshort GetStaticShortField(jclass clazz, jfieldID fieldID);
jint GetStaticIntField(jclass clazz, jfieldID fieldID);
jlong GetStaticLongField(jclass clazz, jfieldID fieldID);
jfloat GetStaticFloatField(jclass clazz, jfieldID fieldID);
jdouble GetStaticDoubleField(jclass clazz, jfieldID fieldID);
- 设置java静态变量的值
根据 java 静态变量的类型的不同需要调用不同的方法,但是参数都是类似的
参数:
- clazz: 要获取那个静态变量的类
- fieldID : java 类当中静态变量的的标识
- value : 设置的值
void SetStaticObjectField(jclass clazz, jfieldID fieldID, jobject value);
void SetStaticBooleanField(jclass clazz, jfieldID fieldID, jboolean value);
void SetStaticByteField(jclass clazz, jfieldID fieldID, jbyte value);
void SetStaticCharField(jclass clazz, jfieldID fieldID, jchar value);
void SetStaticShortField(jclass clazz, jfieldID fieldID, jshort value);
void SetStaticIntField(jclass clazz, jfieldID fieldID, jint value);
void SetStaticLongField(jclass clazz, jfieldID fieldID, jlong value);
void SetStaticFloatField(jclass clazz, jfieldID fieldID, jfloat value);
void SetStaticDoubleField(jclass clazz, jfieldID fieldID, jdouble value);
- Java 方法调用
- 首先获取方法标识 jmethodID
根据是否静态方法调用不同的方法,但是参数都是类似的
参数:
- clazz: 调用那个Java方法的类型
- name : Java 中的方法名
- sig : 方法签名
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig);
jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig);
- 调用普通方法 根据调用方法返回值不同,和传参格式不同有以下不同的方法
参数:
- obj: 调用那个的方法的对象
- methodID: 方法标识
- ...,arg: 传递的参数
jvalue 为类型联合体
typedef union jvalue {
jboolean z;
jbyte b;
jchar c;
jshort s;
jint i;
jlong j;
jfloat f;
jdouble d;
jobject l;
} jvalue;
jobject CallObjectMethod (jobject obj, jmethodID methodID, ...);
jobject CallObjectMethodV (jobject obj, jmethodID methodID, va_list args);
jobject CallObjectMethodA (jobject obj, jmethodID methodID, jvalue* args);
jboolean CallBooleanMethod (jobject obj, jmethodID methodID, ...);
jboolean CallBooleanMethodV (jobject obj, jmethodID methodID, va_list args);
jboolean CallBooleanMethodA (jobject obj, jmethodID methodID, jvalue* args);
jbyte CallByteMethod (jobject obj, jmethodID methodID, ...);
jbyte CallByteMethodV (jobject obj, jmethodID methodID, va_list args);
jbyte CallByteMethodA (jobject obj, jmethodID methodID, jvalue* args);
jchar CallCharMethod (jobject obj, jmethodID methodID, ...);
jchar CallCharMethodV (jobject obj, jmethodID methodID, va_list args);
jchar CallCharMethodA (jobject obj, jmethodID methodID, jvalue* args);
jshort CallShortMethod (jobject obj, jmethodID methodID, ...);
jshort CallShortMethodV (jobject obj, jmethodID methodID, va_list args);
jshort CallShortMethodA (jobject obj, jmethodID methodID, jvalue* args);
jint CallIntMethod (jobject obj, jmethodID methodID, ...);
jint CallIntMethodV (jobject obj, jmethodID methodID, va_list args);
jint CallIntMethodA (jobject obj, jmethodID methodID, jvalue*args);
jlong CallLongMethod (jobject obj, jmethodID methodID, ...);
jlong CallLongMethodV (jobject obj, jmethodID methodID, va_list args);
jlong CallLongMethodA (jobject obj, jmethodID methodID, jvalue* args);
jfloat CallFloatMethod (jobject obj, jmethodID methodID, ...);
jfloat CallFloatMethodV (jobject obj, jmethodID methodID, va_list args);
jfloat CallFloatMethodA (jobject obj, jmethodID methodID, jvalue* args);
jdouble CallDoubleMethod (jobject obj, jmethodID methodID, ...);
jdouble CallDoubleMethodV (jobject obj, jmethodID methodID, va_list args);
jdouble CallDoubleMethodA (jobject obj, jmethodID methodID, jvalue* args);
void CallVoidMethod (jobject obj, jmethodID methodID, ...);
void CallVoidMethodV (jobject obj, jmethodID methodID, va_list args);
void CallVoidMethodA (jobject obj, jmethodID methodID, jvalue* args);
- 调用静态方法
根据调用方法返回值不同,和传参格式不同有以下不同的方法
参数:
- clazz: 调用那个静态方法的类型
- methodID: 方法标识
- ...,arg: 传递的参数
jobject CallStaticObjectMethod (jclass clazz, jmethodID methodID, ...);
jobject CallStaticObjectMethodV (jclass clazz, jmethodID methodID, va_list args);
jobject CallStaticObjectMethodA (jclass clazz, jmethodID methodID, jvalue* args);
jboolean CallStaticBooleanMethod (jclass clazz, jmethodID methodID, ...);
jboolean CallStaticBooleanMethodV (jclass clazz, jmethodID methodID, va_list args);
jboolean CallStaticBooleanMethodA (jclass clazz, jmethodID methodID, jvalue* args);
jbyte CallStaticByteMethod (jclass clazz, jmethodID methodID, ...);
jbyte CallStaticByteMethodV (jclass clazz, jmethodID methodID, va_list args);
jbyte CallStaticByteMethodA (jclass clazz, jmethodID methodID, jvalue* args);
jchar CallStaticCharMethod (jclass clazz, jmethodID methodID, ...);
jchar CallStaticCharMethodV (jclass clazz, jmethodID methodID, va_list args);
jchar CallStaticCharMethodA (jclass clazz, jmethodID methodID, jvalue* args);
jshort CallStaticShortMethod (jclass clazz, jmethodID methodID, ...);
jshort CallStaticShortMethodV (jclass clazz, jmethodID methodID, va_list args);
jshort CallStaticShortMethodA (jclass clazz, jmethodID methodID, jvalue* args);
jint CallStaticIntMethod (jclass clazz, jmethodID methodID, ...);
jint CallStaticIntMethodV (jclass clazz, jmethodID methodID, va_list args);
jint CallStaticIntMethodA (jclass clazz, jmethodID methodID, jvalue*args);
jlong CallStaticLongMethod (jclass clazz, jmethodID methodID, ...);
jlong CallStaticLongMethodV (jclass clazz, jmethodID methodID, va_list args);
jlong CallStaticLongMethodA (jclass clazz, jmethodID methodID, jvalue* args);
jfloat CallStaticFloatMethod (jclass clazz, jmethodID methodID, ...);
jfloat CallStaticFloatMethodV (jclass clazz, jmethodID methodID, va_list args);
jfloat CallStaticFloatMethodA (jclass clazz, jmethodID methodID, jvalue* args);
jdouble CallStaticDoubleMethod (jclass clazz, jmethodID methodID, ...);
jdouble CallStaticDoubleMethodV (jclass clazz, jmethodID methodID, va_list args);
jdouble CallStaticDoubleMethodA (jclass clazz, jmethodID methodID, jvalue* args);
void CallStaticVoidMethod (jclass clazz, jmethodID methodID, ...);
void CallStaticVoidMethodV (jclass clazz, jmethodID methodID, va_list args);
void CallStaticVoidMethodA (jclass clazz, jmethodID methodID, jvalue* args);
- Java 数组操作
- 新建数组
根据数组中元素类型不同,有以下不同的方法
通用参数:
- length : 数组长度
// 对象数组
jobjectArray NewObjectArray(jsize length, jclass elementClass,jobject initialElement);
/*
elementClass : 对象数据元素的类型
initialElement : 初始化数组的对象,可以选择 NULL
*/
jbooleanArray NewBooleanArray(jsize length);
jbyteArray NewByteArray(jsize length);
jcharArray NewCharArray(jsize length);
jshortArray NewShortArray(jsize length);
jintArray NewIntArray(jsize length);
jlongArray NewLongArray(jsize length);
jfloatArray NewFloatArray(jsize length);
jdoubleArray NewDoubleArray(jsize length);
- 数组转化为指针
根据数组中元素类型不同,有以下不同的方法 参数 :
- array : 待转化的数组
- isCopy : 是否拷贝了一份 array数组数据,若返回的指针数组数据是拷贝则isCopy被置为JNI_TRUE,否则置为NULL或JNI_FALSE:
返回一个指向实际元素的指针, 或者分配一些内存来拷贝数据. 无论哪种方式, 返回的原始数据的指针在调用释放方法前是保证一直有效的(这意味着如果数据没有被拷贝, 这个对象数组将被限制在压缩堆数据时不能移动),必须自己释放每个你获取的数组, 同时如果Get方法失败的话,你的代码一定不能尝试释放一个NULL指针.
jboolean* GetBooleanArrayElements(jbooleanArray array, jboolean* isCopy);
jbyte* GetByteArrayElements(jbyteArray array, jboolean* isCopy);
jchar* GetCharArrayElements(jcharArray array, jboolean* isCopy);
jshort* GetShortArrayElements(jshortArray array, jboolean* isCopy);
jint* GetIntArrayElements(jintArray array, jboolean* isCopy);
jlong* GetLongArrayElements(jlongArray array, jboolean* isCopy);
jfloat* GetFloatArrayElements(jfloatArray array, jboolean* isCopy);
jdouble* GetDoubleArrayElements(jdoubleArray array, jboolean* isCopy);
- 释放数组内存
根据数组中元素类型不同,有以下不同的方法
参数 :
- array : 待释放的数组
- elems : 之前调用 GetXXXArrayElements 返回的指针
- mode: 释放模式
- 0 将内容复制回来并释放原生数组(对Java数组进行更新,并释放C/C++的数组)
- JNI_COMMIT 将内容复制回来但不释放原生数组,一般用于周期性更新数组(对Java数组进行更新但不释放C/C++数组)
- JNI_ABORT 释放原生数组但不将内容复制回来O (对Java数组不进行更新,并释放C/C++数组)
void ReleaseBooleanArrayElements(jbooleanArray array, jboolean* elems,jint mode);
void ReleaseByteArrayElements(jbyteArray array, jbyte* elems,jint mode);
void ReleaseCharArrayElements(jcharArray array, jchar* elems,jint mode);
void ReleaseShortArrayElements(jshortArray array, jshort* elems,jint mode);
void ReleaseIntArrayElements(jintArray array, jint* elems,jint mode);
void ReleaseLongArrayElements(jlongArray array, jlong* elems,jint mode);
void ReleaseFloatArrayElements(jfloatArray array, jfloat* elems, jint mode);
void ReleaseDoubleArrayElements(jdoubleArray array, jdouble* elems,jint mode);
- 拷贝数组内容到 buf 指针
根据数组中元素类型不同,有以下不同的方法
参数:
- array :待拷贝的数组
- start: 拷贝数组的起始位置
- len: 拷贝数组的个数
- buf: 目标指针
void GetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len,
jboolean* buf)
void GetByteArrayRegion(jbyteArray array, jsize start, jsize len,
jbyte* buf)
void GetCharArrayRegion(jcharArray array, jsize start, jsize len,
jchar* buf)
void GetShortArrayRegion(jshortArray array, jsize start, jsize len,
jshort* buf)
void GetIntArrayRegion(jintArray array, jsize start, jsize len,
jint* buf)
void GetLongArrayRegion(jlongArray array, jsize start, jsize len,
jlong* buf)
void GetFloatArrayRegion(jfloatArray array, jsize start, jsize len,
jfloat* buf)
void GetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len,
jdouble* buf)
- 拷贝buf指针内容到数组
根据数组中元素类型不同,有以下不同的方法
参数:
- array :要被赋值的目标数组
- start: 被赋值数组的起始位置
- len: 拷贝的个数
- buf: 被拷贝的数据
void SetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len,
const jboolean* buf)
void SetByteArrayRegion(jbyteArray array, jsize start, jsize len,
const jbyte* buf)
void SetCharArrayRegion(jcharArray array, jsize start, jsize len,
const jchar* buf)
void SetShortArrayRegion(jshortArray array, jsize start, jsize len,
const jshort* buf)
void SetIntArrayRegion(jintArray array, jsize start, jsize len,
const jint* buf)
void SetLongArrayRegion(jlongArray array, jsize start, jsize len,
const jlong* buf)
void SetFloatArrayRegion(jfloatArray array, jsize start, jsize len,
const jfloat* buf)
void SetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len,
const jdouble* buf)
- 数组其他函数
// 获取数组长度
jsize GetArrayLength(jarray array);
/*
array:待获取长度的数组
*/
// 获取对象数组相应索引位置的元素
jobject GetObjectArrayElement(jobjectArray array, jsize index);
/*
array: 对象数组
index: 索引位置
*/
// 设置对象数组相应索引位置的元素
void SetObjectArrayElement(jobjectArray array, jsize index, jobject value)
/*
array: 对象数组
index: 索引位置
value: 新的元素对象
*/
- Java 字符串操作
Java 编程语言使用的是 UTF-16。为方便起见,JNI 还提供了使用修改后的 UTF-8 的方法。修改后的编码对 C 代码非常有用,因为它将 \u0000 编码为 0xc0 0x80,而不是 0x00。这样做的好处是,您可以依靠以零终止的 C 样式字符串,非常适合与标准 libc 字符串函数配合使用。但缺点是,您无法将任意 UTF-8 数据传递给 JNI 并期望它能够正常工作
// jchar (字符指针) 转化为 jstring (字符串)
jstring NewString(const jchar* unicodeChars, jsize len);
/*
1. unicodeChars : 要转化为 jstring 的 jchar 指针
2. len : jchar指针 的字符个数
*/
// jstring (字符串) 转化为 jchar 字符指针
const jchar* GetStringChars(jstring string, jboolean* isCopy);
/*
jstring : 要转化为 char 指针的 jstring
isCopy : 是否拷贝了一份字符串资源
*/
// 释放字符串资源 ,当 jstring 转为jchar 指针时会拷贝一份字符串
void ReleaseStringChars(jstring string, const jchar* chars);
/*
1. string : 待释放的 jstring
2. chars : 调用 GetStringChars 生成的 jchar 指针
*/
// 获取 jstring 字符串长度
jsize GetStringLength(jstring string);
// 通过 c 语言字符串生成一个 jstring 对象
jstring NewStringUTF(const char* bytes);
// Java 中的 jstring 对象的转化为 c/c++中在字符指针处理
const char* GetStringUTFChars(jstring string, jboolean* isCopy);
/*
jstring : 要转化为 char 指针的 jstring
isCopy : 是否拷贝了一份字符串资源
*/
// 释放字符串资源 ,当 jstring 转为 char 指针时会拷贝一份字符串
void ReleaseStringUTFChars(jstring string, const char* utf);
/*
1. string : 待释放的 jstring
2. chars : 调用 GetStringUTFChars 生成的 char 指针
*/
// 获取 jstring 字符串长度
jsize GetStringUTFLength(jstring string);
// 拷贝部分字符到 buf 当中
void GetStringRegion(jstring str, jsize start, jsize len, jchar* buf)
/*
1. str : 待拷贝的字符串
2. start : 待拷贝的起始位置
3. len : 要拷贝的长度
4. buf : 目标字符指针
*/
// 拷贝部分utf 字符到 buf 当中
void GetStringUTFRegion(jstring str, jsize start, jsize len, char* buf)
/*
1. str : 待拷贝的字符串
2. start : 待拷贝的起始位置
3. len : 要拷贝的长度
4. buf : 目标字符指针
*/
- Java 类型,对象操作
/* 通过类名查找到类型,类名要完整的,包含包名 且包名之间分割 . 要换成 /
例如 包名为 net.incivility.jnitest 类型是 JniTest net/incivility/jnitest/JniTest */
jclass FindClass(const char* name)
// 通过对象obj 返回该对象的类型 jclass
jclass GetObjectClass(jobject obj);
// 判断对象 obj 是否该类型 clazz 的对象
jboolean IsInstanceOf(jobject obj, jclass clazz)
// 判断两个对象引用 ref1,ref2是否指向相同的对象
jboolean IsSameObject(jobject ref1, jobject ref2)
// clazz2 是否是 clazz1 相同的类型或者是其子类 (clazz2类型的对象是否能给 clazz1类型的对象的值赋值)
jboolean IsAssignableFrom(jclass clazz1, jclass clazz2)
// 获取 clazz 的父类,如果没有父类返回 NULL
jclass GetSuperclass(jclass clazz)
// 不调用构造函数创建对象,对象属性会只会被默认初始化为空
jobject AllocObject(jclass clazz)
// 调用构造函数创建对象
jobject NewObject(jclass clazz, jmethodID methodID, ...)
jobject NewObjectV(jclass clazz, jmethodID methodID, va_list args)
jobject NewObjectA(jclass clazz, jmethodID methodID, jvalue* args)
/*
1. clazz: 要创建对象的类型
2. methodID: 构造函数方法 jmethodID 标识
3. ...,args: 构造函数参数
*/
- 引用操作
// 引用分类
typedef enum jobjectRefType {
JNIInvalidRefType = 0, // 该 obj 是个无效的引用,说明对象已被回收
JNILocalRefType = 1, // 该 obj 是个局部引用 ,普通函数里面生成的对象引用,函数调用完成返回就失效了
JNIGlobalRefType = 2, // 该 obj 是个全局引用,除非主动调用删除引用方法,一直有效,不会被垃圾回收器回收
JNIWeakGlobalRefType = 3
/*
该 obj是个全局的弱引用,除了主动调用删除引用方法外,可能会被垃圾回收器回收,使用该引用时应检查引用的有效性
*/
} jobjectRefType;
// 创建局部引用
jobject NewLocalRef(jobject ref);
// 释放局部引用
void DeleteLocalRef(jobject localRef);
// 创建全局引用
jobject NewGlobalRef(jobject obj)
// 释放全局引用
void DeleteGlobalRef(jobject globalRef)
// 创建全局弱引用
jweak NewWeakGlobalRef(jobject obj)
// 释放全局弱引用
void DeleteWeakGlobalRef(jweak obj)
使用示例
因为通过类名加载类比较耗时,而以下函数又经常被调用,我们就可以定义一个全局引用保存 jclass 引用,即使函数调用完成,全局对象也不会被回收
#include "RefTestMain.h"
static jclass globalTargetClass =0;
JNIEXPORT jni JNICALL Java_RefTestMain_getMember(JNIEnv *env,jclass clazz)
{
jfieldID fid;
jint intField;
jclass targetClass;
if(globalTargetClass0)
{
targetClass=env->FindClass("RefTest");
globalTargetClass=(jclass)env->newGlobalRef(targetClass);
}
fid=env->GetStaticFieldID(globalTargetClass,"intField","I");
return intField
}
7. 加载本地库时,注册JNI本地函数
在Java代码中,System.loadLibrary()方法时,Java虚拟机会加载其参数指定的共享库,然后,Java虚拟机检索共享库中的函数符号
-
检查JNI_OnLoad()函数是否被实现,若共享库中含有相关函数,则JNI_OnLoad()函数就会被自动调用,我们可以使用 jni 提供的函数进行注册 native 方法
-
若库中的JNI_OnLoad()函数未被实现,则Java虚拟机会自动将本地方法与库内的JNI本地函数符号进行比较匹配。
我们现在的代码中都没有使用到 JNI_OnLoad() 函数,都是使用符合进行匹配找到 native 代码中的函数,使用固定格式,如
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject thisObj) {
printf("Hello World!\n");
return;
}
编写 HelloJNI.c
#include <jni.h>
void sayHello(JNIEnv * env, jobject obj);
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM * vm,void *reserved)
{
JNIEnv *env=NULL;
JNINativeMethod nm[1];
jclass cls;
jint result=-1;
if ((*vm)->GetEnv(vm,(void **)&env,JNI_VERSION_1_8)!=JNI_OK)
{
printf("Error");
return JNI_ERR;
}
cls=(*env)->FindClass(env,"HelloJNI");
nm[0].name="sayHello";
nm[0].signature="()V";
nm[0].fnPtr=(void *)sayHello;
(*env)->RegisterNatives(env,cls,nm,1);
return JNI_VERSION_1_8;
}
void sayHello(JNIEnv * env, jobject obj)
{
printf("JNI_OnLoad hello world!\n");
}
编译动态库
gcc -shared -I /Library/Java/JavaVirtualMachines/jdk1.8.0_102.jdk/Contents/Home/include/ -fPIC HelloJNI.c -o libHelloJNI.jnilib
把新生成的 libHelloJNI.jnilib 替换旧的 运行 java HelloJNI
我们没有按照 jni 的固定格式一样能让 Java 找到我们的 sayHello 方法
// 通过 JavaVM 指针获取JNIEnv * env
jint GetEnv(JavaVM * vm,void ** env,jint version)
/*
1. vm : Java 虚拟机指针 JNI_OnLoad 方法中自带 参数
2. env : Java 环境指针的指针
3. version : jni 版本号
*/
typedef struct {
char *name; //Java类中的方法名
char *signature;// 方法的签名
void *fnPtr; // c/c++代码中的函数指针
} JNINativeMethod;
// 将Java类中的本地方法与JNI本地函数映射在一起
jarray RegusterNatives(JNIEnv *env,jclass clazz,const JNINativeMethod *methods,jint nMethods);
/*
1. env : Java 环境指针
2. clazz : 注册native 方法的类
3. methods : JNINativeMethod 结构体指针,里面有方法的映射信息
4.nMethods : JNINativeMethod长度,即注册方法的个数
*/
android 框架中几乎都是使用 RegusterNatives 把 Java 方法和 native 方法进行绑定的
8. 在c/c++程序中运行Java类
JNI 提供了一套 API,允许本地代码在自身内存区域内加载Java虚拟机
- JNITest1.java
import java.util.Arrays;
public class JNITest1 {
public static void main(String argv[])
{
System.out.println(Arrays.toString(argv));
}
}
- 编译 java 文件 生成 class
javac JNITest1.java
- main.c
#include <jni.h>
int main(int argc, char** argv) {
JNIEnv * env;
JavaVM *vm;
JavaVMInitArgs vm_args;
JavaVMOption options[1];
jint res;
jclass cls;
jmethodID mid;
jstring jstr;
jclass stringClass;
jobjectArray args;
options[0].optionString="-Djava.class.path=.";
vm_args.version=0x00010002;
vm_args.options=options;
vm_args.nOptions=1;
vm_args.ignoreUnrecognized=JNI_TRUE;
res=JNI_CreateJavaVM(&vm,(void **)&env,&vm_args);
cls =(*env)->FindClass(env,"JNITest1");
mid=(*env)->GetStaticMethodID(env,cls,"main","([Ljava/lang/String;)V");
jstr=(*env)->NewStringUTF(env,"Hello Invocation API!!");
stringClass=(*env)->FindClass(env,"java/lang/String");
args=(*env)->NewObjectArray(env,1,stringClass,jstr);
(*env)->CallStaticVoidMethod(env,cls,mid,args);
(*vm)->DestroyJavaVM(vm);
return (0);
}
- JavaVM 代表 java 虚拟机
- JNIEnv 代表 Java 环境 ,之前使用的各个操作 Java 代码的函数都是通过该指针使用的
- JavaVMInitArgs 表示可以用来初始化 JVM 的各种 JVM 参数
- JavaVMOption 具有用于 JVM 的各种选项设置
// 创建 Java 虚拟机
jint JNI_CreateJavaVM(JavaVM**, JNIEnv**, void*);
typedef struct JavaVMInitArgs {
jint version; //jni版本
jint nOptions; // JavaVMOption结构体数组元素的个数
JavaVMOption *options; // JavaVMOption结构体地址
jboolean ignoreUnrecognized;//Java虚拟机遇到错误是否继续执行
} JavaVMInitArgs;
typedef struct JavaVMOption {
char *optionString;
void *extraInfo;
} JavaVMOption;
- 编译 main.c 文件,生成可执行文件 main
gcc -g -I/Library/Java/JavaVirtualMachines/jdk1.8.0_102.jdk/Contents/Home/include/ -L/Library/Java/JavaVirtualMachines/jdk1.8.0_102.jdk/Contents/Home/jre/lib/server/ -std=c11 main.c -ljvm -o main
6. 运行 main
直接从 c 代码中生成虚拟机运行 java 代码 System.out.println(Arrays.toString(argv)); 并把argv 参数数据传递到 Java 层
Android app 开发中我们没有看到 Java 当中的 main 方法,其实只是被Android框架隐藏了 (Android 的 main 方法在 android.app.ActivityThread 当中) 其实也是利用 Java Invocation API 运行 Java 类 把代码运行 从 c/c++ 层转移到Java层
9. 其他事项
c 和 c++ 在jni编码风格
在C中:
使用JNIEnv* env要这样 (*env)->方法名(env,参数列表)
使用JavaVM* vm要这样 (*vm)->方法名(vm,参数列表)
在C++中:
使用JNIEnv* env要这样 env->方法名(参数列表)
使用JavaVM* vm要这样 vm->方法名(参数列表)