轉自:
http://hi.baidu.com/fengying765/blog/item/7081113e5fde53e555e7233f.html
作為主調(diào)方的Java源程序TestJNI.java如下。
代碼清單15-4 在Linux平臺上調(diào)用C函數(shù)的例程——TestJNI.java
1. public class TestJNI
2. {
3. static
4. {
5. System.loadLibrary("testjni");//載入靜態(tài)庫,test函數(shù)在其中實現(xiàn)
6. }
7.
8. private native void testjni(); //聲明本地調(diào)用
9.
10. public void test()
11. {
12. testjni();
13. }
14.
15. public static void main(String args[])
16. {
17. TestJNI haha = new TestJNI();
18. haha.test();
19. }
TestJNI.java聲明從libtestjni.so(注意Linux平臺的動態(tài)鏈接庫文件的擴展名是.so)中調(diào)用函數(shù)testjni()。
在Linux平臺上,遵循JNI規(guī)范的動態(tài)鏈接庫文件名必須以“lib”開頭。例如在上面的Java程序中指定的庫文件名為“testjni”,則實際的庫文件應該命名為“libtestjni.so”。
編譯TestJNI.java,并為C程序生成頭文件:
java TestJNI.java
javah TestJNI
提供testjni()函數(shù)的testjni.c源文件如下。
代碼清單15-5 在Linux平臺上調(diào)用C函數(shù)的例程——testjni.c
#include <stdio.h>
#include <TestJNI.h>
JNIEXPORT void JNICALL Java_TestJNI_testjni(JNIEnv *env, jobject obj){
printf("haha---------go into c!!!\n");
編寫Makefile文件如下,JDK安裝的位置請讀者自行調(diào)整:
libtestjni.so:testjni.o
gcc -rdynamic -shared -o libtestjni.so testjni.o
testjni.o:testjni.c TestJNI.h
gcc -c testjni.c -I./ -I/usr/java/jdk1.6.0_00/include -I/usr/java/jdk1.6.0_00/include/linux
在Makefile文件中,我們描述了最終的 libtestjin.so依賴于目標文件testjni.o,而testjni.o則依賴于testjni.c源文件和TestJNI.h頭文件。請注 意,我們在將testjni.o連接成動態(tài)鏈接庫文件時使用了“-rdynamic”選項。
執(zhí)行make命令編譯testjni.c。Linux平臺和在Windows平臺上類似,有3種方法可以讓Java程序找到并裝載動態(tài)鏈接庫文件。
— 將動態(tài)鏈接庫文件放置在當前路徑下。
— 將動態(tài)鏈接庫文件放置在LD_LIBRARY_PATH環(huán)境變量所指向的路徑下。注意這一點和Windows平臺稍有區(qū)別,Windows平臺參考PATH環(huán)境變量。
— 在啟動JVM時指定選項“-Djava.library.path”,將動態(tài)鏈接庫文件放置在該選項所指向的路徑下。
從下一節(jié)開始,我們開始接觸到在JNI框架內(nèi)Java調(diào)用C程序的一些高級話題,包括如何傳遞參數(shù)、如何傳遞數(shù)組、如何傳遞對象等。
各種類型數(shù)據(jù)的傳遞是跨平臺、跨語言互操作的永恒話題,更復雜的操作其實都可以分解為各種 基本數(shù)據(jù)類型的操作。只有掌握了基于各種數(shù)據(jù)類型的互操作,才能稱得上掌握了JNI開發(fā)。從下一節(jié)開始,環(huán)境和步驟不再是闡述的重點,將不再花費專門的篇 幅,例程中的關鍵點將成為我們關注的焦點。
15.2.2.3 傳遞字符串
到目前為止,我們還沒有實現(xiàn)Java程序向C程序傳遞參數(shù),或者C程序向Java程序傳遞參數(shù)。本例程將由Java程序向C程序傳入一個字符串,C程序?qū)υ撟址D成大寫形式后回傳給Java程序。
Java源程序如下。
代碼清單15-6 在Linux平臺上調(diào)用C函數(shù)的例程——Sample1
public class Sample1
{
public native String stringMethod(String text);
public static void main(String[] args)
{
System.loadLibrary("Sample1");
Sample1 sample = new Sample1();
String text = sample.stringMethod("Thinking In Java");
System.out.println("stringMethod: " + text);
}
}
Sample1.java以“Thinking In Java”為參數(shù)調(diào)用libSample1.so中的函數(shù)stringMethod(),在得到返回的字符串后打印輸出。
Sample1.c的源程序如下。
代碼清單15-7 在Linux平臺上調(diào)用C函數(shù)的例程——Sample1.c
#include <Sample1.h>
#include <string.h>
JNIEXPORT jstring JNICALL Java_Sample1_stringMethod(JNIEnv *env, jobject obj, jstring string)
{
const char *str = (*env)->GetStringUTFChars(env, string, 0);
char cap[128];
strcpy(cap, str);
(*env)->ReleaseStringUTFChars(env, string, str);
int i=0;
for(i=0;i<strlen(cap);i++)
*(cap+i)=(char)toupper(*(cap+i));
return (*env)->NewStringUTF(env, cap);
首先請注意函數(shù)頭部分,函數(shù)接收一個jstring類 型的輸入?yún)?shù),并輸出一個jstring類型的參數(shù)。jstring是jni.h中定義的數(shù)據(jù)類型,是JNI框架內(nèi)特有的字符串類型,因為jni.h在 Sample1.h中被引入,因此在Sample1.c中無須再次引入。
程序的第4行是從JNI調(diào)用上下文中獲取UTF編碼的輸入字符,將其放在指針str所指向 的一段內(nèi)存中。第9行是釋放這段內(nèi)存。第13行是將經(jīng)過大寫轉換的字符串予以返回,這一句使用了NewStringUTF()函數(shù),將C語言的字符串指針 轉換為JNI的jstring類型。JNIEnv也是在jni.h中定義的,代表JNI調(diào)用的上下文,GetStringUTFChars()、 ReleaseStringUTFChars()和NewStringUTF()均是JNIEnv的函數(shù)。
15.2.2.4 傳遞整型數(shù)組
本節(jié)例程將首次嘗試在JNI框架內(nèi)啟用數(shù)組:C程序向Java程序返回一個定長的整型數(shù)組成的數(shù)組,Java程序?qū)⒃摂?shù)組打印輸出。
Java程序的源代碼如下。
代碼清單15-8 在Linux平臺上調(diào)用C函數(shù)的例程——Sample2
public class Sample2
{
public native int[] intMethod();
public static void main(String[] args)
{
System.loadLibrary("Sample2");
Sample2 sample=new Sample2();
int[] nums=sample.intMethod();
for(int i=0;i<nums.length;i++)
System.out.println(nums[i]);
}
Sample2.java調(diào)用libSample2.so中的函數(shù)intMethod()。Sample2.c的源代碼如下。
代碼清單15-9 在Linux平臺上調(diào)用C函數(shù)的例程——Sample2.c
#include <Sample2.h>
JNIEXPORT jintArray JNICALL Java_Sample2_intMethod(JNIEnv *env, jobject obj)
{
inti = 1;
jintArray array;//定義數(shù)組對象
array = (*env)-> NewIntArray(env, 10);
for(; i<= 10; i++)
(*env)->SetIntArrayRegion(env, array, i-1, 1, &i);
/* 獲取數(shù)組對象的元素個數(shù) */
int len = (*env)->GetArrayLength(env, array);
/* 獲取數(shù)組中的所有元素 */
jint* elems = (*env)-> GetIntArrayElements(env, array, 0);
for(i=0; i<len; i++)
printf("ELEMENT %d IS %d\n", i, elems[i]);
return array;
Sample2.c涉及了兩個jni.h定義的整型數(shù) 相關的數(shù)據(jù)類型:jint和jintArray,jint是在JNI框架內(nèi)特有的整數(shù)類型。程序的第7行開辟出一個長度為10 的jint數(shù)組。然后依次向該數(shù)組中放入元素1-10。第11行至第16行不是程序的必須部分,純粹是為了向讀者們演示GetArrayLength() 和GetIntArrayElements()這兩個函數(shù)的使用方法,前者是獲取數(shù)組長度,后者則是獲取數(shù)組的首地址以便于遍歷數(shù)組。
15.2.2.5 傳遞字符串數(shù)組
本節(jié)例程是對上節(jié)例程的進一步深化:雖然仍然是傳遞數(shù)組,但是數(shù)組的基類換成了字符串這樣一種對象數(shù)據(jù)類型。Java程序?qū)⑾駽程序傳入一個包含中文字符的字符串,C程序并沒有處理這個字符串,而是開辟出一個新的字符串數(shù)組返回給Java程序,其中還包含兩個漢字字符串。
Java程序的源代碼如下。
代碼清單15-10 在Linux平臺上調(diào)用C函數(shù)的例程——Sample3
public class Sample3
{
public native String[] stringMethod(String text);
public static void main(String[] args) throws java.io.UnsupportedEncodingException
{
System.loadLibrary("Sample3");
Sample3 sample = new Sample3();
String[] texts = sample.stringMethod("java編程思想");
for(int i=0;i<texts.length;i++)
{
texts[i]=new String(texts[i].getBytes("ISO8859-1"),"GBK");
System.out.print( texts[i] );
}
System.out.println();
}
Sample3.java調(diào)用libSample3.so中的函數(shù)stringMethod()。Sample3.c的源代碼如下:
代碼清單15-11 在Linux平臺上調(diào)用C函數(shù)的例程——Sample3.c
#include <Sample3.h>
#include <string.h>
#include <stdlib.h>
#define ARRAY_LENGTH 5
JNIEXPORT jobjectArray JNICALL Java_Sample3_stringMethod(JNIEnv *env, jobject obj, jstring string)
{
jclass objClass = (*env)->FindClass(env, "java/lang/String");
jobjectArray texts= (*env)->NewObjectArray(env, (jsize)ARRAY_LENGTH, objClass, 0);
jstring jstr;
char* sa[] = { "Hello,", "world!", "JNI", "很", "好玩" };
int i=0;
for(;i<ARRAY_LENGTH;i++)
{
jstr = (*env)->NewStringUTF( env, sa[i] );
(*env)->SetObjectArrayElement(env, texts, i, jstr);//必須放入jstring
}
return texts;
第9、10行是我們需要特別關注的地方:JNI框架并 沒有定義專門的字符串數(shù)組,而是使用jobjectArray——對象數(shù)組,對象數(shù)組的基類是jclass,jclass是JNI框架內(nèi)特有的類型,相當 于Java語言中的Class類型。在本例程中,通過FindClass()函數(shù)在JNI上下文中獲取到java.lang.String的類型 (Class),并將其賦予jclass變量。
在例程中我們定義了一個長度為5的對象數(shù)組texts,并在程序的第18行向其中循環(huán)放入預先定義好的sa數(shù)組中的字符串,當然前置條件是使用NewStringUTF()函數(shù)將C語言的字符串轉換為jstring類型。
本例程的另一個關注點是C程序向Java程序傳遞的中文字符,在Java程序中能否正常顯 示的問題。在筆者的試驗環(huán)境中,Sample3.c是在Linux平臺上編輯的,其中的中文字符則是用支持GBK的輸入法輸入的,而Java程序采用 ISO8859_1字符集存放JNI調(diào)用的返回字符,因此在“代碼清單15-10在Linux平臺上調(diào)用C函數(shù)的例程——Sample3”的第14行中將 其轉碼后輸出。
15.2.2.6 傳遞對象數(shù)組
本節(jié)例程演示的是C程序向Java程序傳遞對象數(shù)組,而且對象數(shù)組中存放的不再是字符串,而是一個在Java中自定義的、含有一個topic屬性的MailInfo對象類型。
MailInfo對象定義如下。
代碼清單15-12 在Linux平臺上調(diào)用C函數(shù)的例程——MailInfo
public class MailInfo {
public String topic;
public String getTopic()
{
return this.topic;
}
public void setTopic(String topic)
{
this.topic=topic;
}
Java程序的源代碼如下。
代碼清單15-13 在Linux平臺上調(diào)用C函數(shù)的例程——Sample4
public class Sample4
{
public native MailInfo[] objectMethod(String text);
public static void main(String[] args)
{
System.loadLibrary("Sample4");
Sample4 sample = new Sample4();
MailInfo[] mails = sample.objectMethod("Thinking In Java");
for(int i=0;i<mails.length;i++)
System.out.println(mails[i].topic);
}
Sample4.java調(diào)用libSample4.so中的objectMethod()函數(shù)。Sample4.c的源代碼如下。
代碼清單15-14 在Linux平臺上調(diào)用C函數(shù)的例程——Sample4.c
#include <Sample4.h>
#include <string.h>
#include <stdlib.h>
#define ARRAY_LENGTH 5
JNIEXPORT jobjectArray JNICALL Java_Sample4_objectMethod(JNIEnv *env, jobject obj, jstring string)
{
jclass objClass = (*env)->FindClass(env, "java/lang/Object");
jobjectArray mails= (*env)->NewObjectArray(env, (jsize)ARRAY_LENGTH, objClass, 0);
jclass objectClass = (*env)->FindClass(env, "MailInfo");
jfieldID topicFieldId = (*env)->GetFieldID(env, objectClass, "topic", "Ljava/lang/String;");
int i=0;
for(;i<ARRAY_LENGTH;i++)
{
(*env)->SetObjectField(env, obj, topicFieldId, string);
(*env)->SetObjectArrayElement(env, mails, i, obj);
}
return mails;
程序的第9、10行讀者們應該不會陌生,在上一節(jié)的例 程中已經(jīng)出現(xiàn)過,不同之處在于這次通過FindClass()函數(shù)在JNI上下文中獲取的是java.lang.Object的類型(Class),并將 其作為基類開辟出一個長度為5的對象數(shù)組,準備用來存放MailInfo對象。
程序的第12、13行的目的則是創(chuàng)建一個jfieldID類型的變量,在JNI中,操作對 象屬性都是通過jfieldID進行的。第12行首先查找得到MailInfo的類型(Class),然后基于這個jclass進一步獲取其名為 topic的屬性,并將其賦予jfieldID變量。
程序的第18、19行的目的是循環(huán)向?qū)ο髷?shù)組中放入jobject對象。 SetObjectField()函數(shù)屬于首次使用,該函數(shù)的作用是向jobject的屬性賦值,而值的內(nèi)容正是Java程序傳入的jstring變量 值。請注意在向?qū)ο髮傩再x值和向?qū)ο髷?shù)組中放入對象的過程中,我們使用了在函數(shù)頭部分定義的jobject類型的環(huán)境參數(shù)obj作為中介。至此,JNI框 架固有的兩個環(huán)境入?yún)nv和obj,我們都有涉及。