在 C/C++ 中调用 Java

在 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 代码,有一下几个步骤:

  1. 创建 JVM。调用 JNI_CreateJavaVM()。
  2. 寻找要调用的类。调用 FindClass()。
  3. 找到要调用的方法。调用 GetStaticMethodID()/GetMethodID()。
  4. 运行方法。调用 CallStaticMethod()/CallMethod()。
  5. 退出 JVM。调用 DestroyJavaVM()。

jvm.c 模拟了 JDK 中自带的 java 命令,其调用格式如下:jvm [-classpath classpath] class [args],如果指定了 -classpath 参数,则以此作为类路径,如果没有,则使用 CLASSPATH 环境变量。代码如下:

  1. jvm.c
  2.  
  3.  1 #include <jni.h>
  4.  2
  5.  3 #include <stdlib.h>
  6.  4 #include <stdio.h>
  7.  5
  8.  6 int main(int argc, char*argv[]) {
  9.  7
  10.  8     JavaVM *jvm;
  11.  9     JNIEnv *env;
  12. 10     JavaVMInitArgs vm_args;
  13. 11     JavaVMOption options[1];
  14. 12
  15. 13     jobjectArray applicationArgs;
  16. 14     jstring appArg;
  17. 15
  18. 16     /*
  19. 17      * Setting VM arguments
  20. 18      */
  21. 19     vm_args.version = JNI_VERSION_1_2;
  22. 20     vm_args.ignoreUnrecognized = JNI_TRUE;
  23. 21     vm_args.nOptions = 0;
  24. 22
  25. 23     /*
  26. 24      * Setting classpath
  27. 25      */
  28. 26     char classpath[1024] = "-Djava.class.path=";
  29. 27     char *env_classpath = getenv("CLASSPATH");
  30. 28
  31. 29     int mainclass_index = 1;
  32. 30     if (argc >= 3 &amp;&amp; !strcmp("-classpath", argv[1])) {
  33. 31         options[0].optionString = strcat(classpath, argv[2]);
  34. 32         vm_args.nOptions++;
  35. 33         mainclass_index += 2;
  36. 34     } else if (env_classpath) {
  37. 35         options[0].optionString = strcat(classpath, env_classpath);
  38. 36         vm_args.nOptions++;
  39. 37     }
  40. 38
  41. 39     if (vm_args.nOptions > 0) {
  42. 40         vm_args.options = options;
  43. 41     }
  44. 42
  45. 43     if (mainclass_index >= argc) {
  46. 44         printf("Main class not found, please specify it\n");
  47. 45         return 0;
  48. 46     }
  49. 47
  50. 48     jint res = JNI_CreateJavaVM(&amp;jvm, (void **)&amp;env, &amp;vm_args);
  51. 49     if (res < 0) {
  52. 50         printf("Create Java VM error, code = %d\n", res);
  53. 51         return -1;
  54. 52     }
  55. 53
  56. 54     jclass cls = (*env)->FindClass(env, argv[mainclass_index]);
  57. 55     if (!cls) {
  58. 56         printf("Class %s not found\n", argv[mainclass_index]);
  59. 57         return -1;
  60. 58     }
  61. 59
  62. 60     jmethodID mid = (*env)->GetStaticMethodID(env, cls, "main", "([Ljava/lang/String;)V");
  63. 61
  64. 62     if (!mid) {
  65. 63         printf("Method %s of Class %s not found\n", "main", argv[mainclass_index]);
  66. 64         return -1;
  67. 65     }
  68. 66     applicationArgs = (*env)->NewObjectArray(env, argc - mainclass_index - 1,
  69. 67                                              (*env)->FindClass(env, "java/lang/String"),
  70. 68                                              NULL);
  71. 69
  72. 70     int i = 0;
  73. 71     for (i = mainclass_index + 1; i < argc; i ++) {
  74. 72         appArg = (*env)->NewStringUTF(env, argv[i]);
  75. 73         (*env)->SetObjectArrayElement(env, applicationArgs, i - mainclass_index - 1, appArg);
  76. 74     }
  77. 75
  78. 76     (*env)->CallStaticVoidMethod(env, cls, mid, applicationArgs);
  79. 77
  80. 78     printf("before destroy\n");
  81. 79
  82. 80     /*
  83. 81      * Destroy the JVM.
  84. 82      * This is necessary, otherwise if the called method exits,
  85. 83      * this program will return immediately.
  86. 84      */
  87. 85     (*jvm)->DestroyJavaVM(jvm);
  88. 86
  89. 87     printf("after destroy\n");
  90. 88
  91. 89     return 0;
  92. 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 下进行测试。

  1. gcc -o jvm jvm.c -I $JAVA_HOME/include \
  2.     -I $JAVA_HOME/include/linux/ \
  3.     -L $JAVA_HOME/jre/lib/i386/client/ \
  4.     -ljvm

我们写一个测试的 Java 程序如下:

  1. Test.java
  2.  
  3.  1 import java.io.*;
  4.  2
  5.  3 public class Test {
  6.  4     public static void main(String[] argv) {
  7.  5         try {
  8.  6             for (int i = 0; i &lt; argv.length; i ++) {
  9.  7                 System.err.println("argv " + i + " : " + argv[i]);
  10.  8             }
  11.  9             TestThread thread = new TestThread();
  12. 10             thread.start();
  13. 11             new KillThread(thread).start();
  14. 12             System.err.println("OKOK");
  15. 13         } catch (Exception e) {
  16. 14             e.printStackTrace();
  17. 15         }
  18. 16     }
  19. 17 }
  20. 18
  21. 19 class TestThread extends Thread {
  22. 20     public void run() {
  23. 21         int counter = 0;
  24. 22         while (counter &lt; 10) {
  25. 23             try {
  26. 24                 System.err.println(counter ++);
  27. 25                 Thread.sleep(1000);
  28. 26             } catch (Exception e) {
  29. 27                 e.printStackTrace();
  30. 28                 break;
  31. 29             }
  32. 30         }
  33. 31     }
  34. 32 }
  35. 33
  36. 34 class KillThread extends Thread {
  37. 35     private Thread thread;
  38. 36
  39. 37     public KillThread(Thread thread) {
  40. 38         this.thread = thread;
  41. 39     }
  42. 40
  43. 41     public void run() {
  44. 42         try {
  45. 43             Thread.sleep(5000);
  46. 44             thread.interrupt();
  47. 45         } catch (Exception e) {
  48. 46             e.printStackTrace();
  49. 47         }
  50. 48     }
  51. 49 }

运行的话要设置一下 LD_LIBRARY_PATH 为 libjvm.so 所在的路径,这样在运行的时候就可以找到 jvm 的库。export LD_LIBRARY_PATH=$JAVA_HOME/jre/lib/i386/client:$LD_LIBRARY_PATH

  1. ./jvm -classpath . Test 12 34

运行的结果如下:

  1. argv 0 : 12
  2. argv 1 : 34
  3. 0
  4. OKOK
  5. before destroy
  6. 1
  7. 2
  8. 3
  9. 4
  10. java.lang.InterruptedException: sleep interrupted
  11.   at java.lang.Thread.sleep(Native Method)
  12.   at TestThread.run(Test.java:25)
  13. after destroy

最后说一下 DestroyJavaVM(),当这个方法调用以后,它会等到只剩下当前线程在运行的时候才返回,如果你在运行了 Java 程序以后,还要做其他事,比如循环读取消息之类的,那么 DestroyJavaVM() 可以不调用,其他线程在完成自己的工作后会自行结束。

这里的例子是 C 的例子,C++ 的例子也是类似的,不过更简单一些,可参考 jni.h 或者其他的例子。

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <blockquote>
  • You can use BBCode tags in the text.
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>. The supported tag styles are: <foo>, [foo].
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.

More information about formatting options

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
   ___     ___       _             _____    ___    _     __  __
/ _ \ ( _ ) / \ ___ |_ _| / _ \ | | __ \ \/ /
| | | | / _ \ / _ \ / __| | | | | | | | |/ / \ /
| |_| | | (_) | / ___ \ | (__ | | | |_| | | < / \
\__\_\ \___/ /_/ \_\ \___| |_| \__\_\ |_|\_\ /_/\_\
Enter the code depicted in ASCII art style.