上一篇说的是java的本地调用,即使用jni完成java调用c/c++编写的代码,这回仍然是使用jni,不过是倒过来,使用c/c++调用java编写的代码。本次使用环境是:centos5.4,gcc4.9.0,jdk1.6_45。
配置相关环境。
配置jdk环境,加入JAVA_HOME环境变量,即jdk目录加入path。
配置加载jvm所需要的libjvm.so动态库,所以需要指定LD_LIBRARY_PATH这个环境变量,也就是libjvm.so这个动态库所在的路径, 通常都是:
1
export LD_LIBRARY_PATH=${JAVA_HOME}/jre/lib/i386/client:$LD_LIBRARY_PATH
这样程序运行的时候如果在lib里找不到jvm这个动态库,就会去LD_LIBRARY_PATH所指定路径下去找。c/c++相关编译环境linux下自带。
因为是c/c++调用java代码,然后就先写java的测试代码。写了一个简单的测试类,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class CCallJavaTest
{
//类成员
public String mTestStr;
private static final int CONST_VAL = 10;
//静态方法
public static String staticCallTest(int val)
{
System.out.println("[staticCallTest] called");
return new Integer(val).toString();
}
//成员方法
public int getVal()
{
System.out.println("[getVal] called");
return CONST_VAL + 10;
}
public String getTestStr()
{
return this.mTestStr;
}
}1
javac CCallJavaTest.java
无错生成.class文件,这里只是一个简单的测试,所以没有指定包名,如果指定包名,下文中使用jni在寻找java类的时候就需要全名(包名/类名这种形式,下文会说明)。
查看刚才写的测试类的签名,下面用jni获取类内部类型(包括方法和成员)会用到,这里用到一个jdk里自带的一个分析工具,叫做javap,终端里直接键入javap -help查看帮助。
-s选项是将内部的类型的签名打印出来,-private选项是输出所有的类与成员。这里就使用这二个选项,将输出重定向到一个文件中,以便之后查看:
1
javap -private -s CCallJavaTest > CCallJavaTest.sig
查看sig文件会看到类似如下内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27class CCallJavaTest extends java.lang.Object{
public java.lang.String mTestStr;
Signature: Ljava/lang/String;
private static final int CONST_VAL;
Signature: I
CCallJavaTest();
Signature: ()V
public static java.lang.String staticCallTest(int);
Signature: (I)Ljava/lang/String;
public int getVal();
Signature: ()I
public java.lang.String getTestStr();
Signature: ()Ljava/lang/String;
}Signature部分就是我们需要的部分。
编写c代码(这里使用c语言)通过jni调用java代码,先上代码再看细节:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
int main(int argc, char *argv[])
{
//Java虚拟机
JavaVM *jvm = NULL;
//Jni运行环境
JNIEnv *env = NULL;
//虚拟机初始化参数
JavaVMInitArgs jvm_args;
//java虚拟机参数, 这里只使用classpath这一项,所以长度为1
JavaVMOption opt[1];
//设置初始化参数(classpath 为当前目录)
opt[0].optionString = "-Djava.class.path=.";
//初始化虚拟机初始化参数
memset(&jvm_args, 0, sizeof(jvm_args));
//设置jni环境版本
jvm_args.version = JNI_VERSION16;
//参数的个数
jvm_args.nOptions = 1;
//虚拟机参数
jvm_args.options = opt;
//创建java虚拟机 返回0为启动成功,其他数值的意义在头文件中有定义
long jvm_stat = JNI_CreateJavaVM(&jvm, (void **)&env, &jvm_args);
//错误处理
if (jvm_stat)
{
fprintf(stderr, "%s\n", "JVM Create Error!");
exit(-1);
}
//找到类定义
jclass cls = (*env)->FindClass(env, "CCallJavaTest");
//找到做相应处理
if (cls != 0)
{
//获取静态方法的ID, 通过方法签名和方法的名字
jmethodID static_method = (*env)->GetStaticMethodID(env, cls,
"staticCallTest", "(I)Ljava/lang/String");
//找到方法
if (static_method)
{
jint val = 1024;
//调用静态方法并获取返回值
jstring return_str = (jstring)(*env)->CallStaticObjectMethod(env,
cls, static_method, val);
const char trans_str = (env)->GetStringUTFChars(env, return_str, 0);
fprintf(stdout, "%s\n", trans_str);
//释放内存(减引用)
(*env)->ReleaseStringUTFChars(env, return_str, 0);
}
//通过新建对象调用方法, 调用默认构造方法
jobject obj = (*env)->AllocObject(env, cls);
if (!obj)
{
fprintf(stderr, "%s\n", "Alloc New Object Error!");
exit(-1);
}
//调用类成员方法
jmethodID member_method = (*env)->GetMethodID(env, cls, "getVal", "()I");
if (member_method)
{
//调用成员方法
jint res = (jint)(*env)->CallObjectMethod(env, obj, member_method);
fprintf(stdout, "[%s] %d\n", "Call Member Method", (int)res);
}
//获取类成员
jfieldID member_field = (jfieldID)(*env)->GetFieldID(env, cls,
"mTestStr", "Ljava/lang/String;");
if (member_field)
{
//对象成员的新的成员值
const char *modify_str = "new class member";
//分配待传参数
jstring arg_str = (*env)->NewStringUTF(env, modify_str);
//设置新的成员值
(*env)->SetObjectField(env, obj, member_field, arg_str);
}
//查看新的string的值
member_method = (*env)->GetMethodID(env, cls, "getTestStr",
"()Ljava/lang/String;");
if (member_method)
{
jstring new_set_str = (jstring)(*env)->CallObjectMethod(env,
obj, member_method);
const char new_c_str = (env)->GetStringUTFChars(env, new_set_str, 0);
fprintf(stdout, "[new set str]: %s\n", new_c_str);
(*env)->ReleaseStringUTFChars(env, new_set_str, 0);
}
//使用完成销毁虚拟机
(*jvm)->DestroyJavaVM(jvm);
}
return 0;
}注释已经算详细了,大体的步骤:
1)初始化jni环境,因为都是基于jni来操作的,初始化创建并加载java虚拟机(jvm),因为java生成的字节码需要在jvm中运行。因为就有一个java类在当前目录中,所以JavaVMOption的长度为1,只指定了一个classpath参数。通过JNI_CreateJavaVM函数创建jvm。
2)创建了jvm之后就可以执行java代码了,但是要找到java的类,才能调用方法,获取成员,所以接下来就是找到要装载的java类。通过jni提供的FindClass方法传入java的类名,找到对应的类。(C语言调用:(*env)->FindClass(env, “ClassName”);C++调用:env->FindClass(“ClassName”);)返回的jclass不为空则找到了对应的类。通过找到的类就可以进行访问成员,调用方法操作了。
现在得到了java类,还得继续通过类获取方法,(这里就用到javap所生成出来的类文件的签名了)
1)静态方法。静态方法是不需要实例化对象的,它是类级的。可以通过类名.静态方法名来调用。jni提供了GetStaticMethodID函数通过方法名和方法签名来获取类的静态方法,提供了CallStaticObjectMethod函数来调用获取到的方法。GetStaticMethodID返回一个jni的包装类型jmethodID,对应方法的ID,再把这个ID传给CallStaticObjectMethod函数,通过这个获取到的ID来调用对应的方法。代码中的第42行,GetStaticMethodID函数的最后一个参数就是这个方法的签名,括号里的是方法的形参类型,后面的是方法的返回类型,这里使用的就是javap直接分析出来的对应的签名(区别方法重载)。代码中42-54行就是通过类获取静态方法再调用并获得返回值的过程。
2)成员方法。需要通过对象来调用。通过实例对象.成员方法的形式来调用。所以这里要调用成员方法就必须先产生一个类的实例,再通过这个类的实例去调用成员方法。jni提供了AllocObject来调用java类的默认构造方法。返回一个jobject的jni包装的对象类型。通过GetMethodID函数获取类的成员方法。(如果不是默认构造方法,就可以使用普通的GetMethodID方法获取构造方法如:
1
jmethodID constructor_method = (*env)->GetMethodID(env, cls, "<init>", "(I)");
类似这样,再使用jni的NewObject函数得到了这个类的对象。)这里使用的方法签名也同样是javap工具得来的。得到jmethodID后通过jni提供的CallObjectMethod传入对象与方法ID,调用这个方法并获取返回值。代码58-72行就是生成新对象,调用成员方法的过程。
3)成员变量。同样是需要实例对象来进行获取,修改等操作。jni提供一个叫做GetFieldID的函数,传入类名,成员变量名称,还有其变量类型(javap生成的签名),返回一个jni的jFieldID的包装类型。获取其ID之后,通过SetObjectField函数将新的值设置给这个成员变量。函数依次传入,对象、成员变量ID,将要设置的值。设置完成,再次使用GetMethodID调用对应的get方法查看其值是否改变。代码中74-97就是获取成员变量并设置新值,再次查看的过程。
以上过程都调用都完成之后,使用创建出的jvm指针销毁jvm,释放加载的虚拟机。
gcc下编译代码查看结果:
1
2gcc -Wall -o c_call_java c_call_java.c -I${JAVA_HOME}/include \
{JAVA_HOME}/include/linux -L${JAVA_HOME}/jre/lib/i386/client/ -ljvm-I指定头文件目录,这里是jni的头文件目录(另一个办法是把里面的h文件都拷贝到/usr/local/inlcude中),-L指定要链接的库的目录,-l选项直接指定库的名称,这里要链接的是$JAVA_HOME/jre/lib/i386/client/下的libjvm.so动态库。
编译无错生成c_call_java可执行文件。终端下运行输出:
1
2
3[getVal] called
[Call Member Method] 20
[new set str]: new class memberc通过jni成功调用了java代码。以上就是在没有线程的情况下,c/c++调用java的jni的一般操作方法。