如何使用JNI的一些基本方法和過程在網上多如牛毛,如果你對Jni不甚了解,不知道Jni是做什么的,如何建立一個基本的jni程序,或許可以參考下面下面這些文章:
利用VC++6.0實現JNI的最簡單的例子
?
JNI入門教程之HelloWorld篇
SUN JNI Tutorial
這些資料的例子中,大多數只是輸入一些簡單的參數,獲取沒有參數。而在實際的使用過程中,往往需要對參數進行處理轉換。才可以被C/C++程序識別。比如我們在C++中有一個結構(Struct)DiskInfo ,需要傳遞一個類似于DiskInfo *pDiskInfo的參數,類似于在C++這樣參數如何傳遞到Java中呢?下面我們就來討論C++到Java中方法的一些常見參數的轉換:
定義Native Java類:
如果你習慣了使用JNI,你就不會覺得它難了。既然本地方法是由其他語言實現的,它們在Java中沒有函數體。但是,所有本地代碼必須用本地關鍵詞聲明,成為Java類的成員。假設我們在C++中有這么一個結構,它用來描述硬盤信息:
//
硬盤信息
struct
??
{
????
char
?name[
256
];
????
int
?serial;
}
DiskInfo;
那么我們需要在Java中定義一個類來與之匹配,聲明可以寫成這樣:
class
?DiskInfo?
{
????
//
名字
????
public
?String?name;
????
//
序列號
????
public
?
int
?serial;
}
在這個類中,申明一些Native的本地方法,來測試方法參數的傳遞,分別定義了一些函數,用來傳遞結構或者結構數組,具體定義如下面代碼:
/**/
/*
*****************?定義本地方法?*******************
*/
????
//
輸入常用的數值類型(Boolean,Byte,Char,Short,Int,Float,Double)
????
public
?native?
void
?displayParms(String?showText,?
int
?i,?boolean?bl);
????
//
調用一個靜態方法
????
public
?native?
int
?add(
int
?a,?
int
?b);
????
//
輸入一個數組
????
public
?native?
void
?setArray(boolean[]?blList);
????
//
返回一個字符串數組
????
public
?native?String[]?getStringArray();
????
//
返回一個結構
????
public
?native?DiskInfo?getStruct();
????
//
返回一個結構數組
????
public
?native?DiskInfo[]?getStructArray();
編譯生成C/C++頭文件
定義好了Java類之后,接下來就要寫本地代碼。本地方法符號提供一個滿足約定的頭文件,使用
Java工具Javah
可以很容易地創建它而不用手動去創建。你對Java的class文件使用javah命令,就會為你生成一個對應的C/C++頭文件。
1、在控制臺下進入工作路徑,本工程路徑為:E:\work\java\workspace\JavaJni。
2、運行javah 命令:javah -classpath E:\work\java\workspace\JavaJni com.sundy.jnidemo ChangeMethodFromJni
本文生成的C/C++頭文件名為: com_sundy_jnidemo_ChangeMethodFromJni.h?
在C/C++中實現本地方法
生成C/C++頭文件之后,你就需要寫頭文件對應的本地方法。注意:所有的本地方法的第一個參數都是指向
JNIEnv
結構的。這個結構是用來調用JNI函數的。第二個參數jclass的意義,要看方法是不是靜態的(static)或者實例(Instance)的。前者,jclass代表一個類對象的引用,而后者是被調用的方法所屬對象的引用。
?
返回值和參數類型
根據等價約定映射到本地C/C++類型,如表
JNI類型映射
所示。有些類型,在本地代碼中可直接使用,而其他類型只有通過JNI調用操作。
表A
Java
類型
|
本地類型
|
描述
|
boolean
|
jboolean
|
C/C++8位整型
|
byte
|
jbyte
|
C/C++帶符號的8位整型
|
char
|
jchar
|
C/C++無符號的16位整型
|
short
|
jshort
|
C/C++帶符號的16位整型
|
int
|
jint
|
C/C++帶符號的32位整型
|
long
|
jlong
|
C/C++帶符號的64位整型e
|
float
|
jfloat
|
C/C++32位浮點型
|
double
|
jdouble
|
C/C++64位浮點型
|
Object
|
jobject
|
任何Java對象,或者沒有對應java類型的對象
|
Class
|
jclass
|
Class對象
|
String
|
jstring
|
字符串對象
|
Object[]
|
jobjectArray
|
任何對象的數組
|
boolean[]
|
jbooleanArray
|
布爾型數組
|
byte[]
|
jbyteArray
|
比特型數組
|
char[]
|
jcharArray
|
字符型數組
|
short[]
|
jshortArray
|
短整型數組
|
int[]
|
jintArray
|
整型數組
|
long[]
|
jlongArray
|
長整型數組
|
float[]
|
jfloatArray
|
浮點型數組
|
double[]
|
jdoubleArray
|
雙浮點型數組
|
※????
JNI類型映射
使用數組:
JNI通過JNIEnv提供的操作Java數組的功能。它提供了兩個函數:一個是操作java的簡單型數組的,另一個是操作對象類型數組的。
因為速度的原因,簡單類型的數組作為指向本地類型的指針暴露給本地代碼。因此,它們能作為常規的數組存取。這個指針是指向實際的Java數組或者Java數組的拷貝的指針。另外,數組的布置保證匹配本地類型。
為了存取Java簡單類型的數組,你就要要使用
GetXXXArrayElements
函數(見
表
B
),XXX代表了數組的類型。這個函數把Java數組看成參數,返回一個指向對應的本地類型的數組的指針。
表B
函數
|
Java
數組類型
|
本地類型
|
GetBooleanArrayElements
|
jbooleanArray
|
jboolean
|
GetByteArrayElements
|
jbyteArray
|
jbyte
|
GetCharArrayElements
|
jcharArray
|
jchar
|
GetShortArrayElements
|
jshortArray
|
jshort
|
GetIntArrayElements
|
jintArray
|
jint
|
GetLongArrayElements
|
jlongArray
|
jlong
|
GetFloatArrayElements
|
jfloatArray
|
jfloat
|
GetDoubleArrayElements
|
jdoubleArray
|
jdouble
|
JNI數組存取函數
當你對數組的存取完成后,要確保調用相應的
ReleaseXXXArrayElements
函數,參數是對應Java數組和
GetXXXArrayElements
返回的指針。如果必要的話,這個釋放函數會復制你做的任何變化(這樣它們就反射到java數組),然后釋放所有相關的資源。
為了使用java對象的數組,你必須使用
GetObjectArrayElement
函數和
SetObjectArrayElement
函數,分別去get,set數組的元素。
GetArrayLength
函數會返回數組的長度。
使用對象
JNI提供的另外一個功能是在本地代碼中使用Java對象。通過使用合適的JNI函數,你可以創建Java對象,get、set 靜態(static)和實例(instance)的域,調用靜態(static)和實例(instance)函數。JNI通過ID識別域和方法,一個域或方法的ID是任何處理域和方法的函數的必須參數。
表C
列出了用以得到靜態(static)和實例(instance)的域與方法的JNI函數。每個函數接受(作為參數)域或方法的類,它們的名稱,符號和它們對應返回的
jfieldID
或
jmethodID
。
表C
函數
|
描述
|
GetFieldID
|
得到一個實例的域的ID
|
GetStaticFieldID
|
得到一個靜態的域的ID
|
GetMethodID
|
得到一個實例的方法的ID
|
GetStaticMethodID
|
得到一個靜態方法的ID
|
※域和方法的函數
如果你有了一個類的實例,它就可以通過方法
GetObjectClass
得到,或者如果你沒有這個類的實例,可以通過
FindClass
得到。符號是從域的類型或者方法的參數,返回值得到字符串,如
表
D
所示。
表D
Java
類型
|
符號
|
boolean
|
Z
|
byte
|
B
|
char
|
C
|
short
|
S
|
int
|
I
|
long
|
L
|
float
|
F
|
double
|
D
|
void
|
V
|
objects對象
|
Lfully-qualified-class-name;L類名
|
Arrays數組
|
[array-type [數組類型
|
methods方法
|
(argument-types)return-type(參數類型)返回類型
|
※確定域和方法的符號
下面我們來看看,如果通過使用數組和對象,從C++中的獲取到Java中的DiskInfo 類對象,并返回一個DiskInfo數組:
//
返回一個結構數組,返回一個硬盤信息的結構數組
JNIEXPORT?jobjectArray?JNICALL?Java_com_sundy_jnidemo_ChangeMethodFromJni_getStructArray
(JNIEnv?
*
env,?jobject?_obj)
{
????
//
申明一個object數組?
????jobjectArray?args?
=
?
0
;
????
????
//
數組大小
????jsize????????len?
=
?
5
;
????
//
獲取object所屬類,一般為ava/lang/Object就可以了
????jclass?objClass?
=
?(env)
->
FindClass(
"
java/lang/Object
"
);
????
//
新建object數組
????args?
=
?(env)
->
NewObjectArray(len,?objClass,?
0
);
????
/**/
/*
?下面為獲取到Java中對應的實例類中的變量
*/
????
//
獲取Java中的實例類
????jclass?objectClass?
=
?(env)
->
FindClass(
"
com/sundy/jnidemo/DiskInfo
"
);
????
????
//
獲取類中每一個變量的定義
????
//
名字
????jfieldID?str?
=
?(env)
->
GetFieldID(objectClass,
"
name
"
,
"
Ljava/lang/String;
"
);
????
//
序列號
????jfieldID?ival?
=
?(env)
->
GetFieldID(objectClass,
"
serial
"
,
"
I
"
);
????
//
給每一個實例的變量付值,并且將實例作為一個object,添加到objcet數組中
????
for
(
int
??i
=
0
;?i?
<
?len;?i
++
?)
????
{
????????
//
給每一個實例的變量付值
????????jstring?jstr?
=
?WindowsTojstring(env,
"
我的磁盤名字是?D:
"
);
????????
//
(env)->SetObjectField(_obj,str,(env)->NewStringUTF("my?name?is?D:"));
????????(env)
->
SetObjectField(_obj,str,jstr);
????????(env)
->
SetShortField(_obj,ival,
10
);
????????
//
添加到objcet數組中
????????(env)
->
SetObjectArrayElement(args,?i,?_obj);
????}
????
//
返回object數組
????
return
?args;
?}
全部的C/C++方法實現代碼如下:
/**/
/*
*
*?一縷陽光(sundy)版權所有,保留所有權利。
*/
/**/
/*
*
*?
*??TODO?Jni?中一個從Java到C/C++參數傳遞測試類
*
*??@author?劉正偉(sundy)
*??@see?
http://www.cnweblog.com/sundy
*??@see?mailto:sundy26@126.com
*??@version?1.0
*??@since?2005-4-30
*?
*??修改記錄:
*??
*??日期??????????????修改人?????????????????描述
*??----------------------------------------------------------------------------------------------
*
*
*
*/
//
?JniManage.cpp?:?定義?DLL?應用程序的入口點。
//
package?com.sundy.jnidemo;
#include?
"
stdafx.h
"
#include?
<
stdio.h
>
#include?
<
math.h
>
#include?
"
jni.h
"
#include?
"
jni_md.h
"
#include?
"
./head/Base.h
"
#include?
"
head/wmi.h
"
#include?
"
head/com_sundy_jnidemo_ChangeMethodFromJni.h
"
?
//
通過javah?–jni?javactransfer?生成
#include?
<
stdio.h
>
#include?
"
stdlib.h
"
#include?
"
string.h
"
#pragma?comment?(lib,
"
BaseInfo.lib
"
)
#pragma?comment?(lib,
"
jvm.lib
"
)
//
硬盤信息
struct
??
{
????
char
?name[
256
];
????
int
?serial;
}
DiskInfo;
/**/
/*
BOOL?APIENTRY?DllMain(?HANDLE?hModule,?
???????????????????????DWORD??ul_reason_for_call,?
???????????????????????LPVOID?lpReserved
?????????????????????)
{
????LPTSTR??strName?=?new?CHAR[256]?;
????(*GetHostName)(strName);
????printf("%s\n",strName);
????delete?[]?strName;
????return?TRUE;
}
*/
//
將jstring類型轉換成windows類型
char
*
?jstringToWindows(?JNIEnv?
*
env,?jstring?jstr?);
//
將windows類型轉換成jstring類型
jstring?WindowsTojstring(?JNIEnv
*
?env,?
char
*
?str?);
//
主函數
BOOL?WINAPI?DllMain(HANDLE?hHandle,?DWORD?dwReason,?LPVOID?lpReserved)
{
????
return
?TRUE;
}
//
輸入常用的數值類型?Boolean,Byte,Char,Short,Int,Float,Double
JNIEXPORT?
void
?JNICALL?Java_com_sundy_jnidemo_ChangeMethodFromJni_displayParms
(JNIEnv?
*
env,?jobject?obj,?jstring?s,?jint?i,?jboolean?b)
{
????
const
?
char
*
?szStr?
=
?(env)
->
GetStringUTFChars(s,?
0
?);
????printf(?
"
String?=?[%s]\n
"
,?szStr?);
????printf(?
"
int?=?%d\n
"
,?i?);
????printf(?
"
boolean?=?%s\n
"
,?(b
==
JNI_TRUE?
?
?
"
true
"
?:?
"
false
"
)?);
????(env)
->
ReleaseStringUTFChars(s,?szStr?);
}
//
調用一個靜態方法,只有一個簡單類型輸出
JNIEXPORT?jint?JNICALL?Java_com_sundy_jnidemo_ChangeMethodFromJni_add
(JNIEnv?
*
env,?jobject,?jint?a,?jint?b)
{
????
int
?rtn?
=
?(
int
)(a?
+
?b);
????
return
?(jint)rtn;
}
/**/
///
/輸入一個數組,這里輸入的是一個Boolean類型的數組
JNIEXPORT?
void
?JNICALL?Java_com_sundy_jnidemo_ChangeMethodFromJni_setArray
(JNIEnv?
*
env,?jobject,?jbooleanArray?ba)
{
????jboolean
*
?pba?
=
?(env)
->
GetBooleanArrayElements(ba,?
0
?);
????jsize?len?
=
?(env)
->
GetArrayLength(ba);
????
int
?i
=
0
;
????
//
?change?even?array?elements
????
for
(?i
=
0
;?i?
<
?len;?i
+=
2
?)
????
{
????????pba[i]?
=
?JNI_FALSE;
????????printf(?
"
boolean?=?%s\n
"
,?(pba[i]
==
JNI_TRUE?
?
?
"
true
"
?:?
"
false
"
)?);
????}
????(env)
->
ReleaseBooleanArrayElements(ba,?pba,?
0
?);
}
/**/
///
/返回一個字符串數組
JNIEXPORT?jobjectArray?JNICALL?Java_com_sundy_jnidemo_ChangeMethodFromJni_getStringArray
(JNIEnv?
*
env,?jobject)
{
????jstring??????str;
????jobjectArray?args?
=
?
0
;
????jsize????????len?
=
?
5
;
????
char
*
????????sa[]?
=
?
{?
"
Hello,
"
,?
"
world!
"
,?
"
JNI
"
,?
"
is
"
,?
"
fun
"
?}
;
????
int
??????????i
=
0
;
????args?
=
?(env)
->
NewObjectArray(len,(env)
->
FindClass(
"
java/lang/String
"
),
0
);
????
for
(?i
=
0
;?i?
<
?len;?i
++
?)
????
{
????????str?
=
?(env)
->
NewStringUTF(sa[i]?);
????????(env)
->
SetObjectArrayElement(args,?i,?str);
????}
????
return
?args;
}
//
返回一個結構,這里返回一個硬盤信息的簡單結構類型
JNIEXPORT?jobject?JNICALL?Java_com_sundy_jnidemo_ChangeMethodFromJni_getStruct
(JNIEnv?
*
env,?jobject?obj)
{
????
/**/
/*
?下面為獲取到Java中對應的實例類中的變量
*/
????
//
獲取Java中的實例類
????jclass?objectClass?
=
?(env)
->
FindClass(
"
com/sundy/jnidemo/DiskInfo
"
);
????
//
獲取類中每一個變量的定義
????
//
名字
????jfieldID?str?
=
?(env)
->
GetFieldID(objectClass,
"
name
"
,
"
Ljava/lang/String;
"
);
????
//
序列號
????jfieldID?ival?
=
?(env)
->
GetFieldID(objectClass,
"
serial
"
,
"
I
"
);
????
//
給每一個實例的變量付值
????(env)
->
SetObjectField(obj,str,(env)
->
NewStringUTF(
"
my?name?is?D:
"
));
????(env)
->
SetShortField(obj,ival,
10
);
????
????
return
?obj;
}
//
返回一個結構數組,返回一個硬盤信息的結構數組
JNIEXPORT?jobjectArray?JNICALL?Java_com_sundy_jnidemo_ChangeMethodFromJni_getStructArray
(JNIEnv?
*
env,?jobject?_obj)
{
????
//
申明一個object數組?
????jobjectArray?args?
=
?
0
;
????
????
//
數組大小
????jsize????????len?
=
?
5
;
????
//
獲取object所屬類,一般為ava/lang/Object就可以了
????jclass?objClass?
=
?(env)
->
FindClass(
"
java/lang/Object
"
);
????
//
新建object數組
????args?
=
?(env)
->
NewObjectArray(len,?objClass,?
0
);
????
/**/
/*
?下面為獲取到Java中對應的實例類中的變量
*/
????
//
獲取Java中的實例類
????jclass?objectClass?
=
?(env)
->
FindClass(
"
com/sundy/jnidemo/DiskInfo
"
);
????
????
//
獲取類中每一個變量的定義
????
//
名字
????jfieldID?str?
=
?(env)
->
GetFieldID(objectClass,
"
name
"
,
"
Ljava/lang/String;
"
);
????
//
序列號
????jfieldID?ival?
=
?(env)
->
GetFieldID(objectClass,
"
serial
"
,
"
I
"
);
????
//
給每一個實例的變量付值,并且將實例作為一個object,添加到objcet數組中
????
for
(
int
??i
=
0
;?i?
<
?len;?i
++
?)
????
{
????????
//
給每一個實例的變量付值
????????jstring?jstr?
=
?WindowsTojstring(env,
"
我的磁盤名字是?D:
"
);
????????
//
(env)->SetObjectField(_obj,str,(env)->NewStringUTF("my?name?is?D:"));
????????(env)
->
SetObjectField(_obj,str,jstr);
????????(env)
->
SetShortField(_obj,ival,
10
);
????????
//
添加到objcet數組中
????????(env)
->
SetObjectArrayElement(args,?i,?_obj);
????}
????
//
返回object數組
????
return
?args;
?}
//
將jstring類型轉換成windows類型
char
*
?jstringToWindows(?JNIEnv??
*
env,?jstring?jstr?)
{
????
int
?length?
=
?(env)
->
GetStringLength(jstr?);
????
const
?jchar
*
?jcstr?
=
?(env)
->
GetStringChars(jstr,?
0
?);
????
char
*
?rtn?
=
?(
char
*
)malloc(?length
*
2
+
1
?);
????
int
?size?
=
?
0
;
????size?
=
?WideCharToMultiByte(?CP_ACP,?
0
,?(LPCWSTR)jcstr,?length,?rtn,(length
*
2
+
1
),?NULL,?NULL?);
????
if
(?size?
<=
?
0
?)
????????
return
?NULL;
????(env)
->
ReleaseStringChars(jstr,?jcstr?);
????rtn[size]?
=
?
0
;
????
return
?rtn;
}
//
將windows類型轉換成jstring類型
jstring?WindowsTojstring(?JNIEnv
*
?env,?
char
*
?str?)
{
????jstring?rtn?
=
?
0
;
????
int
?slen?
=
?strlen(str);
????unsigned?
short
?
*
?buffer?
=
?
0
;
????
if
(?slen?
==
?
0
?)
????????rtn?
=
?(env)
->
NewStringUTF(str?);?
????
else
????
{
????????
int
?length?
=
?MultiByteToWideChar(?CP_ACP,?
0
,?(LPCSTR)str,?slen,?NULL,?
0
?);
????????buffer?
=
?(unsigned?
short
?
*
)malloc(?length
*
2
?
+
?
1
?);
????????
if
(?MultiByteToWideChar(?CP_ACP,?
0
,?(LPCSTR)str,?slen,?(LPWSTR)buffer,?length?)?
>
0
?)
????????????rtn?
=
?(env)
->
NewString(??(jchar
*
)buffer,?length?);
????}
????
if
(?buffer?)
????????free(?buffer?);
????
return
?rtn;
}
Java 測試native代碼
這沒有什么多說的,看代碼吧
//
主測試程序
????
public
?
static
?
void
?main(String[]?args)?
{
????????ChangeMethodFromJni?changeJni?
=
?
new
?ChangeMethodFromJni();
????????
//
輸入常用的數值類型(string?int?boolean)
????????System.
out
????????????????.println(
"
------------------輸入常用的數值類型(string?int?boolean)-----------
"
);
????????changeJni.displayParms(
"
Hello?World!
"
,?
100
,?
true
);
????????
//
調用一個靜態方法
????????System.
out
.println(
"
------------------調用一個靜態方法-----------
"
);
????????
int
?ret?
=
?changeJni.add(
12
,?
20
);
????????System.
out
.println(
"
The?result?is:?
"
?
+
?String.valueOf(ret));
????????
//
輸入一個數組
????????System.
out
.println(
"
------------------輸入一個數組-----------
"
);
????????boolean[]?blList?
=
?
new
?boolean[]?
{?
true
,?
false
,?
true
?}
;
????????changeJni.setArray(blList);
????????
//
返回一個字符串數組
????????System.
out
.println(
"
------------------返回一個字符串數組-----------
"
);
????????String[]?strList?
=
?changeJni.getStringArray();
????????
for
?(
int
?i?
=
?
0
;?i?
<
?strList.length;?i
++
)?
{
????????????System.
out
.print(strList[i]);
????????}
????????System.
out
.println();
????????System.
out
.println(
"
------------------返回一個結構-----------
"
);
????????
//
返回一個結構
????????DiskInfo?disk?
=
?changeJni.getStruct();
????????System.
out
.println(
"
name:
"
?
+
?disk.name);
????????System.
out
.println(
"
Serial:
"
?
+
?disk.serial);
????????
//
返回一個結構數組
????????System.
out
.println(
"
------------------返回一個結構數組?-----------
"
);
????????DiskInfo[]?diskList?
=
?changeJni.getStructArray();
????????
for
?(
int
?i?
=
?
0
;?i?
<
?diskList.length;?i
++
)?
{
????????????System.
out
.println(
"
name:
"
?
+
?diskList[i].name);
????????????System.
out
.println(
"
Serial:
"
?
+
?diskList[i].serial);
????????}
????}
注:本程序在VS2003,eclipse (jse5.0) winxp sp2編譯通過