上一篇博客里我们讲了怎样通过JNI和动态链接库实现从Java调C/C++,今天我们来讲怎么在C/C++中获取Java的数据类型以及调用Java的方法。
接着上一篇所讲,我们通过在Java代码中声明native方法,再用javah工具生成头文件,然后编写头文件中的函数,实现功能,再编译成库文件,又Java虚拟机在运行时动态加载和调用,那么这次的重点就是如何编写实现函数。
在这之前,我先讲一下关于Java的native方法在编译成函数时的命名重整规则。熟悉C++朋友可能知道,C++支持函数重载,函数名可能是相同的,但参数不同,或者是类内函数,或者是名字空间内函数,有或者兼而有之。这就是通过命名重整做到得,系统在搜索一个符号(函数)的地址时,无法区分两个名字相同的符号,而在C语言中,函数名字就是其符号,这就导致了C语言无法重载相同名字的函数。而C++通过一套重整规则,把名字空间路径,类名,返回值,函数名,参数,常量值等都作为命名的一部分,这就形成了一个函数独有的函数签名,也就有了唯一的标示。虽然因为编译器对这套规则没有统一的标准而引发很多问题,但却能为我们今天要讲的Java方法的命名带来一些启发。下面我们来看一段Java代码:
public class Hello {
public native static void hello();
public native static void hello(String s);
public native void hello(int i);
public native String hello(char c);
}
在这个类中,我分别定义了几个函数名相同,但函数签名不同的方法。接下来我们生成对应的头文件:
#include
/* Header for class Hello */
#ifndef _Included_Hello
#define _Included_Hello
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: Hello
* Method: hello
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_Hello_hello__
(JNIEnv *, jclass);
/*
* Class: Hello
* Method: hello
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_Hello_hello__Ljava_lang_String_2
(JNIEnv *, jclass, jstring);
/*
* Class: Hello
* Method: hello
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_Hello_hello__I
(JNIEnv *, jobject, jint);
/*
* Class: Hello
* Method: hello
* Signature: (C)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_Hello_hello__C
(JNIEnv *, jobject, jchar);
#ifdef __cplusplus
}
#endif
#endif
下面我们继续看上面头文件的代码,可以看到每个函数的第一个参数都是一个名为JNIEnv的类型,这个参数实际上就是调用本地函数的虚拟机线程的环境信息,也是JNI技术中底层代码调用Java方法的关键接口,我们在编写JNI时最常用到的接口。但是现在我们先跳过它,晚点再讲。可以看到第二个参数有jobject或者是jclass,从名字我们可以看出来,jobject就是一个具体的Java对象,而jclass是一个Java类的对象,就有点像Java中的Class类(即类的类),为什么会有两种不同的参数,其实看看Java代码就知道了,jobject的函数是类内函数(成员函数),jclass的函数是静态函数,静态函数是对整个类而言,而成员函数是对类的一个具体对象作用的。这有点像C++中的成员函数中隐式的传进了一个this指针,而静态函数没有。而之后的参数就是真正的函数的形参对应的实参。可以看到一些像jint,jchar,jstring的类型。下面我们先打开jni.h来看看这些类型是怎么定义的,先看看基本类型:
typedef unsigned char jboolean;
typedef unsigned short jchar;
typedef short jshort;
typedef float jfloat;
typedef double jdouble;
typedef jint jsize;
typedef int jint;
#ifdef _LP64 /* 64-bit Solaris */
typedef long jlong;
#else
typedef long long jlong;
#endif
在回到jni.h这个文件中,我们下面来看一下类的定义:
#ifdef __cplusplus
class _jobject {};
class _jclass : public _jobject {};
class _jthrowable : public _jobject {};
class _jstring : public _jobject {};
class _jarray : public _jobject {};
class _jbooleanArray : public _jarray {};
class _jbyteArray : public _jarray {};
class _jcharArray : public _jarray {};
class _jshortArray : public _jarray {};
class _jintArray : public _jarray {};
class _jlongArray : public _jarray {};
class _jfloatArray : public _jarray {};
class _jdoubleArray : public _jarray {};
class _jobjectArray : public _jarray {};
typedef _jobject *jobject;
typedef _jclass *jclass;
typedef _jthrowable *jthrowable;
typedef _jstring *jstring;
typedef _jarray *jarray;
typedef _jbooleanArray *jbooleanArray;
typedef _jbyteArray *jbyteArray;
typedef _jcharArray *jcharArray;
typedef _jshortArray *jshortArray;
typedef _jintArray *jintArray;
typedef _jlongArray *jlongArray;
typedef _jfloatArray *jfloatArray;
typedef _jdoubleArray *jdoubleArray;
typedef _jobjectArray *jobjectArray;
#else
struct _jobject;
typedef struct _jobject *jobject;
typedef jobject jclass;
typedef jobject jthrowable;
typedef jobject jstring;
typedef jobject jarray;
typedef jarray jbooleanArray;
typedef jarray jbyteArray;
typedef jarray jcharArray;
typedef jarray jshortArray;
typedef jarray jintArray;
typedef jarray jlongArray;
typedef jarray jfloatArray;
typedef jarray jdoubleArray;
typedef jarray jobjectArray;
#endif
struct JNINativeInterface_;
struct JNIEnv_;
#ifdef __cplusplus
typedef JNIEnv_ JNIEnv;
#else
typedef const struct JNINativeInterface_ *JNIEnv;
#endif
struct JNINativeInterface_ {
void *reserved0;
void *reserved1;
void *reserved2;
void *reserved3;
jint (JNICALL *GetVersion)(JNIEnv *env);
jclass (JNICALL *DefineClass)
(JNIEnv *env, const char *name, jobject loader, const jbyte *buf,
jsize len);
......
};
struct JNIEnv_ {
const struct JNINativeInterface_ *functions;
#ifdef __cplusplus
jint GetVersion() {
return functions->GetVersion(this);
}
jclass DefineClass(const char *name, jobject loader, const jbyte *buf,
jsize len) {
return functions->DefineClass(this, name, loader, buf, len);
}
......
};
(*env)->xxx(xxx);
而在C++中则是:
env->xxx(xxx);
这个差异在上面的代码中已经揭示,C++中的JNIEnv是JNIEnv_的类型定义,所以可以直接使用指针操作,而C中是JNINativeInterface的指针,所以要先解引用再使用。关于怎么去调用JNIEnv可以参考官方文档,请记住传进来的对象不能直接使用,因为他们只是一个标示,要获得对象成员属性还得依靠JNIEnv,这点是在JNI编程中一定要牢记并且要养成习惯的。
今天就先讲到这里,更多关于JNI的信息可以参考官方文档或者头文件,甚至感兴趣的朋友可以去下载jdk的源码来阅读。只要记住一点,JNI是用来作为接口的,一般会在其他函数或者库中实现功能。