目录
01. 概述
- Java瓶颈
Java就提供了本地接口,主要用于解决在与底层交互以及图形运算性能方面的缺点。 - 实现方式
主要通过一个标准的方式让Java程序通过虚拟机与原生代码进行交互,也就是Java本地接口(java native interface)。使得在Java虚拟机(JVM) 内部运行的Java代码能够与其它编程语言(如 C、C++和汇编语言)编写的应用程序和库进行互操作。 - JNI优点
- 提高应用程序性能:相对于C/C++、汇编语言来说,Java显得比较“高级”。也就是底层内存交由JVM管理,虚拟机需要回收分配的内存,导致性能有些逊色。
- 实现一些与底层相关的功能:Java平台提供的标准类库,还有强大的API,虽然能完成大部分功能。但有些和底层硬件打交道的功能在java API提供的类库中还是无法完成。
- 与已有的使用原生代码编写的程序进行集成:操作系统上由C/C++等原生语言编写的软件进行集成的时候,可以用JNI。
02. JNI 接口函数和指针
2.1 JNI函数定义
public class Algorithm {
// JNI方法:由native修饰
public native double sqrt(double params);
static{
// 加载动态链接库
System.loadLibrary(“Algorithm”);
}
}
2.2 JNI指针
JNI 函数可通过接口指针来获得。接口指针是指针的指针,它指向一个指针数组,而指针数组中的每个元素又指向一个接口函数。每个接口函数都处在数组的某个预定偏移量中。下图说明了接口指针的组织结构。
- JNI 接口的组织类似于 C++ 虚拟函数表或 COM 接口。
- 使用接口表而不使用硬性编入的函数表的好处是使JNI名字空间与平台相关代码分开。
- 虚拟机可以很容易地提供多个版本的 JNI 函数表。
例如,虚拟机可支持以下两个JNI函数表:
一个表对非法参数进行全面检查,适用于调试程序;
另一个表只进行 JNI 规范所要求的最小程度的检查,因此效率较高。 - JNI接口指针只在当前线程中有效。实现 JNI 的虚拟机可将本地线程的数据分配和储存在 JNI 接口指针所指向的区域中。
- 本地方法将JNI 接口指针当作参数来接受。虚拟机在从相同的 Java 线程中对本地方法进行多次调用时,保证传递给该本地方法的接口指针是相同的。
03. JNI C++
3.1 Java原始与JNI类型映射关系
3.2 Java对象
Java对象做为引用被传递到本地方法中,所有这些Java对象的引用都有一个共同的父类型jobject。
3.2.1 String对象
从java程序中传过去的String对象在本地方法中对应的是jstring类型,jstring类型和c中的char不同,所以如果你直接当做char使用的话,就会出错。因此在使用之前需要将jstring转换成为c/c++中的char*,这里使用JNIEnv的方法转换。
- jstring方法
GetStringUTFChars
: 将jstring转换成为UTF-8格式的char*
GetStringChars
: 将jstring转换成为Unicode格式的char*
ReleaseStringUTFChars
: 释放指向UTF-8格式的char的指针
ReleaseStringChars
: 释放指向Unicode格式的char的指针
NewStringUTF
: 创建一个UTF-8格式的String对象
NewString
: 创建一个Unicode格式的String对象
GetStringUTFLengt
: 获取UTF-8格式的char的长度
GetStringLength
: 获取Unicode格式的char的长度 - 栗子
JNIEXPORT jstring JNICALL Java_adj_felix_algorithm_Algorithm_echo (JNIEnv *env, jobject obj, jstring str, jboolean flag) { char buf[128]; const char *pstr = (*env)->GetStringUTFChars(env, str, 0); printf("%s", pstr); // c++指针处理 // 注意:在使用完你所转换之后的对象之后,需要显示调用ReleaseStringUTFChars方法, // 让JVM释放转换成UTF-8的string的对象的空间,如果不显示的调用的话,JVM中会一直保存该对象, // 不会被垃圾回收器回收,因此就会导致内存溢出。 (*env)->ReleaseStringUTFChars(env, str, pstr); }
3.2.2 Array对象
- 获取数组的长度
JNIEXPORT jobjectArray JNICALL Java_adj_felix_algorithm_Algorithm_union (JNIEnv *env, jobject obj, jstring str, jintArray arrs) { jsize len = (*env)->GetArrayLength(env, arrs); }
- 获取一个指向数组元素的指针
函数 数组类型JNIEXPORT jobjectArray JNICALL Java_adj_felix_algorithm_Algorithm_union (JNIEnv *env, jobject obj, jstring str, jintArray arrs) { jsize len = (*env)->GetArrayLength(env, arrs); // 注意该函数的参数,JNIEnv、数组、数组开始的位置 jint *body = (*env)->GetIntArrayElements(env, arrs, 0); }
GetBooleanArrayElements
boolean
GetByteArrayElements
byte
GetCharArrayElements
char
GetShortArrayElements
short
GetIntArrayElements
int
GetLongArrayElements
long
GetFloatArrayElements
float
GetDoubleArrayElements
double - 使用指针取出Array中的元素
JNIEXPORT jobjectArray JNICALL Java_adj_felix_algorithm_Algorithm_union (JNIEnv *env, jobject obj, jstring str, jintArray arrs) { jsize len = (*env)->GetArrayLength(env, arrs); // 注意该函数的参数,JNIEnv、数组、数组开始的位置 jint *body = (*env)->GetIntArrayElements(env, arrs, 0); int sum = 0; for (int i=0; i<len; i++) { sum += body[i]; } }
- 释放数组元素的引用
函数 数组类型JNIEXPORT jobjectArray JNICALL Java_adj_felix_algorithm_Algorithm_union (JNIEnv *env, jobject obj, jstring str, jintArray arrs) { jsize len = (*env)->GetArrayLength(env, arrs); // 注意该函数的参数,JNIEnv、数组、数组开始的位置 jint *body = (*env)->GetIntArrayElements(env, arrs, 0); int sum = 0; for (int i=0; i<len; i++) { sum += body[i]; } (*env)->ReleaseIntArrayElements(env, arrs, body, 0); // 请实现返回值 }
ReleaseBooleanArrayElements
boolean
ReleaseByteArrayElements
byte
ReleaseCharArrayElements
char
ReleaseShortArrayElements
short
ReleaseIntArrayElements
int
ReleaseLongArrayElements
long
ReleaseFloatArrayElements
float
ReleaseDoubleArrayElements
double
3.2.3 Java对象
-
获取你需要访问的Java对象的类
JNIEXPORT void JNICALL Java_adj_felix_algorithm_Algorithm_println (JNIEnv *env, jobject obj, jobject person) jclass cls = (*env)->GetObjectClass(env, person); }
-
获取MethodID
JNIEXPORT void JNICALL Java_adj_felix_algorithm_Algorithm_println (JNIEnv *env, jobject obj, jobject person) jclass cls = (*env)->GetObjectClass(env, person); // env-->JNIEnv // cls-->第一步获取的jclass // "print"-->要调用的方法名 // "(I)I"-->方法的Signature jmethodID method_id = (*env)->GetMethodID(env, cls, "ageIncr", "(I)I"); }
Signature Java中的类型
Z
boolean
B
byte
C
char
S
short
I
int
J
long
F
float
D
double
L
fully-qualified-class fully-qualified-class
[type
type[]
(arg-types) ret-type
method type一个Java类的方法的Signature可以通过javap命令获取:
javap -s -p Java类名 -
调用方法
JNIEXPORT void JNICALL Java_adj_felix_algorithm_Algorithm_println (JNIEnv *env, jobject obj, jobject person) jclass cls = (*env)->GetObjectClass(env, person); // env --> JNIEnv // cls --> 第一步获取的jclass // "print" --> 要调用的方法名 // "(I)V" --> 方法的Signature jmethodID method_id= (*env)->GetMethodID(env, cls, "ageIncr", "(I)I"); // env --> JNIEnv // person --> 本地方法对应的jobject // method_id --> 要调用的MethodID // args-->方法需要的参数(对应方法的需求,添加相应的参数) // (*env)->CallVoidMethod(env, obj, method_id, args); (*env)->CallVoidMethod(env, obj, method_id, 10); }
-
调用静态方法
GetStaticMethodID
获取对应的静态方法的ID
CallStaticIntMethod
调用静态方法 -
Java对象的属性
访问Java对象的属性和访问Java对象的方法基本上一样,只需要将函数里面的Method改为Field即可。
04. 实现
4.1 java native方法类定义
package com.hotmail.jni;
public class Algorithm {
public native String echo(String msg, boolean flag);
public native int sqrt(int x, int y);
public native float sum(float a, float b);
public native double sum(double a, double b);
public native String[] union(String prefix, int[] arrs);
public native void print(Person person);
public static void main(String[] args) {
// 加载动态库
System.loadLibrary("Algorithm");
Algorithm algorithm = new Algorithm();
String echo = algorithm.echo("i'm jni demo", true);
int sqrt = algorithm.sqrt(3, 4);
float sumFloat = algorithm.sum(3.2f, 4.5f);
double sumDouble = algorithm.sum(20.5, 10.0);
System.out.println("echo = " + echo);
System.out.println("sqrt = " + sqrt);
System.out.println("sum float = " + sumFloat);
System.out.println("sum double = " + sumDouble);
}
class Person {
int age;
public void print() {
System.out.println("age = " + age);
}
public int ageIncr(int age) {
return this.age + age;
}
}
}
4.2 编译JNI类的.h文件
- javah命令
用法:javah [选项] <类>, [选项] 包括:
-help
输出此帮助消息并退出
-classpath <path>
用于装入类的路径
-bootclasspath <path>
用于装入引导类的路径
-d <dir>
输出目录
-o <file>
输出文件(只能使用 -d 或 -o 中的一个)
-jni
生成JNI样式的头文件(默认)
-version
输出版本信息
-verbose
启用详细输出
-force
始终写入输出文件使用全限定名称指定 <类>(例如,java.lang.Object)。 - 编译为.h文件
cd …/src
javah com.hotmail.jni.Algorithm#include <jni.h> #ifndef _Included_adj_felix_algorithm_Algorithm #define _Included_adj_felix_algorithm_Algorithm #ifdef __cplusplus extern "C" { #endif JNIEXPORT jstring JNICALL _Included_com_hotmail_jni_Algorithm_echo (JNIEnv *, jobject, jstring, jboolean); JNIEXPORT jint JNICALL _Included_com_hotmail_jni_Algorithm_sqrt (JNIEnv *, jobject, jint, jint); JNIEXPORT jfloat JNICALL _Included_com_hotmail_jni_Algorithm_sum__FF (JNIEnv *, jobject, jfloat, jfloat); JNIEXPORT jdouble JNICALL _Included_com_hotmail_jni_Algorithm_sum__DD (JNIEnv *, jobject, jdouble, jdouble); JNIEXPORT jobjectArray JNICALL _Included_com_hotmail_jni_Algorithm_union (JNIEnv *, jobject, jstring, jintArray); JNIEXPORT void JNICALL _Included_com_hotmail_jni_Algorithm_print (JNIEnv *, jobject, jobject); #ifdef __cplusplus } #endif #endif
05. C++工程
新建 > 项目 > 模板 -> Visual C++ > DDL
- 引入头文件
调试(选择X64或X86) > JNI属性 > VC++目录
包含文件:加入jdk > include & include/win32 - 编译
生成 > 生成解决方案