原文連接:http://www.cnblogs.com/icejoywoo/archive/2012/02/24/2367116.html
JNI就是Java Native Interface, 便可以實現Java調用本地庫, 也能夠實現C/C++調用Java代碼, 從而實現了兩種語言的互通, 可讓咱們更加靈活的使用。html
經過使用JNI能夠從一個側面瞭解Java內部的一些實現。java
本文使用的環境是:linux
- 64位的win7系統
- 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)
本文使用到的一些功能:編程
- 建立虛擬機
- 尋找class對象, 建立對象
- 調用靜態方法和成員方法
- 獲取成員屬性, 修改爲員屬性
C/C++調用Java代碼的通常步驟:windows
- 編寫Java代碼, 並編譯
- 編寫C/C++代碼
- 配置lib進行編譯, 配置PATH添加相應的dll或so並運行
編寫Java代碼並編譯jvm
這段代碼很是簡單, 有個靜態方法和成員方法, 一個public的成員變量函數
- 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代碼
先貼代碼吧
- #include <jni.h>
- #include <string.h>
- #include <stdio.h>
-
-
- #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)
- {
-
- cls = (*env)->FindClass(env, "Sample2");
- if (cls != 0)
- {
-
- 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);
- }
-
-
-
-
-
-
- mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
- obj = (*env)->NewObject(env, cls, mid);
- if (obj == 0)
- {
- printf("Create object failed!\n");
- }
-
-
-
- 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;
- }
- }
這段代碼大概作了這幾件事:
- 建立虛擬機JVM, 在程序結束的時候銷燬虛擬機JVM
- 尋找class對象
- 建立class對象的實例
- 調用方法和修改屬性
虛擬的建立
與之相關的有這樣幾個變量
- JavaVMOption options[1];
- JNIEnv *env;
- JavaVM *jvm;
- JavaVMInitArgs vm_args;
JavaVM就是咱們須要建立的虛擬機實例
JavaVMOption至關於在命令行裏傳入的參數
JNIEnv在Java調用C/C++中每一個方法都會有的一個參數, 擁有一個JNI的環境
JavaVMInitArgs就是虛擬機建立的初始化參數, 這個參數裏面會包含JavaVMOption
下面就是建立虛擬機
- 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
- #define JNI_ERR (-1)
- #define JNI_EDETACHED (-2)
- #define JNI_EVERSION (-3)
- #define JNI_ENOMEM (-4)
- #define JNI_EEXIST (-5)
- #define JNI_EINVAL (-6)
尋找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);
構造函數也是方法, 相似調用方法的方式.
-
- 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);
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++版本, 請自行下載後面的源代碼來查看, 區別不是很大.
主要是JNIEnv和JavaVM兩個對象, 在C中是結構體, 是函數指針的集合, 在C++中結構體擁有類的能力, 使用起來更爲簡便, 與Java之間的差別更小一些.
結語
本文介紹了一個簡單的例子, 分析了其中的一些代碼, 筆者但願經過這篇文章讓你們對JNI的瞭解更加深刻一些.
水平有限, 錯漏在所不免, 歡迎指正!
源代碼下載: c調用java.zip
使用方法: 參照裏面的build&run.bat, 使用了%JAVA_HOME%環境變量.
注意:
- 動態連接庫和JDK都有32位和64位的區別, 使用64位系統的朋友, 要注意這個問題, 可能致使運行或編譯錯誤.
- 還要注意區分C和C++代碼, 在JNI中兩種代碼有必定的區別, 主要是env和jvm兩個地方.
參考文獻:
- public0821, C++調用JAVA方法詳解, http://public0821.iteye.com/blog/423941
- Scott Stricker, 用 JNI 進行 Java 編程, http://www.ibm.com/developerworks/cn/education/java/j-jni/section3.html
- JDK 6u30 docs, Java Native Interface Specification