用JNI实现java程序对c++库的调用

一、基本介绍

1、JNI是什么?

      Java本机接口(Java Native Interface (JNI))是本机编程接口,它是JDK的一部分,JNI它提供了若干的API,实现了和Java和其他通信(主要是C&C++)。

2、JNI有什么用?

      JNI最常见的两个应用:从Java程序调用C/C++,以及从C/C++程序调用Java代码。

3、使用JNI需要什么环境?

      (1)、JDK

     工具及组件:(Java编译器:javac.exe 、JVM :java.exe 、本地方法C文件生成器:javah.exe)

     库文件和头文件:jni.h( C头文件)、jvm.lib 和jvm.dll(windows下) 或libjvm.so(linux下)。

     (2)、能够创建动态链接库的C和C++编译器

     最常见的两个C编译器是用于Windows的Visual C++ 或用于基于UNIX系统的gcc/g++。

二、Java调用C++代码的完美方法

     JNI是Java与C++之间的桥梁,它们之间的层次关系如下图所示:、

 

用JNI实现java程序对c++库的调用_第1张图片

(1)、实现java端代码

根据c++代码的接口定义,封装一个java版本的接口实现,以分词为例,在c++中定义的接口为:

那么在java中对应这些接口的实现,new一个java文件WordSegment.java:

在java只需要定义函数,在JNI层中实现,跟普通的java函数定义唯一区别是多了一个native关键字。

 

(2)、生成JNI层的header文件

将上面的java文件用javac编译:

如果Windows下就进入cmd,linux直接终端中,cd到WordSegment.java所在的目录下,执行

javac WordSegment.java

执行成功,会在当前路径下生成一个WordSegment.class文件,这个是java编译出来的文件。

然后再执行(注意,是WordSegment,不带.class后缀

javah WordSegment

生成JNI的头文件。执行成功会生成WordSegment.h文件,在头文件中可以看到JNI对java接口做了转译

从.h文件中函数名称可以看到,jd_com这部分是对应WordSegment.java中package的路径,WordSegment是java的类名,后边是接口函数名称,

这个也是JNI与java中函数进行连接的一个标识,就是说如果你改了java文件中的类名、package名称或者接口名称,其实也没有必要再走一遍以上步骤

重新生成.h文件,直接把JNI层的函数名称改一下就可以了。另外这个WordSegment.h的文件名也不重要,改了也没关系,这几个函数定义,你可以拷贝到任意

一个头文件中,只要将它们实现即可。

函数参数的部分

  • 第一个JNIEnv*,这个参数是java的环境参数,JNI也是主要通过它实现C++与java的互通
  • 第二个Jobject是调用这个接口的java对象,在我们例子中就是WordSegment.java中WordSegment类的对象
  • 后边接下来才是接口函数真正的参数

在JNI对java中的各种数据类型也进行的转译,这些定义都在jni.h中。

比如 String ->jstring ,int->jint 。java中所有的对象类型,在JNI中都统一标识为jobject

(3)、实现JNI接口

接口的实现就比较简单了,可以直接在.h中实现,也可以再new一个WordSegment.cpp文件,在cpp文件中实现函数

在我们的例子中比较简单,直接调用分词那边提供好的接口就可以了。

在JNI实现函数中调用C++的接口StringSegment(),是java对c++的调用,接下来就以上面Java_jd_com_WordSegment_StringSegment函数中的ArrayList为例,看一下怎么样实现jc++调用java的

  1. 获取一个java对象类,jclass cls_ArrayList = env->FindClass("java/util/ArrayList");
  2. 获取这个类的构造函数的函数ID:jmethodID construct = env->GetMethodID(cls_ArrayList,"","()V");
  3. 通过构造函数ID,构建一个java类对象实例 :jobject obj_ArrayList = env->NewObject(cls_ArrayList,construct,"");
  4. 获取类Arraylist的add函数的函数ID:jmethodID arrayList_add = env->GetMethodID(cls_ArrayList,"add","(Ljava/lang/Object;)Z"); //第二个参数是函数名称(字符串),第三个是这个函数的参数、返回值,也是个字符串格式的
  5. 调用Arraylist的add函数:env->CallObjectMethod(obj_ArrayList, arrayList_add, jstr);

这里有个问题是,JNI下java和c++的各种标准基本数据类型是可以通用的,比如int long 、bool、float这些,但只要是对象类型就不可以了

所以string 到jstring需要做一个转换,另外vector也要转换成java下的ArrayList。下面一段代码是c++的string转换成JNI的jstring的函数。

(4)、生成动态库

JNI接口实现完成以后,可以有两种方法生成动态库

1、将JNI的实现WordSegment.h、WordSegment.cpp和分词的代码合到一个工程中,然后生成一个WordSegment.so动态库

2、分词代码自己打包成一个静态库libsegment.a,然后再单独编译WordSegment.cpp,编译时加入参数-lsegment,引入静态库,生成WordSegment.so

在java程序中又是怎么调用c++动态库的呢?

很简单,跟c++类似

  • 如果你只是在你的java工程中调用C++的动态库使用的话,需要指定动态库的存放位置
java -Djava.library.path=  //启动java的时候配置好非java类包的位置

java.library.path是非java类包的位置如(dll,so)

如果你是用eclipse的话,工程右键properties->java Build Path->Libraries,找到JRE System Library下边的Native library location 然后edit,改成你的动态库存放位置

  • 这里需要注意的是,如果你的java程序也是包装的一个接口的话,也就是说你要搞成一个jar包给别人用的时候,那么需要将WordSegment.so动态库一起打包起来,

解决方法是是在java的project中,加入一个natives文件夹,将WordSegment.so放进去,然后打jar包的时候natives文件夹也打进去

上面代码可以看到在load动态库的时候,做了一点小技巧,就是用的

NativeUtils.loadLibraryFromJar("/natives/"+System.mapLibraryName("WordSegment"));

这个util也比较简单,就是创建一个临时目录,把动态库拷贝过去,然后load进去

NativeUtil的git地址:https://github.com/adamheinrich/native-utils

(5)、环境、配置等其他设置

JNI要依赖jni.h这个头文件,这个需要在jdk的安装目录下的include目录下找,例如我的jdk安装目录在C:\Program Files\Java\jdk1.8.0_181\include下,

另外在include下的win32文件夹中有个jni_md.h文件,需要把这两个头文件拷贝到WordSegment.h的目录下,把 #include 改成#include "jni.h"

(6)如果接口是C++的,怎么存放指针?

jni提供的是存c的接口,只有函数,调用c++接口init中会new一个对象出来,怎么样保存它呢?因为访问其他接口的时候要通过对象指针来调用c++函数。

解决方案:在java中定义一个long成员来保存对象指针

 
用JNI实现java程序对c++库的调用_第2张图片

在jni中存取方法:
//保存指针
WordSeggerInterface* pWordSegmentInterface = new WordSeggerInterface();
jfieldID f_segmentPointAddr = env->GetFieldID(env->GetObjectClass(obj), "m_segmentPointAddress", "J");
if (!f_segmentPointAddr)
{
     env->SetLongField(obj, f_segmentPointAddr, (jlong)pWordSegmentInterface);
}

//获取指针
WordSeggerInterface* pWordSegmentInterface = NULL;
jfieldID f_segmentPointAddr = env->GetFieldID(env->GetObjectClass(obj), "m_segmentPointAddress", "J");
if (f_segmentPointAddr)
{
	pWordSegmentInterface = (WordSeggerInterface*)env->GetLongField(obj, f_segmentPointAddr);
}	
 这里要注意的是在java中保存类型对应的声明,int 对应是I,long是J,那么获取fieldId的时候
env->GetFieldID(env->GetObjectClass(obj), "m_segmentPointAddress", "J")
获取保存值的时候
env->Get LongField(obj, f_segmentPointAddr)

 jni.h中有个对应表

typedef union jvalue {
    jboolean z;
    jbyte    b;
    jchar    c;
    jshort   s;
    jint     i;
    jlong    j;
    jfloat   f;
    jdouble  d;
    jobject  l;
} jvalue;

 这个声明写错了,会提示找不到Field的

你可能感兴趣的:(eclipse,java,c++)