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);