在c/c++中調用Java方法

JNI就是Java Native Interface, 便可以實現Java調用本地庫, 也能夠實現C/C++調用Java代碼, 從而實現了兩種語言的互通, 可讓咱們更加靈活的使用.html

經過使用JNI能夠從一個側面瞭解Java內部的一些實現.java

本文使用的環境是linux

  1. 64位的win7系統
  2. JDK 1.6.0u30 (32位)
  3. C/C++編譯器是 Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8168 for 80x86 (VC 6.0的, 其餘版本的也能夠編譯經過, 測試過vs2010)

本文使用到的一些功能:編程

  1. 建立虛擬機
  2. 尋找class對象, 建立對象
  3. 調用靜態方法和成員方法
  4. 獲取成員屬性, 修改爲員屬性

C/C++調用Java代碼的通常步驟:windows

  1. 編寫Java代碼, 並編譯
  2. 編寫C/C++代碼
  3. 配置lib進行編譯, 配置PATH添加相應的dll或so並運行

編寫Java代碼並編譯jvm

這段代碼很是簡單, 有個靜態方法和成員方法, 一個public的成員變量函數

?
1
2
3
4
5
6
7
8
9
10
11
public class Sample2 {
     public String name;
     
     public static String sayHello(String name) {
         return "Hello, " + name + "!" ;
     }
     
     public String sayHello() {
         return "Hello, " + name + "!" ;
     }
}

 因爲沒有定義構造函數, 因此會有一個默認的構造函數.測試

運行下面的命令編譯ui

?
>javac Sample2.java

 能夠在當前目錄下看到Sample2.class文件, 編譯成功, 第一步完成了, So easy!spa

能夠查看Sample2類中的簽名

?
>javap -s - private Sample2

 結果以下

?
Compiled from "Sample2.java"
public class Sample2 extends java.lang.Object{
public java.lang.String name;
   Signature: Ljava/lang/String;
public Sample2();
   Signature: ()V
public static java.lang.String sayHello(java.lang.String);
   Signature: (Ljava/lang/String;)Ljava/lang/String;
public java.lang.String sayHello();
   Signature: ()Ljava/lang/String;
}

 關於簽名的含義, 請參看使用JNI進行Java與C/C++語言混合編程(1)--在Java中調用C/C++本地庫.

編寫C/C++代碼調用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
#include <jni.h>
#include <string.h>
#include <stdio.h>
 
// 環境變量PATH在windows下和linux下的分割符定義
#ifdef _WIN32
#define PATH_SEPARATOR ';'
#else
#define PATH_SEPARATOR ':'
#endif
 
 
int main( void )
{
     JavaVMOption options[1];
     JNIEnv *env;
     JavaVM *jvm;
     JavaVMInitArgs vm_args;
     
     long status;
     jclass cls;
     jmethodID mid;
     jfieldID fid;
     jobject obj;
     
     options[0].optionString = "-Djava.class.path=." ;
     memset (&vm_args, 0, sizeof (vm_args));
     vm_args.version = JNI_VERSION_1_4;
     vm_args.nOptions = 1;
     vm_args.options = options;
     
     // 啓動虛擬機
     status = JNI_CreateJavaVM(&jvm, ( void **)&env, &vm_args);
     
     if (status != JNI_ERR)
     {
         // 先得到class對象
         cls = (*env)->FindClass(env, "Sample2" );
         if (cls != 0)
         {
             // 獲取方法ID, 經過方法名和簽名, 調用靜態方法
             mid = (*env)->GetStaticMethodID(env, cls, "sayHello" , "(Ljava/lang/String;)Ljava/lang/String;" );
             if (mid != 0)
             {
                 const char * name = "World" ;
                 jstring arg = (*env)->NewStringUTF(env, name);
                 jstring result = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid, arg);
                 const char * str = (*env)->GetStringUTFChars(env, result, 0);
                 printf ( "Result of sayHello: %s\n" , str);
                 (*env)->ReleaseStringUTFChars(env, result, 0);
             }
             
             /*** 新建一個對象 ***/
             // 調用默認構造函數
             //obj = (*env)->AllocObjdect(env, cls);
             
             // 調用指定的構造函數, 構造函數的名字叫作<init>
             mid = (*env)->GetMethodID(env, cls, "<init>" , "()V" );
             obj = (*env)->NewObject(env, cls, mid);
             if (obj == 0)
             {
                 printf ( "Create object failed!\n" );
             }
             /*** 新建一個對象 ***/
             
             // 獲取屬性ID, 經過屬性名和簽名
             fid = (*env)->GetFieldID(env, cls, "name" , "Ljava/lang/String;" );
             if (fid != 0)
             {
                 const char * name = "icejoywoo" ;
                 jstring arg = (*env)->NewStringUTF(env, name);
                 (*env)->SetObjectField(env, obj, fid, arg); // 修改屬性
             }
             
             // 調用成員方法
             mid = (*env)->GetMethodID(env, cls, "sayHello" , "()Ljava/lang/String;" );
             if (mid != 0)
             {
                 jstring result = (jstring)(*env)->CallObjectMethod(env, obj, mid);
                 const char * str = (*env)->GetStringUTFChars(env, result, 0);
                 printf ( "Result of sayHello: %s\n" , str);
                 (*env)->ReleaseStringUTFChars(env, result, 0);
             }
         }
         
         (*jvm)->DestroyJavaVM(jvm);
         return 0;
     }
     else
     {
         printf ( "JVM Created failed!\n" );
         return -1;
     }
}

 這段代碼大概作了這幾件事

  1. 建立虛擬機JVM, 在程序結束的時候銷燬虛擬機JVM
  2. 尋找class對象
  3. 建立class對象的實例
  4. 調用方法和修改屬性

虛擬的建立

與之相關的有這樣幾個變量

    JavaVMOption options[1];     JNIEnv *env;     JavaVM *jvm;     JavaVMInitArgs vm_args;

JavaVM就是咱們須要建立的虛擬機實例

JavaVMOption至關於在命令行裏傳入的參數

JNIEnv在Java調用C/C++中每一個方法都會有的一個參數, 擁有一個JNI的環境

JavaVMInitArgs就是虛擬機建立的初始化參數, 這個參數裏面會包含JavaVMOption

下面就是建立虛擬機

?
1
2
3
4
5
6
7
8
     options[0].optionString = "-Djava.class.path=." ;
memset (&vm_args, 0, sizeof (vm_args));
vm_args.version = JNI_VERSION_1_4;
vm_args.nOptions = 1;
vm_args.options = options;
 
// 啓動虛擬機
status = JNI_CreateJavaVM(&jvm, ( void **)&env, &vm_args);

 "-Djava.class.path=."看着眼熟吧, 這個就是傳入當前路徑, 做爲JVM尋找class的用戶自定義路徑, 咱們的Sample2.class就在當前路徑(固然也能夠不在當前路徑, 你能夠隨便修改).

vm_args.version是Java的版本, 這個應該是爲了兼容之前的JDK, 可使用舊版的JDK, 這個宏定義是在jni.h中,  有如下四種

?
#define JNI_VERSION_1_1 0x00010001
#define JNI_VERSION_1_2 0x00010002
#define JNI_VERSION_1_4 0x00010004
#define JNI_VERSION_1_6 0x00010006

 vm_args.nOptions的含義是, 你傳入的options有多長, 咱們這裏就一個, 因此是1.

vm_args.options = options把JavaVMOption傳給JavaVMInitArgs裏面去.

而後就是啓動虛擬機了status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args).

能夠經過這個返回值status , 知道虛擬機是否啓動成功

?
#define JNI_OK           0                 /* success */
#define JNI_ERR          (-1)              /* unknown error */
#define JNI_EDETACHED    (-2)              /* thread detached from the VM */
#define JNI_EVERSION     (-3)              /* JNI version error */
#define JNI_ENOMEM       (-4)              /* not enough memory */
#define JNI_EEXIST       (-5)              /* VM already created */
#define JNI_EINVAL       (-6)              /* invalid arguments */

 尋找class對象, 並實例化

JVM在Java中都是本身啓動的, 在C/C++中只能本身來啓動了, 啓動完以後的事情就和在Java中同樣了, 不過要使用C/C++的語法.

獲取class對象比較簡單, FindClass(env, className).

?
cls = (*env)->FindClass(env, "Sample2" );

 在Java中的類名格式是java.lang.String, 可是className的格式有點不一樣, 不是使用'.'做爲分割, 而是'/', 即java/lang/String.

咱們知道Java中構造函數有兩種, 一種是默認的沒有參數的, 一種是自定義的帶有參數的. 對應的在C/C++中, 有兩種調用構造函數的方法.

調用默認構造函數

?
// 調用默認構造函數
obj = (*env)->AllocObjdect(env, cls);

 構造函數也是方法, 相似調用方法的方式.

?
// 調用指定的構造函數, 構造函數的名字叫作<init>
mid = (*env)->GetMethodID(env, cls, "<init>" , "()V" );
obj = (*env)->NewObject(env, cls, mid);

 調用方法和修改屬性

關於方法和屬性是有兩個ID與之對應, 這兩個ID用來標識方法和屬性.

?
jmethodID mid;
jfieldID fid;

 方法分爲靜態和非靜態的, 因此對應的有

?
mid = (*env)->GetStaticMethodID(env, cls, "sayHello" , "(Ljava/lang/String;)Ljava/lang/String;" );
 
mid = (*env)->GetMethodID(env, cls, "sayHello" , "()Ljava/lang/String;" );

上面兩個方法是同名的, 都叫sayHello, 可是簽名不一樣, 因此能夠區分兩個方法.

JNI的函數都是有必定規律的, Static就表示是靜態, 沒有表示非靜態.

方法的調用以下

?
jstring result = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid, arg);
 
jstring result = (jstring)(*env)->CallObjectMethod(env, obj, mid);

 咱們能夠看到靜態方法是隻須要class對象, 不須要實例的, 而非靜態方法須要使用咱們以前實例化的對象.

屬性也有靜態和非靜態, 示例中只有非靜態的.

獲取屬性ID

?
fid = (*env)->GetFieldID(env, cls, "name" , "Ljava/lang/String;" );

 修改屬性的值

?
(*env)->SetObjectField(env, obj, fid, arg); // 修改屬性

 關於jstring的說明

java的String都是使用了unicode, 是雙字節的字符, 而C/C++中使用的單字節的字符.

從C轉換爲java的字符, 使用NewStringUTF方法

?
jstring arg = (*env)->NewStringUTF(env, name);

 從java轉換爲C的字符, 使用GetStringUTFChars

?
const char * str = (*env)->GetStringUTFChars(env, result, 0);

 編譯和運行

編譯須要頭文件, 頭文件在這兩個目錄中%JAVA_HOME%\include和%JAVA_HOME%\include\win32, 第一個是與平臺無關的, 第二個是與平臺有關的, 因爲筆者的系統是windows, 因此是win32.

編譯的時候還要一個lib文件, 是對虛擬機的支持, 保證編譯經過.

?
cl -I%JAVA_HOME%\include -I%JAVA_HOME%\include\win32 Sample2.c %JAVA_HOME%\lib\jvm.lib

咱們能夠看到在當前目錄下Sample2.exe, 運行的時候須要jvm.dll(不要將其複製到當前目錄下, 這樣不能夠運行, 會致使jvm建立失敗)

?
set PATH=%JAVA_HOME%\jre\bin\client\;%PATH%
Sample2

 jvm.dll在%JAVA_HOME%\jre\bin\client\目錄下, 因此我把這個目錄加入到PATH中, 而後就能夠運行

?
Result of sayHello: Hello, World!
Result of sayHello: Hello, icejoywoo!

 

關於C++的說明

 本示例的C++版本, 請自行下載後面的源代碼來查看, 區別不是很大.

主要是JNIEnv和JavaVM兩個對象, 在C中是結構體, 是函數指針的集合, 在C++中結構體擁有類的能力, 使用起來更爲簡便, 與Java之間的差別更小一些.

結語

本文介紹了一個簡單的例子, 分析了其中的一些代碼, 筆者但願經過這篇文章讓你們對JNI的瞭解更加深刻一些.

水平有限, 錯漏在所不免, 歡迎指正!

源代碼下載: c調用java.zip

使用方法: 參照裏面的build&run.bat, 使用了%JAVA_HOME%環境變量.

注意:

  1. 動態連接庫和JDK都有32位和64位的區別, 使用64位系統的朋友, 要注意這個問題, 可能致使運行或編譯錯誤.
  2. 還要注意區分C和C++代碼, 在JNI中兩種代碼有必定的區別, 主要是env和jvm兩個地方.

 

  
做者:icejoywoo
本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,不然保留追究法律責任的權利. 短網址: http://goo.gl/ZiZCi
相關文章
相關標籤/搜索