jni直接转byte_JNI再探之JNI 数据类型及Java与C++之间互调
JNI
什么是JNI
JNI,全稱Java NativeInterface,是一種為Java編寫本地方法和JVM嵌入本地應用程序標準的應用程序接口,它允許運行在JVM上的Java代碼能夠與C/C++實現的本地庫進行交互。
JNI 數據類型
Java中有兩種類型:基本數據類型(int、float、char等)和引用類型(類、對象、數組等)。 JNI定義了一個C/C++類型的集合,集合中每一個類型對應于Java中的每一個類型,其中,對于基本類型而言,JNI與Java之間的映射是一對一的,比如Java中的int類型直接對應于C/C++中的jint;而對引用類型的處理卻是不同的,JNI把Java中的對象當作一個C指針傳遞到本地函數中,這個指針指向JVM中的內部數據結構,而內部數據結構在內存中的存儲方式是不可見的,本地代碼必須通過在JNIEnv中選擇適當的JNI函數來操作JVM中的對象。比如,對于java.lang.String對應的JNI類型是jstring,但本地代碼只能通過GetStringUTFChars這樣的JNI函數來訪問字符串的內容。
JNI映射表
| Java 類型 | JNI本地類型 |方法簽名 | 描述 | | ------ |------|------|------| | boolean | jboolean | Z | unsigned 8 bits| | byte | jbyte | B |signed 8 bits| | char | jchar | C |unsigned 16 bits| | short | jshort | S | signed 16 bits | | int | jint | I | signed 32 bits | | long | jlong | J | signed 64 bits | | float | jfloat | F | 32bits | | double | jdouble | D | 64bits | | void | Void | V | - |
方法簽名
由于Java支持方法重載,在JNI訪問Java層方法時僅靠函數名是無法唯一確定一個方法,因此JNI提供了一套簽名規則(如:Z、B、[Z等),用一個字符串來唯一確定一個方法,其規則:(參數1類型簽名參數2類型簽名…)返回值類型簽名,比如Java方法long getDeviceId(int n, String s, int[] arr)、long getDeviceId(int n)的類型簽名分別為(ILjava/lang/String;[I)J、(I)J。 1. 基本類型: boolean ->Z,byte-> B,char -> C,short-> S,int->I,long->J,float-> F,double->D,void -> V; 2. 如果是類的類型:L+類全名,類名中的.用/代替,比如java.lang.String就是Ljava/lang/String; 3. 如果是數組類型:則在前面加上 然后加類型簽名,幾位數組就加幾個,比如int[]->[I,boolean[][]->[[Z,java.lang.Class[] -> [Ljava/lang/Class; 可以通過javap -s來打印該方法的簽名
Example: public static native String getName(); public static native int calculate(int a,int b);通過Rebuild Project才會在app中的intermediates目錄下javac/debug生成class文件,找到 類到地址 然后右鍵打開命令行
在命令行輸入 javap -s JNIUtils.class 即可獲取到2個方法到簽名,我這里是JNIUtils獲取的兩個簽名如下:
警告: 二進制文件JNIUtils包含com.example.nativejnidemo.JNIUtils Compiled from "JNIUtils.java" public class com.example.nativejnidemo.JNIUtils {public com.example.nativejnidemo.JNIUtils();descriptor: ()Vpublic static native java.lang.String getName(); //括號為空表示當前沒有參數descriptor: ()Ljava/lang/String; //括號中有兩個參數則表示為該類型的簽名I I在返回該方法簽名類型Ipublic static native int calculate(int, int);descriptor: (II)IAndroid studio 3.0 打印日志
- 首先在app下的build.gradle中添加ldLibs("log")
然后在cpp文件中添加
#include <android/log.h> #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "========= Info ========= ", __VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "========= Error ========= ", __VA_ARGS__) #define LOGD(...) __android_log_print(ANDROID_LOG_INFO, "========= Debug ========= ", __VA_ARGS__) #define LOGW(...) __android_log_print(ANDROID_LOG_WARN, "========= Warn ========= ", __VA_ARGS__) 最后調用 `LOGI("from c log");`- 還有一種通過Cmake構建的ndk工程, 導入log庫 在build.gradle中加入ldLibs "log":
JNI 函數解析
Java調用C/C++ 本地函數
/*** CPP 源文件,返回一個字符串* @param env* @return*/ Java_com_example_jnilearndemo_MainActivity_stringFromJNI(JNIEnv *env,jobject /* this */) {std::string hello = "Hello from C++";return env->NewStringUTF(hello.c_str()); } /*** C 源文件,返回一個字符串* @param env* @return*/ Java_com_example_jnilearndemo_MainActivity_stringFromJNI(JNIEnv *env,jobject /* this */) {std::string hello = "Hello from C++";return (*env)->NewStringUTF(env,hello.c_str()); }當源文件為.cpp時,只需要傳env ->就可以;而當源文件為.c時,就需要傳入(*env)->。
上面兩個函數作用都是當Java層調用本地方法時向Java層返回一個UTF-8格式的字符串。兩個函數使用方法不同原因:主要是因為這兩個函數是在不同的源文件中實現的。通過查看jni.h源碼可知,當源文件為.cpp時,JNIEnv實際為結構體JNIEnv_,然后我們再查看JNIEnv_結構體的內容,找到NewStringUTF(constchar *utf)函數,它實際執行了functions->NewStringUTF(this,utf)函數,而這個函數默認傳遞了一個this參數,該this參數則指的是getStringFromC函數原型中JNIEnv指針變量參數。因此,使用C++開發JNI時就無需再傳遞JNIEnv指針變量且在使用JNIEnv_結構體的成員時,直接使用結構體變量指向成員即可。
當源文件為.c時,JNIEnv實際表示的JNINativeInterface_*,JNIEnv*env即JNINativeInterface_**env,因此,我們在調用JNINativeInterface_結構體中的成員時需要使用一級指針來實現,即(*env)->成員。然后,再繼續查看JNINativeInterface_源碼,NewStringUTF函數需要傳入一個JNIEnv結構體類型指針變量,該指針變量指向JNINativeInterface_結構體存儲的地址,因此,還需要將變量env賦值給NewStringUTF即可。
structJNINativeInterface_ {jstring (JNICALL *NewStringUTF) (JNIEnv*env, const char *utf); }C/C++ 訪問Java層屬性及方法
C/C++層訪問Java層對象的實例變量與實例方法
首先獲取構造方法,再通過構造方法獲取類對象,根據類對象調用實例方法;構造方法通過進行標識,傳遞參數為空,返回值也為空。
//C++調用java的實例方法與實例變量 extern "C" JNIEXPORT void JNICALL Java_com_example_jnilearndemo_JNIUtils_callInstanceMethod(JNIEnv *env, jobject instance, jint i) {jclass cls_jniutils = env->FindClass("com/example/jnilearndemo/JNIUtils");if(cls_jniutils==NULL){return;}jmethodID method_instance = env->GetMethodID(cls_jniutils,"method","(Ljava/lang/String;)V");if(method_instance==NULL){return;}//首先獲取構造方法,再通過構造方法獲取類對象,根據類對象調用實例方法;構造方法通過<init>進行標識,傳遞參數為空,返回值也為空jmethodID method_construct = env->GetMethodID(cls_jniutils,"<init>","()V");if(method_construct==NULL){return;}//創建相應的對象,最后參數為空,不需要傳遞參數jobject jnutils = env->NewObject(cls_jniutils,method_construct,NULL);if(jnutils==NULL){return;}jstring msg = env->NewStringUTF("call instance method");//調用Java中的實例變量jfieldID filed_instance = env->GetFieldID(cls_jniutils,"address","Ljava/lang/String;");if(filed_instance==NULL){return;}jstring address = env->NewStringUTF("suzhou");//設置實例變量,需要傳遞對象env -> SetObjectField(jnutils,filed_instance,address);env -> CallVoidMethod(jnutils,method_instance,msg);env->DeleteLocalRef(msg);env->DeleteLocalRef(cls_jniutils);env->DeleteLocalRef(jnutils);env->DeleteLocalRef(address); } public class JNIUtils {static {//加載動態庫System.loadLibrary("JNILearning");}public static String name = "ztz";public String address = "hangzhou";public static native int calculate(int a,int b);public static native void callInstance(int i);public native void callInstanceMethod(int i);public static void LogMessage(String msg){Log.i("C++調用java中的static方法", "LogMessage: "+msg);}public static void staticMethod(String msg){LogMessage(msg);LogMessage(name);}public void method(String msg){LogMessage(msg);LogMessage(address);} }C/C++層訪問Java類的靜態屬性
在.cpp格式的源碼文件中: Java 代碼
public class JNIUtils {static {//加載動態庫System.loadLibrary("JNILearning");}public static String name = "ztz";public static native int calculate(int a,int b);public static native void callInstance(int i);public static void LogMessage(String msg){Log.i("C++調用java中的static方法", "LogMessage: "+msg);}public static void staticMethod(String msg){LogMessage(msg);LogMessage(name);} }JNI C++層代碼
extern "C" JNIEXPORT void JNICALL Java_com_example_jnilearndemo_JNIUtils_callInstance(JNIEnv *env, jclass type, jint i) {// TODO 當源文件為.cpp時,只需要傳env ->就可以;而當源文件為.c時,就需要傳入(*env)->//查找類jclass cls_jniutils = env -> FindClass("com/example/jnilearndemo/JNIUtils");//判斷是否找到,沒找到返回if(cls_jniutils==NULL){return;}//修改java中的靜態變量jfieldID field_name = env->GetStaticFieldID(cls_jniutils,"name","Ljava/lang/String;"); //這里要進行空安全檢查,JNI與Java處理異常機制不一樣,Java遇到異常如果沒有捕獲,程序就立即停止運行,而JNI遇到異常,程序會繼續執行下去, 這樣針對后面的操作非常危險,所以要return跳過后面代碼執行。if(field_name==NULL){return;}jstring new_name = env->NewStringUTF("yif"); //對成員變量進行重新設置env->SetStaticObjectField(cls_jniutils,field_name, new_name);//調用之前methodId所對應的的靜態方法env ->CallStaticVoidMethod(cls_jniutils,method_static,data);//最后釋放之前創建的對象,這里為局部引用env ->DeleteLocalRef(cls_jniutils);env->DeleteLocalRef(new_name); }NewStringUTF函數: 通過調用NewStringUTF函數,會構建一個新的java.lang.String字符串對象。這個新創建的字符串會自動轉換成Java支持的Unicode編碼。如果JVM不能為構造java.lang.String分配足夠的內存,NewStringUTF會拋出一個OutOfMemoryError異常,并返回NULL。在這個例子中我們不必檢查它的返回值,如果NewStringUTF創建java.lang.String失敗,OutOfMemoryError這個異常會被在調用JNI層方法的Java類方法中拋出,比如這里的JNIUtils類。如果NewStringUTF創建java.lang.String成功,則返回一個JNI引用,這個引用指向新創建的java.lang.String對象。
C/C++層訪問Java類的靜態方法
C++層代碼
JNIEXPORT void JNICALL Java_com_example_nativejnidemo_JNIUtils_callInstance__I(JNIEnv *env, jclass type, jint i) {// TODO 當源文件為.cpp時,只需要傳env ->就可以;而當源文件為.c時,就需要傳入(*env)->//查找類jclass cls_hello = (*env)->FindClass(env,"com/example/jnilearndemo/JNIUtils");//判斷是否找到,沒找到返回if(cls_hello==NULL){return;}//然后在查找該類下的方法,參數env,查找的類,查找的方法,方法對應的簽名jmethodID method_static = (*env)->GetStaticMethodID(env,cls_hello,"staticMethod","(Ljava/lang/String;)V");if(method_static==NULL){return;}//傳遞參數為String,所以要創建String對象jstring data = (*env) -> NewStringUTF(env,"call static method");if(data==NULL){return;}//調用之前methodId所對應的的靜態方法(*env) ->CallStaticVoidMethod(env,cls_hello,method_static,data);//最后釋放之前創建的對象,這里為局部引用(*env)->DeleteLocalRef(env,cls_hello);(*env)->DeleteLocalRef(env,data); }總結
以上是生活随笔為你收集整理的jni直接转byte_JNI再探之JNI 数据类型及Java与C++之间互调的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: springboot 替换tomcat_
- 下一篇: 16 分频 32 分频是啥意思_Veri