JNI(Java Native Interface)是Java語言的一部分,可以訪問非Java語言編寫的程序,也可以用於在C++程序中執行Java代碼。
步驟:
1> 編寫帶有native聲明方法的Java類,並且該方法只定義不實現,后期由c++負責實現:
// HelloCpp.java
public class HelloCpp
{
// ...
public native void callCpp();
// ...
}
2> 由於后期的C++實現代碼最終會被編譯為一個動態庫.dll,因此需要在Java類中定義一個靜態代碼塊,提前加載該動態庫,假設動態的名字為hellocpp.dll:
// HelloCpp.java
public class HelloCpp
{
static
{
System.loadLibrary("hellocpp");
}
public native void callCpp();
// ...
}
3> 在Java類中定義main方法調用該native方法:
// HelloCpp.java
public class HelloCpp
{
static
{
System.loadLibrary("hellocpp");
}
public native void callCpp();
public static void main(String[] args)
{
System.out.println("***** JNI Test *****");
HelloCpp instance = new HelloCpp();
instance.callCpp(); // 調用native方法
}
}
4> 編譯包含native方法的Java類,生成class字節碼文件:
javac HelloCpp.java // 生成HelloCpp.class
5> 生成與native方法對應的.h頭文件:
javah –jni HelloCpp // 生成HelloCpp類對於的頭文件HelloCpp.h
// HelloCpp.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloCpp */
#ifndef _Included_HelloCpp
#define _Included_HelloCpp
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloCpp
* Method: callCpp
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_HelloCpp_callCpp(JNIEnv* env, jobject this);
#ifdef __cplusplus
}
#endif
#endif
6> 使用C++實現native方法:
// HelloCpp.cpp
#include "HelloCpp.h"
#include <jni.h>
#include <iostream>
JNIEXPORT void JNICALL Java_HelloCpp_callCpp(JNIEnv* env, jobject this)
{
std::cout << "C++ Implementation" << std::endl;
}
7> 編譯生成動態庫hellocpp.dll:
g++ -Wl,--kill-at –shared –I D:\jdk1.7.0_75\include –I D:\jdk1.7.0_75\include\win32 HelloCpp.cpp –o hellocpp.dll
8> 調用hellocpp.dll來運行Java程序:
Java HelloCpp
結果如下:
***** JNI Test *****
C++ Implementation
說明:
JNIEXPORT void JNICALL Java_HelloCpp_callCpp(JNIEnv* env, jobject this);
JNIWXPORT和JNICALL是宏。
JNIEnv*指向一個位置,該位置包含一個指向函數表的指針,表中的每一項都是一個指向JNI函數的指針,native方法通過JNI函數訪問JVM的中的數據 ,如下所示:
第二個參數對於非靜態方法為jobject,對於靜態方法為jclass。jobject表示調用native方法對象自身的引用,如同C++中的this指針;jclass表示定義native方法的類的引用。
如下介紹帶有參數和返回值的native方法:
// Prompt.java
1 class Prompt 2 { 3 static 4 { 5 System.loadLibrary("Prompt"); 6 } 7 private native String GetLine(String prompt); 8 9 public static void main(String[] args) 10 { 11 Prompt p = new Prompt(); 12 String input = p.GetLine("Enter a line:"); 13 System.out.println("Your Input is: " + input); 14 } 15 }


// Prompt.h
1 /* DO NOT EDIT THIS FILE - it is machine generated */ 2 #include <jni.h> 3 /* Header for class Prompt */ 4 5 #ifndef _Included_Prompt 6 #define _Included_Prompt 7 #ifdef __cplusplus 8 extern "C" { 9 #endif 10 /* 11 * Class: Prompt 12 * Method: GetLine 13 * Signature: (Ljava/lang/String;)Ljava/lang/String; 14 */ 15 JNIEXPORT jstring JNICALL Java_Prompt_GetLine(JNIEnv* env, jobject _this, jstring prompt); 16 17 #ifdef __cplusplus 18 } 19 #endif 20 #endif
// Prompt.cpp
1 #include "Prompt.h" 2 #include <iostream> 3 4 JNIEXPORT jstring JNICALL Java_Prompt_GetLine(JNIEnv* env, jobject _this, jstring prompt) 5 { 6 char buf[1024]; 7 const char* str; 8 str = env->GetStringUTFChars(prompt, NULL); /* 獲得傳入的字符串,將其轉換為native Strings */ 9 if(str == NULL) /* str == NULL意味着JVM為native String分配內存失敗 */ 10 { 11 return NULL; 12 } 13 std::cout << str; /* 顯示傳入的字符串參數 prompt */ 14 env->ReleaseStringUTFChars(prompt, str); /* 通知JVM釋放String所占的內存 */ 15 16 std::cin.get(buf, 1024); 17 return env->NewStringUTF(buf); /* 構造新的Java.lang.String,如果JVM分配內存失敗,則拋出OutOfMemoryError,並且返回NULL */ 18 }
補充信息:
:: UTF-8字符串以’\0’結尾,而Unicode字符串則不是。如果需要獲得Unicode格式的jstring的長度,可以使用GetStringLength;如果需要獲得UTF-8格式的jstring的長度,可以先使用GetStringUTFChars,在其結果上使用strlen,或者直接使用GetStringUTFLength。
:: GetStringUTFChars的第二個參數為jboolean *isCopy,其指向分配的內存空間,如果isCopy被設為JNI_TRUE,那么返回的String是Java String的一個副本;如果被設為JNI_FALSE,那么返回一個指向Java String本身的指針,此時不允許修改返回的String。
:: 函數對Get/ReleaseStringCritical的作用於Get/ReleaseStringChars類似,但是對於程序員而言,該函數對之間的代碼相當於“臨界區”。在該“臨界區”內,native代碼不能調用任何的JNI函數,否則將引起當前線程阻塞。
:: GetStringRegion/GetStringUTFRegion將Unicode格式的String復制到預分配的緩沖區中,由於不需要JVM分配內存,因此也就不需要釋放操作:
1 JNIEXPORT jstring JNICALL Java_Prompt_GetLine(JNIEnv* env, jobject _this, jstring prompt) 2 { 3 char inbuf[1024], outbuf[1024]; 4 int len = env->GetStringUTFLength(prompt); 5 env->GetStringUTFRegion(prompt, 0, len, outbuf); 6 std::cout << outbuf; 7 8 std::cin.get(inbuf, 1024); 9 return env->NewStringUTF(inbuf); 10 }
總結:
1> 數據類型對應關系表:
Java 類型 |
本地 C 類型 |
實際表示的 C 類型(Win32) |
boolean |
jboolean |
unsigned char |
byte |
jbyte |
signed char |
char |
jchar |
unsigned short |
short |
jshort |
short |
int |
jint |
long |
long |
jlong |
__int64 |
float |
jfloat |
float |
double |
jdouble |
double |
void |
void |
N/A |
2> JNI字符串函數
JNI函數 |
描述 |
版本 |
GetStringChars ReleaseStringChars |
獲得/釋放一個Unicode格式的字符串指針,可能返回一個字符串的副本 |
JDK 1.1 |
GetStringUTFChars ReleaseStringUTFChars |
獲得/釋放一個UTF-8格式的字符串指針,可能返回一個字符串的副本 |
JDK 1.1 |
GetStringLength |
返回Unicode格式字符串的長度 |
JDK 1.1 |
GetStringUTFLength |
返回UTF-8格式字符串的長度 |
JDK 1.1 |
NewString |
根據Unicode格式的C字符串創建一個Java字符串 |
JDK 1.1 |
NewStringUTF |
根據UTF-8格式的C字符串創建一個Java字符串 |
JDK 1.1 |
GetStringCritical ReleaseStringCritical |
獲得/釋放一個Unicode格式的字符串指針,可能返回一個字符串的副本【在該函數對區間內,不能使用任何JNI函數】 |
JDK 1.2 |
GetStringRegion |
將Unicode格式的String復制到預分配的緩沖區中 |
JDK 1.2 |
GetStringUTFRegion |
將UTF-8格式的String復制到預分配的緩沖區中 |
JDK 1.2 |