JNI是Java native interface的简写,可以译作Java原生接口。Java可以通过JNI调用C/C++的库,这对于那些对性能要求比较高的Java程序无疑是一个福音。
使用JNI也是有代价。大家都知道JAVA程序是运行在JVM之上的,可以做到平台无关。但是如果Java程序通过JNI调用了原生的代码(比如c/c++等),则Java程序就丧失了平台无关性。最起码需要重新编译原生代码部分。所以应用JNI需要好好权衡,不到万不得已,请不要选择JNI,可以选择替代方案,比如TCP/IP进行进程间通讯等等。这也是为什么谷歌的Android平台的底层虽然用JNI实现,但是他不建议开发人员用JNI来开发Android上面的应用的原因。将会丧失Android上面的应用程序平台无关性。
下面是在linux下java jni调用C语言动态链接库的具体操作步骤。
1、创建一个Java程序(Hello.java)定义原生的c/c++函数。
2、用javac编译Hello.java生成Hello.class。
3、用javah带-jni参数编译Hello.class生成Hello.h文件,该文件中定义了c的函数原型。在实现c函数的时候需要。
4、创建Hello.c,实现Hello.h定义的函数。
5、编译Hello.c生成libHello.so。
6、在java虚拟机运行java程序Hello。
第一步,定义一个Java类-- Hello.它提供SayHello方法:
此时应注意两点:
1.为要使用的每个本地方法编写本地方法声明,其声明方式与普通Java方法接口没什么不同,只是必须指定native关键字,如下所示:
public native void SayHello(String strName);
在这个函数中,我们将根据传进的人名,向某人问好。
2.必须显式地加载本地代码库。当然要调用System.loadLibrary("hello");注意此时不要lib,也不要.so!;我们需在类的一个静态块中加载这个库:
static
{
System.loadLibrary("hello");
}
再加上必要的异常处理就生成如下源文件Hello.java:
public class Hello
{
static
{
System.loadLibrary("hello");
}
//声明的本地方法
publicstaitc native void sayHello(String strName);
}
运行命令javac Hello.java生成Hello.class文件。
第二步,生成本地链接库。具体过程如下:
1.要为以上定义的类生成Java本地接口头文件,需使用javah,Java编译器的javah功能将根据Hello类生成必要的声明,此命令将生成Hello.h文件,我们在共享库的代码中要包含它,javah不使默认内部命令,需要指明路径,它在JDK的bin目录下,在我的Linux环境下命令如下:
javah Hello
但是出现如下错误:
error: cannot access Hello
class file for Hello not found
javadoc: error - Class Hello not found.
Error: No classes were specified on the command line.Try -help.
原因是CLASS_PATH没有把当前目录加入其中。所以必须指定classpath为当前目录。或者在系统CLASS_PATH加入当前路径。执行如下命令:
javah -classpath . Hello
生成的Hello.h文件内容的第一句子为#include 但是gcc里面默认环境可不知道jni.h是什么东西,jni.h在jdk的$JAVA_HOME/include下面,可进去查看一下~
2.在与Hello.h相同的路径下创建一个CPP文件Hello.cpp。注意,自动生成的那个函数名字很长,并且开头的Java是大写的,大小写很致命一定要注意。内容如下:
#include "Hello.h"
#include
//与Hello.h中函数声明相同
JNIEXPORT void JNICALL Java_Hello_sayHello(JNIEnv * env, jclass arg, jstring instring)
{
//从instring字符串取得指向字符串UTF编码的指针
const jbyte *str =
(const jbyte *)env->GetStringUTFChars( instring, JNI_FALSE );
printf("Hello,%s/n",str);
//通知虚拟机本地代码不再需要通过str访问Java字符串。
env->ReleaseStringUTFChars( instring, (const char *)str );
return;
}
所有的JNI调用都使用了JNIEnv *类型的指针,习惯上在CPP文件中将这个变量定义为env,它是任意一个本地方法的第一个参数。env指针指向一个函数指针表,在VC中可以直接用"->"操作符访问其中的函数。
jobject指向在此Java代码中实例化的Java对象LocalFunction的一个句柄,相当于this指针。
后续的参数就是本地调用中有Java程序传进的参数,本例中只有一个String型参数。对于字符串型参数,因为在本地代码中不能直接读取Java字符串,而必须将其转换为C /C++字符串或Unicode。以下是三个我们经常会用到的字符串类型处理的函数:
const char* GetStringUTFChars(jstring string,jboolean* isCopy)
3.编译生成共享库。
使用GCC时,必须通知编译器在何处查找此Java本地方法的支持文件,并且显式通知编译器生成位置无关的代码,在我的环境中按如下过程编译:
g++ -I /usr/lib/jvm/java-6-sun/include/linux/ -I /usr/lib/jvm/java-6-sun/include/ -fPIC -c Hello.cpp
生成Hello.o
g++ -shared -Wl,-soname,libhello.so.1 -o libhello.so.1.0 Hello.o
生成libhello.so.1.0
接下来将生成的共享库拷贝为标准文件名
cp libhello.so.1.0 libhello.so
或者使用:
g++ -I /usr/lib/jvm/java-6-sun/include/linux/ -I /usr/lib/jvm/java-6-sun/include/ -fPIC -shared -o libLexical.so Lexical.cpp
注意在linux下,动态链接库的名字必须是lib****.so,必须以lib开头!4.编写一个简单的Java程序来测试我们的本地方法。
将如下源码存为ToSay.java: