看了一些博客的內容,總結常用的。
具體是哪些,懶得去弄了。做個筆記,方便自己。
(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的作用,它用於指定要取得的屬性或方法的類型簽名。
(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;
- }
- 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 *
-
- (*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命令生成的函數的規則
請看這篇博客:
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時再使用是無效的。