參考資料:使用JNI進行Java與C/C++語言混合編程(1)--在Java中調用C/C++本地庫html
JNI是Java Native Interface的英文縮寫,中文翻譯爲本地調用, 自從Java 1.1開始就成爲了Java標準的一部分.linux
C/C++是系統級的編程語言, 能夠用來開發任何和系統相關的程序和類庫, 可是Java自己編寫底層的應用比較難實現, 使用JNI能夠調用現有的本地庫, 極大地靈活了Java的開發.編程
C/C++的效率是目前最好的語言, 可使用C/C++來實現一些實時性很是高的部分. C/C++和Java自己都是很是流行的編程語言, 一些大型軟件中常用語言之間的混合編程.windows
鑑於目前網絡上JNI的文章不是特別多, 我將本身的一些總結寫在這裏. 若有錯漏, 歡迎指正!api
Java調用C/C++大概有這樣幾個步驟網絡
首先咱們看一下:Linux下JNI技術使用的一個簡單實例oracle
首先,實現的是Java本地接口Hello.java,代碼以下所示:
編程語言
class HelloWorld { public native void sayHello(); static { System.loadLibrary("HelloWorld"); } public static void main(String[] args) { (new HelloWorld()).sayHello(); } }
其中,方法聲明爲native,其實HelloWorld類就至關於一個接口,是爲其餘編程語言聲明的接口。System.loadLibrary("HelloWorld");語句是一個static塊,也就是在該HelloWorld類加載的時候進行執行。其中,該語句實現了加載本地的動態鏈接庫(DLL),在Linux平臺下,動態鏈接庫文件是以.so做爲擴展名的,也就是標準對象(Standard Object)。
對該本地接口類進行編譯:
函數
[root@localhost jni]# javac HelloWorld.java
接着,經過編譯的HelloWorld.class文件,生成C語言的頭文件,執行命令:
[root@localhost jni]# javah -jni HelloWorld
能夠看到,在當前目錄下生成一個HelloWorld.h文件,該文件就是C的接口文件,爲使用C實現Java接口中定義的方法,能夠發如今HelloWorld.h中有一個方法聲明:
/* DO NOT EDIT THIS FILE - it is machine generated */ #ifndef __HelloWorld__ #define __HelloWorld__ #include <jni.h> #ifdef __cplusplus extern "C" { #endif JNIEXPORT void JNICALL Java_HelloWorld_sayHello (JNIEnv *env, jobject); #ifdef __cplusplus } #endif #endif /* __HelloWorld__ */
而後,用C實現該方法,在HelloWorld.c文件中,代碼以下:
#include <jni.h> #include "HelloWorld.h" #include <stdio.h> JNIEXPORT void JNICALL Java_HelloWorld_sayHello (JNIEnv *env, jobject obj) { printf("Hello,the World!!!"); }
這裏,方法簽名爲Java_HelloWorld_sayHello (JNIEnv *env, jobject obj),添加了形參obj,不然沒法經過編譯。
接下來,生成動態鏈接庫libHelloWorld.so,執行命令:
[root@localhost jni]# gcc -fPIC -shared -o libHelloWorld.so HelloWorld.c
(這裏可能會遇到jni.h找不到的狀況,再你配置的JAVA_HOME 文件夾目錄下加上以下路徑選項便可:
-I $JAVA_HOME)/include -I $JAVA_HOME/include/linux
)
能夠在當前目錄下看到libHelloWorld.so,動態鏈接庫文件名稱以lib開頭。將該文件拷貝到usr/lib目錄下面,就能夠測試了。
如今執行以下命令進行測試:
[root@localhost jni]# java HelloWorld
輸出以下:
Hello,the World!!!
這只是一個很是簡單的例子,主要是瞭解JNI在Linux下該如何用。在實際應用中,可能會很是複雜,而且要記住,一旦使用了JNI技術,系統的可移植性被破壞了。有些應用中,正是基於這種特性實現,好比限制軟件的傳播使用,保護開發商權益,等等。
主要參考了這篇文章:http://blog.csdn.net/hemowolf/article/details/6925243
JAVA 的標準JNI中並無enum類型,可是有jobject對象,那麼類和枚舉類型均可以經過jobject進行傳遞。
2.1 建立枚舉類型
每一個枚舉對象都有一個對應的索引值,枚舉類中有一個返回值函數。
package test; public class enumclass { public enum envelopeType { NOT_SPECIFIED(-1), NONE(0), IMAGE(1), BITMAP(2); private int value; private envelopeType(int value) { this.value = value; } public int getValue() { return value; } } }
2.2 JAVA 本地函數
package test; import test.enumclass.envelopeType; public class enumtest { private envelopeType ent; public native static void printValue(envelopeType entype); public static void main(String[] args) { System.load("/home/sk/workspace/testenumjni2/jni/test.so"); enumtest t1 = new enumtest(); envelopeType e1 = null; int a = e1.IMAGE.getValue(); System.out.println("------ the envelopeType value is :" + a); envelopeType e2 = envelopeType.NOT_SPECIFIED; // e2.IMAGE; printValue(e2); } }
在命令行中(linux環境下)切換到工程的bin目錄下執行以下命令:編譯生成.h文件。
javah -jni test.enumtest
2.3 JNI 接口部分
根據上步的.h文件,編寫對應的c/cpp文件。
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class test_enumtest */ #ifndef _Included_test_enumtest #define _Included_test_enumtest #ifdef __cplusplus extern "C" { #endif /* * Class: test_enumtest * Method: printValue * Signature: (Ltest/enumclass/envelopeType;)V */ JNIEXPORT void JNICALL Java_test_enumtest_printValue (JNIEnv *env, jobject obj1, jobject obj2) { jclass enumclass= env->GetObjectClass(obj2); if(enumclass == NULL) { printf(" get enumclass failed\r\n"); return; } jmethodID getVal = env->GetMethodID(enumclass, "getValue", "()I"); jint value = env->CallIntMethod(obj2, getVal); printf("------ the envelopeType value is :%d\r\n", value); } #ifdef __cplusplus } #endif #endif
env->GetMethodID 有三個參數,第一個表示獲取的類的名字,第二個參數是函數名,最後一個參數應該這樣得到:
這個參數應該是JAVA的類的方法的簽名,它能夠經過jdk的工具 javap 獲得。如上面的 test.enumtest.envelopeType 類中的 getValue 方法,能夠在命令行裏執行:
javap -s test.enumtest.envelopeType
獲得 test.enumtest.envelopeType 的全部方法的簽名。
2.4 下面是另外一個沒有索引值的枚舉類型列子
考慮本地函數參數中含有枚舉類型的對象,在JNI部分要獲取的是枚舉對象,而非對象的一個函數。
package test; public class enumclass { public enum DATE { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATUDAY, SUNDAY; } }
import test.enumclass.DATE; public class enumtest { private DATE date; public native static void callobj(DATE date); public static void main(String[] args){ System.load("/home/sk/workspace/testenumjni3/jni/test.so"); enumtest e1 = new enumtest(); e1.date = DATE.SATUDAY; System.out.println(e1.date); //Now we call the native method DATE today = DATE.TUESDAY; callobj(today); } }
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> #include<string.h> /* Header for class test_enumtest */ #ifndef _Included_test_enumtest #define _Included_test_enumtest #ifdef __cplusplus extern "C" { #endif /* * Class: test_enumtest * Method: callobj * Signature: (Ltest/enumclass/DATE;)V */ JNIEXPORT void JNICALL Java_test_enumtest_callobj (JNIEnv *env, jobject obj1, jobject obj2){ jclass enumclass= env->GetObjectClass(obj2); if(enumclass == NULL) { printf(" get enumclass failed\r\n"); return; } jmethodID getVal = env->GetMethodID(enumclass, "name", "()Ljava/lang/String;"); jstring value = (jstring)env->CallObjectMethod(obj2, getVal); const char * valueNative = env->GetStringUTFChars(value, 0); if (strcmp(valueNative, "MONDAY") == 0) { printf("TODAY IS MONDAY!"); } else printf("TODAY IS NOT MONDAY!"); } #ifdef __cplusplus } #endif #endif
jmethodID getVal = env->GetMethodID(enumclass, "name", "()Ljava/lang/String;");
這裏name()函數是enum類型自帶的函數,()Ljava/lang/String;是對應的簽名。具體的ENUM類型能夠參考官方文檔:
http://docs.oracle.com/javase/1.5.0/docs/api/java/lang/Enum.html
另外就是這裏換成了(jstring)env->CallObjectMethod(obj2, getVal);方法。
下面的一個例子,是要在JNI中完成由C++寫的代碼函數的調用,函數的參數包括枚舉類型的。須要在JNI中對原來C/C++定義的枚舉型從新作一次映射(在數據量小的時候這樣操做是可行的)。
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> #include<string.h> /*emun類型定義*/ enum DATE { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATUDAY, SUNDAY; } void returndate(DATE date); void returndate(DATE date){ printf("%d/n","TODAY IS :"); switch(date){ case MONDAY: printf("%d/n","MONDAY"); break; case TUESDAY: printf("%d/n","TUESDAY"); break; case WEDNESDAY: printf("%d/n","WEDNESDAY"); break; case THURSDAY: printf("%d/n","THURSDAY"); break; case FRIDAY: printf("%d/n","FRIDAY"); break; case SATUDAY: printf("%d/n","SATUDAY"); break; case SUNDAY: printf("%d/n","SUNDAY"); } } /* Header for class test_enumtest */ #ifndef _Included_test_enumtest #define _Included_test_enumtest #ifdef __cplusplus extern "C" { #endif /* * Class: test_enumtest * Method: callobj * Signature: (Ltest/enumclass/DATE;)V */ JNIEXPORT void JNICALL Java_test_enumtest_callobj (JNIEnv *env, jobject obj1, jobject obj2){ jclass enumclass= env->GetObjectClass(obj2); if(enumclass == NULL) { printf(" get enumclass failed\r\n"); return; } jmethodID getVal = env->GetMethodID(enumclass, "name", "()Ljava/lang/String;"); jstring value = (jstring)env->CallObjectMethod(obj2, getVal); const char * valueNative = env->GetStringUTFChars(value, 0); DATE date; //這裏完成一個轉換 if (strcmp(valueNative, "MONDAY") == 0) { date = MONDAY; } if.....(這裏接着判斷) returndate(date); } #ifdef __cplusplus } #endif #endif
若是在JNI函數中調用C++的函數中帶有枚舉型參數,用jstring類型或者 char*類型的作參數都會報錯,能夠作的就是再作一次名字的映射,將上述類型映射成enmu類型,再看成參數去傳遞,就不會有問題了。