JAVA 调用 c语言动态库教程
1、首先我们先用eclipse建立项目,项目名为JNIDemo
建好项目之后,在src目录下创建我们的包com.fox.main
- 在com.fox.main包中定义两个Java类,一个是Main类,一个是Washer类。
package com.fox.main; public class Main { public static void main(String[] args) { Washer washer = new Washer(); if (!washer.is_Open()) { System.out.print("washer closed"); } } } |
package com.fox.main; public class Washer { public Washer(){} public native boolean open();//打开 public native boolean close();//关闭 public native boolean is_Open();//是否已经打开 public native boolean is_error();//设备是否出错 static{ System.loadLibrary("washer");//加载c动态库,注意这里没有lib前缀和.so后缀 //System.load("/usr/lib/libwasher.so");//使用绝对路径加载c动态库 } } |
- 进入Washer所在目录,使用javac命令将Washer.java编译为Washer.class文件
fox@fox-QTS5:~/workspace/JNIDemo/src/com/fox/main$ javac Washer.java |
- 退到src目录下,使用javah命令将Washer.class编译为C语言的头文件
注意:这里我们编译Washer.class时不需要写.class后缀名。
(文件名会根据包名的不同而不同,这也就是我们为什么要退到src目录下的原因)
fox@fox-QTS5:~/workspace/JNIDemo/src$ javah com.fox.main.Washer |
- 这样就在src目录下产生了c语言的头文件com_fox_main_Washer.h,我们需要自己编写.c源文件来实现该头文件中的函数。
注意:源头文件中的函数声明可能没有形参的名字 JNIEXPORT jboolean JNICALL Java_com_fox_main_Washer_open (JNIEnv *, jobject); |
我们在定义该函数时应该添加形参名字,即使我们不使用形参也要添加,否则编译不通过 JNIEXPORT jboolean JNICALL Java_com_fox_main_Washer_open (JNIEnv *env, jobject jobj){ //实现代码 return 0; } |
- 这里对源文件的名字没有要求,但为了便于识别,我们将源文件的名字写为:com_fox_main_Washer.c。
#include "com_fox_main_Washer.h"
#ifdef __cplusplus extern "C" { #endif
JNIEXPORT jboolean JNICALL Java_com_fox_main_Washer_open (JNIEnv *env, jobject jobj){ return 0; }
JNIEXPORT jboolean JNICALL Java_com_fox_main_Washer_close (JNIEnv *env, jobject jobj){ return 1; }
JNIEXPORT jboolean JNICALL Java_com_fox_main_Washer_is_1Open (JNIEnv *env, jobject jobj){ return 0; }
JNIEXPORT jboolean JNICALL Java_com_fox_main_Washer_is_1error (JNIEnv *env, jobject jobj){ return 0; } #ifdef __cplusplus } #endif |
- 编写完我们的.c源文件后,我们需要将其编译为.o的目标代码
注意,这里需要指定-fPIC选项来说明要编译为与位置无关的目标代码。
-I/usr/lib/jvm/java-8-oracle/include选项用来引入jni.h这个头文件,这个头文件在java的jdk目录下,自己可以根据自己的文件路径进行相应的修改。
-I/usr/lib/jvm/java-8-oracle/include/linux选项也是引入头文件jni_md.h。
fox@fox-QTS5:~/workspace/JNIDemo/src$ gcc -fPIC -c com_fox_main_Washer.c -I/usr/lib/jvm/java-8-oracle/include -I/usr/lib/jvm/java-8-oracle/include/linux |
- 我们在通过gcc将目标代码编译为.so的动态库
fox@fox-QTS5:~/workspace/JNIDemo/src$ gcc -shared -o libWasher.so com_fox_main_Washer.o |
-shared 选项是将目标代码编译为共享库也就是动态库, -o 表示输出文件名
根据Linux的动态库命名规范我们应将动态库命名为:libXXX.so的形式。例如这里的libWasher.so。而在Java代码中加载是则不需要带lib前缀。 后边跟的是要编译的目标代码。
到此,动态库就制作完成了,但是当你通过eclipse运行Main中的主程序时会报异常,因为是我们自己做到,没有配置到系统目录,这样Java程序找不到它就报异常。
- 将我们制作好的动态库拷贝到系统库,当然得需要root权限。
fox@fox-QTS5:~/workspace/JNIDemo/src$ sudo cp libWasher.so /usr/lib |
配置好之后就可以通过eclipse运行Java程序了,正常显示应打印:washer closed
通过eclipse导出可运行的Java程序
- 首先我们需要将Java项目的原文件打包成可执行的.jar包。
在项目列表中选中要打包的项目,右键,点击export选项。 选择Java中的Runnable JAR File选项,点击 下一步 选中运行配置的main方法类,在export destination中选择导出路径和导出的文件名, 也就相当于exe文件。在下边选择export required libraries选项。点击finish完成。 |
- 然后我们就可以通过java命令执行该程序了,在导出的.jar文件目录下打开终端,执行命令java -jar 文件名.jar来执行该程序。
fox@fox-QTS5:~/download$ java -jar washer.jar |
- 我们可以将该命令写进脚本中,配置开机启动项。
写启动脚本文件vim washer
#! /bin/sh java -jar /home/fox/washer.jar |
将这个脚本文件中丢到/etc/init.d/目录中并添加可执行权限
sudo mv ./washer /etc/init.d/ sudo chmod 715 xxxx |
在/etc/rc_n.d目录中添加向相应的软连接。这里添加到/etc/rc3.d目录中。
注意这里rc3.d中的命名规范:S_nn_服务名,K_nn_服务名
S表示该级别启动时需要开启的服务,K表示级别启动时需要关闭的服务
ln -s /etc/init.d/washer /etc/rc3.d/S03washer |
测试服务是否正常,在rc3.d中执行脚本
./S03washer |
如果成功打印信息说明脚本配置成功。
通过命令行导出可运行的Java程序
由于设备无法显示的原因,当我们通过命令行来进行Java程序开发的时候,如何将其编译成可运行的.jar包。
1、Java程序的编写,建立包名com.fox.main,其对应的目录为/com/fox/main
在com/fox/main中添加两个Java文件
Main.java文件 package com.fox.main; public class Main { /** * @param args */ public static void main(String[] args) { Washer washer = new Washer(); if (!washer.is_Open()) { System.out.print("washer closed"); }else{ System.out.print("washer opened"); } } } |
Washer.java文件 package com.fox.main; public class Washer { public Washer(){} public native boolean open(); public native boolean close(); public native boolean is_Open(); public native boolean is_error(); static{ System.load("/usr/local/lib/libWasher.so");//注意这里的绝对路径 } } |
- 编译这两个文件
先说一下目录结构src/com/fox/main/,在src目录下执行命令
javac com/fox/main/*.java |
上面这条指令直接将com.fox.main中的所有Java文件一起编译,最好这样编译,因为当各个类中有依赖关系的时候,单独编译会报错。
- 生成C语言的.h文件,在src目录下
Javah com.fox.main.Washer |
上面这条指令会将com/fox/main/Washer.class文件编译出com_fox_main_Washer.h文件并保存到当前目录(也就是src目录中)
- 编写.c文件实现头文件中的所有函数,最好通过下面的方式实现,因为.h文件中的函数名很长,自己写容易写错。
cp com_fox_main_Washer.h com_fox_main_Washer.c //将.h文件复制一份 vim com_fox_main_Washer.c //修改里面的代码 |
修改后的代码如下 #include "com_fox_main_Washer.h"
#ifdef __cplusplus extern "C" { #endif
JNIEXPORT jboolean JNICALL Java_com_fox_main_Washer_open (JNIEnv *env, jobject obj){ return 1; }
JNIEXPORT jboolean JNICALL Java_com_fox_main_Washer_close (JNIEnv *env, jobject obj){ return 0; }
JNIEXPORT jboolean JNICALL Java_com_fox_main_Washer_is_1Open (JNIEnv *env, jobject obj){ return 1; }
JNIEXPORT jboolean JNICALL Java_com_fox_main_Washer_is_1error (JNIEnv *env, jobject obj){ return 0; }
#ifdef __cplusplus } #endif |
注意:源头文件中的函数声明可能没有形参的名字 JNIEXPORT jboolean JNICALL Java_com_fox_main_Washer_open (JNIEnv *, jobject); |
我们在定义该函数时应该添加形参名字,即使我们不使用形参也要添加,否则编译不通过 JNIEXPORT jboolean JNICALL Java_com_fox_main_Washer_open (JNIEnv *env, jobject jobj){ //实现代码 return 0; } |
- 将com_fox_main_Washer.c编译为.o的目标代码。
gcc -fPIC -c com_fox_main_Washer.c |
这里的-fPIC选项是将代码编译为与位置无关的目标代码。
这样当多个进程使用该动态库时,只需要加载一份就可以了。
6、然后将目标代码编译为动态库
gcc -shared -o libWasher.so com_fox_main_Washer.o |
这是就生成了libWasher.so的动态库
我们可以将
通过命令行打包Java程序
1、首先我们先编译Java项目
javac com/fox/main/*.java |
- 编写Java启动的配置文件MANIFEST.MF
Manifest-Version: 1.0 Class-Path: . Main-Class: com.fox.main.Main |
- 在src目录下打包Java程序
jar cvfm Washer.jar ./MANIFEST.MF com/ |
会在src目录中生成Washer.jar的文件
- 运行该Java程序
java -jar Washer.jar |