您的位置:首页 > 其它

JNI 笔记 (总结一些基础的,常用的)

2014-08-13 21:27 204 查看
看了一些博客的内容,总结常用的。

具体是哪些,懒得去弄了。做个笔记,方便自己。

(JNI笔记 01)
先说说几个常用的参数:
JavaVM *g_jvm : JavaVM 接口,g_jvm指针指向一个虚拟机进程,通过该指针,该进程下的所有线程可以获取 JNIEnv 的指针。
以下函数可以获取g_jvm:

jint JNI_OnLoad(JavaVM*
jvm, void* reserved) ; // java中加载动态连接库时,会自动调用这个函数,如果有的话。

jint JNI_CreateJavaVM(&jvm,
(void**)&env, &vm_args);
传入一个全局变量g_jvm,得到后就可以在任意上下文中使用,具体例子见(JNI笔记 04)

JNIEnv *env :JNIEnv 接口,env指针指向用来分配和存储线程本地数据的区域(如下图),不同线程的env是不同的。
可以通过以下函数获取 env ,必须传入g_jvm
JNIEnv* JNU_GetEnv()
{

JNIEnv* env;

(*g_jvm)->GetEnv(g_jvm, (void**)&env, JNI_VERSION_1_4);



//(*jvm)->AttachCurrentThread(jvm,
(void**)&env, NULL); 这个也可以
return env;

}
Jobject thiz :根据native方法是一个static方法还是一个实例方法而不同的。

如果是一个实例方法,它就是调用该方法的对象的引用,和C++中的this指针类似(此时类型为jobject)。
如果是一个static方法,那它就对应着包含该方法的类(此时类型为jclass)

jfieldID var_id : 成员变量的id类型 , 本质是个指针
jmethodID method_id : 成员函数的id类型,本质是个指针
jclass clazz :类的类型,在C中是void* , C++ 中是 _jclass*

注意: 在C中, (*env)指向JNINativeInterface结构体的指针,可以用这个指针调用结构体的成员函数指针

===============================================================================
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

(JNI笔记 02)

(1) java调用C动态库中的方法。
我把 从java调用本地C/C++代码的过程分为三层:



java 层 : 编写一个.java文件,比如test.java,定义一个类,加载静态库: static { System.loadLibrary("pbochighapi"); }

声明要用到的native方法:public native int iCreate (byte[]
dataIn, int datalen, String str);
之后就可以正常使用这个类的所有方法(本地or非本地)

JNI 层: 编写一个.c文件,属于一个桥梁的作用,将 java文件中的native声明的方法,转换为JNI调用的接口。
通俗易懂的说,就是转换方法名,转换参数列表(对应关系见下图);
以便java中的函数调用可以调用对应的函数。我们要做的大部分工作在这个地方。注意要包C底层的 .h文件
调用过程 : java的native函数 -----> 此文件中转换好的对应方法 -----> 每个对应方法中调用了.h文件中声明的一个方法

C 底层: 纯粹的C代码,C函数。不用任何修改。只需向上提交 .h 文件

java 和 jni 参数转换对应关系

其他引用映射:

Class --------------- jclass
String --------------- jstring
Object --------------- jobject 任何对象
Object[] --------------- jobjectArray
boolean[] --------------- jbooleanArray
byte[] -------------- jbyteArray
char[] -------------- jcharArray
short[] -------------- jshortArray
int[] -------------- jintArray
long[] -------------- jlongArray
float[] -------------- jfloatArray
double[] -------------- jdoubleArray

1)创建声明了native 方法的类(HelloWorld.java)。

2)使用javac编译源文件,生成类文件(HelloWorld.class)。 javac xxxx.java

3)使用javah -jni生成C头文件(HelloWorld.h)。 javah -jni XXXXX.class

4)用C、C++实现native方法(HelloWorld.c、HelloWorld.cpp),调用c动态库中的方法。(JNI层)

以上在ecplise中还需要把动态库 * .so 文件 或者 *.dll 文件放到 libs下
以上动作完成后,实现本地方法 helloworld.c文件的时候,在类似
JNIEXPORT void JNICALL Java_HelloWorld_print(JNIEnv *env, jobject obj)的方法中,
需要把jni的参数类型转换为C类型,或者把C类型的参数转换为 JNI 类型的,就可以给C动态库的函数传入参数或者传出值
具体 JNI <-->C 转换参数的方法见(JNI笔记03)

(2) JNI 层调用java层中的对象方法、类方法,访问对象中的属性。
在 JNI调用中,不仅仅Java可以调用本地方法,本地代码也可以调用Java中的方法和成员变量。

jclass FindClass("com/hxsmart/NativeTest") ; // 获取一个类的class
jfieldID GetFieldID(jclass clazz, const char *name,const char *sign) // 取得普通成员变量的id

jfieldID GetStaticFieldID(jclass clazz, const char*name, const char *sign) // 取得静态成员变量的id
jmethodID GetStaticMethodID(jclass clazz, const char*name, const char *sign) // 取得静态成员函数的id

jmethodID GetMethodID(jclass clazz, const char *name,constchar *sign) // 取得普通成员函数的id

这四个方法的参数列表都是一模一样的,每个参数的含义:

第一个参数 jclass clazz : 代表我们操作的Class类,我们要从这个类里面取的属性和方法的ID。

第二个参数 const char *name : 这是一个常量字符数组,代表我们要取得的方法名或者变量名。

第三个参数 const char *sig : 这也是一个常量字符数组,代表我们要取得的方法或变量的签名

什么是方法或者变量的签名呢?

我们来看下面的例子,如何来获得属性和方法ID:

public class NativeTest {

public void show(int i){

System.out.println(i);

}

public void show(double d){

System.out.println(d);

}

}

本地代码部分:

//首先取得要调用的方法所在的类的Class对象,在C/C++中即jclass对象

jclass clazz_NativeTest= (*env)->FindClass("com/hxsmart/NativeTest");

//取得jmethodID

jmethodID id_show=(*env)->GetMethodID(clazz_NativeTest,“show”,"???");

上述代码中的id_show取得的jmethodID到底是哪个show方法呢?
由于Java语言有方法重载的面向对象特性,所以只通过函数名不能明确的让JNI找到Java里对应的方法。
所以这就是第三个参数sig的作用,它用于指定要取得的属性或方法的类型签名。

(1)基本类型以特定的大写字母表示

(2)Java对象以L开头,然后以“/”分隔包的完整类型,例如String的签名为:Ljava/lang/String; 注意最后面的逗号也是要的。

(3)在Java里数组类型也是引用类型,数组以[ 开头,后面跟数组元素类型的签名,例如:int[] 签名就是[I ,
对于二维数组,如int[][] 签名就是[[I,object 数组签名就是 [Ljava/lang/Object;

(4) 方法签名 :
(参数1类型签名参数2类型签名参数3类型签名.......)返回值类型签名

注意:

1. 函数名,在签名中没有体现出来

2. 参数列表相挨着,中间没有逗号,没有空格

3. 返回值出现在()后面

例如:

Java方法 对应签名

boolean isLedOn(void) ; ()Z

void setLedOn(int ledNo); (I)V

String substr(String str, int idx, int count); (Ljava/lang/String;II)Ljava/lang/String

char fun (int n, String s, int[] value); (ILjava/lang/String;[I)C

boolean showMsg(View v, String msg); (Landroid/View;Ljava/lang/String;)Z

好了,下面我们就可以根据获取的ID调用相关的方法 和 设置、访问成员变量了。

Get方法:
第一个参数代表要获取的属性所属对象或jclass对象
第二个参数即属性ID

jXXX GetXXXField ( jobject thiz, jfieldID fieldID); //普通
jXXX GetStaticXXXField (
jclass thiz, jfieldID fieldID); //静态

Set方法:
第一个参数代表要设置的属性所属的对象或jclass对象
第二个参数即属性ID
第三个参数代表要设置的值。

void SetXXXField(jobject thiz , jfieldID fieldID , jxxx val);
//普通
void SetStaticXXXField(jobject thiz , jfieldID fieldID , jxxx val); //静态

下面是方法的调用

第一个参数代表调用的这个方法所属于的对象,或者这个静态方法所属的类。

第二个参数代表jmethodID。

第三个参数以及后面的参数,代表这个方法的参数列表

<TYPE>是返回值的类型,比如返回值是void,则 CallVoidMethod

调用普通方法:

Call<Type>Method(jobject thiz, jmethodID methodID,...);

Call<Type>MethodV(jobject thiz, jmethodID methodID,va_listargs);

Call<Type>tMethodA(jobject thiz, jmethodID methodID,const jvalue *args);

调用静态方法:

CallStatic<Type>Method(jclass clazz, jmethodID methodID,...);

CallStatic<Type>MethodV(jclass clazz, jmethodID methodID,va_listargs);

CallStatic<Type>tMethodA(jclass clazz, jmethodID methodID,const jvalue *args);

例子:
(1)

// Java方法

public int show(int i,double d,char c){



}

// 本地调用Java方法

jint i=10L;

jdouble d=2.4;

jchar c=L'd';

(*env)->CallIntMethod(thiz , id_show , i, d, c);

(2) 用得少,略过。
(3) 略过

静态方法也是一样的。

(3) JNI 层 创建java对象
其实就是去调用java某个类的构造函数,所以还是用到了 调用静态方法的那一套:
jobject NewObject(jclass clazz, jmethodID methodID,...);

jobject NewObjectV(jclass clazz, jmethodID methodID,va_list args);

jobject NewObjectA(jclass clazz, jmethodID methodID, const jvalue *args) ;

步骤: (1) jclass clazz=(*env)->FindClass("java/util/Date");

(2)jmethodID id_date=(*env)->GetMethodID(clazz,"Date","()V"); //取得某一个构造方法的jmethodID,后面的
()V 根据实际的构造函数写。有参数的话,括号内就写上参数签名

(3)jobject
date= (*env)->NewObject(clazz,id_date); //调用NewObject方法创建java.util.Date对象

===============================================================================
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

(JNI 笔记 03)
今天搞搞 JNI 的参数怎么和 C语言的参数进行转换,基本类型可以直接用,数组 和 引用类型需要转换。

(1) jstring <---> char *buf ( char buf[] )

JNIEXPORT jstring JNICALL Java_Prompt_getLine(JNIEnv *env, jobject thiz, jstring string)
{
// 1. jstring ---> char *
const char *str;
str = (*env)->GetStringUTFChars(env, string,0); //把 string转换 const char* ,str 引用 它
if (str == NULL) {
return NULL; /* OutOfMemoryError already thrown */
}
printf("%s", str); //测试是否转换成功 // str可以拿去用了,用完就释放
// const char * 转换 char *
int len = strlen(str);
char buf[len+1] ;
strcpy(buf,str);
printf("%s", buf); //测试是否转换成功
(*env)->ReleaseStringUTFChars(env, string , str); // 释放引用,后面的两个参数: jstring ,const char *

// 从C底层传出的 char * ,转换为 string
// 2. char * ----> jstring
return (*env)->NewStringUTF(buf);
}

以上只是其中的两个字符串函数,还有许多其他的,具体使用的时候可以依据下面的图做选择

(2) 基础类型数组 <----> int[] ,float[], double[] byte[](char [ ] , char *).........
实际写的函数,如果传入的是 int[],还要传入一个数组大小的参数。
jintArray <----> int[]

JNIEXPORT jintArray JNICALL

Java_IntArray_test(JNIEnv *env, jobject thiz, jintArray arr)

{

jint *carr;
jint i, sum = 0;
// 1. jintArray ----> int[]
// 获取jintArray中的 jint 所有元素

carr = (*env)->GetIntArrayElements(env, arr, NULL); // carr 是一个指针,指向一个array的副本

if (carr == NULL) {

return 0; /* exception occurred */

}

//使用carr[i]访问数组元素
int len = (*env)->GetArrayLength(env, arr);
int des[len];

for (i=0; i<len; i++) {

des[i]= carr[i];

}
// des[] 就是转换好的 int数组
// 释放引用

(*env)->ReleaseIntArrayElements(env, arr, carr, 0);

// 1. int[] ----> jintArray
int test[] = {1,2,3,4,5,6,7,8};
int len = sizeof(test)/sizeof(int) ; // 模拟底层C函数带出来的 int test[]数组,大小也是带出来的。
//定义数组对象
jintArray array = (*env)-> NewIntArray(env, len);
//从start开始复制长度为len
的数据 从 buffer到 array 中

(*env)->SetIntArrayRegion(env,array,0,len,test)

return array;

}

(3) 对象数组(Strings和数组都是引用类型。你可以使用上述两个函数来访问字符串的数组和数组的数组)

和基础类型数组不一样的是,你不能一次获取所有的对象元素,或者一次复制多个对象元素。
主要函数:

1. (*env)->GetIntArrayElements(env,jarray, isCopy) , 返回jarray中的所有数据的指针。
If isCopy is not NULL, then *isCopy is set to JNI_TRUE if a copy ismade; if no copy is made, it is set to JNI_FALSE.
2. (*env)->GetIntArrayRegion(env,array,start,len,buffer), 从start开始复制长度为len 的数据到buffer中
3. (*env)->SetIntArrayRegion(env,array,start,len,buffer) , 从start开始复制长度为len 的数据 从 buffer到 array 中

4. jobject (*GetObjectArrayElement)(JNIEnv*, jobjectArray, index); //得到在某一个索引的值

5. void (*SetObjectArrayElement)(JNIEnv*, jobjectArray, index, jobject); //设置某个索引上的值

例子:
JNIEXPORT jobjectArray JNICALL

Java_ObjectArrayTest_initInt2DArray(JNIEnv *env, jclass cls, int size) // 这是一个 静态方法,返回值是对象数组

{

jobjectArray result;

int i;

jclass intArrCls = (*env)->FindClass(env, "[I"); // int[] 类

if (intArrCls == NULL) {

return NULL; /* exception thrown */

}

result = (*env)->NewObjectArray(env, size, intArrCls, NULL); //生成数组类型的数组,大小为size,即二维数组

if (result == NULL) {

return NULL; /* out of memory error thrown */

}

// int[size][size]

for (i = 0; i < size; i++)
{
int
tmp[256]; /* make sure it is large enough! */
int
j;
jintArray
iarr = (*env)->NewIntArray(env, size); //1. 生成一个int数组,在二维数组中只是一个元素
if
(iarr == NULL) {

return NULL; /* out of memory error thrown */
}
// 2. int数组赋值,封装好一个元素
for
(j = 0; j < size; j++)
{

tmp[j] = i + j;
}
//3. 将int数组作为一个元素放到 Object数组中
(*env)->SetIntArrayRegion(env,
iarr, 0, size, tmp);
// env->SetIntArrayRegion(intArray, 0, size, (const int*)tmp); 如果tmp是malloc出来的,使用这种方式,并记得最后free掉。

(*env)->SetObjectArrayElement(env,
result, i, iarr);
(*env)->DeleteLocalRef(env,
iarr); //4. 删除int数组的引用
}
// free(tmp);
return result;
}

如果是string数组,与上面步骤相同,在 最后的地方改为 :

jstring jstr;
char* sa[] = { "Hello,", "world!", "JNI",
"很", "好玩" }; //这里是模拟字符串数组,应用中要根据实际得来的数据
int i=0;

for(;i<ARRAY_LENGTH;i++) //一般数组的长度也是底层C带出来,或者传入的jarray根据jsize len =(*env)->GetArrayLength(env, jarray);

{

jstr = (*env)->NewStringUTF( env, sa[i] ); // 差别在这里,数组的元素不同,所以生成元素的函数不同而已。

(*env)->SetObjectArrayElement(env, texts, i, jstr);//必须放入jstring

(*env)->DeleteLocalRef(env, jstr); //4. 删除int数组的引用

}

===============================================================================
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

(JNI 笔记 04)

先说下注册函数,然后开始讲 本地代码如何访问java 中的方法,属性 ,对象等

JNI注册函数 ,注册以后的jni函数的命名可以不需要符合类似javah命令生成的函数的规则
请看这篇博客:
/article/7889555.html

java中调用System.loadLibrary("somelib")的时候,系统会自动调用jni的函数JNI_OnLoad
在程序退出的时候,系统卸载“somelib”,会自动调用jni的函数JNI_OnUnload
所以感觉有点像C++ 中的构造函数 和析构函数,我们一般重写这两个方法,来做一些初始化的动作。

jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved)

{

JNIEnv *env = NULL;

if ((*jvm)->GetEnv(jvm, (void**)&env, JNI_VERSION_1_4))

{
return JNI_ERR;

}
g_jvm = jvm ; // 在static变量保存 jvm指针
sg_env= env; // 在static 变量保存env指针

// jclass cls = (*env)->FindClass(env, JNIT_CLASS);

// if (cls == NULL)
// {

// return JNI_ERR;

// }

// jint nRes = (*env)->RegisterNatives(env, cls, gMethods, sizeof(gMethods)/sizeof(gMethods[0]));

// if (nRes < 0)

// {

// return JNI_ERR;

// }
// 其他初始化代码:比如
iHxEmvInit(iIMateTestCard,iIMateResetCard,iIMateExchangeApdu,iIMateCloseCard);

sendReceiveCallBack = (*sg_env)->GetMethodID(sg_env, sg_class, "sendReceiveCallBack", "([BI[BI)I");
return JNI_VERSION_1_4;

}

OnUnLoad 就不写了!

=========================================================================
=========================================================================
在native中向LogCat输出调试信息

另外,在JNI层,可以包含 #include <android/log.h>
__android_log_print(ANDROID_LOG_INFO, "JNIMsg", "
num = %d",num); 打印JNI层的信息。
记得 加入了头文件后还必须给链接器指定__android_log_print函数所在的库文件liblog.so,
在Android.mk文件中加上一行 LOCAL_LDLIBS := -llog

关于jclass
jclass代表JAVA中的java.lang.Class。
我们看jclass的定义:

1. #ifdef __cplusplus
2. /*Reference types, in C++*/
3. class _jobject {};
4. class _jclass : public _jobject {}; /*_jclass继承_jobject*/
5. typedef _jclass* jclass;
6. #else
7. /*Reference types, in C.*/
8. typedef void* jobject;
9. typedef jobject jclass;
10. #endif

在C中jclass代表类型void*,在C++中代表类型_jclass*。
当多个native函数都需要使用同一个JAVA类的jclass变量时,不能够这样做:
定义一个 jclass 类型全局变量,并只对其赋初值一次,然后在多次对native函数调用中使用这个jclass变量。不能企图以此方式来节约获得jclass变量的开销。
jclass StudentClazz = (*env)->FindClass(env,"com/example/hellojni/HelloJni$Student");
$表示一个类的内部类
每次JAVA调用native都必须重新获得jclass,上次调用native所得到的jclass在下次调用native时再使用是无效的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: