JNI操作的详细步骤
网上有很多关于如何编写dll的帖子,但是真正应用到实际还是需要自己去摸索。现在将自己编写的过程和经验与大家分享。
JNI(Java 本地方法)的操作步骤比较固定,但是在具体的细节上,却会出很多问题。现详述如下:
一、首先创建Java项目,然后新建一个自己要使用的类,在我们的项目中,我们做的是广播加密,Java调用底层的c加密。所以我在Java中新建的类名为:BroadcastEnc.java;
二、在该类中编写本地化方法。为了格式的统一,将本地化方法放在Java方法的前面。因为该本地化方法只有方法头,所以要做好注释,我们本地化方法的一个实例是:
/**服务器调用该方法初始化广播加密系统,并将相关参数保存在文件systemFileName
*中
* 初始化用到的素数等参数保存在文件pairFileName,在这个方法中也确定系统最大
*用户数目
* @param number:广播加密系统允许的用户个数
* @param pairFileName:初始化广播加密系统需要的参数
* @param systemfile:保存广播加密系统的公钥在该文件中,需要传递给用户
* @param myprivkeyfile:在计算用户密钥时,需要用到伽马,但是该数不能保存
* 系统参数中,而应作为一个单独的参数保密
*/
public native void Setup_broadcast_sys(int number,String pairFileName,String systemfile,String myprivkeyfile);
三、在声明完本地化方法之后,还要在Java程序中加载动态链接库,语句是:
static{
System.loadLibrary("BroadcastEnc");
}
如果此时编写main方法进行测试,就会发现Java编译器会报无法加载动态链接库,这就是我们需要做的最主要工作:编写dll文件。
四、编写dll的过程较为复杂,而且该过程不智能,会出现很多问题。平时我们都习惯于用Eclipse编写程序,并运行。实际上,Eclipse在创建每一个项目时,在项目目录中都会包括src和bin文件夹,src放Java源文件,bin放编译之后生成的.class文件。但是要编写dll文件,我们就需要将源文件和字节码放在一块,所以我们不能用Eclipse编译程序,而需要用命令行模式,转到src文件夹,运行javac命令生成.class文件。
五、在src文件夹下生成.class文件之后,我们就可以在命令行模式下运行javah,生成相应的.h文件,作为C程序项目的头文件。在运行javah命令时,有可能提示找不到类文件,即使你感觉类文件就在那里。这时的解决方法是:设置环境变量,增加CLASSPATH变量,值就是你此次要编译的项目src所在文件夹的路径名,我的CLASSPATH值为:C:/Documents and Settings/Administrator/workspace/DSMS6/src。这个地方有可能令人疑惑,一般设置环境变量不是都设置java的bin目录吗,在此环境变量中还有path变量,这其中包含你原来认为的路径。实际上,设置完毕之后,很有可能还会报错,提示找不到类文件,这时我感觉是环境变量尚未写入系统,重启可以解决这个问题。
六、在第一次操作时,对jni还不是很了解,所有的类文件都放在默认包中。这种方式对一个小的项目来说还是可以的,但是对于一个有很多不同功能的包的大项目就会显得很乱,体现不出模块化的优势。图片一有很多类还放在默认包中,而在图片二中,已经不再使用默认包。
一
二
要实现对包中的类文件生成.h头文件,其实很简单。只需要在运行javah命令时将包名加在类名之前即可,一个实例是:javah Broadcast.BroadcastEnc,这样就会在src文件夹下生成相应的头文件。这样生成的头文件就会包含包的信息,在每一个本地化方法中都会包含包名。我们项目生成的一个本地化方法是:
JNIEXPORT void JNICALL Java_Broadcast_BroadcastEnc_Setup_1broadcast_1sys
(JNIEnv *, jobject, jint, jstring, jstring, jstring);
与在Java中声明的方法相比,生成的头文件中,方法名多了类名,包名,和关键词Java。后面还有几个1,这都是自动生成的,不要尝试修改、删除。
七、以上都属于Java项目下的操作,下面需要进行C项目下的操作。首先用VC新建一个空的动态链接库项目,名字就是你在Java下要load的动态链接库名,也就是第三版中的名字:BroadcastEnc,然后将先前生成的头文件放在该项目的Head Files中,然后编写相应的.C文件实现头文件中声明的方法。在这个过程中一定要注意不能修改方法名,直接复制粘贴就行。自动生成的头文件方法中的形式参数只包括类型,不包括变量,所以在.c文件中需要添上变量名,否则也会提示错误。
八、当然,编辑C 项目还会遇到很多其它更加匪夷所思的问题,那属于C语言遇到的问题,在此就不再论述。
九、如果一切运行正常,下面就会生成dll文件,该文件会在C项目的Debug文件夹下,将该dll文件复制到Java项目中,复制时无需复制到src下,只是复制到该项目根目录下即可,如图三。
三
以上即是整个JNI步骤,这其中很有可能还会遇到其它问题,但是以上所列是自己已经遇到的问题,也是最为常见的问题,希望对大家有所帮助。
在此还想多说说关于JNI中字符串参数的传递方法。在Java高级语言中,有String类型,但是在C语言中没有该类型,实现时只能用char *来代替,这其中的转换关系较为复杂,以我们写的一个函数为例来展示转换:
JNIEXPORT void JNICALL Java_Broadcast_BroadcastEnc_Setup_1broadcast_1sys
(JNIEnv *env, jobject obj, jint number, jstring pairFileName, jstring systemfile, jstring myprivkeyfile)
{
char *pairFileName2=(char*)(*env)->GetStringUTFChars(env,pairFileName,NULL);
char *systemfile2=(char*)(*env)->GetStringUTFChars(env,systemfile,NULL);
char *myprivkeyfile2=(char*)(*env)->GetStringUTFChars(env,myprivkeyfile,NULL);
Setup_broadcast_sys(number,pairFileName2,systemfile2,myprivkeyfile2);
printf("初始化广播加密系统成功!!!!/n");
}
首先,javah会将String类型变为jstring类型,然后我们调用jni中已经存在的字符串转换方法GetStringUTFChars(…)来将字符串变为char *数组。这里格式固定,可直接套用。
一般可以用高级语言实现的就都用高级语言实现,并不要为了追求速度而放弃简单性和安全性。JNI调用dll虽说在速度上有了很大提高,但是却不简单,而且这其中遇到的错误让人呕吐。不仅如此,在出现问题时,Java给你的错误提示根本就读不懂,所以能少用就少用。但是如果已经用C语言写过,不想再重复开发车轮,则可以尝试利用JNI来提高性能。