Java基础篇之什么是本机方法

602 阅读5分钟

尽管这种情况极少发生,你也许希望调用不是用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开发系统文件的本机方法。