1 简介
- 网上有很多教程,但是很多都没有一个完整的实例,我在学习的时候,踩了不少坑。
- 我们使用JNI的目的,无非就是想要用调用C++的接口,并且最好JAVA也能为C++提供一个接口(用于回调),这两个目的都很重要。尤其后面那个目的,如果要实现异步操作,那么java必须能给C++提供接口
2 类型的映射关系
- JNI 它通过类型映射,把JAVA中的基本数据类型,映射到了C++中,这个映射关系记录在了jni.h文件里(该文件在JDK根路径/include下)
- 所以,在C++中,为了兼容JNI,你要尽量使用jni.h中定义的数据类型。
- 以下是映射关系(左侧为Java,右侧为jni.h中定义的)
- boolean(布尔型)-》jboolean,无符号8个比特
- byte(字节型) -》jbyte,有符号8个比特
- char(字符型)-》char,无符号16个比特
- short(短整型)-》jshort,有符号16个比特
- int(整型)-》jint,有符号32个比特
- long(长整型)-》jlong,有符号64个比特
- float(浮点型),jfloat,32个比特
- double(双精度浮点型)-》jdouble,64个比特
- void(空型)-》void,N/A
- 注意
- 在JAVA中,很多方法的形参是包装类型。在C++中,使用包装类型不与基本数据类型兼容!!!如果java方法中的形参是Integer,在C++中你不能使用 jint 来调用!你必须在C++中手动创建对象(jobect类型)!!创建对象挺麻烦的。。
3 一个实例
3.1 写JAVA的接口
- 在JAVA中新建一个工程,写一个Demo类。
- 注意包名和类名,在C++中写的方法会通过名称映射到指定的native方法上,比如C++ 中的Java_com_wu_jni_Demo_callback 方法指的是com.wu.jni.Demo 类中的callback方法。
package com.wu.jni;
public class Demo {
public native void sayHello();
}
- 我们需要把该Demo.java文件手动编译一下。
- 打开文件的路径,打开命令提示行(CMD),然后输入
- javac Demo.java
- cd 到src/main/java目录下
- javap -p -s com.wu.jni.Demo
- 这里会得到com_wu_jni_Demo.h这个文件,这个文件后面我们要用到
3.2 写 C++ 接口
- 打开VS2019,新建一个DLL项目
- 不要勾选【将解决方案和项目放在同一目录中(D)】
- 新建好项目后,不要动自动创建的文件。
- 下面是关键了
- 将上面得到的com_wu_jni_Demo.h文件放入项目目录下
- 新建一个.cpp文件,名称随便起,需要调用#include "com_wu_jni_Demo.h"这个文件
- 还需要将【jdk根路径/include/jni.h】和【jdk根路径/include/win32/jni_md.h】这两个文件复制到项目中
- 将com_wu_jni_Demo.h 文件中的 #include <jni.h> 改为#include “jni.h”
- 编写刚才创建的.cpp文件
// MathLibrary.cpp : Defines the exported functions for the DLL.
#include "pch.h" // use stdafx.h in Visual Studio 2017 and earlier
#include <utility>
#include <limits.h>
#include <iostream>
#include "com_wu_jni_Demo.h"
using namespace std;
//实现sayHello方法
JNIEXPORT void JNICALL Java_com_wu_jni_Demo_sayHello(JNIEnv* env, jobject obj) {
cout << "Hello World" << endl;
}
- 注意输出的模式,右键项目 - 生成
- 在解决方案/x64/Release下找到生成的.dll文件
3.3 编写java的入口
- 将上面生成的.dll文件放到我们的java项目的rescources文件夹下
- 接下来我们就可以在java中调用了
public static void main(String[] args) throws UnsupportedEncodingException {
// 获取Dll的路径,这里的写法是为了兼容中文路径。
// 但是不建议项目路径中有中文
URL url = Demo.class.getResource("/JniDll.dll");
String filePath = URLDecoder.decode(url.getPath(), "utf8");
// 加载DLL文件,加载后native方法自动就有实现了
System.load(filePath);
//System.loadLibrary("Win32Project1");
Demo demo = new Demo();
demo.sayHello();
}
- 如果控制台打印出了消息,那么恭喜你!你成功了!
4 C++中使用jobject对象
- jobject 对应 java 中的对象。在native方法中,只要不是基本数据类型的形参或返回值,都会被变成jobject对象,以便在C++中使用。
- 但是使用它比较繁琐。。。。
- 让我们在java中新创建两个native方法
package com.wu.jni;
import java.util.function.Function;
public class Demo {
public native void sayHello();
public native void add(int a,double b);
public native void callback(Function<Integer,Integer> fun);
}
- 还是按照刚才的套路,通过 javac Demo.java 获得.class文件
- 在通过src\main\java路径下,通过 javap -s -p com.wu.jni.Demo 生成com_wu_jni_Demo.h文件
- 将文件中的内容复制,粘贴到刚才VS2019DLL项目中的com_wu_jni_Demo.h文件里
- 在DLL项目的.cpp文件中,我们新增两个方法
// MathLibrary.cpp : Defines the exported functions for the DLL.
#include "pch.h" // use stdafx.h in Visual Studio 2017 and earlier
#include <utility>
#include <limits.h>
#include <iostream>
#include "com_wu_jni_Demo.h"
using namespace std;
//实现sayHello方法
JNIEXPORT void JNICALL Java_com_wu_jni_Demo_sayHello(JNIEnv*, jobject) {
cout << "Hello World" << endl;
}
JNIEXPORT void JNICALL Java_com_wu_jni_Demo_add(JNIEnv*, jobject, jint a, jdouble b) {
cout << a << endl;
cout << b << endl;
}
JNIEXPORT void JNICALL Java_com_wu_jni_Demo_callback(JNIEnv* env, jobject, jobject call) {
// 找到对应的class
// 必须是全限定类名
jclass cls = env->FindClass("java/util/function/Function");
// 为参数创建实例
// Integer类
jclass cls2 = env->FindClass("java/lang/Integer");
// 找到构造方法
// 【参数1】类
// 【参数2】构造方法的名统一为:<init>
// 【参数3】需要通过java -s -p 类的全限定类名,来获取
jmethodID param1_mid = env->GetMethodID(cls2, "<init>", "(I)V");
jint param1Temp = 10;
// 有参构造对象
jobject param1 = env->NewObject(cls2, param1_mid, param1Temp);
// 无参构造对象
//jobject param1 = env->AllocObject(cls2);
// 找到对应的方法
// 【参数1】类
// 【参数2】类中的方法
// 【参数3】方法签名,可以用javap -s -p 全限定类名,获取到
jmethodID mid = env->GetMethodID(cls, "apply", "(Ljava/lang/Object;)Ljava/lang/Object;");
// 调用对应的方法
// 【参数1】类实例
// 【参数2】方法ID
// 【后续的参数】给定的形参,这个形参不能随便填,必须对应JAVA中的实例。
env->CallObjectMethod(call, mid, param1);
}
- 然后生成解决方案
- 将生成的DLL文件放到JAVA项目的rescourses目录下,加载它,调用对应的类方法即可
public static void main(String[] args) throws UnsupportedEncodingException {
// 获取路径
URL url = Demo.class.getResource("/JniDll.dll");
String filePath = URLDecoder.decode(url.getPath(), "utf8");
System.load(filePath);
//System.loadLibrary("Win32Project1");
Demo demo = new Demo();
demo.sayHello();
demo.add(10,15 );
demo.callback(param1->{
System.out.println("猜猜我是几?" + param1);
System.out.println("啊哈哈哈哈");
return null;
});
System.out.println("哈哈");
}
5 后记
- 第4节中体现了jobject的创建过程,以及调用它对应方法的过程。
- 关于方法签名
- 让我们来看看Function的方法签名
- javap -s -p java.util.function.Function
Compiled from "Function.java"
public interface java.util.function.Function<T, R> {
public abstract R apply(T);
descriptor: (Ljava/lang/Object;)Ljava/lang/Object; // 这里是我们需要的
public <V> java.util.function.Function<V, R> compose(java.util.function.Function<? super V, ? extends T>);
descriptor: (Ljava/util/function/Function;)Ljava/util/function/Function;
public <V> java.util.function.Function<T, V> andThen(java.util.function.Function<? super R, ? extends V>);
descriptor: (Ljava/util/function/Function;)Ljava/util/function/Function;
public static <T> java.util.function.Function<T, T> identity();
descriptor: ()Ljava/util/function/Function;
private static java.lang.Object lambda$identity$2(java.lang.Object);
descriptor: (Ljava/lang/Object;)Ljava/lang/Object;
private java.lang.Object lambda$andThen$1(java.util.function.Function, java.lang.Object);
descriptor: (Ljava/util/function/Function;Ljava/lang/Object;)Ljava/lang/Object;
private java.lang.Object lambda$compose$0(java.util.function.Function, java.lang.Object);
descriptor: (Ljava/util/function/Function;Ljava/lang/Object;)Ljava/lang/Object;
}
- 让我们来看看 Integer 的方法签名
- javap -s -p java.lang.Integer
Compiled from "Integer.java"
public final class java.lang.Integer extends java.lang.Number implements java.lang.Comparable<java.lang.Integer> {
public static final int MIN_VALUE;
descriptor: I
public static final int MAX_VALUE;
descriptor: I
public static final java.lang.Class<java.lang.Integer> TYPE;
descriptor: Ljava/lang/Class;
static final char[] digits;
descriptor: [C
static final char[] DigitTens;
descriptor: [C
static final char[] DigitOnes;
descriptor: [C
static final int[] sizeTable;
descriptor: [I
private final int value;
descriptor: I
public static final int SIZE;
descriptor: I
public static final int BYTES;
descriptor: I
private static final long serialVersionUID;
descriptor: J
public static java.lang.String toString(int, int);
descriptor: (II)Ljava/lang/String;
public static java.lang.String toUnsignedString(int, int);
descriptor: (II)Ljava/lang/String;
public static java.lang.String toHexString(int);
descriptor: (I)Ljava/lang/String;
public static java.lang.String toOctalString(int);
descriptor: (I)Ljava/lang/String;
public static java.lang.String toBinaryString(int);
descriptor: (I)Ljava/lang/String;
private static java.lang.String toUnsignedString0(int, int);
descriptor: (II)Ljava/lang/String;
static int formatUnsignedInt(int, int, char[], int, int);
descriptor: (II[CII)I
public static java.lang.String toString(int);
descriptor: (I)Ljava/lang/String;
public static java.lang.String toUnsignedString(int);
descriptor: (I)Ljava/lang/String;
static void getChars(int, int, char[]);
descriptor: (II[C)V
static int stringSize(int);
descriptor: (I)I
public static int parseInt(java.lang.String, int) throws java.lang.NumberFormatException;
descriptor: (Ljava/lang/String;I)I
public static int parseInt(java.lang.String) throws java.lang.NumberFormatException;
descriptor: (Ljava/lang/String;)I
public static int parseUnsignedInt(java.lang.String, int) throws java.lang.NumberFormatException;
descriptor: (Ljava/lang/String;I)I
public static int parseUnsignedInt(java.lang.String) throws java.lang.NumberFormatException;
descriptor: (Ljava/lang/String;)I
public static java.lang.Integer valueOf(java.lang.String, int) throws java.lang.NumberFormatException;
descriptor: (Ljava/lang/String;I)Ljava/lang/Integer;
public static java.lang.Integer valueOf(java.lang.String) throws java.lang.NumberFormatException;
descriptor: (Ljava/lang/String;)Ljava/lang/Integer;
public static java.lang.Integer valueOf(int);
descriptor: (I)Ljava/lang/Integer;
public java.lang.Integer(int);
descriptor: (I)V // 这里是我们需要的
public java.lang.Integer(java.lang.String) throws java.lang.NumberFormatException;
descriptor: (Ljava/lang/String;)V
public byte byteValue();
descriptor: ()B
public short shortValue();
descriptor: ()S
public int intValue();
descriptor: ()I
public long longValue();
descriptor: ()J
public float floatValue();
descriptor: ()F
public double doubleValue();
descriptor: ()D
public java.lang.String toString();
descriptor: ()Ljava/lang/String;
public int hashCode();
descriptor: ()I
public static int hashCode(int);
descriptor: (I)I
public boolean equals(java.lang.Object);
descriptor: (Ljava/lang/Object;)Z
public static java.lang.Integer getInteger(java.lang.String);
descriptor: (Ljava/lang/String;)Ljava/lang/Integer;
public static java.lang.Integer getInteger(java.lang.String, int);
descriptor: (Ljava/lang/String;I)Ljava/lang/Integer;
public static java.lang.Integer getInteger(java.lang.String, java.lang.Integer);
descriptor: (Ljava/lang/String;Ljava/lang/Integer;)Ljava/lang/Integer;
public static java.lang.Integer decode(java.lang.String) throws java.lang.NumberFormatException;
descriptor: (Ljava/lang/String;)Ljava/lang/Integer;
public int compareTo(java.lang.Integer);
descriptor: (Ljava/lang/Integer;)I
public static int compare(int, int);
descriptor: (II)I
public static int compareUnsigned(int, int);
descriptor: (II)I
public static long toUnsignedLong(int);
descriptor: (I)J
public static int divideUnsigned(int, int);
descriptor: (II)I
public static int remainderUnsigned(int, int);
descriptor: (II)I
public static int highestOneBit(int);
descriptor: (I)I
public static int lowestOneBit(int);
descriptor: (I)I
public static int numberOfLeadingZeros(int);
descriptor: (I)I
public static int numberOfTrailingZeros(int);
descriptor: (I)I
public static int bitCount(int);
descriptor: (I)I
public static int rotateLeft(int, int);
descriptor: (II)I
public static int rotateRight(int, int);
descriptor: (II)I
public static int reverse(int);
descriptor: (I)I
public static int signum(int);
descriptor: (I)I
public static int reverseBytes(int);
descriptor: (I)I
public static int sum(int, int);
descriptor: (II)I
public static int max(int, int);
descriptor: (II)I
public static int min(int, int);
descriptor: (II)I
public int compareTo(java.lang.Object);
descriptor: (Ljava/lang/Object;)I
static {};
descriptor: ()V
}