JNI重新认识
头文件:
1.头文件中存放的是对某个库中所定义的函数、宏(define)、类型、全局变量等进行声明,它类似于一份仓库清单。若用户程序中需要使用某个库中的函数,则只需要将该库所对应的头文件include到程序中即可。
2.头文件中定义的是库中所有函数的函数原型。而函数的具体实现则是在库文件中。
3.在连接器连接程序时,会依据用户程序中导入的头文件,将对应的库函数导入到程序中。头文件以.h为后缀名。
头文件是给编译器用的,库文件是给连接器用的
函数库:
1.动态库:在编译用户程序时不会将用户程序内使用的库函数连接到用户程序的目标代码中,只有在运行时,且用户程序执行到相关函数时才会调用该函数库里的相应函数,因此动态函数库所产生的可执行文件比较小。
2.静态库:在编译用户程序时会将其内使用的库函数连接到目标代码中,程序运行时不再需要静态库。使用静态库生成可执行文件比较大。
为什么要进行交互?
首先,java语言提供的类库无法满足要求,且在数学运算,实时渲染的游戏上,音视频处理等方面上与c/c++相比效率稍低。然后,java语言无法直接操作硬件,c/c++代码不仅能操作硬件而且还能发挥硬件最佳性能。接着,使用java调用本地的c/c++代码所写的库,省去了重复开发的麻烦,并且可以利用很多开源的库提高程序效率。
java call c
Java调用C/C++大概有这样几个步骤
- 编写带有native方法的Java类, 使用javac工具编译Java类
- 使用javah来生成与native方法对应的头文件
- 实现相应的头文件, 并编译为动态链接库
我们对这个还是很清楚的,看代码:
c代码:
//
// Created by Administrator on 2016/8/1.
//
#include "JNIUtils.h"
#include <string.h>
#include<stdio.h>
#include<stdlib.h>
#include <android/log.h>
/** * 把一个jstring转换成一个c语言的char* 类型. */
char* _JString2CStr(JNIEnv* env, jstring jstr) {
char* rtn = NULL;
jclass clsstring = (*env)->FindClass(env, "java/lang/String");
jstring strencode = (*env)->NewStringUTF(env,"GB2312");
jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid, strencode); // String .getByte("GB2312");
jsize alen = (*env)->GetArrayLength(env, barr);
jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);
if(alen > 0) {
rtn = (char*)malloc(alen+1); //"\0"
memcpy(rtn, ba, alen);
rtn[alen]=0;
}
(*env)->ReleaseByteArrayElements(env, barr, ba,0);
return rtn;
}
JNIEXPORT jint JNICALL Java_com_losileeya_jnimaster_JNIUtils_intMethod
(JNIEnv *env, jclass jobj,jint num){
return num*num;
}
JNIEXPORT jboolean JNICALL Java_com_losileeya_jnimaster_JNIUtils_booleanMethod
(JNIEnv * env, jclass jobj,jboolean boolean){
return !boolean;
}
JNIEXPORT jstring JNICALL Java_com_losileeya_jnimaster_JNIUtils_stringMethod
(JNIEnv * env, jclass jobj,jstring jstr){
//jstring jstr-->char*
char* fromJava = _JString2CStr(env,jstr);
char* fromC = "add I am from C!! ";
//字符串的拼接函数,会把拼接后的结果放在第一个参数里面
strcat(fromJava,fromC);
return (*env)->NewStringUTF(env,fromJava);
}
JNIEXPORT jint JNICALL Java_com_losileeya_jnimaster_JNIUtils_intArrayMethod
(JNIEnv * env, jclass jobj,jintArray array){
int i, sum = 0;
jsize len = (*env)->GetArrayLength(env, array);
jint *body = (*env)->GetIntArrayElements(env, array, 0);
for (i = 0; i < len; ++i)
{
sum += body[i];
}
(*env)->ReleaseIntArrayElements(env, array, body, 0);
return sum;
}
c++代码:
/
// Created by Administrator on 2016/8/1.
//
#include "JNIUtils.h"
#include <string.h>
#include<stdlib.h>
#include<stdio.h>
#include <android/log.h>
/** * 把一个jstring转换成一个c++语言的char* 类型. */
char* _JString2CStr(JNIEnv* env, jstring jstr) {
char* rtn = NULL;
jclass clsstring = env->FindClass( "java/lang/String");
jstring strencode = env->NewStringUTF("GB2312");
jmethodID mid = env->GetMethodID( clsstring, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barr = (jbyteArray)env->CallObjectMethod(jstr, mid, strencode); // String .getByte("GB2312");
jsize alen = env->GetArrayLength( barr);
jbyte* ba = env->GetByteArrayElements( barr, JNI_FALSE);
if(alen > 0) {
rtn = (char*)malloc(alen+1); //"\0"
memcpy(rtn, ba, alen);
rtn[alen]=0;
}
env->ReleaseByteArrayElements(barr, ba,0);
return rtn;
}
JNIEXPORT jint JNICALL Java_com_losileeya_jnimaster_JNIUtils_intMethod(JNIEnv * env, jclass jobj,jint num){
return num *num;
}
JNIEXPORT jboolean JNICALL Java_com_losileeya_jnimaster_JNIUtils_booleanMethod(JNIEnv * env, jclass jobj,jboolean boolean){
return !boolean;
}
JNIEXPORT jstring JNICALL Java_com_losileeya_jnimaster_JNIUtils_stringMethod
(JNIEnv *env , jclass jobj, jstring jstr){
//jstring jstr-->char*
char* fromJava = _JString2CStr(env,jstr);
char* fromC = "add I am from C!! ";
//字符串的拼接函数,会把拼接后的结果放在第一个参数里面
strcat(fromJava,fromC);
return env->NewStringUTF(fromJava);
}
JNIEXPORT jint JNICALL Java_com_losileeya_jnimaster_JNIUtils_intArrayMethod(JNIEnv * env, jclass jobj,jintArray array){
int sum = 0;
jsize len = env->GetArrayLength(array);
jint *arr = env->GetIntArrayElements(array, 0);
for(int i = 0;i<len; i++){
sum+=arr[i];
}
env->ReleaseIntArrayElements(array, arr,0);
return sum;
}
从上面Native函数的命名上我们可以了解到JNI函数的命名规则: Java代码中的函数声明需要添加native 关键 字;Native的对应函数名要以“Java”开头,后面依次跟上Java的“package名”、“class名”、“函数名”,中间以下划线“” 分割,在package名中的“.”也要改为“_”。此外,关于函数的参数和返回值也有相应的规则。对于Java中的基本类型如int 、double 、char 等,在Native端都有相对应的类型来表示,如jint 、jdouble 、jchar 等;其他的对象类型则统统由jobject 来表示(String 是个例外,由于其使用广泛,故在Native代码中有jstring 这个类型来表示,正如在上例中返回值String 对应到Native代码中的返回值jstring )。而对于Java中的数组,在Native中由jarray 对应,具体到基本类型和一般对象类型的数组则有jintArray 等和jobjectArray 分别对应(String 数组在这里没有例外,同样用jobjectArray 表示)。还有一点需要注意的是,在JNI的Native函数中,其前两个参数JNIEnv 和jobject* 是必需的——前者是一个JNIEnv 结构体的指针,这个结构体中定义了很多JNI的接口函数指针,使开发者可以使用JNI所定义的接口功能;后者指代的是调用这个JNI函数的Java对象,有点类似于C++中的this 指针。在上述两个参数之后,还需要根据Java端的函数声明依次对应添加参数。在上例中,Java中声明的JNI函数没有参数,则Native的对应函数只有类型为JNIEnv 和jobject* 的两个参数。
效果图:
c call java
一般来说,要在Native代码中访问Java对象,有如下几个步骤:
- 得到该Java对象的类定义。JNI定义了jclass 这个类型来表示Java的类的定义,并提供了FindClass接口,根据类的完整的包路径即可得到其jclass 。
- 根据jclass 创建相应的对象实体,即jobject 。在Java中,创建一个新对象只需要使用new 关键字即可,但在Native代码中创建一个对象则需要两步:首先通过JNI接口GetMethodID得到该类的构造函数,然后利用NewObject接口构造出该类的一个实例对象。
- 访问jobject 中的成员变量或方法。访问对象的方法是先得到方法的Method ID,然后使用CallMethod 接口调用,这里Type对应相应方法的返回值——返回值为基本类型的都有相对应的接口,如CallIntMethod;其他的返回值(包括String) 则为CallObjectMethod。可以看出,创建对象实质上是调用对象的一个特殊方法,即构造函数。访问成员变量的步骤一样:首先 GetFieldID得到成员变量的ID,然后Get/SetField读/写变量值。
寻找class对象, 并实例化
JVM在Java中都是自己启动的, 在C/C++中只能自己来启动了, 启动完之后的事情就和在Java中一样了, 不过要使用C/C++的语法.
获取class对象比较简单, FindClass(env, className).
cls = (*env)->FindClass(env, "xxxx");
在Java中的类名格式是java.lang.String, 但是className的格式有点不同, 不是使用’.’作为分割, 而是’/’, 即java/lang/String.
我们知道Java中构造函数有两种, 一种是默认的没有参数的, 一种是自定义的带有参数的. 对应的在C/C++中, 有两种调用构造函数的方法.
调用默认构造函数
// 调用默认构造函数 obj = (*env)->AllocObjdect(env, cls);
构造函数也是方法, 类似调用方法的方式.
// 调用指定的构造函数, 构造函数的名字叫做<init> mid = (*env)->GetMethodID(env, cls, "<init>", "()V"); obj = (*env)->NewObject(env, cls, mid);
调用方法和修改属性
关于方法和属性是有两个ID与之对应, 这两个ID用来标识方法和属性.
jmethodID mid; jfieldID fid;
方法分为静态和非静态的, 所以对应的有
mid = (*env)->GetStaticMethodID(env, cls, "sayHello", "(Ljava/lang/String;)Ljava/lang/String;"); mid = (*env)->GetMethodID(env, cls, "sayHello", "()Ljava/lang/String;");
上面两个方法是同名的, 都叫sayHello, 但是签名不同, 所以可以区分两个方法.
JNI的函数都是有一定规律的, Static就表示是静态, 没有表示非静态.
方法的调用如下
jstring result = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid, arg); jstring result = (jstring)(*env)->CallObjectMethod(env, obj, mid);
我们可以看到静态方法是只需要class对象, 不需要实例的, 而非静态方法需要使用我们之前实例化的对象.
属性也有静态和非静态, 示例中只有非静态的.
获取属性ID
fid = (*env)->GetFieldID(env, cls, "name", "Ljava/lang/String;");
改属性的值
(*env)->SetObjectField(env, obj, fid, arg); // 修改属性
关于jstring的说明
java的String都是使用了unicode, 是双字节的字符, 而C/C++中使用的单字节的字符。
从C转换为java的字符, 使用NewStringUTF方法
jstring arg = (*env)->NewStringUTF(env, name);
从java转换为C的字符, 使用GetStringUTFChars
const char* str = (*env)->GetStringUTFChars(env, result, 0);
下面我们来看代码:
c代码:
/* * Class: com_losileeya_jnimaster_JNIUtils * Method: ccallJava_helloFromJava * Signature: ()V */
JNIEXPORT void JNICALL Java_com_losileeya_jnimaster_JNIUtils_ccallJava_1helloFromJava
(JNIEnv *env, jobject jobj){
jclass jclazz=(*env)->FindClass(env,"com/losileeya/jnimaster/JNIUtils");
jmethodID jmethodid=(*env)->GetMethodID(env,jclazz,"helloFromJava","()V");
jobject jobjs=(*env)->AllocObject(env,jclazz);
(*env)->CallVoidMethod(env,jobjs,jmethodid);
}
/* * Class: com_losileeya_jnimaster_JNIUtils * Method: ccallJava_add * Signature: ()V */
JNIEXPORT void JNICALL Java_com_losileeya_jnimaster_JNIUtils_ccallJava_1add
(JNIEnv *env, jobject jobj){
//1.得到类对应的字节码
//全类名,把.改成/
//jclass (*FindClass)(JNIEnv*, const char*);
jclass jclazz = (*env)->FindClass(env, "com/losileeya/jnimaster/JNIUtils");
//2.得到要调用的方法名
//第三个参数:方法名
//第四个但是:方法签名
//jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
jmethodID jmethodid = (*env)->GetMethodID(env, jclazz, "add",
"(II)I");
//3.得到要调用的方法对应的类的实例
// jobject (*AllocObject)(JNIEnv*, jclass);
jobject jobjs = (*env)->AllocObject(env, jclazz);
//4.调用方法
// jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
int reuslt = (*env)->CallIntMethod(env,jobjs,jmethodid,99,1);
}
/* * Class: com_losileeya_jnimaster_JNIUtils * Method: ccallJava_printString * Signature: ()V */
JNIEXPORT void JNICALL Java_com_losileeya_jnimaster_JNIUtils_ccallJava_1printString
(JNIEnv *env, jobject jobj){
//1.得到类对应的字节码
//全类名,把.改成/
//jclass (*FindClass)(JNIEnv*, const char*);
jclass jclazz = (*env)->FindClass(env, "com/losileeya/jnimaster/JNIUtils");
//2.得到要调用的方法名
//第三个参数:方法名
//第四个但是:方法签名
//jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
jmethodID jmethodid = (*env)->GetMethodID(env, jclazz, "printString",
"(Ljava/lang/String;)V");
//3.得到要调用的方法对应的类的实例
// jobject (*AllocObject)(JNIEnv*, jclass);
jobject jobjs = (*env)->AllocObject(env, jclazz);
//4.调用方法
// void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
jstring text = (*env)->NewStringUTF(env,"I am from C!!");
(*env)->CallVoidMethod(env, jobjs, jmethodid,text); //成功调用了Java中JNI里面的printString(String s);
}
/* * Class: com_losileeya_jnimaster_JNIUtils * Method: ccallJava_sayHello * Signature: ()V */
JNIEXPORT void JNICALL Java_com_losileeya_jnimaster_JNIUtils_ccallJava_1sayHello
(JNIEnv * env, jobject jobj){
//1.得到字节码
jclass jclazz = (*env)->FindClass(env,"com/losileeya/jnimaster/JNIUtils");
//2.得到方法
jmethodID jmethodid = (*env)->GetStaticMethodID(env,jclazz,"sayHello","(Ljava/lang/String;)V");
//3.调用
//void (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...);
jstring text = (*env)->NewStringUTF(env,"I am from C!! I am static method !!!");
(*env)->CallStaticVoidMethod(env,jclazz,jmethodid,text);//成功调用了Java中JNI类的静态方法sayHello(String text)
}
c++代码:
/* * Class: com_losileeya_jnimaster_JNIUtils * Method: ccallJava_helloFromJava * Signature: ()V */
JNIEXPORT void JNICALL Java_com_losileeya_jnimaster_JNIUtils_ccallJava_1helloFromJava(JNIEnv*env,jobject jobj){
jclass jclazz = env->FindClass("com/losileeya/jnimaster/JNIUtils");
jmethodID jmethodid = env->GetMethodID(jclazz, "helloFromJava", "()V");
jobject jobjs = env->AllocObject(jclazz);
env->CallVoidMethod(jobjs, jmethodid);
}
/* * Class: com_losileeya_jnimaster_JNIUtils * Method: ccallJava_add * Signature: ()V */
JNIEXPORT void JNICALL Java_com_losileeya_jnimaster_JNIUtils_ccallJava_1add(JNIEnv*env,jobject jobj){
//1.得到类对应的字节码
//全类名,把.改成/
//jclass (*FindClass)(JNIEnv*, const char*);
jclass jclazz = env->FindClass( "com/losileeya/jnimaster/JNIUtils");
//2.得到要调用的方法名
//第三个参数:方法名
//第四个但是:方法签名
//jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
jmethodID jmethodid = env->GetMethodID(jclazz, "add",
"(II)I");
//3.得到要调用的方法对应的类的实例
// jobject (*AllocObject)(JNIEnv*, jclass);
jobject jobjs = env->AllocObject(jclazz);
//4.调用方法
// jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
int reusle = env->CallIntMethod(jobjs,jmethodid,99,1);
}
/* * Class: com_losileeya_jnimaster_JNIUtils * Method: ccallJava_printString * Signature: ()V */
JNIEXPORT void JNICALL Java_com_losileeya_jnimaster_JNIUtils_ccallJava_1printString(JNIEnv*env,jobject jobj){
//1.得到类对应的字节码
//全类名,把.改成/
//jclass (*FindClass)(JNIEnv*, const char*);
jclass jclazz = env->FindClass( "com/losileeya/jnimaster/JNIUtils");
//2.得到要调用的方法名
//第三个参数:方法名
//第四个但是:方法签名
//jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
jmethodID jmethodid = env->GetMethodID( jclazz, "printString",
"(Ljava/lang/String;)V");
//3.得到要调用的方法对应的类的实例
// jobject (*AllocObject)(JNIEnv*, jclass);
jobject jobjs = env->AllocObject(jclazz);
//4.调用方法
// void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
jstring text = env->NewStringUTF("I am from C!!");
env->CallVoidMethod( jobjs, jmethodid,text); //成功调用了Java中JNI里面的printString(String s);
}
/* * Class: com_losileeya_jnimaster_JNIUtils * Method: ccallJava_sayHello * Signature: ()V */
JNIEXPORT void JNICALL Java_com_losileeya_jnimaster_JNIUtils_ccallJava_1sayHello(JNIEnv*env,jobject jobj){
//1.得到字节码
jclass jclazz = env->FindClass("com/losileeya/jnimaster/JNIUtils");
//2.得到方法
jmethodID jmethodid = env->GetStaticMethodID(jclazz,"sayHello","(Ljava/lang/String;)V");
//3.调用
//void (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...);
jstring text = env->NewStringUTF("I am from C!! I am static method !!!");
env->CallStaticVoidMethod(jclazz,jmethodid,text);//成功调用了Java中JNI类的静态方法sayHello(String text)
}
可以看到,上述代码和前面讲到的步骤完全相符。这里提一下编程时要注意的要点:1、FindClass要写明Java类的完整包路径,并将 “.”以“/”替换;2、GetMethodID的第三个参数是方法名(对于构造函数一律用“”表示),第四个参数是方法的“签 名”,需要用一个字符串序列表示方法的参数(依声明顺序)和返回值信息。由于篇幅所限,这里不再具体说明如何根据方法的声明构造相应的“签名”,请参考 JNI的相关文档。
关于上面谈到的步骤再补充说明一下:在JNI规范中,如上这种使用NewObject创建的对象实例被称为“Local Reference”,它仅在创建它的Native代码作用域内有效,因此应避免在作用域外使用该实例及任何指向它的指针。如果希望创建的对象实例在作用 域外也能使用,则需要使用NewGlobalRef接口将其提升为“Global Reference”——需要注意的是,当Global Reference不再使用后,需要显式的释放,以便通知JVM进行垃圾收集。
顺便看下截图:
JNI 更新UI
在Android使用Jni时,为了能够使UI线程即主线程与工作线程分开,经常要创建工作线程,然后在工作线程中调用C/C++函数.为了在C/C++ 函数中更新Android的UI,又时常使用回调。jni更新ui的话,我们就要注重jobject的使用了。
看代码:(使用)
static {
System.loadLibrary("CCallJavaForUI");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void CCallJavaForUI(View view){
this.callShowToast();
}
public void showToast(){
//this - Activity的实例
//startActitity();-->
//new MainActivity();
System.out.println("showToast()----------");
Toast.makeText(this, "showToast()---------", Toast.LENGTH_LONG).show();
}
/** * 调用MainActivity中的showToast()方法 */
public native void callShowToast();
c代码 :
//
// Created by Administrator on 2016/8/6.
//
#include "JNIUtils.h"
#include<stdio.h>
#include<stdlib.h>
/** * 调用java 中MainActivity中的showToast()方法 * jobject jobj:谁调用就是谁的实例,当前是JNI.this--->MainActivity.this */
JNIEXPORT void JNICALL Java_com_losileeya_jniupdateui_MainActivity_callShowToast
(JNIEnv * env, jobject jobj){
//1.得到字节码
jclass jclazz = (*env)->FindClass(env,"com/losileeya/jniupdateui/MainActivity");
//2.得到方法
//jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
jmethodID jmethodid = (*env)->GetMethodID(env,jclazz,"showToast","()V");
//3.得到对象
// jobject jobjs = (*env)->AllocObject(env,jclazz);
//4.调用方法
//void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
(*env)->CallVoidMethod(env,jobj,jmethodid);//成功调用了中MainActivity中的showToast()方法
};
c++代码:
//
// Created by Administrator on 2016/8/6.
//
#include "JNIUtils.h"
#include<stdio.h>
#include<stdlib.h>
/** * 调用java 中MainActivity中的showToast()方法 * jobject jobj:谁调用就是谁的实例,当前是JNI.this--->MainActivity.this */
JNIEXPORT void JNICALL Java_com_losileeya_jniupdateui_MainActivity_callShowToast
(JNIEnv * env, jobject jobj){
//1.得到字节码
jclass jclazz = (*env)->FindClass(env,"com/losileeya/jniupdateui/MainActivity");
//2.得到方法
//jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
jmethodID jmethodid = (*env)->GetMethodID(env,jclazz,"showToast","()V");
//3.得到对象
// jobject jobjs = (*env)->AllocObject(env,jclazz);
//4.调用方法
//void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
(*env)->CallVoidMethod(env,jobj,jmethodid);//成功调用了中MainActivity中的showToast()方法
};
效果图:
C和C++函数时的JNI使用区别
Java调用C和C++函数时的JNI使用区别:
注意:jni.h头文件中对于.c & .cpp采用不同的定义
在C的定义中,env是一个两级指针,而在C++的定义中,env是个一级指针
C形式需要对env指针进行双重deferencing,而且须将env作为第一个参数传给jni函数
jclass (JNICALL *GetObjectClass) (JNIEnv *env, jobject obj);
jclass GetObjectClass(jobject obj)
{
return functions->GetObjectClass(this,obj);
}
对于*.c
1.jclass test_class = (*env)->GetObjectClass(env, obj);
2.jfieldID id_num = (*env)->GetFieldID(env, test_class, “num”, “I”);
对于 *.cpp
1.jclass test_class = env->GetObjectClass(obj);
2.jfieldID id_num = env->GetFieldID(test_class, “num”, “I”);
在 C 中,
JNI 函数调用由“(*env)->”作前缀,目的是为了取出函数指针所引用的值。
在 C++ 中,
JNIEnv 类拥有处理函数指针查找的内联成员函数。
下面将说明这个细微的差异,其中,这两行代码访问同一函数,但每种语言都有各自的语法。
C 语法:jsize len = (*env)->GetArrayLength(env,array);
C++ 语法:jsize len =env->GetArrayLength(array);
1、jni 可以调用本地C函数。
2、jni 调用C++库时,首先要将C++库提供的功能封装成纯C格式的函数接口,然后jni里面调用这些C接口。
总结,没什么区别。一个是 jni调用c。另一个是jni调用c,c调用c++。
传送门:jnimaster
总结
JNI使用c和cpp的基本使用和了解就讲的差不多了,更多的学习可以去看jni的使用安全手册。