Java JNI开发
来源:互联网 发布:网络语233333什么意思 编辑:程序博客网 时间:2024/06/07 19:38
Java JNI开发
JNI,即Java Native Interface,字面意思“Java本地接口”,这里的本地接口,指的就是c/c++开发的接口。由于JNI是JVM规范的一部分,因此我们写的JNI程序可以在任何实现了JNI规范的Java虚拟机中运行(跨平台)。这里先粗略了解一下Java的JNI开发,为学习Android NDK开发做准备。
为什么要JNI开发
- 标准Java类库不支持与平台相关的应用程序所需的功能;
- 已经拥有了一个用另一种语言实现的库,而有希望能通过JNI使Java代码能够访问该库;
- 想利用低级语言(如汇编语言)来实现一小段限制代码。
JNI编程能干什么
- 创建、检查及更新Java对象(包括数组和字符串);
- 调用Java的方法;
- 捕捉和抛出异常;
- 加载和获得类信息;
- 执行运行时类型检查;
- 与Java Api调用一同使用,使得任意本地应用程序可以潜入到Java虚拟机中。
JNI开发流程
JNI开发流程的主要步骤:
- 编写声明了native方法的Java类;
- 将Java源代码编译成class字节码文件;
- 用javah -jni命令生成.h头文件(javah是jdk自带的一个命令,-jni表示将class中用native声明的函数生成jni规则的函数)
- 用本地代码实现.h头文件中的函数(即用c或c++编写.h头文件中声明的方法的实现)
- 将本地代码编译成动态库(Windows环境下编译成*.dll文件;Linux/Unix下编译成*.so;Mac下编译成*.jniLib)
- 拷贝动态库只java.library.path本地库搜索目录下,运行Java程序即可;
示例
下面将以一个HelloWorld的实例来说明上述过程
本机环境macOs High Sierra 10.13,编辑器IDE采用的是IntelliJ IDEA 2016.3.7
编写声明了native方法的Java文件
HelloWorld.java
package com.rainmonth;import java.io.IOException;import static com.rainmonth.Utils.addLibraryDir;public class HelloWorld { public static void main(String[] args) throws IOException { } public native String sayHello(String name);}
上面定义了一个native方法sayHello
编译生成class字节码文件
javac -d out/production/JniLearn/ src/com/rainmonth/HelloWorld.java
运行上面命令,即可在项目根目录的bin/com/rainmonth生成HelloWorld.class文件注意:bin如果之前不存在,需要手动创建,不然会提示找不到目录:bin
温馨提示:可以直接输入javac查看该命令的使用帮助
生成对应的.h头文件
这一步需要用到javah命令,先看看该命令的用法,终端输入javah
用法: javah [options] <classes>其中, [options] 包括: -o <file> 输出文件 (只能使用 -d 或 -o 之一) -d <dir> 输出目录 -v -verbose 启用详细输出 -h --help -? 输出此消息 -version 输出版本信息 -jni 生成 JNI 样式的标头文件 (默认值) -force 始终写入输出文件 -classpath <path> 从中加载类的路径 -cp <path> 从中加载类的路径 -bootclasspath <path> 从中加载引导类的路径<classes> 是使用其全限定名称指定的(例如, java.lang.Object)。
所以如果你想在根目录的lib下生成HelloWorld.h文件,输入
javah -o lib/HelloWorld.h -classpath bin/ com.rainmonth.Hello
输入后lib目录下得到文件HelloWorld.h
如果你想采用默认的命名方式,只想输出到lib目录,输入
javah -d lib/ -classpath bin/ com.rainmonth.HelloWorld
输入后lib目录下得到文件com_rainmonth_HelloWorld.h(这是该命令默认生成文件名的命名方式)
注意:上面的-classpath是用来指定存放.class文件的目录的,.class文件在输入是不用带后缀
用本地代码实现.h头文件中的函数
这里采用c实现(也可采用c++实现,二者稍许不同),在lib目录下新建文件HelloWorld.c
// HelloWorld.c#include "HelloWorld.h"#ifdef __cplusplusextern "C"{#endif/* * Class: com_rainmonth_HelloWorld * Method: sayHello * Signature: (Ljava/lang/String;)Ljava/lang/String; */JNIEXPORT jstring JNICALL Java_com_rainmonth_HelloWorld_sayHello (JNIEnv *, jobject, jstring){ const char *c_str = NULL; char buff[128] = { 0 }; c_str = (*env)->GetStringUTFChars(env, j_str, NULL); if (c_str == NULL) { printf("out of memory.\n"); return NULL; } (*env)->ReleaseStringUTFChars(env, j_str, c_str); printf("Java Str:%s\n", c_str); sprintf(buff, "hello %s", c_str); return (*env)->NewStringUTF(env, buff);}#ifdef __cplusplus}#endif
将本地代码编译成动态库
这一步要用到gcc命令,
上面已经说到了,不同的系统对应的生成命令不同
Mac OS
gcc -dynamiclib -o lib/libHelloWorld.dylib lib/HelloWorld.c -framework JavaVM -I /Library/Java/JavaVirtualMachines/jdk1.8.0_45.jdk/Contents/Home/include/ -I /Library/Java/JavaVirtualMachines/jdk1.8.0_45.jdk/Contents/Home/include/darwin/
注意,这里只是为了说明才这样写的,实际上命令见用空格隔开即可( -I甚至都不用空格)。
执行完毕后,lib目录下就得到了libHelloWorld.dylib文件,这就是我们的动态库。
命令参数讲解
-dynamiclib:表示要生存动态库
-o:表示要生成的库文件的名字
lib/HelloWorld.c:指的是实现代码对应的文件
-I:表示生成动态库所要引用的头文件(第一个是平台无关的,第二个是平台相关的,可以打开对应文件看看里面到底是什么鬼。
Windows
该平台我尚未尝试,这里贴上别人的说明(以Windows7下VS2012为例)
开始菜单–>所有程序–>Microsoft Visual Studio 2012–>打开VS2012 X64本机工具命令提示,用cl命令编译成dll动态库:
cl -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -LD HelloWorld.c -FeHelloWorld.dll
其中,JAVA_HOME为jdk的安装路径,其他参数选项说明如下:
-I : 和mac os x一样,包含编译JNI必要的头文件
-LD:标识将指定的文件编译成动态链接库
-Fe:指定编译后生成的动态链接库的路径及文件名
Linux/Unix
gcc -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -fPIC -shared HelloWorld.c -o lib/libHelloWorld.so
参数说明:
-I: 包含编译JNI必要的头文件
-fPIC: 编译成与位置无关的独立代码
-shared:编译成动态库
-o: 指定编译后动态库生成的路径和文件名
运行java程序
将第一步中的HelloWorld.java修改如下:
package com.rainmonth;import java.io.IOException;public class HelloWorld { static {// 采用静态代码引入动态库 System.loadLibrary("HelloWorld"); } public static void main(String[] args) throws IOException { HelloWorld helloWorld = new HelloWorld(); System.out.println(helloWorld.sayHello("Hello")); } public native String sayHello(String name);}
运行结果:
Exception in thread "main" java.lang.UnsatisfiedLinkError: no HelloWorld in java.library.path at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1865) at java.lang.Runtime.loadLibrary0(Runtime.java:870) at java.lang.System.loadLibrary(System.java:1122) at com.rainmonth.HelloWorld.<clinit>(HelloWorld.java:8) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:264) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:123)
意思是,在java.library.path中找不到对应动态库,也就是说动态库的位置可能存在问题。这稍后再解决,我们换一种方式引入动态库,采用绝对路径来引入试试:
package com.rainmonth;import java.io.IOException;public class HelloWorld { static {// 采用静态代码引入动态库// System.loadLibrary("HelloWorld"); System.load("/Users/RandyZhang/IdeaProjects/JniLearn/lib/libHelloWorld.dylib"); } public static void main(String[] args) throws IOException { HelloWorld helloWorld = new HelloWorld(); System.out.println(helloWorld.sayHello("Hello")); } public native String sayHello(String name);}
得到了我们想要的结果
hello HelloJava Str:Hello
那面上面抛出异常是什么原因引起的呢,然来在java.library.path下找不到我们的动态库文件,这时我们只要将生成的库文件复制到任意一个java.library.path下即可(这个java.library.path可以通过如下方式获取)
public static void main(String[] args) { System.out.println(System.getProperty("java.library.path")); }
那很快我们就想到了用System.setProperty(“java.library.path”, pathname)来动态设置这个值,很抱歉,在下尝试了,并没有成功,看了相关的代码你会发现,这个值只在JVM启动时初始化一次:
if (sys_paths == null) { usr_paths = initializePath("java.library.path"); sys_paths = initializePath("sun.boot.library.path");}
你后面通过set的方式没有生效,当然有大神已经通过反射的方式解决了这一问题:
public static void addLibraryDir(String libraryPath) throws IOException { try { Field field = ClassLoader.class.getDeclaredField("usr_paths"); field.setAccessible(true); String[] paths = (String[]) field.get(null); for (int i = 0; i < paths.length; i++) { if (libraryPath.equals(paths[i])) { return; } } String[] tmp = new String[paths.length + 1]; System.arraycopy(paths, 0, tmp, 0, paths.length); tmp[paths.length] = libraryPath; field.set(null, tmp); } catch (IllegalAccessException e) { throw new IOException("Failed to get permissions to set library path"); } catch (NoSuchFieldException e) { throw new IOException("Failed to get field handle to set library path"); } }
通过调用这个方法动态的添加库目录到usr_paths,当然局限性就是若表示库目录的变量(即usr_paths)修改了,这种方式就失效了。
我们可以通过配置vmOptions来动态制定java.library.path的值,如:
-Djava.library.path="lib库目录"
具体位置就在Edit Configurations下,或者直接运行命令也可:
java -Djava.library.path=/Users/RandyZhang/IdeaProjects/JniLearn/lib -classpath ./bin/ com.rainmonth.HelloWorld
总结
- Mac系统下编译成.so文件时,采用绝对路径加载动态库同样也可以;
- 注意设置和修改java.library.path属性的不同方式
- 要熟悉Java常用的命令行的使用,具体可以查看相应命令的帮助选项。
- java JNI开发
- java JNI开发
- java JNI开发
- java JNI开发
- java jni 开发
- java 开发 jni
- Java JNI开发
- jni 开发 c调用java
- JAVA JNI开发应用实例
- 利用JNI实现JAVA插件开发
- JNI开发中Java与C++联调
- Java跨平台开发神器之JNI
- 安卓开发 java调用jni
- JNI开发之访问java的实例
- JNI开发
- JNI开发
- jni开发
- JNI开发
- MXNet官方文档中文版教程(9):大规模图像分类
- 高并发下的抽奖优化
- C++动态库与静态库
- 堆和栈的区别
- RIP和OSPF详解与对比
- Java JNI开发
- 快速排序
- Python之xml解析
- 插入排序
- 彻底的卸载干净oracle 11g
- java中的fail-fast(快速失败)机制
- 【JAVA_SE学习笔记】Scanner类
- T1 fibonacci
- Eclipse自动补全设置与Eclipse源代码下载