JAVA 使用JNI与C++交互的详细过程

9 篇文章 0 订阅

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
}
  • 6
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: Java调用JNI实例的过程如下: 在Java中调用JNI实例的第一步是通过System.loadLibrary()方法加载包含本地函数实现的动态链接库。这将使得Java程序能够找到并访问JNI实例中的方法。 接下来,在Java代码中声明一个native方法,这个方法将由JNI实例来实现。native关键字告诉编译器该方法是一个JNI方法。 然后,使用javah命令自动生成一个C/C++头文件,该头文件中包含了Java声明的native方法的函数原型。头文件的生成步骤是在命令行中进入Java源代码目录,并执行javah命令。 接下来,打开生成的头文件,将native方法的函数原型拷贝到一个C/C++源文件中,并实现该方法。 在C/C++源文件中,需要包含jni.h头文件,并实现native方法的功能。在实现JNI方法时,可以使用JNIEnv接口调用Java方法,通过JNI实例和Java对象进行交互。 最后,使用C/C++的编译器将源文件编译成动态链接库。 在Java代码中,可以通过调用声明的native方法来使用JNI实例,这样就可以调用C/C++中实现的功能了。 需要注意的是,Java和C/C++之间的数据类型转换可能需要注意,因为Java和C/C++使用不同的数据类型系统。为了正确传递和处理参数,可能需要使用JNIEnv接口的一些方法来处理数据类型的转换。 总结来说,Java调用JNI实例的过程包括加载JNI实例的动态链接库、声明native方法、使用javah命令自动生成头文件、实现native方法的功能、编译源文件成动态链接库、通过调用native方法来使用JNI实例。 ### 回答2: Java调用JNI实例是指在Java程序中使用JNI技术(Java Native Interface)来调用本地方法。 JNIJava与本地代码之间的桥梁,它允许Java程序在运行时调用本地编程语言(如C、C++)编写的代码。这样做的好处是可以结合Java的跨平台特性和本地编程语言的高效性能。 要在Java程序中调用JNI实例,首先需要编写本地代码。以C语言为例,我们需要编写一个与Java类对应的本地方法。 编写本地方法时,需要使用特定的修饰符来表明这是一个本地方法,并且使用JNI提供的函数来与Java交互。然后将本地代码编译成动态链接库(如.so文件)。 然后,在Java程序中通过System.loadLibrary方法加载动态链接库。加载成功后,就可以通过Java调用本地方法了。 在调用本地方法时,需要使用native关键字来声明这是一个本地方法,并且使用JNI提供的接口来调用本地方法。 在调用JNI实例时,需要注意以下几点: 1. 确保本地代码与Java代码的数据类型匹配,可以使用JNI提供的函数转换数据类型。 2. 传递参数时,可以将Java的对象、基本数据类型或数组作为参数传递给本地方法。 3. 在本地方法中修改Java的对象时,需要使用JNI提供的函数来获取并修改Java对象的属性或调用Java对象的方法。 总结: Java调用JNI实例是一种充分利用Java跨平台特性和本地编程语言高性能的方式。通过编写本地代码并加载动态链接库,可以在Java程序中调用本地方法。在调用JNI实例时,需要注意数据类型匹配和参数传递等问题。 ### 回答3: Java调用JNIJava Native Interface)是一种机制,用于在Java程序中调用本机(Native)方法。Java开发者可以使用JNI将具有特定目标平台相关的操作或功能集成到Java应用程序中。 为了实现Java调用JNI,开发者首先需要编写一个Java类,其中包含用于调用本机方法的声明。然后,在本机方法中编写原生代码,以实现需要的功能。在编写完本机代码后,需要将其编译为动态连接库(.dll,.so等),以供Java程序调用。 在Java类中,使用`native`关键字声明本机方法。然后,通过JavaJNI库提供的函数,将本机方法与本机库中对应的函数进行关联。具体步骤如下: 1. 编写Java类,并在其中声明本机方法的原型,使用`native`关键字标记这些方法。 2. 使用Java的`javac`命令编译Java类,生成字节码文件。 3. 使用Java的`javah`命令生成头文件,该头文件包含了本机方法的声明。 4. 在C/C++中编写实现本机方法的代码,并将其编译为动态连接库。可以根据平台的不同,使用不同的编译选项和工具链进行编译。 5. 将生成的动态连接库与Java程序绑定。这可以通过在Java程序中使用`System.loadLibrary("library_name")`的方式来实现,其中`library_name`为动态连接库的名称。 6. 在Java程序中调用本机方法,通过JNI库提供的函数进行连接。 需要注意的是,Java调用JNI过程需要遵循特定的规范和约定,以确保本机方法与Java代码之间的正确交互。此外,使用JNI可能会涉及到与本机平台相关的问题,比如内存管理和线程安全等。因此,在使用JNI时,开发者需要仔细考虑潜在的问题,并进行适当的测试和调试。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值