问题背景
之前的文章中,笔者将超长整数的四则运算利用C语言实现,因个人需要在web项目中使用该功能,
此时能想到的办法是重写实现过程,即利用Java重写一遍C的实现过程
不谈工作量的多少,单单是这个重写的过程就让我望而生畏,程序员最头疼的一个是bug找不到,还有一个就是重复劳动。
解决思路:
一个很偶然的机会让我看到了不需要重复劳动的希望--JNI
由于之前从来没有跨语种合作一个工程的经验,因此本篇将记录个人的实践过程
解决过程:
1、导入JNI的头文件jni.h和jni_md.h
用“导入”这个词其实就是一个包含关系,在实际编码过程中用户可以选择将两个头文件复制到本地项目工程目录或者根本就不用做任何移动操作
该文件位于JDK安装路径的include目录下(其实不止一处,开发人员可以根据自己的系统winows/Linux搜索)。
2、Java侧编写native方法
package org.demo.jni; public abstract class ICalcHBInteger { public native int init_HBInt(); public native void input_HBInt(String in); public native String output_HBInt(); public native String add_HBInt(String a1,String a2); public native String sub_HBInt(String s1,String s2); public native String mul_HBInt(String m1,String m2); public native String div_HBInt(String d1,String d2); public native String mod_HBInt(String mod1,String mod2); public native String powerMod_HBInt(String p1,String p2, String p3); }
这些方法的名称自定义,对参数个数、类型没有任何限制,但是一定要加上native关键字
(注:示例中用的是抽象类,这只是笔者个人习惯,不是必须的,同时在类中也可以定义其它非native 的方法,用户自行斟酌)
3、利用javah命令生成.h头文件
利用定义好的含有native方法类进行转换,改转换由javah命令完成
即:javah -jni org.demo.jni.ICalcHBInteger
正确执行后,会在当前执行命令的目录下生成org_demo_jni_ICalcHBInteger.h文件
文件内容类似如下:
/* * Class: org_demo_jni_ICalcHBInteger * Method: output_HBInt * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_org_demo_jni_ICalcHBInteger_output_1HBInt (JNIEnv *, jobject);
4、在自定义的C文件中实现该头文件定义的方法
生成了头文件后,就是对头文件声明的方法进行实现,此时因为在C侧已经完成了主要的方法(笔者采用的是C实现超长整数),
所以只需要在自定义的C文件中利用已经实现的方法来填充
Java_org_demo_jni_ICalcHBInteger_output_1HBInt
例如,笔者自定义的是HBIntegerVM.c文件,而已经实现的超长整数文件名为HBInteger.c(其对应的头文件是HBInteger.h)
HBIntegerVM.c内容如下:
JNIEXPORT jstring JNICALL Java_com_zsf_jni_ICalcHBInteger_output_1HBInt( JNIEnv *env, jobject obj) { int ret; ret = writeHBInt(&last, result); if (ret) { printf( "Error in exec func [writeHBInt(&last, result.\n"); } return nativeTojstring(env, result); }
需要说明的是 writeHBInt 方法就是 已经实现在HBInteger.c文件中的。类比于用户,就是已经完成的C语言方法。
5、生成动态库,以备Java侧可以调用
因笔者使用的是Linux系统,因此编译生成的是.so文件,如果是windows,则需要生成dll文件。
生成动态库文件编译命令:gcc -I/usr/lib/jvm/java-7-openjdk-i386/include -fPIC -shared -o libHBIntegerVM.so HBIntegerVM.c HBInteger.c
需要特别说明的是 -I(是i的大写) 参数必须有,因为使用了jni.h头文件,至于路径则依赖于各个系统安装的JDK版本和路径,笔者的路径是默认JDK安装路径
如果是集成环境则需要把 -I 的路径值加入到编译path里,因为集成工具可能不同,这里无法具体操作,请用户自行查找解决办法。
如果一切顺利,则生成的是 libHBIntegerVM.so 文件,一定要 使用lib开头,并且以 .so 结尾(Linux要求),windows没有这个限制,只要.dll结尾即可。
6、将动态库加入Java项目的JNI路径中
因笔者使用的是eclipse,所以直接在工程的 Build path 处 添加了Native JNI 路径
(即动态库所在目录的路径,可以自定义一个路径,将生成的库文件拷贝到指定路径,也可以直接定位到生成的库文件所在路径)
7、使用库文件中的方法
例如:
public class TestJNI extends ICalcHBInteger{ static{ System.loadLibrary("HBIntegerVM"); } /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub TestJNI test = new TestJNI(); test.init_HBInt(); String get; test.add_HBInt("12345678909876543210", "9876543210123456789"); get = test.output_HBInt(); System.out.println("return: " + get); } }
需要说明的是:
System.loadLibrary("HBIntegerVM");
就是在加载动态库,其中 HBIntegerVM 就是去掉库文件的头和尾 即:lib 和 .so 如果是windows平台的dll文件,则只去掉尾,即 .dll 部分
还看到
test.output_HBInt();
就是之前代码中的native方法名
小结:
1、JNI技术目前应用不是很广泛,究其原因是因为这样做影响了调用层的性能(主要是交互时间长)
2、在非必要情况下(比如笔者这种情况就属于个人为了偷懒),尽量不要使用该技术,以免带来性能和调试上的麻烦。
3、在使用过程中,如果涉及到传递参数,一定要注意参数类型的转换,具体转换方法,根据类型的不同,方法各异。
4、在进行工程分块时,特别是针对积木类型结构的工程,该方式可以值得借鉴并加以利用,甚至优化。