全部博文(135)
2010年(135)
分类: Java
2010-02-06 22:40:28
Java Native Interface (JNI) 是一种编程框架,使运行在JVM之上的Java代码能够访问由其它语言(C,C++,汇编)编写的本地应用程序(特定于某个硬件和操作系统平台的程序)和库。 示例包括四个文件: HelloWorld.java Main.java HelloWorld.h HelloWorld.c 其中HelloWorld.h是自动生成的文件。 HelloWorld.java: --------------------------------------------------------------- public class HelloWorld { public native void displayMessage(); static { System.loadLibrary("HelloWorld"); } } HelloWorld类中只有一个方法。但这个方法用关键字native来修辞。这个关键字会告诉编译器,这个方法会在本地实现,即不在HelloWorld.java中实现,是由其它语言以动态库的方式来实现的。对于其它Java类来说,它们并不知道这些,只会把HelloWorld当成一个普通的类,displayMessage是它的一个方法。 这个类中有一块static{...}域,这段代码是在类初始化时执行,这是因为这个类的部分功能是在一个动态库中实现的,所以这个库在类初始化的时候得以加载。 Main.java -------------------------------------------------------------- public class Main { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub HelloWorld hello = new HelloWorld(); hello.displayMessage(); } } 这个类为执行类,主要是为了测试HelloWorld的功能。Main类只需要调用HelloWorld类实例的方法即可。不需要关注这个类的具体实现。 完成这两个类以后,编译这两个类: $ javac HelloWorld.java $javac Main.java 生成的HelloWorld.class会在后面用到。 HelloWorld.h ------------------------------------------------------------- /* DO NOT EDIT THIS FILE - it is machine generated */ #include /* Header for class HelloWorld */ #ifndef _Included_HelloWorld #define _Included_HelloWorld #ifdef __cplusplus extern "C" { #endif /* * Class: HelloWorld * Method: displayMessage * Signature: ()V */ JNIEXPORT void JNICALL Java_HelloWorld_displayMessage (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif 这个头文件是自动生成的,通过以下命令: javah -jni HelloWorld 这个自动生成的H文件中,包含了jni.h文件,所以你不需要自己添加它。jni.h中定义了一些JNI的类型。这些类型封装了Java环境。 注意这个文件中的声明的函数的名字,它们是经过”装饰“的,并且必须遵循一些规则,函数名以Java_开头,接着是在Java中声明对应方法的类名,再接“_",最后才是“真正的”函数名。 除了注意函数名外,还有两个特殊的修辞符:JNIEXPORT,JNICALL。这两个修辞符是用来保证这个函数在运行时能够在共享库中找到。 另外还需要说明的是这个函数的参数。在HelloWorld.java中声明方法时,displayMessage并没有参数,但在本地方法实现中却有两个隐含的参数。一个是指向JNI运行环境的指针,它使得函数可以调用JVM中的函数。另外一个参数是调用这个函数的java对象的指针,本质上它类似于C++中的this指针。虽然在这个函数中没有用到这些参数,但在定义时必须加上它们。 另外值得关注是的注释中类型签名,java在调用动态库中的函数时,必须保证类型在二进制上是一致的。也就是说每个参数和返回值的类型所占字节宽度都必须约定好。上面的签名也是自动生成的,可以帮助你理解Java类型是怎样映射到C/C++类型的。有签名由两部分组成,第一部分是由括号封闭,表示这个函数参数的个数和类型。这个函数没有参数(JAVA类对应的方法),所以只有一个空的()。另一部分紧跟在右括号后面,表示函数的返回值。这个函数返回void,所以类型是V。最終组成的签名是()V。下面是一个JAVA类型映射成签名的列表: Type Character boolean Z byte B char C double D float F int I long J object L short S void V array [ 例如一个函数有一个double类型的参数和一个byte类型的参数,返回值是boolean类型,那么它的签名就是"(DZ)B"。为指针一个数组,将一个“["字符放在签名字符之前,比如浮点数组为"[F"。为指定一个对象,将“L”放在类名前面,后面跟着一个分号,比如String的签名为"Ljava/lang/String;"。 HelloWorld.c ------------------------------------------------------------------------------ #include #include "HelloWorld.h" JNIEXPORT void JNICALL Java_HelloWorld_displayMessage(JNIEnv * env, jobject obj){ printf("Hello World!\n"); } 这个文件中实现了在JAVA中声明的函数。为了避免出错,可以从javah生成的H文件中复制函数的原型。 完成以后,用以下的命令编译这个C文件: $gcc -shared -I /usr/lib/jvm/java-6-sun-1.6.0.15/include/linux/ -I/usr/lib/jvm/java-6-sun-1.6.0.15/include/ HelloWorld.c -o libHelloWorld.so 这是用gcc编译器将HelloWorld.c编译成动态库。命令行中指定了包含文件的路径,你安装的JDK include路径可能不一样,需要修改对应的值。/usr/lib/jvm/java-6-sun-1.6.0.15/include目录下有jni.h文件,它会引用到和平台相关的头文件jni_md.h,这个文件位于/usr/lib/jvm/java-6-sun-1.6.0.15/include/linux/下。 完成以上步骤以后,最后就是测试我们的代码。为了让Java类能够正常地装载C的动态库,我们需要将生成的libHelloWorld.so文件复制到系统的库目录中,比如: $ sudo cp ./libHelloWorld.so /usr/lib 这需要根用户权限,也可以把so复制到HelloWorld.class所在的目录,并且在命令行中指定库的搜索路径: $ export LD_LIBRARY=./ (当前目录下有HelloWorld.class和libHelloWorld.so) 最后执行class文件: $ java Main 一切顺利的话,就会看到执行結果: Hello World! |