step by step-尊龙游戏旗舰厅官网
文章为本人编纂,转载请联系作者并注明出处。
在日常项目中,我们可能会遇到需要用java去命令行执行命令或执行shell脚本的情况,但有时可能又会因为某些环境或者权限等无法排查的原因调用失败,这时候就可以通过一个中间介质c来执行。尤其是在对某些项目代码(已经过广泛测试或需要访问特定设备)进行重写,java恐怕有些力不从心,而sun公司定义的jni规范,规定了java对本地方法的调用规则,这就大可不必废弃旧有代码。
以下将以一个实际例子展示java通过jni调用c打印“hello world!”主要记录实现的过程和方法,对其中的一些原理和规范不做具体展开。想深入了解的可以参考oracle的官方文档,贴上地址:
jni interface functions and pointers
环境介绍
操作系统:ubuntu gnome 16.04 lts
java:java 1.8.0_111
c:gcc version 5.4.0
实现步骤
hello world
1、定义一个java类——javacallc.java
首先定义一个java类javacallc.java,在类中实现一个sayhello方法,并用关键字native为本地方法编写本地声明;
public native void sayhello();然后在类中的静态代码块显示地加载本地代码库;
static {system.loadlibrary("hello"); //加载本地共享库 }再加上main方法和一些必要的异常处理程序,就生成以下源文件(当然,也可以将本地方法放在另外一个单独的类中)。
package com.jni.c;public class javacallc {/*** java通过jni调用c* @author xiaosong 2017-04-03*/public static void main(string[] args) {javacallc call = new javacallc();call.sayhello();} /*** 加载共享库的本地方法*/public native void sayhello();static {try {system.loadlibrary("hello"); //加载本地共享库}catch(unsatisfiedlinkerror e) {system.err.println("无法加载共享库:" e.tostring());}}}2、生成 java 本地接口头文件
p.s. 如果没有使用ide的,需先用 javac 将类编译为 .class 文件。
要为以上定义的类生成 java 本地接口头文件,需使用 javah,java 编译器的 javah 功能将根据 javacallc 类生成必要的声明,此命令将生成一个 .h 后缀的头文件,我们在共享库的代码中要包含它。在工程项目的编译文件 bin 目录(也可能是build)下执行如下命令():
执行命令后生成了一个 com_jni_c_javacallc.h 头文件,头文件的内容如下:
/* do not edit this file - it is machine generated */ #include3、创建共享库c文件
在与 com_jni_c_javacallc.h 相同的路径下创建一个 .c 文件 hello.c ,在c文件中引入该头文件 ,并使用和头文件中一致的方法来声明函数。内容如下:
记得要为 jnienv * 指针和 jobject 对象定义变量,习惯上将这两个变量定义为 env 和 obj 。
env 指针是任意一个本地方法的第一个参数,它指向一个函数指针表。jobject 指向在此 java 代码中实例化的 java 对象 localfunction 的一个句柄,相当于 this 指针。
4、编译生成共享库文件
编译文件时,需要告知 gcc 编译器在何处查找java本地方法的支持文件 jni.h 和 jni_md.h ,这两个文件一般是在 ../jdk/include 和 ../jdk/include/linux 两个目录下(aix在 ../jdk/include/aix 目录;windows在 ../jdk/include/win32 目录),在我的环境中按如下过程编译。
先生成 hello.o :
再生成 libhello.so :
(共享库 .so 的文件名必须是 lib 文件名)
拷贝 libhello.so 到共享库目录:
(共享库目录一般为 ../jre/lib/amd64/server)
5、运行java程序
由于我未配置 $ld_library_path 环境变量,所以程序无法加载到共享库 hello ,因而我改写成通过全路径的方式来加载共享库。
//system.loadlibrary("hello"); //加载本地共享库 system.load("/usr/lib/jdk1.8.0_111/jre/lib/amd64/server/libhello.so");运行结果如下:
传递参数
接下来看一下java如何通过jni向c传递参数。本文中仅以 string 字符串为例,其他类型的参数的处理可参考文首提供的oracle官方文档,方法大体上是一致的。
1、定义本地方法参数
先在声明的本地方法中定义参数:
public native void sayhello(string strname1, string strname2);然后在 main 方法中调用它并传递参数:
public static void main(string[] args) {javacallc call = new javacallc();call.sayhello("info", "xiaosong"); }2、编译并生成头文件
生成头文件的方法同上,这时候看一下生成的头文件有何区别。
/* do not edit this file - it is machine generated */ #include可以看到函数声明里多了两个 jstring ,这就对应于我们要传递的两个 string 参数。
其他数值型参数和数组型参数对照如下:
3、创建共享库c文件
同样地,在编写 hello.c 文件时,我们需要为传递的两个参数定义变量;
jniexport void jnicall java_com_jni_c_javacallc_sayhello (jnienv * env, jobject obj, jstring instring1, jstring instring2)对于字符串型参数,因为在本地代码中不能直接读取 java 字符串,而必须将其转换为 c /c 字符串或 unicode。此处c的写法和c 的写法略微不同;
/*** c 写法*/ //从instring字符串取得指向字符串utf编码的指针; const char *info = (*env)->getstringutfchars(env, instring1, 0); /*** c 写法*/ const char *info = env->getstringutfchars(instring1, 0);//通知虚拟机本地代码不再需要通过 info 访问java字符串;
/*** c 写法*/ (*env)->releasestringutfchars(env, instring1, info); /*** c 写法*/ env->releasestringutfchars(instring1, info);再加上一些简单的异常处理,完整的含参的 hello.c 如下:
#include "com_jni_c_javacallc.h" #include4、编译生成共享库文件
方法和操作同上
5、运行java程序
以下是调用 call.sayhello("information", "xiaosong"); 执行的结果:
以下是调用 call.sayhello("information", ""); 执行的结果:
至此,java通过jni调c的例子全部结束,当中如有什么不足或错误还请指正。
总结
以上是尊龙游戏旗舰厅官网为你收集整理的step by step_java通过jni调c程序执行的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇:
- 下一篇: css中常见的长度单位