Chinaunix首页 | 论坛 | 博客
  • 博客访问: 497511
  • 博文数量: 135
  • 博客积分: 3010
  • 博客等级: 中校
  • 技术积分: 905
  • 用 户 组: 普通用户
  • 注册时间: 2010-01-24 19:31
文章分类

全部博文(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!
阅读(1084) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~