jni的简单使用

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


就写这么多,细节还需要使用者在使用过程摸索。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值