如何在c++中調(diào)用java代碼(轉(zhuǎn)載)
在我們的日常工作中,可能會遇到不同語言之間相互調(diào)用的問題,常見的有java調(diào)用C/C++或者在C/C++中調(diào)用java,我們可以基于sun提供的jni技術(shù)來實現(xiàn)這兩種語言之間的相互調(diào)用,這篇文章來說一下在c++中調(diào)用java的情況,至于java如何調(diào)用c我會在另外一篇文章中單獨講。
c++調(diào)用java其實并不復(fù)雜,分為幾個步驟:
在說調(diào)用之前,我們先來看看我們需要調(diào)用的java類
public class Test {
- public Test() {
- }
- public String getMessage(){
- return "test ok";
- }
- public TestObject getObject() {
- System.out.println("invoke getObject ok***");
- TestObject to = new TestObject();
- to.setName("name");
- to.setPwd("pwd");
- return to;
- }
- public void test() {
- System.out.println("%%% invoke test ok***");
- }
這是一個很簡單的類,他有一個無參的構(gòu)造函數(shù),有三個方法,一個帶有String返回值的方法,一個是返回一個我們自定義的對象TestObject,另外還有一個沒有返回值的test方法。
接下來是TestObject類
- public class TestObject {
- public String name;
- public String pwd;
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public String getPwd() {
- return pwd;
- }
- public void setPwd(String pwd) {
- this.pwd = pwd;
- }
- public void callback(String content) {
- System.out.println("%%%call back ok*** cotnent is: " + content);
- System.out.println("name is: " + name);
- System.out.println("pwd is: " + pwd);
- }
- }
也是一個很簡單的類,有兩個屬性,還有一個回調(diào)函數(shù),主要是想演示一下我們?nèi)缭贑++中獲取該對象以后,進行回調(diào),這也是我們經(jīng)常會遇到的問題。
上面看了我們的java測試類,接下來,我們來具體看看如何進行調(diào)用
創(chuàng)建jvm
我們首先需要初始化一些jvm的參數(shù),然后創(chuàng)建出jenv和jvm以便我們之后的調(diào)用
- JavaVMOption options[1];
- JNIEnv *env;
- JavaVM *jvm;
- JavaVMInitArgs vm_args;
- long status;
- jclass testCls;
- jmethodID testMid;
- jobject testJobj;
- jobject tookitReturnObj;
- //設(shè)置Java類的路徑
- options[0].optionString = "-Djava.class.path=. ;D:\\workspace\\my\\JniTest\\jnitest.jar";
- vm_args.version = JNI_VERSION_1_6;
- vm_args.nOptions = 1;
- vm_args.options = options;
- vm_args.ignoreUnrecognized = JNI_TRUE;
- status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
這里需要指出的是,在設(shè)置classpath的時候,如果需要引用的是jar包,需要把jar包的名稱一并引入,而不能是只指定jar所在目錄,這就會造成一個問題,如果我們依賴的jar很多,那么classPath就需要逐一寫出jar包名稱來,這對于有著幾十個引用jar的項目來說還是會造成一定的麻煩,我推薦大家使用fatjar的一個工具,fatjar可以把依賴的jar合并打成一個jar包,這樣的話,可以減少我們在書寫classpath的過程中,寫太多的jar包,為我們省點事,而且對于維護來講也比較清晰。
加載調(diào)用的java類
當(dāng)我們創(chuàng)建完jvm后,會得到一個返回值,如果為0說明創(chuàng)建成功,如果否則創(chuàng)建失敗。
當(dāng)我們成功創(chuàng)建完jvm后,接下來我們找到需要調(diào)用的java類
- if (status != JNI_ERR)
- {
- testCls = env->FindClass("jni/test/Test");
- }
這里我們要使用Test的一個java類,它所在的包尾jni.test。
如果加載成功,那么testCls不為0,否則將返回0
調(diào)用構(gòu)造函數(shù),創(chuàng)建對象
像我們在java語言中一樣,我們首先需要調(diào)用構(gòu)造函數(shù)創(chuàng)建一個對象實例,然后再進行方法調(diào)用,那么在C++中同樣需要我們這么做,當(dāng)然調(diào)用靜態(tài)方法例外。
testMid = env->GetMethodID( testCls, "<init>", "()V");
- if (testMid != 0)
- {
- testJobj = env->NewObject(testCls,testMid);
- std::cout << "init test class ok" << std::endl;
- }
我們可以看到我們首先進行GetMethodID調(diào)用,這個函數(shù)有三個參數(shù),一個是我們之前獲取的jclass對象,第二參數(shù)是需要調(diào)用的java類中的方法名稱,由于我們是調(diào)用構(gòu)造函數(shù),所以<init>是個固定的寫法,他就是說明我們要調(diào)用構(gòu)造函數(shù),第三參數(shù)是調(diào)用方法的參數(shù)類型,關(guān)于這個參數(shù)的類型,我們可以使用javap命令來獲取,具體命令如下:
javap -p -s jni.test.Test
然后我們會得到這樣的信息
- Compiled from "Test.java"
- public class jni.test.Test extends java.lang.Object{
- public jni.test.Test();
- Signature: ()V
- public java.lang.String getMessage();
- Signature: ()Ljava/lang/String;
- public jni.test.domain.TestObject getObject();
- Signature: ()Ljni/test/domain/TestObject;
- public void test();
- Signature: ()V
- }
我們可以看到Test()構(gòu)造函數(shù),在Signature后面就是該方法的參數(shù)類型。
當(dāng)我們獲取構(gòu)造函數(shù)以后,我們就可以對其進行實例化了,使用NewObject
方法調(diào)用
接下來我們就可以進行方法調(diào)用了
- testMid = env->GetMethodID( testCls, "test","()V");
- if (testMid != 0)
- {
- env->CallVoidMethod(testJobj,testMid);
- }
- testMid = env->GetMethodID( testCls, "getMessage","()Ljava/lang/String;");
- if (testMid != 0)
- {
- jobject msg = env->CallObjectMethod(testJobj,testMid);
- std::cout << msg << std::endl;
- printf("msg : %d",msg);
- }
上面的代碼里我們調(diào)用兩個方法,分別是test和getMessage,我們都是先調(diào)用GetmethodID來獲取一個jmethodID對象,然后調(diào)用CallXXXMethod來進行調(diào)用,跟java中的反射調(diào)用是一樣的。
如果我們需要調(diào)用靜態(tài)方法,我們只需先GetStaticMethodID然后調(diào)用CallStaticXXXMethod即可,jni.h中定義了很多Call的方法,比如CallIntMethod,CallBooleanMethod等等,有興趣可以去查看jni.h
下面我們來說一下如何進行回調(diào)
我們的java測試類Test中有一個getObject的測試方法,得到了一個java對象,我們拿到該獨享后如何進行下一步的調(diào)用呢,其實很簡單,跟咱們之前的例子是類似的
- testMid = env->GetMethodID( testCls, "getObject","()Ljni/test/domain/TestObject;");
- if (testMid != 0)
- {
- jobject obj = env->CallObjectMethod(testJobj,testMid);
- jobject listener = env->NewGlobalRef(obj);
- jclass clsj = env->GetObjectClass(listener);
- jmethodID func = env->GetMethodID(clsj, "callback", "(Ljava/lang/String;)V");
- if(func != NULL)
- {
- jobject msg1 = env->CallObjectMethod(testJobj,testMid);
- env->CallVoidMethod(listener, func, msg1);
- }
- std::cout << obj << std::endl;
- printf("obj : %d",obj);
- }
同樣我們通過GetMethodID獲取getObject方法,然后調(diào)用該方法,得到一個返回的對象obj,我在代碼里進行一下處理,創(chuàng)建了一個全局引用,如果大家只想把他當(dāng)做局部變量使用,那么不創(chuàng)建全局引用也可以;然后通過GetObjectClass我們得到了jclass對象,接下來就跟我們之前的過程一樣了,獲取方法,進行調(diào)用即可。
上面的代碼中我在調(diào)用callback方法前有調(diào)用了一次getObject方法,主要是測試一下,獲取的對象,在進行callback的時候能不能保持其狀態(tài)。經(jīng)驗證是可以的。