本地方法是使用System.loadLibrary
方法进行加载。在下面的例子中,类的初始化方法加载了一个在特定平台的本地库,在这个库中定义了本地方法f:
package pkg; class Cls { native double f(int i, String s); static { System.loadLibrary(“pkg_Cls”); } }
System.loadLibrary方法
的参数是由程序员任意选择的一个库名。该系统遵循一个标准,但特定于平台的方法来将库的名称转换到本地库的名称。例如,在Solaris系统将pkg_Cls的名称转换为libpkg_Cls.so
,而Win32系统将同样的pkg_Cls
的名字转换成pkg_Cls.dll
。
程序员可以使用一个库来存储所有的被其他任意数量的类所需要的本地方法,因为这些类都会被同一个类加载器进行加载。VM内部会为每一个类加载器维护一个被加载的本地类库的列表。提供者在选择类库的名字过程中应该确保名称冲突的可能性降到最低。
如果底层操作系统不支持动态连接,所有本地的方法必须使用VM进行预加载。在这种情况下,虚拟机只需要完成System.loadLibrary方法
的调用,而无需实际加载库。程序员也可调用JNI的函数RegisterNatives()来注册与类相关的本地方法
。RegisterNatives()
函数对静态链接函数特别有用。
本地方法命名的解析
动态连接器根据自己的名字解析项目。本地方法的名称的值由以下部分组成:
1.前缀Java
2.一个重组的完全合格的类名
3.下划线(“_”)分隔
4.重组的方法名
5.对于重载的本地方法,两个下划线(“__”)跟在错位的参数签名后
VM检查方法名并与属于本地库的方法进行匹配。VM首先查找短名称,也就是不带参数签名的名称。然后,它寻找长的名字,这是有参数签名的名称。程序员只有在一个本地方法被另一个本地方法重载的时候才需要使用长名称。当然,本地方法与非本地方法有同样的名称也不是问题。非本地方法(Java方法)不属于本地库。
在下面的例子中,本地方法g不需要使用长名字进行加载,因为另一方法g并不是本地方法,因此不存在于本地库。
class Cls1 {
int g(int i);
native int g(double d);
}
我们通过一个简单的名称重整方案,以确保所有的Unicode字符转化为有效的C函数名。我们在完全限定类名中使用下划线(“_”)字符对斜杠(“/”)进行替换。由于名称或类型描述符从来没有以数字开头,我们可以使用_0
,...,_9作为
转义序列,如下表所示:
转义序列
|
表示
|
---|---|
_0XXXX |
一个Unicode字符
XXXX 。 |
_1 |
字符“_”
|
_2 |
签名中的字符“;”
|
_3 |
签名中的字符“[”
|
在给定平台上的本地方法和接口API都遵循标准的库调用约定。例如,UNIX系统使用的是C调用约定,而Win32系统使用__stdcall。
本地方法的参数
JNI接口指针是本地方法的第一个参数。JNI接口指针是JNIEnv类型的。第二个参数根据本地方法是否是静态的而有不同,一个非静态本地方法的第二个参数是一个对象的引用,静态本地方法的第二个参数是其Java类的引用。其余的参数对应于常规的Java方法的参数。本地方法通过返回值将其结果返回给调用程序。下面的代码展示了如何使用C函数来实现本地方法f
。本地方法F
的声明如下:
package pkg; class Cls { native double f(int i, String s); ... }
长重组名称的c函数Java_pkg_Cls_f_ILjava_lang_String_2
实现本地方法f
:
示例展示了使用c实现一个本地方法:
jdouble Java_pkg_Cls_f__ILjava_lang_String_2 ( JNIEnv *env, /* 接口指针 */ jobject obj, /* "this" 指针*/ jint i, /* 参数1 */ jstring s) /* 参数2 */ { /* 获得C拷贝的Java字符串 */ const char *str = (*env)->GetStringUTFChars(env, s, 0); /* 处理字符串 */ ... /* 现在我们已经完成了str*/ (*env)->ReleaseStringUTFChars(env, s, str); return ... }
需要注意的是,我们总是使用的接口指针ENV操作Java对象。在C++使用中,你可以写一个稍微干净的版本的代码,如下所示:
extern "C" /* 指定C调用约定 */ jdouble Java_pkg_Cls_f__ILjava_lang_String_2 ( JNIEnv *env, /* 接口指针 */ jobject obj, /* "this" 指针*/ jint i, /* 参数1 */ jstring s) /* 参数2 */ { const char *str = env->GetStringUTFChars(s, 0); ...
env->ReleaseStringUTFChars(s, str); return ... }
使用C++,额外级别的间接寻址和接口指针参数从源代码中消失了,但是,基本的机制是和使用C完全一样的.在C++中,JNI函数被定义为内联成员函数,扩展到对应的C代码中。