如何在c++中調用java代碼(轉載)
在我們的日常工作中,可能會遇到不同語言之間相互調用的問題,常見的有java調用C/C++或者在C/C++中調用java,我們可以基于sun提供的jni技術來實現這兩種語言之間的相互調用,這篇文章來說一下在c++中調用java的情況,至于java如何調用c我會在另外一篇文章中單獨講。
c++調用java其實并不復雜,分為幾個步驟:
在說調用之前,我們先來看看我們需要調用的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***");
- }
這是一個很簡單的類,他有一個無參的構造函數,有三個方法,一個帶有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);
- }
- }
也是一個很簡單的類,有兩個屬性,還有一個回調函數,主要是想演示一下我們如在C++中獲取該對象以后,進行回調,這也是我們經常會遇到的問題。
上面看了我們的java測試類,接下來,我們來具體看看如何進行調用
創建jvm
我們首先需要初始化一些jvm的參數,然后創建出jenv和jvm以便我們之后的調用
- JavaVMOption options[1];
- JNIEnv *env;
- JavaVM *jvm;
- JavaVMInitArgs vm_args;
- long status;
- jclass testCls;
- jmethodID testMid;
- jobject testJobj;
- jobject tookitReturnObj;
- //設置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);
這里需要指出的是,在設置classpath的時候,如果需要引用的是jar包,需要把jar包的名稱一并引入,而不能是只指定jar所在目錄,這就會造成一個問題,如果我們依賴的jar很多,那么classPath就需要逐一寫出jar包名稱來,這對于有著幾十個引用jar的項目來說還是會造成一定的麻煩,我推薦大家使用fatjar的一個工具,fatjar可以把依賴的jar合并打成一個jar包,這樣的話,可以減少我們在書寫classpath的過程中,寫太多的jar包,為我們省點事,而且對于維護來講也比較清晰。
加載調用的java類
當我們創建完jvm后,會得到一個返回值,如果為0說明創建成功,如果否則創建失敗。
當我們成功創建完jvm后,接下來我們找到需要調用的java類
- if (status != JNI_ERR)
- {
- testCls = env->FindClass("jni/test/Test");
- }
這里我們要使用Test的一個java類,它所在的包尾jni.test。
如果加載成功,那么testCls不為0,否則將返回0
調用構造函數,創建對象
像我們在java語言中一樣,我們首先需要調用構造函數創建一個對象實例,然后再進行方法調用,那么在C++中同樣需要我們這么做,當然調用靜態方法例外。
testMid = env->GetMethodID( testCls, "<init>", "()V");
- if (testMid != 0)
- {
- testJobj = env->NewObject(testCls,testMid);
- std::cout << "init test class ok" << std::endl;
- }
我們可以看到我們首先進行GetMethodID調用,這個函數有三個參數,一個是我們之前獲取的jclass對象,第二參數是需要調用的java類中的方法名稱,由于我們是調用構造函數,所以<init>是個固定的寫法,他就是說明我們要調用構造函數,第三參數是調用方法的參數類型,關于這個參數的類型,我們可以使用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()構造函數,在Signature后面就是該方法的參數類型。
當我們獲取構造函數以后,我們就可以對其進行實例化了,使用NewObject
方法調用
接下來我們就可以進行方法調用了
- 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);
- }
上面的代碼里我們調用兩個方法,分別是test和getMessage,我們都是先調用GetmethodID來獲取一個jmethodID對象,然后調用CallXXXMethod來進行調用,跟java中的反射調用是一樣的。
如果我們需要調用靜態方法,我們只需先GetStaticMethodID然后調用CallStaticXXXMethod即可,jni.h中定義了很多Call的方法,比如CallIntMethod,CallBooleanMethod等等,有興趣可以去查看jni.h
下面我們來說一下如何進行回調
我們的java測試類Test中有一個getObject的測試方法,得到了一個java對象,我們拿到該獨享后如何進行下一步的調用呢,其實很簡單,跟咱們之前的例子是類似的
- 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方法,然后調用該方法,得到一個返回的對象obj,我在代碼里進行一下處理,創建了一個全局引用,如果大家只想把他當做局部變量使用,那么不創建全局引用也可以;然后通過GetObjectClass我們得到了jclass對象,接下來就跟我們之前的過程一樣了,獲取方法,進行調用即可。
上面的代碼中我在調用callback方法前有調用了一次getObject方法,主要是測試一下,獲取的對象,在進行callback的時候能不能保持其狀態。經驗證是可以的。