您的位置:首页 > 其它

jni的简单使用

2015-12-17 15:09 477 查看
JNI(java native interface)主要用于java调用原生代码(java中间代码运行于虚拟机,虚拟机不具跨平台性,原生代码也是一样的,这个都知道的^_^)。所以JNI应该是在java的代码和native的库间存在映射关系,java代码调用native code时通过jvm查找到相应函数的地址执行,调用思路和dll、so类似,然后dll、so和jvm在同一个进程的不同地址上,平行的关系(个人看法)。

开发jni可以使用c/c++或其他的编译性语言。开发过程中有些jni的规定需要遵守,虽然jni不运行于虚拟机,但总是要和jvm打交道,每个厂商都会提供一套和jvm打交道的接口实现,基础接口都是一样的。其中主要是jni代码使用jvm资源的管理,不同语言数据类型的转换,怎么回调,线程调用jvm资源等。

下面逐个展开说明:

1.怎么定义JNI接口。

首先需要引用jni.h这个头文件,以后很多和jvm打交道的函数都在这个头文件中。

我在摸索JNI的写法时,最初使用javah生产头文件,先写好一个java的native接口文件,然后通过Javah生成jni头文件,生成头文件如下:

JNIEXPORT jint JNICALL Java_com_test_NativeAPI_Init

(JNIEnv *, jclass);

其中com.test是包名,NativeAPI是java的类名,Init是方法名。里面的JNIEnv, jclass(jobject)是每个Jni接口必须要用的参数,表示每次调用的上下文环境和调用者。

在开发过程中我们往往会加接口,每次都去javah生成时很麻烦的,当然也可以自己按上述格式写,但这么长的函数名很不爽。

也可以不用这样,但是要在dll或so加载的时候注册方法,而且要知道怎么算出函数的签名,java中加载dll、so的时候会严格比对签名。

如此写法:

int Init(JNIEnv *, jclass); // jni函数声明

注册函数:

// 注册函数列表

static JNINativeMethod s_methods[] = {

{"Init", "()I", (void *)Init},

};

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)

{

g_jvm = vm;

JNIEnv* env = NULL;

if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK)

{

return JNI_ERR;

}

// register jni method

jclass clz = env->FindClass("x/x/x");
// 包名,类名 com/test/jni/NativeAPI

if (clz == NULL) {

return JNI_ERR;

}

int len = sizeof(s_methods) / sizeof(s_methods[0]);

if (env->RegisterNatives(clz, s_methods, len) < 0) {

return JNI_ERR;

}

return JNI_VERSION_1_4;

}

JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved)

{

JNIEnv* env = NULL;

if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK)

{

return;

}

}

2.java c/c++类型的转换

使用必定有很多java和类型和c类型的转换,比如java byte对应c的char等。

Java类型  本地类型  字节(bit)

boolean   jboolean   8, unsigned

byte    jbyte    8

char    jchar    16, unsigned

short    jshort    16

int     jint     32

long    jlong    64

float    jfloat    32

double   jdouble   64

void    void     n/a 

3.函数签名的字符串的计算方法

方法的Signature是由方法的参数和返回值的类型共同构成的,

"(argument-types)return-type"

其中Java程序中参数类型和其对应的值如下:

Signature  Java中的类型

Z       boolean

B       byte

C       char

S       short

I        int

J        long

F       float

D       double

L fully-qualified-class;   fully-qualified-class
如函数int SetParam(JNIEnv *, jclass, long, int, jstring);签名为"(JILjava/lang/String;)I"

4.线程调用jvm资源

线程调用Jvm资源必须attach当前线程到jvm环境。

JNIEnv *env = NULL;

bool isAttach = false;

jint status = g_jvm->GetEnv((void **) &env, JNI_VERSION_1_4);
// g_jvm全局变量在load是保存。

if(status != JNI_OK)

{

#if WIN32

status = g_jvm->AttachCurrentThread((void **)&env, NULL);

#else

status = g_jvm->AttachCurrentThread(&env, NULL);

#endif

if(status < 0) {

return;

}

isAttach = true;

}

// your code......

if (isAttach)

{

g_jvm->DetachCurrentThread();

}

5.有了上面几个步骤可以写基本的Jni了,但是要将java传入的非数值类型在Jni中使用、jni中返回Java可使用的字符串、jni中调用java的类还需要做很多准备。

关键一点要知道使用jni自带的函数调用后是全局还是局部应用,全局引用必须手动释放,局部引用在离开作用域后会自动解除引用,但是在作用域中也不能产生过多的局部应用,否则可能存在突破局部引用表上限。

6.回调java代码中的方法

java的方法写在class中,所以要调用java代码中的方法必须要向jni中传入类的对象。

jobject g_cbObj;

jmethodID g_midFun;

int GetMethod(JNIEnv *env, jclass, jobject obj)

{

// 获取java class

jclass cls = env->GetObjectClass(obj);

// 获取方法

g_midFun= env->GetMethodID(cls, "Callback_FUN", "(JJ)V");
// java方法 void Callback_FUN(long, long);

// 全局引用,回调的时候使用,彻底不用需要调用env->DeleteGlobalRef(g_cbObj);

g_cbObj= env->NewGlobalRef(UserContext);

}

回调Callback_FUN方法:

env->CallVoidMethod(g_cbObj, g_midFun, (jlong)1, (jlong)1);

就写这么多,细节还需要使用者在使用过程摸索。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: