Java中使用JNI

     前两天CSDN给我推送了一篇关于简单实用jni的博客,因为之前自己有接触过一点,所以感兴趣点进去看,看得一头雾水,跟自己印象中的使用不太一样。自己查了资料之后发现原来人家说的Java JNI和我所理解的Android JNI还是有一点点差别,原理一样,调用方式有区别。大三暑假在一家公司实习时做过android开发,组长叫我去学习这方面的内容,运用到android开发中。一方面,使用jni在运行效率上会有所提高(只是运行这部分的代码效率高哦,整体的话倒不见得,因为调用jni切换场景找库文件还是挺耗资源的);另一方面,很多反编译工具能直接获取apk里的关键信息,而使用JNI的话关键代码不可见,要破解至少需要将二进制文件读懂,安全程度自然比读懂高级代码高一些。
     J ava Native Interface (JNI)标准是 java平台的一部分,它允许 Java代码和其他语言写的代码进行交互。 JNI 是本地编程接口,它使得在 Java 虚拟机 (VM) 内部运行的 Java 代码能够与用其它编程语言 (CC++ 和汇编语言 )编写的应用程序和库进行交互操作(百度百科)。
     在Android中使用JNI技术,因为有NDK的存在工作变得简单很多。NDK实质上是对make工具的一层包装,暴露出简单的接口给你调用。首先,在windows环境下你需要安装cygwin、ndk等来配置一个编译链接库(.so共享库或者.a静态库)的环境。其次,按照Android使用JNI的流程编写代码,编译,运行。相关文档看以查看我另一篇 android下使用jni技术的笔记。
     在Java中使用JNI技术,就稍微要比Android的复杂一丢丢,因为很多东西没有工具帮你包装好,编译,链接到库这些你要自己来。在windows环境下的话,动态链接库的后缀为.dll,在没有其他工具辅助工具下要得到这个dll,就得去VS或者VC平台编译个,相关的资料有 推送的博文,和 这篇博文(比较推荐,虽然有点年代)。没有在Eclipse下捣鼓,因为链接到库的时候找不到插件库,懒得去折腾。直接在linux shell终端用vim来写个简单的demo。

     环境:CentOS6.5 x86_64, oracle jdk1.7
     工具:gcc,vim
     步骤1:
     建立java文件,在我的例子中为Hello.java。为了方便,我在用户主目录(/home/<用户名>/)下建了个jniDemo的目录,所有文件直接都放在这里,没有在代码中package到哪个包目录下。
Java中使用JNI_第1张图片
     步骤2:在jniDemo目录下,使用javac编译Hello.java,生成Hello.class文件。
     步骤3:在jniDemo目录下,使用javah生成头文件,命令是“javah -jni <类的路径>.<类名>”。由于前面已经介绍过,我的java代码没有package,直接就是在jniDemo目录下,因此直接使用类名即可,即“javah -jni Hello”,其中“Hello”就是Hello.java文件中的public class Hello中的Hello类(够啰嗦了吧!)
Java中使用JNI_第2张图片
     自动生成的头文件内容如下。代码中使用的<jni.h>头文件在jdk的include目录下,编译的时候需要指定,而且jni.h里面很多头文件和字段定义是在上述include/linux下(如果是windows平台就是include/win32或者按照实际的来)。至于里面一些其他类型的定义,比如jstring,jbyte其实都在jni.h和关联的其他头文件中有typedef定义,就是C/C++中的对应的类型。
Java中使用JNI_第3张图片
     步骤4:新建一个.c文件,命名随意,代码如下:
Java中使用JNI_第4张图片
     这里值得注意的是,一开始我按照网上代码写成“env->GetStringUTFChars(str, 0)”总是不奏效,编译时提示没有“GetStringUTFChars”和“ReleaseStringUTFChars”这个结构或者Union类型,去头文件查看之后发现需要的参数都对应不上,网上许多都是两个参数,头文件中却有三个参数。后来在网上找到该问题的 解决办法,谢谢博主~原来在linux下如果是.c文件则使用“(*env)->”,在.cpp文件中则用“env->”。
     
    步骤5:使用gcc编译.c文件,其中-I后面接头文件目录,就是在步骤3中所述的那两个。-shared表示编译常共享库,也即动态库,类似windows下的dll。要注意的是你所编译的库的名字要跟你的java代码中loadLibrary中的库名对应,规则是“lib+库名+.so”。比如在步骤1中我在代码中的库名为jniLib,则在此步骤编译时编译的库为“libjniLib.so”。至于箭头所指的-fPIC参数,一开始没有这个参数,编译出错并提示我要加上去,加上去之后编译成功。
Java中使用JNI_第5张图片
     步骤6:运行程序,直接在jniDemo目录下,运行“java Hello”,前提是在步骤2的时候已经编译生成.class文件。运行结果,po~fect!
Java中使用JNI_第6张图片

      拓展

    上述Demo参考了
      https://www.ibm.com/developerworks/cn/java/l-linux-jni/
      http://blog.csdn.net/hackooo/article/details/48395765
     尤其是链接2,后半部分对java调用jni库的追踪分析,挺精彩的。原来是按照绝对路径-classLoader-sys_paths-usr_paths一级一级的路劲去搜索库文件,然后调用的都是classLoader.loadLibrary()方法,追踪到最后,都是调用系统的操作动态库的库函数实现的,比如dlopen打开库文件,dlsys根据标志符号找到函数地址等等。

你可能感兴趣的:(java,jni)