if (arg == NULL) {
args[nwords].p = NULL;
} else if (env->IsInstanceOf(arg, Class_Integer)) { args[nwords].i =
env->GetIntField(arg, FID_Integer_value); } else if (env->IsInstanceOf(arg, Class_Float)) { args[nwords].f =
env->GetFloatField(arg, FID_Float_value); } else if (env->IsInstanceOf(arg, Class_CPointer)) { args[nwords].p = (void *)
env->GetLongField(arg, FID_CPointer_peer); } else if (env->IsInstanceOf(arg, Class_String)) { char * cstr =
JNU_GetStringNativeChars(env, (jstring)arg); if ((args[nwords].p = cstr) == NULL) { goto cleanup; // error thrown }
is_string[nwords] = JNI_TRUE; } else {
JNU_ThrowByName(env,
\ \ goto cleanup; }
env->DeleteLocalRef(arg); }
void *func =
(void *)env->GetLongField(self, FID_CPointer_peer); int conv = env->GetIntField(self, FID_CFunction_conv);
// now transfer control to func.
ires = asm_dispatch(func, nwords, args, conv);
cleanup:
// free all the native strings we have created for (int i = 0; i < nwords; i++) { if (is_string[i]) { free(args[i].p); } }
return ires; }
上面的代码中我们假设已经有了一些全局变量来缓存一些类引用和字段ID。例如,全局变量FID_CPointer_peer缓存了CPointer.peer的字段ID,而全局变量Class_String是对java.lang.String类对象的全局引用。类型word_t定义如下:
typedef union { jint i; jfloat f; void *p; } word_t;
函数Java_CFunction_callInt遍历参数数组并检查每一个元素的类型。
1、
如果元素是null,向C函数传递一个NULL指针。
如果参数是java.lang.Integer类的实例,取出其中的int值并传递给C函数。 如果元素是java.lang.Float类的实例,取出其中的float值传递给C函数。 如果元素是一个CPointer类的实例,取出其中的peer指针并传递给C函数。
2、 3、 4、 5、 6、
如果参数是一个java.lang.String的实例,则把字符串转换成本地C字符串,然后传递给C函数。
否则的话,抛出IllegalArgumentException。
在Java_CFunction_callInt函数之前,我们会在参数转换时检查可能会发生的错误,然后释放掉为C字符串临时分配的内存。
下面的代码需要把参数从临时缓冲区args中传递到C函数中,这个过程需要直接操作C的栈(stack),因此需要用到汇编,代码和对代码的解释不再翻译,懂得不多,翻译出来也是莫名其妙,不能保证正确性。 9.5 Peer
无论哪种封装方式,都会遇到一个问题,就是数据结构的传递。我们先看一下CPointer这个类的定义。
public abstract class CPointer { protected long peer;
public native void copyIn(int bOff, int[] buf, int off, int len); public native void copyOut(...); ... }
这个类中包含了一个指向本地数据结构的64位的peer字段。CPointer的子类用这个指针来操作C里面的数据结构:
CPointer、CMalloc这些类被称作peer classes。你可以用这些类封装各种各样的本地数据结构,如:
1、 2、 3、
文件描述符(file descriptors)。 Socket描述符(socket descriptors)。 窗口或者其它UI元素。
9.5.1 JAVA平台下的Peer Classes
JDK中,java.io、java.net和java.awt等包的内部实现就是利用了peer classes。例如,一个java.io.FileDescriptor类的实例,其实就包含了一个私有的字段fd,而fd这个字段就是指向一个本地文件描述符。
// Implementation of the java.io.FileDescriptor class public final class FileDescriptor { private int fd; ... }
假如现在你想做一个JAVA平台的文件API不支持的操作,你可能就会通过本地方法中的JNI来找到一个java.io.FileDescriptor中的fd字段,然后试图去操作这个字段所代表的文件。这样会存在一些问题: 1、 2、
首先,这种方式严重依赖于java.io.FileDescriptor的实现,如果有一天这个类的内部你直接操作fd字段可能会破坏java.io.FileDescriptor内部的完整性。比如内部实现中,发生了变动,本地方法就要修改。 fd字段可能会和其它某个数据相关联。
解决这些问题最根本的方案就是定义你自己的peer classes来封装本地数据结构。在上面的情况中,你可以定义自己的peer class来包含file descriptor,并在这个peer class上面定义一些自己的操作。并且,你也可以很容易地定义一个自己的peer class来实现一个标准的JAVA API中的接口。
9.5.2 释放本地数据结构
Peer classes被定义在JAVA中,因此它们的实例对象会被自动回收,因此,你要保证在这些对象被回收的时候,它们所指向的C语言数据结构的内存块也要被释放。
前面提到过,CMalloc类包含一个用来手动释放被malloc分配的C内存的free方法:
public class CMalloc extends CPointer {
public native void free(); ... }
所以,有些人爱这么干:
public class CMalloc extends CPointer {
public native synchronized void free(); protected void finalize() { free(); } ... }
JVM在回收CMalloc的对象实例之前,会调用对象的finalize方法。这样的话,即使你忘记调用free,finalize方法也会帮你释放掉malloc分配的内存。
可是,为了防止本地方法被重复调用,你不仅要在free方法前面加上synchronized关键字,还需要对CMalloc.free这个本地方法的实现做一些修改:
JNIEXPORT void JNICALL
Java_CMalloc_free(JNIEnv *env, jobject self) {
long peer = env->GetLongField(self, FID_CPointer_peer); if (peer == 0) {
return; /* not an error, freed previously */ }
free((void *)peer); peer = 0;
env->SetLongField(self, FID_CPointer_peer, peer); }
请注意,要设置peer的值的话,需要用两句来完成:
peer = 0;
env->SetLongField(self, FID_CPointer_peer, peer);
而不是一句:
env->SetLongField(self, FID_CPointer_peer, 0);
因为C++编译器会把0当作32位int值来处理。
另外,定义finalize方法是一个很好的保障措施,但决不能把它作为释放本地C语言数据结构的主要方式: 1、
是本地数据结构可能会消耗比它们的peer对象实例更多的资源,但JVM看在眼里的是,这个对象只有一个long型的字段,这样JVM可能就会以为它占用很少的资源而不会及时回收掉。 2、
定义了finalize方法的类,在对象的创建和回收时可能会比没有定义finalize方法的类在效率上要差些。
其实,你完全不必用finalize方法就可以手动保证一个本地C语言数据结构被释放。但这样的话,你就必须确保在所有的执行路径上面都要执行释放代码,否则可能会造成内存泄漏。比如下面这种情况就是需要提起注意的:
CMalloc cptr = new CMalloc(10);
try {
... // use cptr } finally {
cptr.free(); }
9.5.3 peer对象背后的东西
前面我们介绍了一个peer class通常会包含一个指向本地数据结构的私有字段。其实,有些情况下,在本地数据结构中包含一个指向peer class的引用也是很有用的。比如,当本地代码需要回调peer class中的实例方法的时候。
假设KeyInput是一个UI控件:
class KeyInput {
private long peer;
private native long create();
private native void destroy(long peer); public KeyInput() { peer = create(); }
public destroy() { destroy(peer); }
private void keyPressed(int key) { ... /* process the key event */ } }
还有一个本地数据结构key_input:
// C++ structure, native counterpart of KeyInput struct key_input {
jobject back_ptr; // back pointer to peer instance int key_pressed(int key); // called by the operating system };
它们的关系如下:
整个流程是这样的,JAVA当中生成一个KeyInput对象用来处理按键。KeyInput对象生成的时候,会在本地内存中创建一个key_input结构,这个结构中包含一个方法key_pressed供操作系统在发生事件时调用。
当用户按某个键时,操作系统产生一个事件,并调用key_pressed(int key);,在这个方法里面,本地代码会调用KeyInput的keyPressed方法,并把键值传入。 KeyInput的两个本地方法实现如下:
JNIEXPORT jlong JNICALL
Java_KeyInput_create(JNIEnv *env, jobject self) {
key_input *cpp_obj = new key_input();
cpp_obj->back_ptr = env->NewGlobalRef(self); return (jlong)cpp_obj; }
JNIEXPORT void JNICALL
Java_KeyInput_destroy(JNIEnv *env, jobject self, jlong peer) {