03

您所在的位置:网站首页 jni语法与Java区别 03

03

2023-03-13 02:38| 来源: 网络整理| 查看: 265

native方法为static

在java中声明的JNI的native方法静态方法和非静态,对于底层的C/C++代码来说是有区别的: 1,JNI函数的参数也由三部分组成:首先是JNIEnv*,是一个指向JNI运行环境的指针;2,第二个参数随本地方法是静态还是非静态而有所不同一一非静态本地方法的第二个参数是对对象的引用,而静态本地方法的第二个参数是对其Java类的引用; 3,其余的参数对应通常Java方法的参数,参数类型需要根据一定规则进行映射。

native方法static和非static,在c/c++中回调java方法的区别 1,static:JNI回调静态方法: 不需要对象实例,只需要拿到class即可

void jniCallUnStaticMethod() { getJNIEnv(&env); clazz = env->FindClass("com.test.Test"); jmethodID method = env->GetStaticMethodID(clazz, "staticTestMethod", "(I)V"); env->CallStaticVoidMethod(clazz, method, channel); }

2,非static:JNI回调非静态方法: 需要class及其一个实例,可通过如下方式注册一个实例。

void jniCallUnStaticMethod() { getJNIEnv(&env); clazz = env->FindClass("com.test.Test"); jmethodID method = env->GetMethodID(clazz, "unStaticTestMethod", "(I)V"); env->CallVoidMethod(initedObj, method, channel); } 传递数据 传递int public native int add(int x , int y); JNIEXPORT jint JNICALL Java_com_huachao_jnipassdata_JNI_add (JNIEnv *env, jobject obj, jint x, jint y){ return x+y; } 传递字符串 ①, Java字符串转为c字符串 #include //因为下面用到的NULL在stdlib库中,所以导入该库 #include /** * 把一个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; }

②, 与①函数结合,将字符串的每一个字符+1,实现简单加密

//对java字符串进行处理 public native String sayHelloInC(String c); JNIEXPORT jstring JNICALL Java_com_huachao_jnipassdata_JNI_sayHelloInC (JNIEnv *env, jobject obj, jstring jstr){ //调用①函数将java字符串转为c语言字符串 char* cstr = _JString2CStr(env , jstr); //调用strlen获取cstr字符串的长度 int length = strlen(cstr); int i; for (i= 0; i< length; i++) { *(cstr+i)+=1; } return (*env)->NewStringUTF(env , cstr); } 传递java的int数组 public native int[] arrElementIncarse(int[] arr); JNIEXPORT jintArray JNICALL Java_com_huachao_jnipassdata_JNI_arrElementIncarse (JNIEnv *env, jobject obj, jintArray jArray){ //获取java数组的长度 jsize length = (*env)->GetArrayLength(env,jArray); LOGI("size=%d",length); /* * 将java数组转为c语言指针 * 参数:(JNIEnv*, jintArray, jboolean*) * 第三个参数 : &iscopy */ jboolean iscopy = 1; jint* arrayPointer = (*env)->GetIntArrayElements(env,jArray,NULL); int i; for (i = 0; iSetIntArrayRegion(env , jArray , 0,length , arrayPointer); return jArray; }

注意:上面(*env)->SetIntArrayRegion(env , jArray , 0,length , arrayPointer);

JNI中使用LogCat输出

1,在Android.mk中添加(一定要添加在include $(CLEAR_VARS)之后)

#增加log函数对应的log库liblog.so LOCAL_LDLIBS += -llog

实质就是加载F:\as\plugin\android-ndk-r9d\platforms\android-14\arch-arm\usr\lib下的liblog.so库,例如:如果想加载libEGL.so,那么就在Android.mk中添加LOCAL_LDLIBS += -lEGL 2,在.c文件中添加`

include `

3,在.c文件中添加宏

#define TAG "MY JNILog " // 这个是自定义的LOG的标识 #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型 #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型 #define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__) // 定义LOGW类型 #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__) // 定义LOGE类型 #define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__) // 定义LOGF类型

4,调用上面的宏打印日志(与printf用法相似)

LOGI("size=%d",length); 或 LOGI("arr[%d]=%d",i,*(arrayPointer+i));

注意:JNI开发中,如果在eclipse的.c文件中报错,可能并不是你代码的错误,而是插件的问题,解决:将项目clean一下,或者将项目close然后open。

C回调JAVA(利用放射) JNI.java package com.hhc.ccalljava; import android.util.Log; public class JNI { static{ System.loadLibrary("callback"); } public native void callbackvoidmethod(); public native void callbackintparammethod(); public native void callbackstringparammethod(); //c调用带两个int参数的方法 public int add(int x , int y) { return x+y; } //c调用java空的方法 public void helloFromJava() { System.out.println("hello from java"); } //c调java带字符串的方法 public void printString(String s) { Log.i("hhc", s); } } 获取方法签名

1,cd 到:项目根目录\bin\classes目录下,本例中如下: F:\as\Eclipse\personal\JNI\04-C回调Java\bin\classes> 2,使用javap命令:javap -s com.hhc.ccalljava.JNI

Compiled from "JNI.java" public class com.hhc.ccalljava.JNI { public com.hhc.ccalljava.JNI(); descriptor: ()V public native void callbackvoidmethod(); descriptor: ()V public int add(int, int); descriptor: (II)I public void helloFromJava(); descriptor: ()V public void printString(java.lang.String); descriptor: (Ljava/lang/String;)V } callback.c #include #include #define TAG "myJNILog" // 这个是自定义的LOG的标识 #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) JNIEXPORT void JNICALL Java_com_hhc_ccalljava_JNI_callbackvoidmethod (JNIEnv *env, jobject obj){ //1,获取字节码对象 //jclass (*FindClass)(JNIEnv*, const char*); jclass clazz = (*env)->FindClass(env , "com/hhc/ccalljava/JNI"); //2,获取Method方法 //jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*); /* * 参数3:方法名称 * 参数4:方法签名(因为java中有方法重载) * 使用javap查看 */ jmethodID methodID = (*env)->GetMethodID(env , clazz , "helloFromJava" , "()V"); //3,通过字节码创建java对象,如果native方法和要回调的java方法在同一个类里面,可以直接用JNI传过来的java对象调用创建的方法 //此处的obj就是这个java对象,所以不需要创建 //4,通过对象调用方法 //void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); //obj 其实就是我们要调用的方法的那个对象的实例 (*env)->CallVoidMethod(env , obj , methodID); } JNIEXPORT void JNICALL Java_com_hhc_ccalljava_JNI_callbackintparammethod (JNIEnv *env, jobject obj){ LOGI("running"); //1,获取字节码对象 jclass claz = (*env)->FindClass(env , "com/hhc/ccalljava/JNI"); //2,获取Method方法 jmethodID methodID = (*env)->GetMethodID(env , claz ,"add", "(II)I"); //jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...); int result = (*env)->CallIntMethod(env , obj , methodID ,9,6); LOGI("result=%d" , result); } JNIEXPORT void JNICALL Java_com_hhc_ccalljava_JNI_callbackstringparammethod (JNIEnv *env, jobject obj){ jclass clazz = (*env)->FindClass(env , "com/hhc/ccalljava/JNI"); jmethodID methodID = (*env)->GetMethodID(env ,clazz,"printString","(Ljava/lang/String;)V"); //void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); jstring str = (*env)->NewStringUTF(env,"weiwei,caonima"); (*env)->CallVoidMethod(env ,obj,methodID,str); } MainActivity中调用 public class MainActivity extends Activity { private JNI jni; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); jni = new JNI(); } public void cCallJava(View v) { jni.callbackvoidmethod(); } public void cCallJavaInt(View v) { jni.callbackintparammethod(); } public void cCallJavaString(View v) { jni.callbackstringparammethod(); } }

注意:CallVoidMethod、CallIntMethod这里的void和int都是指返回值类型

C回调显示Toast JNI.java public class JNI { static{ System.loadLibrary("callback"); } public native void callbackShowToast(); }

callback.c

/** * AllocObject(env,clazz);这相当于新建一个Activity,但Activity是不能新建的,如果新建的话,Context上下文是空, * 但是显示Toast,要传Context,这个时候会报NullPointerException * 解决方案一:native方法和显示Toast的方法都定义在Activity中,这样传过来的jobject就是Activity实例了,就不需要新建 * 解决方案二:在JNI的构造方法中传入上下文,showToast方法放入JNI.java中 */ JNIEXPORT void JNICALL Java_com_hhc_ccalljava_JNI_callbackShowToast (JNIEnv *env, jobject obj){ //1,获取字节码对象 jclass clazz = (*env)->FindClass(env , "com/hhc/ccalljava/MainActivity"); //2,获取方法methodID //jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*); jmethodID methodID = (*env)->GetMethodID(env,clazz,"showToast","(Ljava/lang/String;)V"); //此时的obj是JNI.java这个类的实例,而被调的函数在MainActivity中,所以不能直接用obj调用 //3,通过字节码创建java对象 //jobject (*AllocObject)(JNIEnv*, jclass); jobject instance = (*env)->AllocObject(env,clazz); //4,通过对象调用方法 jstring *jstr = (*env)->NewStringUTF(env,"this is C"); (*env)->CallVoidMethod(env,instance,methodID,jstr); }

MainActivity.java

public class MainActivity extends Activity { private JNI jni; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); jni = new JNI(); } public void cCallJavaShowToast(View v) { jni.callbackShowToast(); } public void showToast(String s) { Toast.makeText(getApplicationContext(), "from c="+s, Toast.LENGTH_LONG).show(); } }

注意:上面方式会抛NullPointerException

解决和优化

1,解决方案一:native方法和显示Toast的方法都定义在Activity中 2,解决方案二:JNI.java的构造函数传入Context上下文,showToast方法放入JNI.java中。

JNI.java

public class JNI { static{ System.loadLibrary("callback"); } private Context mContext; public JNI(Context mContext) { super(); this.mContext = mContext; } public native void callbackShowToast(); public void showToast(String s) { Toast.makeText(mContext, "from c="+s, Toast.LENGTH_LONG).show(); } }

callback.c

JNIEXPORT void JNICALL Java_com_hhc_ccalljava_JNI_callbackShowToast (JNIEnv *env, jobject obj){ //1,获取字节码对象 jclass clazz = (*env)->FindClass(env , "com/hhc/ccalljava/JNI"); //2,获取方法methodID //jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*); jmethodID methodID = (*env)->GetMethodID(env,clazz,"showToast","(Ljava/lang/String;)V"); //此时的obj是JNI.java这个类的实例,而被调的函数在MainActivity中,所以不能直接用obj调用 //3,通过对象调用方法 jstring *jstr = (*env)->NewStringUTF(env,"this is C"); (*env)->CallVoidMethod(env,obj,methodID,jstr); }

MainActivity.java调用

public class MainActivity extends Activity { private JNI jni; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); jni = new JNI(this); } public void cCallJavaShowToast(View v) { jni.callbackShowToast(); } } jstring转char* 方式一:env提供的函数 //字符串逆置 char *strrev(char *s){ char *p = s; char *q = s; char temp; while(*q != '\0') q++; q--;//移动'\0'之前的字符位置 while(p < q){ temp = *p; *p = *q; *q = temp; p++; q--; } return s; } JNIEXPORT jstring JNICALL Java_com_hhc_ccalljava_JNI_jstringtocchar (JNIEnv *env, jobject obj, jstring jstr){ char* cstr = NULL; jsize len = 0; jstring new_str; //字符串长度 len = (*env)->GetStringLength(env,jstr); //申请内存,大小等于len+1 int char_len = sizeof(char); cstr = (char *)malloc((len+1)*char_len);//需要结束符 //把java字符串转为c字符串 //void (*GetStringUTFRegion)(JNIEnv*, jstring, jsize, jsize, char*); (*env)->GetStringUTFRegion(env,jstr,0,len,cstr); //字符串逆置 // LOGI("before---%s" , cstr); cstr = strrev(cstr,len); // LOGI("hou---%s" , cstr); //转为jstr new_str = (*env)->NewStringUTF(env,cstr); //释放申请的内存空间 free(cstr); return new_str; }

注意: 1,实际测试,在genymotion android 6.0的模拟器返回的字符串尾部会多一个tochar,而用下面的方式二不会,应该是编码的问题,因为下面的方式可以指定编码方式 2,上面的问题经过反复尝试将(*env)->GetStringUTFRegion(env, jstr, 0, len, cstr); 替换成char* cs = (*env)->GetStringUTFChars(env,jstr,0);问题解决,但是会有警告。

方式二:自定义函数实现 //详解jstring转char*的函数 char* _JString2CStr(JNIEnv* env, jstring jstr) { char* rtn = NULL; //获取String的字符串 jclass clsstring = (*env)->FindClass(env, "java/lang/String"); //"GB2312"的jstring类型 jstring strencode = (*env)->NewStringUTF(env,"GB2312"); //就是调用java的String类的:byte[] bs = .getBytes("GB23122");方法 jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B"); jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid, strencode); // String .getByte("GB2312"); //byte数组的长度 jsize alen = (*env)->GetArrayLength(env, barr); //转为jbyte*,就是c语言的char*, 指向数组的首地址 jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE); if(alen > 0) { //c的字符串实质就是字符数组,C的字符数组必须要有结束符,所以需要加1 //申请堆内存 rtn = (char*)malloc(alen+1); //"\0" //内存拷贝,将ba拷贝到rtn,长度为alen memcpy(rtn, ba, alen); //结束符为0 rtn[alen]=0; } //释放不再使用的变量内存 (*env)->ReleaseByteArrayElements(env, barr, ba,0); return rtn; } JNI开发中编码问题

1,JNI返回字符串乱码或报错 2,在.c代码中打印中文字符串乱码 解决方式:将.c文件的编码方式改为UTF-8编码

声明:java中所有引用类型(非基本数据类型),在jni底层都是对应的void* typedef void* jobject; C++开发JNI C的预处理命令 #if defined(__cplusplus) typedef _JNIEnv JNIEnv; typedef _JavaVM JavaVM; #else typedef const struct JNINativeInterface* JNIEnv; typedef const struct JNIInvokeInterface* JavaVM; #endif #开头的就是C/C++的预处理命令 在编译之前,先会走预编译阶段,预编译阶段的作用就是把include进来的头文件copy到源文件中,define这些宏定义,用真实的值替换一下 c++开发JNI时,env不再是结构体JNINativeInterface的二级指针 _JNIEnv、JNIEnv:_JNIEnv是C++的结构体,C++结构体跟C的区别:C++结构体可以定义函数 env 是JNIEnv的一级指针,也就是结构体_JNIEnv的一级指针,env->来调用 _JNIEnv的函数,实际上调用的就是结构体JNINativeInterface的同名函数指针,在调用时第一个参数env已经传过去了。 C++的函数要先声明在使用,可以把javah生成的头文件include进来作为函数的声明 native方法 static{ System.loadLibrary("jnicpp"); } public native String hellofromcpp();

jnicpp.cpp

#include #include "com_huachao_jnicpp_MainActivity.h" JNIEXPORT jstring JNICALL Java_com_huachao_jnicpp_MainActivity_hellofromcpp (JNIEnv *env, jobject obj){ return env->NewStringUTF("1234haha"); } Fork子进程

MainActivity.java中调用

public class MainActivity extends Activity { static{ System.loadLibrary("cfork"); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public native void cfork(); public void cforksub(View v){ cfork(); } }

cfork.c

#include #include #define TAG "MYJNILog" // 这个是自定义的LOG的标识 #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) JNIEXPORT void JNICALL Java_com_huachao_forksub_MainActivity_cfork (JNIEnv *env, jobject obj){ //fork成功的分叉出一个子进程,会返回当前进程的id,但是只能在主进程中fork成功 //在子进程中运行fork,会返回0,但是不能再分叉出新进程 //所以这个方法会被调用两次,第二次是在子进程中调用 //fork的返回值三种可能 >0 ==0


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3