C调用JAVA
目录:
1.JNIENV
2.函数签名
3.c调用java
4.java调用c
5. java和c的对应关系!
1. JNIEnv分析
Env: c中和c++的区别
Env:c 与 java 相互调用的桥梁,是一个线程相关的结构体,该结构体代表了Java在本线程的执行环境
可以理解为我们java中的上下文参数
typedef const struct JNINativeInterface*C_JNIEnv;
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
定义一个结构体,结构体指针
在c中, 使用: JNIEnv *env, 2级指令
在C++中 *env: 1级指令, 只有1级指针才能用---->可以用lamda表达式
在C的定义中,env是一个两级指针,而在C++的定义中,env是个一级指针
举例: c++ return env->NewStringUTF(hello.c_str());
c中: return (*env--->)NewStringUTF(hello.c_str());
意思就是把c中的字符串传给java
https://blog.csdn.net/zhangmiaoping23/article/details/103855018
原理:
need-to-insert-img
我们知道 ,JNIEnv是JNINativeInterface_结构体的指针别名 , 在JNINativeInterface_结构体中 , 定义很多操作函数 。例如:
jstring(JNICALL*NewStringUTF)(JNIEnv*env,constchar*utf);jsize(JNICALL*GetStringUTFLength)(JNIEnv*env,jstring str);constchar*(JNICALL*GetStringUTFChars)(JNIEnv*env,jstring str,jboolean*isCopy);void(JNICALL*ReleaseStringUTFChars)(JNIEnv*env,jstring str,constchar*chars);
由上述函数可以看出,每个函数都需要一个JNIEnv指针,但是为什么需要呢 ?
有两点:
第一:函数需要 , 在函数中仍然需要JNINativeInterface_结构体中的函数做处理
第二:区别对待C和C++
JNIEnv类型实际上代表了Java环境,通过JNIEnv*指针就可以对Java端的代码进行操作。比如我们可以使用JNIEnv来创建Java类中的对象,调用Java对象的方法,获取Java对象中的属性等。
基本用法:
// 得到jclassjclass jcls=(*env)->GetObjectClass(env,jobj);
// com.zeno.jni_HelloJNI.hJNIEXPORTvoidJNICALL Java_com_zeno_jni_HelloJni_accessJavaStringField(JNIEnv*,jobject);// Hello_JNI.c/*C语言访问java String类型字段*/JNIEXPORTvoidJNICALL Java_com_zeno_jni_HelloJni_accessJavaStringField(JNIEnv*env,jobject jobj){// 得到jclassjclass jcls=(*env)->GetObjectClass(env,jobj);// 得到字段IDjfieldID jfID=(*env)->GetFieldID(env,jcls,"name","Ljava/lang/String;");// 得到字段的值jstring jstr=(*env)->GetObjectField(env,jobj,jfID);// 将jstring类型转换成字符指针char*cstr=(*env)->GetStringUTFChars(env,jstr,JNI_FALSE);//printf("is vaule:%s\n", cstr);// 拼接字符chartext[30]=" xiaojiu and ";strcat(text,cstr);//printf("modify value %s\n", text);// 将字符指针转换成jstring类型jstring new_str=(*env)->NewStringUTF(env,text);// 将jstring类型的变量 , 设置到java 字段中(*env)->SetObjectField(env,jobj,jfID,new_str);}
如果native方法是static, obj就代表native方法的类的class 对象实例(static 方法不需要类实例的,所以就代表这个类的class对象)。
举一个简单的例子:我们在TestJNIBean中创建一个静态方法testStaticCallMethod和非静态方法testCallMethod,我们看在cpp文件中该如何编写?
1public class TestJNIBean{
2 public static final String LOGO = "learn android with aserbao";
3 static {
4 System.loadLibrary("native-lib");
5 }
6 public native String testCallMethod(); //非静态
7
8 public static native String testStaticCallMethod();//静态
9
10 public String describe(){
11 return LOGO + "非静态方法";
12 }
13
14 public static String staticDescribe(){
15 return LOGO + "静态方法";
16 }
17}
1extern "C"
2JNIEXPORT jstring JNICALL
3Java_com_example_androidndk_TestJNIBean_testCallMethod(JNIEnv *env, jobject instance) {
4 jclass a_class = env->GetObjectClass(instance); //因为是非静态的,所以要通过GetObjectClass获取对象
5 jmethodID a_method = env->GetMethodID(a_class,"describe","()Ljava/lang/String;");// 通过GetMethod方法获取方法的methodId.
6 jobject jobj = env->AllocObject(a_class); // 对jclass进行实例,相当于java中的new
7 jstring pring= (jstring)(env)->CallObjectMethod(jobj,a_method); // 类调用类中的方法
8 char *print=(char*)(env)->GetStringUTFChars(pring,0); // 转换格式输出。
9 return env->NewStringUTF(print);
10}
11
12extern "C"
13JNIEXPORT jstring JNICALL
14Java_com_example_androidndk_TestJNIBean_testStaticCallMethod(JNIEnv *env, jclass type) {
15 jmethodID a_method = env->GetMethodID(type,"describe","()Ljava/lang/String;"); // 通过GetMethod方法获取方法的methodId.
16 jobject jobj = env->AllocObject(type); // 对jclass进行实例,相当于java中的new
17 jstring pring= (jstring)(env)->CallObjectMethod(jobj,a_method); // 类调用类中的方法
18 char *print=(char*)(env)->GetStringUTFChars(pring,0); // 转换格式输出。
19 return env->NewStringUTF(print);
20}
上面的两个方法最大的区别就是静态方法会直接传入jclass,从而我们可以省去获取jclass这一步,而非静态方法传入的是当前类
谈谈你对JNIEnv 和JavaVM 理解?
2.JNIEnv
JNIEnv 表示Java 调用native 语言的环境,是一个封装了几乎全部JNI 方法的指针。
JNIEnv 只在创建它的线程生效,不能跨线程传递,不同线程的JNIEnv 彼此独立。
native 环境中创建的线程,如果需要访问JNI,必须要调用AttachCurrentThread关联,并使用DetachCurrentThread 解除链接。
C++子线程调用Java方法
我们通常都是在C++主线程中调用java方法,很简单,但是在子线程中调用java方法却不能采用在主线程中调用的方式,
因为调用java方法是要用JNIEnv去调用的,但是JNIEnv是线程相关的,子线程中不能直接使用创建线程的JNIEnv,
所以需要JVM进程相关的,可以通过JVM来获取当前线程的JNIEnv,然后就可以调用java方法了。
通过JVM获取JniEnv:JNIEnv*env=jvm->AttachCurrentThread(&env,0);
jvm->DetachCurrentThread();
JavaVM:JavaVM是Java虚拟机在JNI层的代表,JNI全局仅仅有一个
JNIEnv:JavaVM 在线程中的代码,每个线程都有一个,JNI可能有非常多个JNIEnv;
JavaVM 是虚拟机在JNI 层的代表,一个进程只有一个JavaVM,所有的线程共用一个JavaVM。
什么时候使用?动态注册
————————————————
主要步骤就是,通过运行时函数JNI_OnLoad初始化JavaVM,再通过JavaVM调用AttachCurrentThread函数初始化当前线程的JNIEnv,
此时这个JNIEnv就可以调用java方法了,调用完后要调用JavaVM的DetachCurrentThread函数来释放JavaVM引用。
--------------------------------------------------------------------------------
2.为什么JNI中突然多出了一个概念叫"签名"?----(因为java支持重载,c不支持,但是c++支持)
因为Java是支持函数重载的,也就是说,可以定义相同方法名,但是不同参数的方法,然后Java根据其不同的参数,
找到其对应的实现的方法。这样是很好,所以说JNI肯定要支持的,那JNI要怎么支持那,如果仅仅是根据函数名,
没有办法找到重载的函数的,所以为了解决这个问题,JNI就衍生了一个概念——"签名",即将参数类型和返回值类型的组合。
如果拥有一个该函数的签名信息和这个函数的函数名,我们就可以顺序的找到对应的Java层中的函数了。
3. 方法的签名是如何定义的?
方法签名具体方法:
获取方法的签名比较麻烦一些,通过下面的方法也可以拿到属性的签名。
打开命令行,输入javap
注意:
类描述符开头的 'L' 与结尾的 ';' 必须要有
数组描述符,开头的 '[' 必须要有
方法描述符规则: "(各参数描述符)返回值描述符",其中参数描述符间没有任何分隔符号
从上表可以看出, 基本数据类型的签名基本都是单词的首字母大写, 但是boolean和long除外因为B已经被byte占用, 而long也被Java类签名的占用.
对象和数组的签名稍微复杂一些.
对象的签名就是对象所属的类签名, 比如String对象, 它的签名为Ljava/lang/String; .
数组的签名为[+类型签名, 例如byte数组. 其类型为byte, 而byte的签名为B, 所以byte数组的签名就是[B.同理可以得到如下的签名对应关系:
第八: java创建c层对象
Native 构建对象如何与 Java 层对应
一般返回一个地址:
4. C调用java的方法, 变量, 构造函数, 对象
第七: c创建java对象 ; 如何对属性进行赋值(源码就是parcelbale)
解决办法: 使用函数NewObject可以用来创建JAVA对象;
原理使用了6: java的构造方法!
创建一个native对象,然后得到一个对象的地址
JNIEXPORT void JNICALL Java_com_example_jni_1test_sayHello(JNIEnv * evn, jobject obj)
{
//获取java的Class
jclass my_class=evn->FindClass("com/example/Person");
//获取java的Person构造方法id---构造函数的函数名为<init>,返回值为void
jmethodID init_id=evn->GetMethodID(my_class,"<init>","(Ljava/lang/String;I)V");//(类,属性名.签名)
//创建Person对象--使用NewObject方法
jobject person=evn->NewObject(my_class,init_id, (evn)->NewStringUTF("mike"),20);
//获取Person的Desc方法id
jmethodID desc_id=evn->GetMethodID(my_class,"Desc","()V");
//调用创建的person里的desc方法
evn->CallVoidMethod(person,desc_id)
}
第六. .间接访问Java类的父类的方法。(java是做不到的)
但是通过底层C的方式可以间接访问到父类Human的方法,跳过子类的实现,甚至你可以直接哪个父类(如果父类有多个的话),这是Java做不到的!
必须知道父类的类名!
jclass cls = (*env)->GetObjectClass(env, jobj);
//获取man属性(对象)
jfieldID fid = (*env)->GetFieldID(env, cls, "human", "Lcom/haocai/jni/Human;");
//获取
jobject human_obj = (*env)->GetObjectField(env, jobj, fid);
//执行sayHi方法
jclass human_cls = (*env)->FindClass(env, "com/haocai/jni/Human");
jmethodID mid = (*env)->GetMethodID(env, human_cls, "sayHi", "()V");
//执行Java相关的子类方法
(*env)->CallObjectMethod(env, human_obj, mid);
//执行Java相关的父类方法
(*env)->CallNonvirtualObjectMethod(env, human_obj, human_cls, mid);
第五. .访问Java类的构造方法。
和之前的区别: FindClass, NewObject, 构造方法用<init>
jclasscls = (*env)->FindClass(env, "java/util/Date");
//jmethodID
jmethodIDconstructor_mid= (*env)->GetMethodID(env, cls,"<init>","()V");
//实例化一个Date对象(可以在constructor_mid后加参)
jobjectdate_obj = (*env)->NewObject(env, cls, constructor_mid);
//调用getTime方法
jmethodIDmid = (*env)->GetMethodID(env, cls, "getTime", "()J");
jlongtime = (*env)->CallLongMethod(env, date_obj, mid);
第四. 获取java的静态方法
总结: 访问静态方法和非静态方法的区别: 调用的方法不一样!
共同点: 流程都一样. 要获取类, 获取方法的Id
//Jclass
jclass cls = (*env)->GetObjectClass(env, jobj);
//JmethodID
jfieldID mFid = (*env)->GetStaticMethodID(env, cls, "getUUID", "()Ljava/lang/String;");
//调用
//CallStaticMethod
jstring uuid = (*env)->CallStaticObjectMethod(env, jobj, mFid);
第三. 获取java的方法
3步走: 类,方法签名,Call方法
//Jclass
jclass cls = (*env)->GetObjectClass(env, jobj);
//JmethodID
jfieldID mFid = (*env)->GetMethodID(env, cls, "genRandomInt", "(I)I");
//调用
//CallMethod
jint random = (*env)->CallIntMethod(env, jobj, mFid, 200);
总结: 访问静态属性和非静态属性的区别: 调用的方法不一样!
共同点: 流程都一样. 要获取类, 获取属性的Id
第二. 获取java的static属性
//访问静态属性
//jclass
jclass cls = (*env)->GetObjectClass(env, jobj);
//jfieldID
jfieldID fid =(*env)->GetStaticFieldID(env, cls, "count", "I");
//GetStaticField
jint count = (*env)->GetStaticIntField(env, cls, fid);
第一. 获取java的属性
1. 获取class
2. 得到属性字段的id ,通过属性的签名得到
3.得到java的属性
jclass cls = env->GetObjectClass(jobj);//obj是传入的参数,得到的是B类的实例
// 获取java的属性
char* name="";
jfieldID jfieldId = env->GetFieldID(cls, name, "Ljava/lang/String");
env->GetObjectField(jobj, jfieldId)
2.Java调用C函数
在Java中声明Native方法(即需要调用的本地方法)
编译上述 Java源文件javac(得到 .class文件) 3。 通过 javah 命令导出JNI的头文件(.h文件)
使用 Java需要交互的本地代码 实现在 Java中声明的Native方法
编译.so库文件
通过Java命令执行 Java程序,最终实现Java调用本地代码
CMake:一个跨平台的编译构建工具,替代 Android.mk
LLDB:一个高效的 C/C++ 的调试工具
NDK:即我们需要下载的工具,会生成到 SDK 根目录下的 ndk-bundle 目录下
java调用C:
在JNI 中,java调用C的流程步骤:(个人的理解整理,如有误,请包容也请指正)
总结:
1.ndk,会先加载so库
2.ndk开发工具包提供调用java的方法直接调用。(我们可以通过静态注册和动态注册)
————————————————
静态注册和动态注册?
他们的好处如何?
如何实现动态注册
如何加载NDK 库 ?如何在JNI 中注册Native 函数,有几种注册方法 ?
当执行一个 Java 的 native 方法时,虚拟机是怎么知道该调用 so 中的哪个方法呢?
这就需要用到注册的概念了,通过注册,将指定的 native 方法和 so 中对应的方法绑定起来(函数映射表),这样就能够找到相应的方法了。
参考回答:
public class JniTest{
//加载NDK 库
static{
System.loadLirary("jni-test");
}
注册JNI 函数的两种方法
o静态方法
. 缺点
必须遵循注册规则
名字过长
运行时去找效率不高
动态注册:
无需向静态注册一样,遵循特定的方法命名格式。
原因:
我们在调用 System.loadLibrary的时候,会在C/C++文件中回调一个名为 JNI_OnLoad ()的函数,在这个函数中一般是做一些初始化相关操作, 我们可以在这个方法里面注册函数, 注册整体流程如下:
通过 RegisterNatives 方法手动完成 native 方法和 so 中的方法的绑定,这样虚拟机就可以通过这个函数映射表直接找到相应的方法了。
b. 通常我们在 JNI_OnLoad 方法中完成动态注册,native-lib.cpp 如下:
JNI_OnLoad入口:然后分别调用下面
//注册函数 registerNatives(把类写好) ->registerNativeMethods(添加方法,把什么的类传来) ->env->RegisterNatives
// jni头文件 #include<jni.h>#include<cassert>#include<cstdlib>#include<iostream>usingnamespacestd;//native 方法实现jintget_random_num(){returnrand();}/*需要注册的函数列表,放在JNINativeMethod 类型的数组中,
以后如果需要增加函数,只需在这里添加就行了
参数:
1.java中用native关键字声明的函数名
2.签名(传进来参数类型和返回值类型的说明)
3.C/C++中对应函数的函数名(地址)
*/staticJNINativeMethod getMethods[]={{"getRandomNum","()I",(void*)get_random_num},};//此函数通过调用RegisterNatives方法来注册我们的函数staticintregisterNativeMethods(JNIEnv*env,constchar*className,JNINativeMethod*getMethods,intmethodsNum){jclass clazz;//找到声明native方法的类clazz=env->FindClass(className);if(clazz==NULL){returnJNI_FALSE;}//注册函数 参数:java类 所要注册的函数数组 注册函数的个数if(env->RegisterNatives(clazz,getMethods,methodsNum)<0){returnJNI_FALSE;}returnJNI_TRUE;}staticintregisterNatives(JNIEnv*env){//指定类的路径,通过FindClass 方法来找到对应的类constchar*className="com/example/wenzhe/myjni/JniTest";returnregisterNativeMethods(env,className,getMethods,sizeof(getMethods)/sizeof(getMethods[0]));}//回调函数JNIEXPORT jint JNICALLJNI_OnLoad(JavaVM*vm,void*reserved){JNIEnv*env=NULL;//获取JNIEnvif(vm->GetEnv(reinterpret_cast<void**>(&env),JNI_VERSION_1_6)!=JNI_OK){return-1;}assert(env!=NULL);//注册函数 registerNatives ->registerNativeMethods ->env->RegisterNativesif(!registerNatives(env)){return-1;}//返回jni 的版本 returnJNI_VERSION_1_6;}
来源:https://www.jianshu.com/p/1d6ec5068d05
4. Java中类型和native中类型映射关系。
类型转换
JavaNativeSignature
bytejbyteB
charjcharC
doublejdoubleD
floatjfloatF
intjintI
shortjshortS
longjlongJ
booleanjbooleanZ
voidvoidV
对象jobjectL+classsname+;
ClassjclassLjava/lang/Class;
StringjstringLjava/lang/String;
ThrowablejthrowableLjava/lang/Throwable;
Object[]jobjectArray[L+classname+;
byte[]jbyteArray[B
char[]jcharArray[C
double[]jdoubleArray[D
float[]jfloatArray[F
int[]jintArray[I
short[]jshortArray[S
long[]jlongArray[J
booleanjbooleanArray[Z
总结: java调用c和C调用java