使用JNI進(jìn)行混合編程:在C/C++中調(diào)用Java代碼
JNI就是Java Native Interface, 即可以實現(xiàn)Java調(diào)用本地庫, 也可以實現(xiàn)C/C++調(diào)用Java代碼, 從而實現(xiàn)了兩種語言的互通, 可以讓我們更加靈活的使用。
通過使用JNI可以從一個側(cè)面了解Java內(nèi)部的一些實現(xiàn)。
本文使用的環(huán)境是:
- 64位的win7系統(tǒng)
- JDK 1.6.0u30 (32位)
- C/C++編譯器是 Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8168 for 80x86 (VC 6.0的, 其他版本的也可以編譯通過, 測試過vs2010)
本文使用到的一些功能:
- 創(chuàng)建虛擬機(jī)
- 尋找class對象, 創(chuàng)建對象
- 調(diào)用靜態(tài)方法和成員方法
- 獲取成員屬性, 修改成員屬性
C/C++調(diào)用Java代碼的一般步驟:
- 編寫Java代碼, 并編譯
- 編寫C/C++代碼
- 配置lib進(jìn)行編譯, 配置PATH添加相應(yīng)的dll或so并運行
編寫Java代碼并編譯
這段代碼非常簡單, 有個靜態(tài)方法和成員方法, 一個public的成員變量
- public class Sample2 {
- public String name;
- public static String sayHello(String name) {
- return "Hello, " + name + "!";
- }
- public String sayHello() {
- return "Hello, " + name + "!";
- }
- }
由于沒有定義構(gòu)造函數(shù), 所以會有一個默認(rèn)的構(gòu)造函數(shù).
運行下面的命令編譯
>javac Sample2.java
可以在當(dāng)前目錄下看到Sample2.class文件, 編譯成功, ***步完成了, So easy!
可以查看Sample2類中的簽名
>javap -s -private Sample2
結(jié)果如下
- 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;
- }
關(guān)于簽名的含義, 請參看使用JNI進(jìn)行Java與C/C++語言混合編程(1)--在Java中調(diào)用C/C++本地庫.
編寫C/C++代碼調(diào)用Java代碼
先貼代碼吧
- #include <jni.h>
- #include <string.h>
- #include <stdio.h>
- // 環(huán)境變量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;
- // 啟動虛擬機(jī)
- status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
- if (status != JNI_ERR)
- {
- // 先獲得class對象
- cls = (*env)->FindClass(env, "Sample2");
- if (cls != 0)
- {
- // 獲取方法ID, 通過方法名和簽名, 調(diào)用靜態(tài)方法
- 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);
- }
- /*** 新建一個對象 ***/
- // 調(diào)用默認(rèn)構(gòu)造函數(shù)
- //obj = (*env)->AllocObjdect(env, cls);
- // 調(diào)用指定的構(gòu)造函數(shù), 構(gòu)造函數(shù)的名字叫做<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); // 修改屬性
- }
- // 調(diào)用成員方法
- 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;
- }
- }
這段代碼大概做了這幾件事:
- 創(chuàng)建虛擬機(jī)JVM, 在程序結(jié)束的時候銷毀虛擬機(jī)JVM
- 尋找class對象
- 創(chuàng)建class對象的實例
- 調(diào)用方法和修改屬性
虛擬的創(chuàng)建
與之相關(guān)的有這樣幾個變量
- JavaVMOption options[1];
- JNIEnv *env;
- JavaVM *jvm;
- JavaVMInitArgs vm_args;
JavaVM就是我們需要創(chuàng)建的虛擬機(jī)實例
JavaVMOption相當(dāng)于在命令行里傳入的參數(shù)
JNIEnv在Java調(diào)用C/C++中每個方法都會有的一個參數(shù), 擁有一個JNI的環(huán)境
JavaVMInitArgs就是虛擬機(jī)創(chuàng)建的初始化參數(shù), 這個參數(shù)里面會包含JavaVMOption
下面就是創(chuàng)建虛擬機(jī)
- 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;
- // 啟動虛擬機(jī)
- status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
"-Djava.class.path=."看著眼熟吧, 這個就是傳入當(dāng)前路徑, 作為JVM尋找class的用戶自定義路徑, 我們的Sample2.class就在當(dāng)前路徑(當(dāng)然也可以不在當(dāng)前路徑, 你可以隨便修改).
vm_args.version是Java的版本, 這個應(yīng)該是為了兼容以前的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里面去。
然后就是啟動虛擬機(jī)了status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args)。
可以通過這個返回值status , 知道虛擬機(jī)是否啟動成功
- #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中構(gòu)造函數(shù)有兩種, 一種是默認(rèn)的沒有參數(shù)的, 一種是自定義的帶有參數(shù)的. 對應(yīng)的在C/C++中, 有兩種調(diào)用構(gòu)造函數(shù)的方法.
調(diào)用默認(rèn)構(gòu)造函數(shù)
- // 調(diào)用默認(rèn)構(gòu)造函數(shù)
- obj = (*env)->AllocObjdect(env, cls);
構(gòu)造函數(shù)也是方法, 類似調(diào)用方法的方式.
- // 調(diào)用指定的構(gòu)造函數(shù), 構(gòu)造函數(shù)的名字叫做<init>
- mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
- obj = (*env)->NewObject(env, cls, mid);
調(diào)用方法和修改屬性
關(guān)于方法和屬性是有兩個ID與之對應(yīng), 這兩個ID用來標(biāo)識方法和屬性.
- jmethodID mid;
- jfieldID fid;
方法分為靜態(tài)和非靜態(tài)的, 所以對應(yīng)的有
- mid = (*env)->GetStaticMethodID(env, cls, "sayHello", "(Ljava/lang/String;)Ljava/lang/String;");
- mid = (*env)->GetMethodID(env, cls, "sayHello", "()Ljava/lang/String;");
上面兩個方法是同名的, 都叫sayHello, 但是簽名不同, 所以可以區(qū)分兩個方法.
JNI的函數(shù)都是有一定規(guī)律的, Static就表示是靜態(tài), 沒有表示非靜態(tài).
方法的調(diào)用如下
- jstring result = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid, arg);
- jstring result = (jstring)(*env)->CallObjectMethod(env, obj, mid);
我們可以看到靜態(tài)方法是只需要class對象, 不需要實例的, 而非靜態(tài)方法需要使用我們之前實例化的對象.
屬性也有靜態(tài)和非靜態(tài), 示例中只有非靜態(tài)的.
獲取屬性ID
- fid = (*env)->GetFieldID(env, cls, "name", "Ljava/lang/String;");
改屬性的值
- (*env)->SetObjectField(env, obj, fid, arg); // 修改屬性
關(guān)于jstring的說明
java的String都是使用了unicode, 是雙字節(jié)的字符, 而C/C++中使用的單字節(jié)的字符。
從C轉(zhuǎn)換為java的字符, 使用NewStringUTF方法
- jstring arg = (*env)->NewStringUTF(env, name);
從java轉(zhuǎn)換為C的字符, 使用GetStringUTFChars
- const char* str = (*env)->GetStringUTFChars(env, result, 0);
const char* str = (*env)->GetStringUTFChars(env, result, 0);
|
編譯和運行
編譯需要頭文件, 頭文件在這兩個目錄中%JAVA_HOME%\include和%JAVA_HOME%\include\win32, ***個是與平臺無關(guān)的, 第二個是與平臺有關(guān)的, 由于筆者的系統(tǒng)是windows, 所以是win32.
編譯的時候還要一個lib文件, 是對虛擬機(jī)的支持, 保證編譯通過.
- cl -I%JAVA_HOME%\include -I%JAVA_HOME%\include\win32 Sample2.c %JAVA_HOME%\lib\jvm.lib
- set PATH=%JAVA_HOME%\jre\bin\client\;%PATH%
- Sample2
- Result of sayHello: Hello, World!
- Result of sayHello: Hello, icejoywoo!
本示例的C++版本, 請自行下載后面的源代碼來查看, 區(qū)別不是很大.
主要是JNIEnv和JavaVM兩個對象, 在C中是結(jié)構(gòu)體, 是函數(shù)指針的集合, 在C++中結(jié)構(gòu)體擁有類的能力, 使用起來更為簡便, 與Java之間的差異更小一些.
結(jié)語
本文介紹了一個簡單的例子, 分析了其中的一些代碼, 筆者希望通過這篇文章讓大家對JNI的了解更加深入一些.
水平有限, 錯漏在所難免, 歡迎指正!
源代碼下載: c調(diào)用java.zip
使用方法: 參照里面的build&run.bat, 使用了%JAVA_HOME%環(huán)境變量.
注意:
- 動態(tài)鏈接庫和JDK都有32位和64位的區(qū)別, 使用64位系統(tǒng)的朋友, 要注意這個問題, 可能導(dǎo)致運行或編譯錯誤.
- 還要注意區(qū)分C和C++代碼, 在JNI中兩種代碼有一定的區(qū)別, 主要是env和jvm兩個地方.
參考文獻(xiàn):
- public0821, C++調(diào)用JAVA方法詳解, http://public0821.iteye.com/blog/423941
- Scott Stricker, 用 JNI 進(jìn)行 Java 編程, http://www.ibm.com/developerworks/cn/education/java/j-jni/section3.html
- JDK 6u30 docs, Java Native Interface Specification
原文鏈接:http://www.cnblogs.com/icejoywoo/archive/2012/02/24/2367116.html
【編輯推薦】