在Java编程中,本地方法的声明一般如下:
private native String getLine(String prompt);
跟其他普通的Java类的方法相比,只有声明,没有方法体,且必须带一个native修饰符。另外,在类中,通过静态代码初始化的方法,调用System.loadLibrary(...)方法,加载本地方法的实现——JNI库,如下为Java本地方法使用的一个范例:
public class Prompt implements TestCase{
//native method that prints a prompt and reads a line
private native String getLine(String prompt);
static {
String path = System.getProperty("java.library.path");
System.loadLibrary("Prompt");
}
@Override
public void doTest() {
String input = this.getLine("Please input something and enter return");
System.out.println("User Typed: " + input);
}
}
如上所示,系统将加载libPrompt.so库。在JNI本地实现代码中,对应的方法声明如下:
JNIEXPORT jstring JNICALL Java_com_fyj_test_Prompt_getLine
(JNIEnv *, jobject, jstring);
第一个参数JNIEnv是一个接口指针,它指向了一个位置,该位置是一个指针数组,它包含了许多JNI函数的指针。根据本地方法的声明方式,如果声明了类的静态方法,则第二个参数是该类的一个引用,否则这个参数就是对该类实例的一个引用,相当于this指针。
基本类型
在Java中,有两种类型,一种是初始类型如int,float,以及char,还有一种是引用类型如类,实例以及数组。JNI对这两种类型的处理不一样。对于初始类型,与C/C++编程语言类型的映射很简单,如int对应jint, float对应jfloat等等。而对于引用类型,JNI则当作不透明引用,即C的指针类型,它们指向Java虚拟机的内部数据结构。本地方法必须通过合适的JNI函数来操控它们。例如,对于java.lang.String类,它对应的C/C++类型为jstring。本地代码要访问这个字符串的内容必须通过调用JNI函数如GetStringUTFChars。
字符串访问
JNI层本地代码对应的数据类型为jstring,但是,不能直接当作普通的C字符串,必须使用合适的JNI函数来将jstring对象转换为C/C++字符串。JNI支持Unicode和UTF-8字符串与jstring对象的双向转换。示例代码如下:
JNIEXPORT jstring JNICALL Java_com_fyj_test_Prompt_getLine
(JNIEnv *env, jobject obj, jstring prompt)
{
#if 0
char buf[128];
const jbyte *str;
str = (*env)->GetStringUTFChars(env, prompt, NULL);
if (str == NULL) {
return NULL; /* OutOfMemoryError already thrown */
}
printf("%s", str);
(*env)->ReleaseStringUTFChars(env, prompt, str);
/* We assume here that the user does not type more than
* 127 characters */
scanf("%s", buf);
return (*env)->NewStringUTF(env, buf);
#endif
/* assume the prompt string and user input has less than 128
characters */
char outbuf[128], inbuf[128];
int len = (*env)->GetStringLength(env, prompt);
(*env)->GetStringUTFRegion(env, prompt, 0, len, outbuf);
printf("%s", outbuf);
scanf("%s", inbuf);
return (*env)->NewStringUTF(env, inbuf);
}
字符串相关的JNI函数如下所示:
JNI Function | Description | Since |
GetStringChars ReleaseStringChars | Obtains or releases a pointer to the contents of a string in Unicode format. May return a copy of the string. | JDK1.1 |
GetStringUTFChars ReleaseStringUTFChars | Obtains or releases a pointer to the contents of a string in UTF-8 format. May return a copy of the string. | JDK1.1 |
GetStringLength | Returns the number of Unicode characters in the string. | JDK1.1 |
GetStringUTFLength | Returns the number of bytes needed (not including the trailing 0) to represent a string in the UTF-8 format. | JDK1.1 |
NewString | Creates a java.lang.String instance that contains the same sequence of characters as the given Unicode C string. | JDK1.1 |
NewStringUTF | Creates a java.lang.String instance that contains the same sequence of characters as the given UTF-8 encoded C string. | JDK1.1 |
GetStringCritical ReleaseStringCritical | Obtains a pointer to the contents of a string in Unicode format. May return a copy of the string. Native code must not block between a pair of Get/ReleaseStringCritical calls. | JDK1.1 |
GetStringRegion SetStringRegion | Copies the contents of a string to or from a preallocated C buffer in the Unicode format. | Java 2 SDK1.2 |
GetStringUTFRegion SetStringUTFRegion | Copies the content of a string to or from a preallocated C buffer in the UTF-8 format. | Java 2 SDK 1.2 |
初始类型数组访问
初始数据类型的数组访问与对象类型的数组访问又有不同。访问初始类型的数据与访问字符串一样,也需要使用JNI函数进行转换。
如下代码所示:
JNIEXPORT jint JNICALL Java_com_fyj_test_IntArray_sumArray
(JNIEnv *env, jobject obj, jintArray arr)
{
#if 0
jint buf[100];
jint i, sum = 0;
(*env)->GetIntArrayRegion(env, arr, 0, 100, buf);
for (i = 0; i < 100; i++) {
sum += buf[i];
}
return sum;
#endif
jint *carr;
jint i, sum = 0;
carr = (*env)->GetIntArrayElements(env, arr, NULL);
if (carr == NULL) {
return 0; /* exception occurred */
}
for (i=0; i<100; i++) {
sum += carr[i];
}
(*env)->ReleaseIntArrayElements(env, arr, carr, 0);
return sum;
}
相关的JNI函数如下:
JNI Function | Description | Since |
Get<Type>ArrayRegion Set<Type>ArrayRegion | Copies the contents of primitive arrays to or from a pre-allocated C buffer. | JDK 1.1 |
Get<Type>ArrayElements Release<Type>ArrayElements | Obtains a pointer to the contents of a primitive array. May return a copy of the array. | JDK 1.1 |
GetArrayLength | Returns the number of elements in the array. | JDK 1.1 |
New<Type>Array | Creates an array with the given length. | JDK 1.1 |
GetPrimitiveArrayCritical ReleasePrimitiveArrayCritica | Obtains or releases a pointer to the contents of a primitive array. May disable garbage collection, or return a copy of the array. | Java 2 SDK 1.2 |
对象类型数组访问
访问对象类型数组需要使用一组不同的JNI函数:
GetObjectArrayElement:返回某个下标处的元素。
SetObjectArrayElement:设置某个下标处的元素。
不同于初始类型数组,你不能一次获取整个对象元素或复制多个对象元素。
由于字符串和数组也属于引用类型,所以可以使用上述函数访问字符串数组或数组的数组。代码示例如下:
JNIEXPORT jobjectArray JNICALL Java_com_fyj_test_ObjectArrayTest_initInt2DArray
(JNIEnv *env, jclass cls, jint size)
{
jobjectArray result;
int i;
jclass intArrCls = (*env)->FindClass(env, "[I");
if (intArrCls == NULL) {
return NULL; /* exception thrown */
}
result = (*env)->NewObjectArray(env, size, intArrCls,
NULL);
if (result == NULL) {
return NULL; /* out of memory error thrown */
}
for (i = 0; i < size; i++) {
jint tmp[256]; /* make sure it is large enough! */
int j;
jintArray iarr = (*env)->NewIntArray(env, size);
if (iarr == NULL) {
return NULL; /* out of memory error thrown */
}
for (j = 0; j < size; j++) {
tmp[j] = i + j;
}
(*env)->SetIntArrayRegion(env, iarr, 0, size, tmp);
(*env)->SetObjectArrayElement(env, result, i, iarr);
(*env)->DeleteLocalRef(env, iarr);
}
return result;
}