Java JNI之深入浅出

01. 概述

  1. Java瓶颈
    Java就提供了本地接口,主要用于解决在与底层交互以及图形运算性能方面的缺点。
  2. 实现方式
    主要通过一个标准的方式让Java程序通过虚拟机与原生代码进行交互,也就是Java本地接口(java native interface)。使得在Java虚拟机(JVM) 内部运行的Java代码能够与其它编程语言(如 C、C++和汇编语言)编写的应用程序和库进行互操作。
  3. 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{
		// 加载动态链接库
		SystemloadLibrary(Algorithm)}
}

2.2 JNI指针

JNI 函数可通过接口指针来获得。接口指针是指针的指针,它指向一个指针数组,而指针数组中的每个元素又指向一个接口函数。每个接口函数都处在数组的某个预定偏移量中。下图说明了接口指针的组织结构。
在这里插入图片描述

  1. JNI 接口的组织类似于 C++ 虚拟函数表或 COM 接口。
  2. 使用接口表而不使用硬性编入的函数表的好处是使JNI名字空间与平台相关代码分开。
  3. 虚拟机可以很容易地提供多个版本的 JNI 函数表。

    例如,虚拟机可支持以下两个JNI函数表:
    一个表对非法参数进行全面检查,适用于调试程序;
    另一个表只进行 JNI 规范所要求的最小程度的检查,因此效率较高。

  4. JNI接口指针只在当前线程中有效。实现 JNI 的虚拟机可将本地线程的数据分配和储存在 JNI 接口指针所指向的区域中。
  5. 本地方法将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
  • 编译
    生成 > 生成解决方案
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值