c++調Java走的坑
1.傳引數
c++層 string str 傳引數 str.c_str();
jni層 const char *
java層 final String
2.處理過程 jni層 先轉換,後釋放
jni的意思是java本地呼叫,通過jni可以實現java層程式碼和其他語言寫得程式碼進行互動。在cocos2d-x中,如果想要在c++層呼叫java層的程式碼,就是通過jni技術。通過呼叫java層的程式碼,我們就可以在Android平臺下實現一些引擎沒有提供給我們的功能,或者做一些其他的功能。比如加個廣告,加個分享,呼叫Android原生的對話方塊等等吧。Cocos2d-x比較人性化的是為我們封裝了jni呼叫的一些介面,這個類就是JniHelper,我們只需要使用這個類提供給我們的介面就可以完成呼叫java層程式碼的功能。
首先使用之前要包含標頭檔案,寫法如下,記住要加上條件編譯,這個東西是Android平臺下才用到。
#if(CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) |
|
2 |
|
3 |
#include "platform/android/jni/JniHelper.h" |
4 |
#include <jni.h> |
5 |
|
6 |
#endif |
接著通過一小段程式碼來說明一下這個類的用法。
1 |
#if(CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) |
2 |
// typedef struct JniMethodInfo_ |
3 |
// { |
4 |
// JNIEnv * env; |
5 |
// jclass classID; |
6 |
// jmethodID methodID; |
7 |
// } JniMethodInfo; |
8 |
|
9 |
JniMethodInfo info; |
10 |
|
11 |
//getStaticMethodInfo判斷java定義的靜態函式是否存在,返回bool |
12 |
bool ret = JniHelper::getStaticMethodInfo(info,"org/cocos2dx/cpp/TestJni" |
13 |
if(ret) |
14 |
{ |
15 |
log("call void func1() succeed"); |
16 |
//傳入類ID和方法ID,小心方法名寫錯,第一個字母是大寫 |
17 |
info.env->CallStaticVoidMethod(info.classID,info.methodID); |
18 |
} |
19 |
#endif |
大家書寫程式碼的時候同樣需要將程式碼使用條件編譯寫到裡面,JniMethodInfo是一個結構體,這個結構體的定義就是程式碼中註釋掉的地方,然後使用JniHelper呼叫了靜態函式getStaticMethodInfo,從它的名字就知道這個函式的作用了,就是獲得java層中靜態函式的資訊,這個資訊儲存在什麼地方呢,當然是JniMethodInfo中了,我們要獲取哪個類的哪個函式呢,第二個引數和第三個引數就是告訴JniHelper我們要獲取的是哪個函式的資訊了,第二個引數是類檔案的包名路徑,我在org/cocos2dx/cpp這個路徑下新建了一個類,叫做TestJni。其實前面的路徑就是一個包名,這裡使用的時候用/代替.。org的路徑當然就是我新建的這個工程的Android平臺目錄了。一會我要將這個專案打包然後測試一下,在eclipse下看看輸出。第三個引數當然就是方法名字了,第四個引數是需要注意的一個,有人把它叫做簽名,其實就是你要呼叫的java層函式的返回值和引數的型別說明。它把呼叫函式的引數寫到前面的括號中,返回值跟在括號的後邊,和我們平時書寫函式的時候正好相反了。那那個V是什麼東西呢,這個大寫字母就是對應的一個型別,如果是void型別,那麼就用一個V來代替,如果是一個int型別,那麼就用一個I代替,是不是很簡單,那其他的型別呢,如圖所示。
放了倆張表,用得時候查就好了,關於這個引數其他的細節問題待會討論。整個函式的返回值是一個bool型別,什麼意思不用說了吧。當這個函式的資訊存在的時候我們就進入到了if中了,然後我使用了info結構體的第一個變數來呼叫了函式CallStaticVoidMethod,這個函式可真是需要說一說。首先它的呼叫者就是儲存函式資訊的結構體JniMethodInfo的第一個成員變數env,這貨是什麼東西不用管,用就好了。然後這個函式的第一個字母是大寫,這一點要小心,Call後邊如果跟Static代表的就是我要呼叫的是一個靜態的函式,如果沒有那就不是靜態的,不是靜態的函式,我們使用JniHelper獲取資訊的時候用得就是getMethodInfo這個函式。然後Void代表的是函式的返回值,來看我們的例子,我呼叫的函式func1是一個無參無返回值的函式,這個看什麼地方,當然java程式碼我接著會向你展示,但是你可以直接看getStaticMethodInfo這個函式的第四個引數啊。這裡的這個void代表的是函式的返回值型別,所以如果呼叫的是返回值為int的java函式,那就是CallStaticIntMethod了。裡邊的引數就是結構體info的第二個和第三個成員變量了,代表的是類ID和函式ID。這樣的話基本的用法就說清楚了,接著就是TestJni中得程式碼了,我把要呼叫到得函式都寫了出來。
1 |
package org.cocos2dx.cpp; |
2 |
|
3 |
import android.util.Log; |
4 |
|
5 |
publicclass TestJni |
6 |
{ |
7 |
publicstaticvoid func1() |
8 |
{ |
9 |
Log.e("xiaota","java:func1,called succeed!"); |
10 |
} |
11 |
publicstaticint func2() |
12 |
{ |
13 |
return3838438; |
14 |
} |
15 |
publicstatic String func3(int i) |
16 |
{ |
17 |
String str = "get int value:"+i; |
18 |
Log.e("xiaota",str); |
19 |
return str; |
20 |
} |
21 |
publicstatic String func4(String str) |
22 |
{ |
23 |
Log.e("xiaota",str); |
24 |
return str; |
25 |
} |
26 |
publicstaticint func5(int a,int b) |
27 |
{ |
28 |
int c = a+b; |
29 |
Log.e("xiaota","func5"); |
30 |
return c; |
31 |
} |
32 |
} |
然後打包到Android平臺,我們使用USB連線上電腦,開啟eclipse,進行除錯,看看資訊輸出了沒有。
好了,這樣的話就把這個流程都說明白了,下面我們看一些細節的地方。
1 |
#if(CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) |
2 |
|
3 |
log("android platform!"); |
4 |
// typedef struct JniMethodInfo_ |
5 |
// { |
6 |
// JNIEnv * env; |
7 |
// jclass classID; |
8 |
// jmethodID methodID; |
9 |
// } JniMethodInfo; |
10 |
|
11 |
JniMethodInfo info; |
12 |
|
13 |
//getStaticMethodInfo判斷java定義的靜態函式是否存在,返回bool |
14 |
bool ret = JniHelper::getStaticMethodInfo(info,"org/cocos2dx/cpp/TestJni","func1","()V"); |
15 |
if(ret) |
16 |
{ |
17 |
log("call void func1() succeed"); |
18 |
//傳入類ID和方法ID,小心方法名寫錯,第一個字母是大寫 |
19 |
info.env->CallStaticVoidMethod(info.classID,info.methodID); |
20 |
} |
21 |
|
22 |
//呼叫的函式有返回值 |
23 |
ret = JniHelper::getStaticMethodInfo(info,"org/cocos2dx/cpp/TestJni", "func2","()I"); |
24 |
if(ret) |
25 |
{ |
26 |
log("call int func2() succeed"); |
27 |
//返回的int值,用jint型別來接收 |
28 |
jint iret = info.env->CallStaticIntMethod(info.classID,info.methodID); |
29 |
log("func2的返回值是%d",iret); |
30 |
} |
31 |
|
32 |
//呼叫的函式有引數有返回值,這裡有坑,注意Ljava/lang/String;後邊的; |
33 |
ret = JniHelper::getStaticMethodInfo(info,"org/cocos2dx/cpp/TestJni"," func3","(I)Ljava/lang/String;"); |
34 |
if(ret) |
35 |
{ |
36 |
log("call int func3(int) succeed"); |
37 |
//java層的類型別對應的是jobject,把需要傳遞的引數寫到呼叫函式的後邊 |
38 |
jobject jobj = info.env->CallStaticObjectMethod( info.classID,info.methodID,1438); |
39 |
} |
40 |
|
41 |
//引數和返回值都是類型別 |
42 |
ret = JniHelper::getStaticMethodInfo(info, "org/cocos2dx/cpp/TestJni","func4","(Ljava/lang/String;)Ljava/lang/String;"); |
43 |
if(ret) |
44 |
{ |
45 |
log("call string func4(string) succeed"); |
46 |
jobject para = info.env->NewStringUTF("haha"); |
47 |
jstring jstr = (jstring)info.env->CallStaticObjectMethod(info.classID,info.methodID,para); |
48 |
//使用jstring2string函式將返回的jstring型別的值轉化為c++中的string型別 |
49 |
std::string text = JniHelper::jstring2string(jstr); |
50 |
log("%s",text.c_str()); |
51 |
} |
52 |
|
53 |
//如果函式需要的引數是倆個或者是多個,可以採用如下的寫法 |
54 |
ret = JniHelper::getStaticMethodInfo(info, "org/cocos2dx/cpp/TestJni","func5","(II)I"); |
55 |
if(ret) |
56 |
{ |
57 |
log("call int func5(int a,int b) succeed"); |
58 |
jint iret = info.env->CallStaticIntMethod(info.classID,info.methodID,1,2); |
59 |
log("return value is %d",iret); |
60 |
} |
61 |
#endif |
上邊的程式碼主要還是那倆個函式呼叫的說明,getStaticMethodInfo的第四個引數如果是類型別,注意要使用的簽名,後邊的分號也要加,如果引數有多個,直接連起來書寫就可以了。使用CallStaticMethod呼叫的時候注意一下引數和返回值的型別,傳遞引數的時候直接寫到函式的後邊,但是引數型別要正確,返回值使用對應的型別來接受,這個型別就是前面加一個j,比如java層返回的型別是int,那接受的型別就是jint,java層返回object,接受型別就是jobject。
以上是呼叫java的靜態函式,接下來是非靜態函式的呼叫。我將c++的程式碼和java的程式碼都貼出來。
1 |
JniMethodInfo info; |
2 |
bool ret = JniHelper::getStaticMethodInfo(info,"org/cocos2dx/cpp/TestJniHelper","getObj","()Ljava/lang/Object;"); |
3 |
//先獲得類的物件,然後用這個物件去呼叫它的非靜態函式 |
4 |
jobject jobj; |
5 |
if(ret) |
6 |
{ |
7 |
log("call static method"); |
8 |
jobj = info.env->CallStaticObjectMethod(info.classID,info.methodID); |
9 |
} |
10 |
//getMethodInfo判斷java定義的類非靜態函式是否存在,返回bool |
11 |
bool re = JniHelper::getMethodInfo(info,"org/cocos2dx/cpp/TestJniHelper","func","()V"); |
12 |
if(re) |
13 |
{ |
14 |
log("call no-static method"); |
15 |
//非靜態函式呼叫的時候,需要的是物件,所以與靜態函式呼叫的第一個引數不同 |
16 |
info.env->CallVoidMethod(jobj,info.methodID); |
17 |
} |
1 |
package org.cocos2dx.cpp; |
2 |
|
3 |
import android.util.Log; |
4 |
|
5 |
publicclass TestJniHelper |
6 |
{ |
7 |
privatestatic TestJniHelper instance = new TestJniHelper(); |
8 |
publicstatic Object getObj() |
9 |
{ |
10 |
return instance; |
11 |
} |
12 |
publicvoid func() |
13 |
{ |
14 |
Log.e("xiaota","func is called"); |
15 |
} |
16 |
} |
因為呼叫的是非靜態的函式,所以我們使用CallVoidMethod的時候就不能傳入類ID了,要傳入一個物件,告訴它呼叫的是哪個物件的方法,所以為了有這麼一個物件,我們就得先呼叫一個靜態的方法來返回這個物件,然後用這個物件作為引數呼叫非靜態函式。好了,關於Jni的基本用就是這樣了