Jni:架起 Java 和 C/C++的桥梁,Java Native Interface

来源:互联网 发布:从事数据可视化的公司 编辑:程序博客网 时间:2024/06/03 02:09

前言

正如标题所说,Jni(Java Native Interface) 把 Java 和 C/C++之间联系起来了。这样的话,Java可以直接调用C/C++语言编写的代码。Jni的主要问题在于破坏JVM的跨平台特征。Jni的优点在于:C/C++运行快一些;可以利用C/C++的历史遗留代码;有时候只能用C/C++来完成项目需求。


图:Java jni 示意图,引自华清远见的新浪博客。

Talk is cheap, show me the code. 下面的代码是在Linux环境下写的。总的来说,是一个五步曲,按顺序依次分为:Java代码、编译Java、C代码、编译C、运行。在关键部分做了详细的说明。

注:使用Linux操作系统,下面的第一~第五步中的文件和操作都是在同一个目录/home/Jni_example下面,即项目的根目录


第一步:Java代码

// 文件:/home/Jni_example/Hello.javapublic class Hello{        public native void sayHi(String who, int times);        static        {             System.loadLibrary("HelloC");        }        public static void main(String[] args)       {                Hello hello = new Hello();                System.out.println("Say hello to " + args[0] + " " + args[1] + " times." );                hello.sayHi(args[0], Integer.parseInt(args[1]));        }}

Java程序通过如下方法调用C的代码:

public native void sayHi(String who, int times);

Java程序加载C语言编写的动态链接库需要loadLibrary。这个动态库不能随便自己起名字!如果你在Java代码中System.loadLibrary(“HelloC”); 那么C的动态库的名字必须是libHelloC.so,从”HelloC” 到 “libHelloC.so”,一个字母不能多,一个字母不能少。

static{      System.loadLibrary("HelloC");}

其实还可以使用 System.load()方法,请看此博客最底下的讨论部分。


第二步:编译Java

前提是,你要安装好 Java JDK,不然没有 javac 和 javah 这两个编译程序

javac Hello.java     // 编译 Java 程序,生成Hello.classjavah -jni Hello     // 产生一个 Hello.h 头文件,这个头文件包含了关键的native接口函数的声明。

javah 自动生成的Hello.h头文件如下:

// 文件:/home/Jni_example/Hello.h/* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>/* Header for class Hello */#ifndef _Included_Hello#define _Included_Hello#ifdef __cplusplusextern "C" {#endif/* * Class:     Hello * Method:    sayHi * Signature: (Ljava/lang/String;I)V */JNIEXPORT void JNICALL Java_Hello_sayHi  (JNIEnv *, jobject, jstring, jint);#ifdef __cplusplus}#endif#endif

第三步:C代码

C程序 Hello.c,用来实现 Hello.h 里面的函数

// 文件:/home/Jni_example/Hello.c#include <stdio.h>#include "Hello.h"JNIEXPORT void JNICALL Java_Hello_sayHi(JNIEnv * env, jobject obj, jstring who, jint times){        jint i;        jboolean iscopy;        const char *name;        name = (*env)->GetStringUTFChars(env, who, &iscopy);        for(i = 0; i < times; i++)        {                printf("Hello %s\n", name);        }}

这里的 jint 和 jboolean 你可能没有见过。他们其实就是在 JAVA_HOME/include/jni.h 和 JAVA_HOME/include/linux/jni_md.h 头文件里面通过typedef定义的数据类型,你可以自己去看一看。

C程序和Java程序之间的参数传递是一个比较麻烦和复杂的事情。普通参数的传递方法,jobect参数的传递方法,Jni 大全


第四步:编译C

使用 gcc 编译C程序。

gcc -o libHelloC.so -fPIC -shared -I$JAVA_HOME/include -I$JAVA_HOME/include/linux Hello.c

-fPIC:告诉编译器产生与位置无关代码(Position Independent Code)
-shared:告诉编译器生成动态链接库,即共享库
JAVA_HOME/include 目录下面有 jni.h
JAVA_HOME/include/Linux 目录下面有 jni_md.h

编译结果是:在当前目录(即项目的根目录/home/Jni_example)下生成一个共享库,名字叫做libHelloC.so


第五步:运行

export 共享库,然后运行Java程序。如果忘记了export,则会出现UnsatisfiedLinkError的错误。

假设共享库的文件名是:/home/Jni_example/libHello.so
那么需要进行如下export:

export LD_LIBRARY_PATH=/home/Jni_example

值得注意的是,你可以把生成的libHelloC.so移到其它的目录(比如/usr/lib)下面去,实现共享库和源代码的分离。这样的话,你就要export LD_LIBRARY_PATH=/usr/lib。
 

最终运行Java程序,Caitao 和 5 是传给main方法的两个参数。java 找到 Hello.class文件,Hello.class调用libHello.so里面编译的C代码,成功运行。

java Hello Caitao 5

Jni Exmple 图片
图:运行图片。

参考网页:参考1,参考2


讨论

讨论1:System.load()和System.loadLibrary()的区别
这两种方法都可以导入共享库(.so 文件)。假设需要导入的共享库是 /home/Jni_example/libHelloC.so,那么load和loadLibrary的区别如下:

比较 System.loadLibrary() System.load() 方法的参数 “HelloC” /home/Jni_example/libHelloC.so” export 需要export LD_LIBRARY_PATH=”/home/Jni_example” 不需要export

 
总结一下:load是绝对路径,loadLibrary是相对路径。
 

讨论2:项目的java代码不直接在项目的根目录/home/Jni_example下面,而是在子目录里面
假设java文件是/home/Jni_example/com/caitao/test/Hello.java
此时 javah 应该如下操作:

cd /home/Jni_examplejavah -jni com.caitao.test.Hello

这样的话,会在项目的根目录下面生成 com_caitao_test_Hello.h 的头文件。
如果不这样做的话,会出现UnsatisfiedLinkError的错误。

 

讨论3:关于Makefile
步骤4中的在Linux环境下使用gcc编译C文件,也是一大学问,参考下面一个Makefile模板:

#变量定义target  := mytargetsources := $(wildcard *.c)    # 源代码是当前目录下面的所有C文件objects := $(sources:.c=.o)deps := $(sources:.c=.d)CC := gccRM := rm -rf#终极目标规则,一般是一个可执行文件,或者共享库$(target): $(objects)    $(CC) -o $@ $^#子规则,使用静态模式规则来简化很多子规则$(objects): %.o: %.c    $(CC) -o $@ -c $<#删除 .o 文件.PHONY: clean  #伪目标clean:    @$(RM) $(objects) $(deps)#依赖文件,必须写在最后sinclude $(deps)$(deps):%.d:%.c    $(CC) -MM $< > $@

一个学习Mafefile的很不错的教程,来自实验楼(这个实验需要会员)

 
讨论4:异常处理
Java程序通过JNI调用C/C++的程序的时候,如果C/C++的程序发生了异常,可能导致Java程序崩溃。此时此刻需要加上异常处理机制,防止Java程序崩溃。在Native代码中捕获与抛出的案例,这个关于JNI的博客写的不错。


last update: 2017/4/5

1 0