前情提要
JNI技术 , 是java世界与C/C++世界的通信基础 , java语言可以通过native方法去调用C/C++的函数 , 也可以通过C/C++来调用java的字段与方法 。 在上篇中 , 我们了解了JNI开发的基本流程 , 接下来我们来分析分析C语言代码以及头文件 。
.h头文件分析
头文件生成命令 : javah com.zeno.jni.HelloJni
public static native String getStringFromC() ;
上述代码 通过javah
命令 , 则会生成如下头文件中的函数:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h"
/* Header for class com_zeno_jni_HelloJni */
#ifndef _Included_com_zeno_jni_HelloJni
#define _Included_com_zeno_jni_HelloJni
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_zeno_jni_HelloJni
* Method: getStringFormC
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_zeno_jni_HelloJni_getStringFromC
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
通过上述代码, 我们可以看出getStringFromC()
方法 , 生成的函数是Java_com_zeno_jni_HelloJni_getStringFromC (JNIEnv *, jclass)
函数 。其实 ,我们不用javah
命令 , 也能写出头文件 , 除了#endif,#ifdef __cplusplus
中间是变化的 , 其他的都不变 , javah
通过native
方法生成的函数 , 命名都是有规律 。
函数名称规则:Java_完整类名_方法名 , 包名的.号 , 以`_`表示
其中jstring是返回的java的String类型 , jstring类型是jni里面定义的类型 , 标准C里面是没有的 。那么 , jstring是什么类型呢 ?
使用VS的转到定义功能 , 我们可以看到 , jstring在jni.h的定义 , jstring是jobject的别名 , jobject是一个_jobject结构体的指针 。
typedef jobject jstring;
typedef struct _jobject *jobject;
因为我们getStringFromC()
方法返回的是一个String类型 , 所以C函数的返回值是jstring
类型 。
/*
* Class: com_zeno_jni_HelloJni
* Method: getStringFormC
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_zeno_jni_HelloJni_getStringFromC
(JNIEnv *Env, jclass jclazz) {
return (*Env)->NewStringUTF(Env, "Jni C String");
}
在C语言系列的最后一篇 , 我们分析了jni.h的头文件 , 了解了JNIEnv结构体指针 , 大致知道里面都有些什么函数 , NewStringUTF(Env, "Jni C String")
这个函数 , 就是将C语言中的字符指针转换成java的String
类型的字符串。
JNI数据类型对应java的标准数据类型
Java Type | Native Type | Description |
---|---|---|
boolean | jboolean | unsigned 8 bits |
byte | jbyte | signed 8 bits |
char | jchar | unsigned 16 bits |
short | jshort | signed 16 bits |
int | jint | signed 32 bits |
long | jlong | signed 64 bits |
float | jfloat | 32 bits |
double | jdouble | 64 bits |
void | void | not applicable |
JNI数据类型对应java的引用数据类型
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;
在jni源码中 , 我们可以看到如上定义 , 可以发现 , 所有的引用类型都是_jobject
结构体指针类型。
JNIEnv分析
我们知道 ,JNIEnv是JNINativeInterface_
结构体的指针别名 , 在JNINativeInterface_
结构体中 , 定义很多操作函数 。例如:
jstring (JNICALL *NewStringUTF) (JNIEnv *env, const char *utf);
jsize (JNICALL *GetStringUTFLength) (JNIEnv *env, jstring str);
const char* (JNICALL *GetStringUTFChars)(JNIEnv *env, jstring str, jboolean *isCopy);
void (JNICALL *ReleaseStringUTFChars)(JNIEnv *env, jstring str, const char* chars);
由上述函数可以看出,每个函数都需要一个JNIEnv指针,但是为什么需要呢 ?
有两点:
第一:函数需要 , 在函数中仍然需要JNINativeInterface_结构体中的函数做处理
第二:区别对待C和C++
我们知道 , jni是支持C/C++的,在jni.h头文件中 , 那么C++是怎么表示JNIEnv的呢 ?源码如下:
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);
}
jclass FindClass(const char *name) {
return functions->FindClass(this, name);
}
jmethodID FromReflectedMethod(jobject method) {
return functions->FromReflectedMethod(this,method);
}
在C++环境下 ,还是使用的NINativeInterface_
结构体指针调用函数, 使用NewStringUTF
函数时, 则不需要传入Env
这个二级指针 ,因为C++是面向对象的语言 , 传入了this , 当前环境的指针
jstring NewStringUTF(const char *utf) {
return functions->NewStringUTF(this,utf);
}
示例:
/*
* Class: com_zeno_jni_HelloJni
* Method: getStringFormC
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_zeno_jni_HelloJni_getStringFromCPP
(JNIEnv * env, jclass jclazz) {
return env->NewStringUTF("From C++ String");
}
在C++环境中 , JNIEnv就成了一级指针了 , 为什么会是这样呢 ?我们在源码中找到这样一段代码:
/*
* JNI Native Method Interface.
*/
struct JNINativeInterface_;
struct JNIEnv_;
#ifdef __cplusplus
typedef JNIEnv_ JNIEnv;
#else
typedef const struct JNINativeInterface_ *JNIEnv;
#endif
由上可知 , 在C和C++两个环境中 , 使用了两个不同的JNIEnv , 一个是JNIEnv二级指针 , 一个是JNIEnv一级指针 。
模拟C语言中的JNIEnv写法
#include <stdio.h>
#include <stdlib.h>
// 定义一个JNIEnv , 是JavaNativeInterface结构体指针的别名
typedef struct JavaNativeInterface* JNIEnv;
// 模拟java本地化接口结构体
struct JavaNativeInterface {
void(*hadlefunc)(JNIEnv*);
char*(*NewStringUTF)(JNIEnv*, char*);
};
// 模拟 , 在调用NewStringUTF函数的时候 , 需要处理一些事情
void handleFunction(JNIEnv* env) {
printf("正在处理...\n");
}
// 最终调用的函数实现
char* NewStringUTF(JNIEnv* env,char* utf) {
(*env)->hadlefunc(env);
return utf;
}
void main() {
//实例化结构体
struct JavaNativeInterface jnf;
jnf.hadlefunc = handleFunction;
jnf.NewStringUTF = NewStringUTF;
// 结构体指针
JNIEnv e = &jnf;
// 二级指针
JNIEnv* env = &e;
// 通过二级指针掉用函数
char* res = (*env)->NewStringUTF(env, "模拟JNIEnv实现方式\n");
// 打印
printf("调用结果:%s", res);
system("pause");
}
输出:
正在处理...
调用结果:模拟JNIEnv实现方式
结语
.h头文件的分析就到这里 ,关键是了解清楚 , native
方法在C中生成函数名称的规则 , 以及对JNIEnv有个良好的认识 。
本文由老司机学院【动脑学院】特约提供 。