java JNI编程指南 下载本文

CFunction类还可以定义许多类似的方法,如callFloat、callDouble等来处理其它返回类型的C函数。 CPointor的定义如下:

public abstract class CPointer { public native void copyIn(

int bOff, // offset from a C pointer int[] buf, // source data

int off, // offset into source

int len); // number of elements to be copied public native void copyOut(...); ... }

CPointer是一个抽象类,它支持对任意C指针的访问。例如copyIn这个方法,它会把一个int数组里面的元素复制到C指针指向的位置中去。但是这种操作方式可以访问地址空间里面任意的内存位置,一定要小心地使用。像

CPointer.copyIn这样的本地方法可对直接对C指针进行操作,是不安全的。CMalloc是CPointer的一个子类,它指向内存中由malloc在heap上分配的一块儿内存。

public class CMalloc extends CPointer {

public CMalloc(int size) throws OutOfMemoryError { ... } public native void free(); ... }

CMalloc的构造函数根据给定的大小,在C的heap上创建一块儿内存。

CMalloc.free方法用来释放这个内存块儿。我们可以用CFunction和CMalloc重新实现Win32.CreateFile: public class Win32 {

private static CFunction c_CreateFile =

new CFunction (\ \ \

public static int CreateFile(

String fileName, // file name

int desiredAccess, // access (read-write) mode int shareMode, // share mode

int[] secAttrs, // security attributes int creationDistribution, // how to create int flagsAndAttributes, // file attributes

int templateFile) // file with attr. to copy {

CMalloc cSecAttrs = null; if (secAttrs != null) {

cSecAttrs = new CMalloc(secAttrs.length * 4);

cSecAttrs.copyIn(0, secAttrs, 0, secAttrs.length); }

try {

return c_CreateFile.callInt(new Object[] { fileName,

new Integer(desiredAccess), new Integer(shareMode), cSecAttrs,

new Integer(creationDistribution), new Integer(flagsAndAttributes), new Integer(templateFile)}); } finally {

if (secAttrs != null) { cSecAttrs.free(); } } } ... }

我们在一个静态变量当中缓存CFunction对象,Win32的CreateFile 这个API从kernel32.dll中通过调用方法CreateFileA来访问,另外一个方法

CreateFileW需要传入一个Unicode字符串参数作为文件名。CFunction负责做标准的Win32调用转换(stdcall)。

上面的代码中,首先在C的heap上面分配一个足够大的内存块儿来存储安全属性,然后把所有的参数打包成一个数组并通过CFunction这个函数调用处理器来调用底层的C函数CreateFileA。最后释放掉存储安全属性的C内存块儿。

9.3 一对一映射(one-to-one mapping)和Shared Stubs的对比

这是两种把本地库封装成包装类的方式,各有自己的优点。

Shared Stubs的主要优点是程序员不必在本地代码中写一大堆的stub函数。一旦像CFunction这样的shared stub被创建以后,程序员可能就不用写代码了。 但是,使用shared stubs时一定要非常小心,因为这相当于程序员在JAVA语言中写C代码,已经违反了JAVA中的类型安全机制。一旦使用的过程中出现错误,就有可能引起内存破坏甚至程序崩溃。 一对一映射的优点是高效,因为它不需要太多附加的数据类型转换。而这一点正是shared stubs的缺点,例如CFunction.callInt必须为每一个int创建一个Integer对象。

9.4 如何实现Shared Stubs

到现在为止,我们一直是把CFunction、CPointer、CMalloc这三个类当作黑匣子的。本节中,我们就来详细描述一下它们是如何使用JNI实现的。

9.4.1 CPointer的实现

抽象类CPointer包含了一个64位的字段peer,它里面存放的是一个C指针: public abstract class CPointer { protected long peer;

public native void copyIn(int bOff, int[] buf, int off,int len); public native void copyOut(...); ... }

对于像copyIn这样的本地方法的C++实现是比较简明的: JNIEXPORT void JNICALL

Java_CPointer_copyIn__I_3III(JNIEnv *env, jobject self, jint boff, jintArray arr, jint off, jint len) {

long peer = env->GetLongField(self, FID_CPointer_peer); env->GetIntArrayRegion(arr, off, len, (jint *)peer + boff); }

在这里,我们假设FID_CPointer_peer是CPointer.peer的字段ID,是被提前计算出来。

9.4.2 CMalloc

CMalloc这个类中添加了两个本地方法用来分配和释放C内存块儿: public class CMalloc extends CPointer {

private static native long malloc(int size);

public CMalloc(int size) throws OutOfMemoryError { peer = malloc(size); if (peer == 0) {

throw new OutOfMemoryError(); } }

public native void free(); ... }

这个类的构造方法调用了本地方法CMalloc.malloc,如果CMalloc.malloc分配失败的话,会抛出一个OutOfMemoryError。我们可以像下面这样实现CMalloc.malloc和CMalloc.free两个方法: JNIEXPORT jlong JNICALL

Java_CMalloc_malloc(JNIEnv *env, jclass cls, jint size) {

return (jlong)malloc(size); }

JNIEXPORT void JNICALL

Java_CMalloc_free(JNIEnv *env, jobject self) {

long peer = env->GetLongField(self, FID_CPointer_peer); free((void *)peer); }

9.4.3 CFunction

这个类的实现要求操作系统支持动态链接,下面的代码是针对Win32/Intel X86平台的。一旦你理解了CFunction这个类背后的设计思想,你可以把它扩展到其它平台。

public class CFunction extends CPointer { private static final int CONV_C = 0; private static final int CONV_JNI = 1; private int conv;

private native long find(String lib, String fname);

public CFunction(String lib, // native library name String fname, // C function name String conv) { // calling convention if (conv.equals(\ conv = CONV_C;

} else if (conv.equals(\ conv = CONV_JNI; } else {

throw new IllegalArgumentException( \ }

peer = find(lib, fname); }

public native int callInt(Object[] args); ... }

类中使用了一个conv字段来保存C函数的调用转换类型。 JNIEXPORT jlong JNICALL

Java_CFunction_find(JNIEnv *env, jobject self, jstring lib, jstring fun) {

void *handle; void *func; char *libname; char *funname;

if ((libname = JNU_GetStringNativeChars(env, lib))) { if ((funname = JNU_GetStringNativeChars(env, fun))) { if ((handle = LoadLibrary(libname))) {

if (!(func = GetProcAddress(handle, funname))) { JNU_ThrowByName(env,

\ funname); } } else {

JNU_ThrowByName(env,

\ libname); }

free(funname); }

free(libname); }

return (jlong)func; }

CFunction.find把库名和函数名转化成本地C字符串,然后调用Win32下的APILoadLibrary和GetProcAddress来定义本地库中的函数。 方法callInt的实现如下: JNIEXPORT jint JNICALL

Java_CFunction_callInt(JNIEnv *env, jobject self, jobjectArray arr) {

#define MAX_NARGS 32 jint ires;

int nargs, nwords;

jboolean is_string[MAX_NARGS]; word_t args[MAX_NARGS];

nargs = env->GetArrayLength(arr); if (nargs > MAX_NARGS) { JNU_ThrowByName(env,

\ \ return 0; }

// convert arguments

for (nwords = 0; nwords < nargs; nwords++) { is_string[nwords] = JNI_FALSE;

jobject arg = env->GetObjectArrayElement(arr, nwords);