尽管这种情况极少发生,你也许希望调用不是用Java语言写的子程序。通常,这样的子程序是CPU的或是你所工作环境的执行代码——也就是说,本机代码。例如,你希望调用本机代码子程序来获得较快的执行时间。
或者,你希望用一个专用的第三方的库,例如统计学包。然而,因为Java程序被编译为字节码,字节码由Java运行时系统解释(或动态编译),看起来在Java程序中调用本机代码子程序是不可能的。幸运的是,这个结论是错误的。
Java提供了native关键字,该关键字用来声明本机代码方法。一旦声明,这些方法可以在Java程序中被调用,就像调用其他Java方法一样。
为声明一个本机方法,在该方法之前用native修饰符,但是不要定义任何方法体。例如:
public native int meth() ;
声明本机方法后,必须编写本机方法并要执行一系列复杂的步骤使它与Java代码链接。
很多本机方法是用C写的。把C代码结合到Java 程序中的机制是调用Java Native Interface (JNI)。该方法学由Java 1.1创建并在Java 2中增强。(Java 1.0是用不同的方法,该方法已经过时),关于JNI的详尽描述超出了本书的范围。但是下面的描述为多数应用程序提供了足够的信息。
理解该过程的最简单的方法是完成一个例子。开始,输入下面的短程序,该程序使用了一个名为test( )的native方法。
// A simple example that uses a native method.
public class NativeDemo {
int i;
public static void main(String args[]) {
NativeDemo ob = new NativeDemo();
ob.i = 10;
System.out.println("This is ob.i before the native method:" +
ob.i);
ob.test(); // call a native method
System.out.println("This is ob.i after the native method:" +
ob.i);
}
// declare native method
public native void test() ;
// load DLL that contains static method
static {
System.loadLibrary("NativeDemo");
}
}
注意test( )方法声明为native且不含方法体。简而言之这是我们用C语言实现的方法。同时注意static块。像本书前面解释过的,一个static块仅在程序开始执行时执行(更为简单的说,当它的类被加载时执行)。这种情况下,它用来加载包含本地执行方法test( )的动态链接库(你不久就会看到怎样创建这个库)。
该库由loadLibrary( )方法加载。loadLibrary( )方法是System类的组成单元。它的一般形式为:
static void loadLibrary(String filename)
这里,filename是指定保存该库文件名的字符串。在Windows环境下,该文件的扩展名为.DLL。
写完程序后,编译它生成NativeDemo.class。然后,你必须用javah.exe生成一个文件:NativeDemo.h(javah.exe包含在JDK中)。在执行test( )时你要包含NativeDemo.h。 为生成NativeDemo.h,用下面的命令:
javah -jni NativeDemo
该命令生成名为NativeDemo.h的头文件。该文件必须包含在实现test()的C文件中。该命令的输出结果如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class NativeDemo */
#ifndef _Included_NativeDemo
#define _Included_NativeDemo
#ifdef _ _cplusplus
extern "C" {
#endif
/*
* Class: NativeDemo
* Method: test
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_NativeDemo_test
(JNIEnv *, jobject);
#ifdef _ _cplusplus
}
#endif
#endif
请特别注意下面一行,该行定义了所要创建的test( )函数的原型:
JNIEXPORT void JNICALL Java_NativeDemo_test(JNIEnv *, jobject);
注意函数的名称是Java_NativeDemo_test( )。调用本机函数你必须用这样的名字。也就是说,不是生成一个名为test( )的C函数,而是创建一个名为Java_NativeDemo_test( )函数。
加入前缀NativeDemo是因为它把test( )方法作为NativeDemo类的一部分。
记住,其他类可以定义它们自己的与NativeDemo定义的完全不同的本地test( )方法。前缀中包括类名的方法解决了区分不同版本的问题。作为一个常规方法,给本机函数取名,前缀中必须包括声明它们的类名。
生成了必备的头文件后,可以编写test( )执行文件并把它存在一个名为NativeDemo.c的文件中:
/* This file contains the C version of the
test() method.
*/
#include <jni.h>
#include "NativeDemo.h"
#include <stdio.h>
JNIEXPORT void JNICALL Java_NativeDemo_test(JNIEnv *env, jobject obj)
{
jclass cls;
jfieldID fid;
jint i;
printf("Starting the native method.\n");
cls = (*env)->GetObjectClass(env, obj);
fid = (*env)->GetFieldID(env, cls, "i", "I");
if(fid == 0) {
printf("Could not get field id.\n");
return;
}
i = (*env)->GetIntField(env, obj, fid);
printf("i = %d\n", i);
(*env)->SetIntField(env, obj, fid, 2*i);
printf("Ending the native method.\n");
}
注意此文件包含具有接口信息的jni.h文件。该文件由你的Java 编译器提供。头文件NativeDemo.h预先已由javah创建。
该函数中,GetObjectClass( )方法用来获得一个含有NativeDemo类信息的C结构。GetFieldID( )方法返回一个包含该类域名“i”信息的C结构。GetIntField()检索该域原来的值。SetIntField( )存储该域的一个更新值(别的处理其他数据类型的方法参看文件jni.h)。
生成NativeDemo.c文件后,必须编译它生成一个DLL文件。用微软C/C++编译器来做,使用下面的命令行:
Cl /LD NativeDemo.c
它生成了一个名为NativeDemo.dll的文件。该步骤完成,你可以执行Java 程序。该程序输出如下:
This is ob.i before the native method: 10
Starting the native method.
i = 10
Ending the native method.
This is ob.i after the native method: 20
使用native的特殊环境是依赖于实现和环境的。而且,与JAVA代码接口的指定方式必须改变。你必须仔细考虑完成你Java开发系统文件的本机方法。