1. 通过java定义需要调用的函数接口
需要用到的工具:IDEA或Eclipse
1.1 新建java工程 定义用到库函数的类
import java.util.ArrayList;public class HelloWorld {public native void displayHelloWorld();public native String getString();public native float[] getCoordF();public native int[] getCoordI();}
我定义了一个HelloWorld类,其中用到 displayHelloWorld()方法,没有输入参数和返回值,作用就是显示一行”helloworld”字符串。
这次尝试主要是项目需求要得到返回三个浮点数的数组,所以定义了getCoordF()方法,返回一个float类型的数组,其他的方法就是顺带的测试一下。native关键字指明该方法需要用到本地的c++库文件。
1.2 用命令行编译用到库函数的类生成.h头文件
命令行转到项目目录下,一开始项目目录中文件如下,有用的是HelloWorld.java文件:
执行命令
javac -d . HelloWorld.java
生成HelloWorld.class的类文件
再通过类文件HelloWorld.class,执行命令
javah -jni HelloWorld
注意:最后一个参数是包名,如package为package com.example.arserver 类名class为ARcompute
则执行
javah -jni com.example.arserver.ARcompute
得到HelloWorld.h文件
注:根据java版本的不同,也有可能出现javah命令不存在的情况(比如我的windows,装的java是16.0.1)这是因为javah命令被合并到javac -h里面了,具体怎么用可以百度一下,有点忘了。我这个项目是在linux服务器上运行的,java版本是1.8.292,运行没有问题。
生成HelloWorld.h文件,这个文件就是jni通过java定义的接口函数自动生成的c++的函数类型声明,之后的函数实现只要以这个.h文件为原型去实现就可以啦。文件内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>/* Header for class HelloWorld */#ifndef _Included_HelloWorld#define _Included_HelloWorld#ifdef __cplusplusextern "C" {#endif/** Class: HelloWorld* Method: displayHelloWorld* Signature: ()V*/JNIEXPORT void JNICALL Java_HelloWorld_displayHelloWorld(JNIEnv *, jobject);/** Class: HelloWorld* Method: getString* Signature: ()Ljava/lang/String;*/JNIEXPORT jstring JNICALL Java_HelloWorld_getString(JNIEnv *, jobject);/** Class: HelloWorld* Method: getCoordF* Signature: ()[F*/JNIEXPORT jfloatArray JNICALL Java_HelloWorld_getCoordF(JNIEnv *, jobject);/** Class: HelloWorld* Method: getCoordI* Signature: ()[I*/JNIEXPORT jintArray JNICALL Java_HelloWorld_getCoordI(JNIEnv *, jobject);#ifdef __cplusplus}#endif#endif
可以看到这里声明了四个函数,分别是前面在HelloWorld.java中定义的四个方法,前面的函数返回值和参数类型是jni自动生成的将java数据类型转换成的对应的c++数据类型,类型声明都存放在jni.h的头文件里,也就是第2行include进来的那个文件。
函数名就是 包名类名方法名 组成的,比较有辨识度。
1.3 找到jni.h的位置
这里有个坑,自动生成的HelloWorld.h文件好是好,但是他自动引入的#include<jni.h>不一定总是找的到,因为尖括号方式引入的头文件会在环境变量里找,一般是找不到的。网上多数的方法是找到这个文件,将他拖动到cpp源文件的同一目录下,然后将尖括号改成引号,#include"jni.h",这个方法是可行的,但是为了项目目录整洁一点,我还是通过g++编译的参数来引入头文件。jni.h文件位于java SDK安装目录下,我这里的服务器/usr/lib/jvm/java-8-openjdk-amd64在这个目录下安装了java,(好像和前面安装的版本号对不上?不管了,找得到能用就行)。在这个目录的/include/目录下,就有我们要的jni.h文件。
除了这个文件,还需要jni_md.h文件,他在/include/liunx/下
2. 编写cpp源文件并编译成动态库
2.1 开写
这里为了测试,我写了多文件helloworld.cc
// helloworld.cc#include "HelloWorld.h"#include "extra.h"#include <stdio.h>JNIEXPORT void JNICALL Java_HelloWorld_displayHelloWorld(JNIEnv *, jobject){printf("Hello Java!");print();}JNIEXPORT jstring JNICALL Java_HelloWorld_getString(JNIEnv *env, jobject){return env->NewStringUTF((char *)"Hello from JNI !");}JNIEXPORT jfloatArray JNICALL Java_HelloWorld_getCoordF(JNIEnv *env, jobject){jfloatArray array_f = env->NewFloatArray(3);jint start=0, range=1;jfloat data[3] = {1.0, 2.0, 3.0};env->SetFloatArrayRegion(array_f, start++, range, data);env->SetFloatArrayRegion(array_f, start++, range, data+1);env->SetFloatArrayRegion(array_f, start++, range, data+2);return array_f;}JNIEXPORT jintArray JNICALL Java_HelloWorld_getCoordI(JNIEnv *env, jobject){jintArray array_i = env->NewIntArray(3);jint start=0, range=1;jint data[3] = {1, 2, 3};env->SetIntArrayRegion(array_i, start++, range, data);env->SetIntArrayRegion(array_i, start++, range, data+1);env->SetIntArrayRegion(array_i, start++, range, data+2);return array_i;}
extra.c
// extra.c#include "extra.h"#include "stdio.h"void print(){printf("In extra.c");}
extra.h
// extra.hvoid print();
注:这里编写程序有很多坑,要遵循jni定义的接口使用,各种数据类型的使用可以参照jni.h和网上的一些教程。我参考的是这篇:博客。
如果最后报错UnsatisfiedLinkError:方法名(),一般就是c++源文件函数实现有问题。
还有要注意的是,JNI里c++和c的函数实现方式是不同的,这里也会导致报错。比如env->...和*(env)->两种区别,多百度。
2.2 编译C文件
在命令行输入
g++ -fPIC -c helloworld.cc -I /usr/lib/jvm/java-8-openjdk-amd64/include -I /usr/lib/jvm/java-8-openjdk-amd64/include/linux/
这里 `-I` 后面跟的是需要include的文件地址,这里引入刚刚找到的`jni.h`和`jni_md.h`,得到`helloworld.o`中间文件。<br /><br />还需要编译`extra.c`文件:
g++ -fPIC -c extra.cc
将得到的helloworld.o和extra.o连接,生成动态链接库helloworld.so文件
g++ -shared helloworld.o extra.o -o helloworld.so
2.3 写一个makefile
写一个makefile自动编译,省的老是敲命令行
helloworld.so: helloworld.o extra.og++ -shared helloworld.o extra.o -o helloworld.sohelloworld.o: helloworld.cc HelloWorld.h extra.hg++ -fPIC -c helloworld.cc -I /usr/lib/jvm/java-8-openjdk-amd64/include -I /usr/lib/jvm/java-8-openjdk-amd64/include/linux/extra.o: extra.cc extra.hg++ -fPIC -c extra.ccclean:rm helloworld.o extra.o
注:在vscode里好像一个tab键和linux上的tab键不一样,如果makefile运行报错的话,就在linux上重新敲缩进,编译指令变红就ok了。
make 运行一下, make clean可以清除中间文件:
现在能一键生成.so文件了~
3. Java调用动态库函数
3.1 安装运行idea(Eclipse也行)报错处理
在安装的bin目录下运行./idea.sh启动idea,需要预装xmanager等图形界面程序。
出现报错java.awt.AWTError: Can't connect to X11 window server using 'localhost:12.0' as the value of the DISPLAY variable.
参考以下方法
// idea图形界面启动不了:vncserverexport DISPLAY=localhost:x // 看vnc启用的端口xhost +// 或者:exit// 重新登录
运行vncserver根据输出信息,或者ps -ef | grep vnc查看vnc服务的进程
我这边已经运行过vncserver进程了,所以采用后面的命令查看进程
可以看到在冒号后面的数字就是端口号,是1
运行 export DISPLAY=localhost:1
然后运行 xhost +
看到返回信息 access control disabled, clients can connect from any host就表示成功了
然后快乐地打开idea~
好像又出现了X Input extension isnt available, error: {0} java.lang.Throwable: toolbar creation trace 这样的报错
退出服务器,重新登录,然后就进去了。不知道什么原因,有可能是我上次登录之后没有退出?
3.2 写一个test程序,测试动态库
先把生成的helloworld.so文件移动到程序的目录下
测试文件编写如下
public class test {static {// System.load("/root/IdeaProjects/hw/src/libTEST.so");// System.load("/root/IdeaProjects/hw/src/librelocalization.so");System.load("/root/IdeaProjects/hw/src/helloworld.so");}public native String displayHelloWorld();public static void main(String []args){HelloWorld hw = new HelloWorld();// hw.displayHelloWorld();float[] coord = hw.getCoordF();// System.out.println(hw.getString());System.out.println(coord[0]+" " +coord[1]+" " +coord[2]);// hw.displayHelloWorld();}}
然后再在idea里选择添加动态库文件:`File-->Project Structure-->Libraries`添加生成的库文件,然后在程序入口初始化 `System.load("helloworld");`这个方法我在windows上可以,但是linux上不行,所以程序里我直接写了绝对地址,程序正常运行~~<br />
4. Windows平台调用动态库dll
与linux的动态库.so文件不同, windows上的动态库文件后缀是.dll。这个文件生成比较简单,可以直接在Visual Studio2017上新建DLL工程,注意要选择release模式下x64的generator。
生成dll文件后,用类似的方法导入java工程。
参考博客:链接
5. PS
摸索了两三天,踩了无数坑,感谢互联网上各种各样的教程!今天整理了下这些天总结的经验。

