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时再使用是无效的。
具体是哪些,懒得去弄了。做个笔记,方便自己。
(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时再使用是无效的。
相关文章推荐
- 细微笔记(快捷键以及一些常用指令总结)
- Linux总结笔记1-常用命令及脚本基础
- 总结IT的知识基础,开博客写个书,汇总一些华为常用的知识
- java 基础学习总结(附带eclipse一些常用操作)
- javascript温习的一些笔记 基础常用知识小结
- linux基础的一些常见问题总结_学习笔记
- Linux总结笔记1-常用命令及脚本基础
- XNA 学习笔记(1.基础 : 常用的一些属性)
- Python学习笔记——一些常用函数、常见错误总结
- javascript温习的一些笔记 基础常用知识小结
- 常用的一些工具总结
- 常用的一些工具总结
- 移动项目开发笔记(C#泛型编程基础知识总结)
- SQLSERVER 的一些常用知识总结
- 项目中常用到的一些CSS总结
- 对操作系统一些常用模块调用的简单总结
- Effictive C++中的一些笔记总结
- Effictive C++中的一些笔记总结
- SQLSERVER 的一些常用知识总结(二)
- java学习笔记,关于java的一些基础知识,适用于初学者,第一节