在我们的日常工作中,可能会遇到不同语言之间相互调用的问题,常见的有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的时候能不能保持其状态。经验证是可以的。