编译JNI的动态库, 遇到的坑

来源:互联网 发布:中国与大数据 编辑:程序博客网 时间:2024/06/05 02:15

一. 前言

最近工作中,需要编译JNI的动态库.这个动态库,要被Java调用.


JNI的定义,在维基百科[1]中这样介绍:

当你遇到一些情况, 不能完全使用Java语言完成开发的时候, JNI允许程序员编写native methods去解决问题. 很多特定的平台特征和标准库都是基于JNI开发的(PS:图像处理领域,很多都是C/C++开发的库,例如opencv). Java程序通过JNI访问其他语言提供的方法, 同样其他语言也可以通过JNI去访问Java对象.


原文如下:

JNI enables programmers to write native methods to handle situations when an application cannot be written entirely in the Java programming language, e.g. when the standard Java class library does not support the platform-specific features or program library. It is also used to modify an existing application (written in another programming language) to be accessible to Java applications. Many of the standard library classes depend on JNI to provide functionality to the developer and the user, e.g. file I/O and sound capabilities. Including performance- and platform-sensitive API implementations in the standard library allows all Java applications to access this functionality in a safe and platform-independent manner.

The JNI framework lets a native method use Java objects in the same way that Java code uses these objects. A native method can create Java objects and then inspect and use these objects to perform its tasks. A native method can also inspect and use objects created by Java application code.



二. Java调用JNI的典型过程[2]

1.首先, 在Java中编写调用C代码的方法

(1) 使用native关键字声明方法,

(2) 使用javac命令, 生成class文件. 然后使用javah命令去生成.h文件

(3) 新建.c文件, 去实现.h中的函数

(4) 将.h和.c文件, 编译成.so库

(5)确保Java加载了so库


java代码如下:

class HelloWorld{public native void displayHelloWorld();static{System.loadLibrary("hello");}public static void main(String[] args){new HelloWorld().displayHelloWorld();}}


使用javah命令,  生成的.h文件:    (注意,实际操作中,需要根据路径来使用javah,否则生成.h失败)

# javac  HelloWorld.java    (得到 java class 文件)

# javah  HelloWorld           (得到 HelloWorld.h 文件)


HelloWorld.h 文件如下:

#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);#ifdef __cplusplus}#endif#endif


HelloWorldImp.c 如下:

#include <jni.h>#include "HelloWorld.h"#include <stdio.h>JNIEXPORT void JNICALLJava_HelloWorld_displayHelloWorld(JNIEnv *env, jobject obj){printf("Hello world!\n");return;}


编译成动态库:libhello.so

# gcc -shared HelloWorldImpl.c -o libhello.so


三. 细说填坑的事情

说了半天,终于到填坑的重点了。以下填坑的故事,都是血泪史啊!!!

感谢张工,感谢老大哥,否则真的爬不出来了。


问题0 : 准备工作

请不要在32位和64位上踩坑!

请确保64位工作环境,包括操作系统是64位的,gcc是64位的,JVM是64位的。

JVM最好是Oracle公司的,最好1.7以上版本。



问题1 : 找不到库

报错信息:java.lang.UnsatisfiedLinkError: Library hello-jni not found

解决方案

(1)将库放入/usr/lib下

(2)运行此行代码 System.out.println(System.getProperty("java.library.path"));然后根据输出的地址,放入对应的路径下即可。

(3)将库放入eclipse指定读取native library的目录下,可以在配置项中进行配置,配置如下图所示,图片来自文献[5]:





问题2 : 找不到方法

报错信息: java.lang.UnsatisfiedLinkError: Native method not found

解决方案

(1)首先检查,库是否正常加载,库是否更新,不要忘记删除旧的lib.

(2)检查函数名称是否正常,函数实现是否存在,如果没有实现,则添加实现函数代码.

(3)检查.h文件和.c文件中,函数名称是否一致,参数是否一样多.

(4)命名规则是否弄错,包括大小写都必须与包名一致,C代码一定要与java代码的包名,类名匹配.



问题3 : 未定义,找不到符号表

报错信息:Exception in thread "main" java.lang.UnsatisfiedLinkError: xxx undefined symbol xxx

解决方案

(1)删除,或者注释掉,报错提示的相关函数。因为你可能真的没定义,而且没用到。

(2)如果你用到了,不能删除,怎么办?那就缺什么,加什么,把依赖库加上。例如在编译的时候,最后面加上 -lpthread,如果缺少头文件,就添加头文件即可。请熟练使用man命令。

(3)什么?到了这里,问题还没解决!别急,还有办法。请仔细检查,你用到函数所在的.c文件,是否已经生成了目标文件(.o文件),并且已经将此.o文件,链接进入了最终的.so文件。即 :.so文件没有把所有需要的库都链接上。或者使用了库中定义的实体,但没有指定库(-lXXX)或者没有指定库路径(-LYYY),会导致该错误。具体更进一步的说明,请看文献[3]。

(4)我们需要注意多个库文件链接顺序问题,在链接命令中给出所依赖的库时,需要注意库之间的依赖顺序,最基础的库要放在最右边,依赖其他库的库一定要放到被依赖库的前面(左边),这样才能真正避免undefined reference的错误,完成编译链接。[4]



问题4 :运行时,库方法抛异常(最难填的坑,来了,血泪史在此啊!)

报错信息:

Exception in thread "main" java.lang.UnsatisfiedLinkError: com.example.program.ClassName.foo()V


或者这样报错:

Exception in thread "main" java.lang.UnsatisfiedLinkError:TestJNI.jgd_function()V

at TestJNI.jgd_function(Native Method)

at Test.jgd_function(FedExTest.java:12)

at main.main(main.java:5)


问题环境复现

(1)库已经放置正确位置,而且库名正确,可以加载得到

(2)函数名正确,函数已经实现

(3)使用 # ldd -r xxx 检查,一切正常,而且没发现 undefine的方法,甚至用nm命令,也没有异常信息


解决方案:

什么?这种问题,你都有解决方案!是真的吗?不是骗人的吧!

是的,把你的下巴收起来,哥就是这么牛逼!往下看。


出现问题的原因分析:

(1)首先,保证没有32位和64位的环境问题和版本问题(见问题0)

(2)然后需要保证,没有出现动态库(.so)依赖静态库(.a)的情况(见问题5)

(3)请检查,库所在路径都被正确加入了makefile里面,makefile里需要添加-fPIC选项

(4)请检查,依赖库是否按顺序,从左至右填写。

(5)分析到此,如果还没解决,说明这个.so文件,是有问题的,不能被Java代码正确的调用!

(6)你一定想问,这个.so有问题,该怎么解决?如果是第三方的,请直接咨询。如果是自己写的,那就好办了,想怎么改都行啊,先都做成.o文件,然后再链接成.so文件即可



问题5 : 动态库依赖静态库的问题

解决方案

(1)请慎重考虑使用这种方案,建议将源文件,用统一的编译命令进行处理,统一生成.o文件,在统一链接成一个.so文件,不要生成.a文件了,容易出问题,而且无法排查问题。

(2)如果一定要这么做,建议用相同的编译参数处理。




问题6 : JVM崩溃、JVM报错

报错信息:

## A fatal error has been detected by the Java Runtime Environment:##  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x0000000e, pid=6240, tid=1404## JRE version: Java(TM) SE Runtime Environment (8.0_11-b12) (build 1.8.0_11-b12)# Java VM: Java HotSpot(TM) Client VM (25.11-b03 mixed mode windows-x86 )# Problematic frame:# C  0x0000000e## Failed to write core dump. Minidumps are not enabled by default on client versions of Windows## If you would like to submit a bug report, please visit:#   http://bugreport.sun.com/bugreport/crash.jsp# The crash happened outside the Java Virtual Machine in native code.# See problematic frame for where to report the bug.#

解决方案

(1)经过多次反复实践,出现这种异常大约只有一种情况,就是C代码出现内存泄漏。

例如指针为空,野指针,使用了未初始化的指针等等。

从内存的角度去排查问题就行了,具体代码,具体分析。

(2)由于JNI的代码,会出现通过env指针,进行Java与C的交互,所以Java代码如果编写有问题(例如Java里面的类定义改了,新增或者删除了某个类等等),会造成C代码获取Java对象时,获取失败,造成空指针的情况,进而出现内存访问异常,造成JVM的报错异常。此时,需要修改Java代码,或者修改C代码才能解决问题。



四. 参考文献:

[1] https://baike.baidu.com/item/JNI/9412164?fr=aladdin

[2] https://www.cl.cam.ac.uk/teaching/0910/CandC++/lecture8-6up.pdf

[3] http://blog.csdn.net/cserchen/article/details/5503556

[4] http://blog.csdn.net/aiwoziji13/article/details/7330333/

[5] http://blog.csdn.net/larrylgq/article/details/7515362

原创粉丝点击