在 C/C++ 中调用 Java
这两天研究了一下在 C/C++ 中怎么来调用 Java 程序。以前知道 JNI(Java Native Interface),知道在 Java 中怎么来调用 C/C++ 的库,但是反过来还没仔细研究过。其实反过来想一下,Java 本身是跨平台的,靠的是 JVM(Java Virtual Machine),那 JVM 是谁来实现的呢?自然是 JVM 的提供者,SUN 自己的就不用说了,其他的实现还有 [IBM]、[Kaffe],在 [Wikipeida] 上列出了一堆的实现。这些 JVM 的实现大多是 C/C++ 实现的,那从 C/C++ 中调用 Java 应该问题不大,而所用的工具仍然是 JNI。[这里]列出了一些有用的链接。
总的来说,从 C/C++ 中调用 Java 代码,有一下几个步骤:
- 创建 JVM。调用 JNI_CreateJavaVM()。
- 寻找要调用的类。调用 FindClass()。
- 找到要调用的方法。调用 GetStaticMethodID()/GetMethodID()。
- 运行方法。调用 CallStaticMethod()/CallMethod()。
- 退出 JVM。调用 DestroyJavaVM()。
jvm.c 模拟了 JDK 中自带的 java 命令,其调用格式如下:jvm [-classpath classpath] class [args],如果指定了 -classpath 参数,则以此作为类路径,如果没有,则使用 CLASSPATH 环境变量。代码如下:
-
jvm.c
-
-
1 #include <jni.h>
-
2
-
3 #include <stdlib.h>
-
4 #include <stdio.h>
-
5
-
6 int main(int argc, char*argv[]) {
-
7
-
8 JavaVM *jvm;
-
9 JNIEnv *env;
-
10 JavaVMInitArgs vm_args;
-
11 JavaVMOption options[1];
-
12
-
13 jobjectArray applicationArgs;
-
14 jstring appArg;
-
15
-
16 /*
-
17 * Setting VM arguments
-
18 */
-
19 vm_args.version = JNI_VERSION_1_2;
-
20 vm_args.ignoreUnrecognized = JNI_TRUE;
-
21 vm_args.nOptions = 0;
-
22
-
23 /*
-
24 * Setting classpath
-
25 */
-
26 char classpath[1024] = "-Djava.class.path=";
-
27 char *env_classpath = getenv("CLASSPATH");
-
28
-
29 int mainclass_index = 1;
-
30 if (argc >= 3 && !strcmp("-classpath", argv[1])) {
-
31 options[0].optionString = strcat(classpath, argv[2]);
-
32 vm_args.nOptions++;
-
33 mainclass_index += 2;
-
34 } else if (env_classpath) {
-
35 options[0].optionString = strcat(classpath, env_classpath);
-
36 vm_args.nOptions++;
-
37 }
-
38
-
39 if (vm_args.nOptions > 0) {
-
40 vm_args.options = options;
-
41 }
-
42
-
43 if (mainclass_index >= argc) {
-
44 printf("Main class not found, please specify it\n");
-
45 return 0;
-
46 }
-
47
-
48 jint res = JNI_CreateJavaVM(&jvm, (void **)&env, &vm_args);
-
49 if (res < 0) {
-
50 printf("Create Java VM error, code = %d\n", res);
-
51 return -1;
-
52 }
-
53
-
54 jclass cls = (*env)->FindClass(env, argv[mainclass_index]);
-
55 if (!cls) {
-
56 printf("Class %s not found\n", argv[mainclass_index]);
-
57 return -1;
-
58 }
-
59
-
60 jmethodID mid = (*env)->GetStaticMethodID(env, cls, "main", "([Ljava/lang/String;)V");
-
61
-
62 if (!mid) {
-
63 printf("Method %s of Class %s not found\n", "main", argv[mainclass_index]);
-
64 return -1;
-
65 }
-
66 applicationArgs = (*env)->NewObjectArray(env, argc - mainclass_index - 1,
-
67 (*env)->FindClass(env, "java/lang/String"),
-
68 NULL);
-
69
-
70 int i = 0;
-
71 for (i = mainclass_index + 1; i < argc; i ++) {
-
72 appArg = (*env)->NewStringUTF(env, argv[i]);
-
73 (*env)->SetObjectArrayElement(env, applicationArgs, i - mainclass_index - 1, appArg);
-
74 }
-
75
-
76 (*env)->CallStaticVoidMethod(env, cls, mid, applicationArgs);
-
77
-
78 printf("before destroy\n");
-
79
-
80 /*
-
81 * Destroy the JVM.
-
82 * This is necessary, otherwise if the called method exits,
-
83 * this program will return immediately.
-
84 */
-
85 (*jvm)->DestroyJavaVM(jvm);
-
86
-
87 printf("after destroy\n");
-
88
-
89 return 0;
-
90 }
接下来是编译了,需要设定必要的头文件路径,这里是在 Linux 下,相应的路径是在 $JAVA_HOME/include 和 $JAVA_HOME/include/linux,还要链接 jvm.so,位于 $JAVA_HOME/jre/lib/i386/client/。对于 Solaris 相应的头文件路径是 $JAVA_HOME/include/solaris/,库路径是在 $JAVA_HOME/jre/lib/sparc 或者 $JAVA_HOME/jre/lib/sparcv9。对于 win32 分别是 $JAVA_HOME/include/win32/ 以及 $JAVA_HOME/lib(静态库) 和 $JAVA_HOME/jre/bin/client(动态库)。这里没有在 Solaris 和 Win32 下进行测试。
-
gcc -o jvm jvm.c -I $JAVA_HOME/include \
-
-I $JAVA_HOME/include/linux/ \
-
-L $JAVA_HOME/jre/lib/i386/client/ \
-
-ljvm
我们写一个测试的 Java 程序如下:
-
Test.java
-
-
1 import java.io.*;
-
2
-
3 public class Test {
-
4 public static void main(String[] argv) {
-
5 try {
-
6 for (int i = 0; i < argv.length; i ++) {
-
7 System.err.println("argv " + i + " : " + argv[i]);
-
8 }
-
9 TestThread thread = new TestThread();
-
10 thread.start();
-
11 new KillThread(thread).start();
-
12 System.err.println("OKOK");
-
13 } catch (Exception e) {
-
14 e.printStackTrace();
-
15 }
-
16 }
-
17 }
-
18
-
19 class TestThread extends Thread {
-
20 public void run() {
-
21 int counter = 0;
-
22 while (counter < 10) {
-
23 try {
-
24 System.err.println(counter ++);
-
25 Thread.sleep(1000);
-
26 } catch (Exception e) {
-
27 e.printStackTrace();
-
28 break;
-
29 }
-
30 }
-
31 }
-
32 }
-
33
-
34 class KillThread extends Thread {
-
35 private Thread thread;
-
36
-
37 public KillThread(Thread thread) {
-
38 this.thread = thread;
-
39 }
-
40
-
41 public void run() {
-
42 try {
-
43 Thread.sleep(5000);
-
44 thread.interrupt();
-
45 } catch (Exception e) {
-
46 e.printStackTrace();
-
47 }
-
48 }
-
49 }
运行的话要设置一下 LD_LIBRARY_PATH 为 libjvm.so 所在的路径,这样在运行的时候就可以找到 jvm 的库。export LD_LIBRARY_PATH=$JAVA_HOME/jre/lib/i386/client:$LD_LIBRARY_PATH。
-
./jvm -classpath . Test 12 34
运行的结果如下:
-
argv 0 : 12
-
argv 1 : 34
-
0
-
OKOK
-
before destroy
-
1
-
2
-
3
-
4
-
java.lang.InterruptedException: sleep interrupted
-
at java.lang.Thread.sleep(Native Method)
-
at TestThread.run(Test.java:25)
-
after destroy
最后说一下 DestroyJavaVM(),当这个方法调用以后,它会等到只剩下当前线程在运行的时候才返回,如果你在运行了 Java 程序以后,还要做其他事,比如循环读取消息之类的,那么 DestroyJavaVM() 可以不调用,其他线程在完成自己的工作后会自行结束。
这里的例子是 C 的例子,C++ 的例子也是类似的,不过更简单一些,可参考 jni.h 或者其他的例子。




Post new comment