JNI/NDK開發指南(一)—— JNI開發流程及HelloWorld

       

        轉載請註明出處:http://blog.csdn.net/xyang81/article/details/41777471java


       JNI全稱是Java Native Interface(Java本地接口)單詞首字母的縮寫,本地接口就是指用C和C++開發的接口。因爲JNI是JVM規範中的一部份,所以能夠將咱們寫的JNI程序在任何實現了JNI規範的Java虛擬機中運行。同時,這個特性使咱們能夠複用之前用C/C++寫的大量代碼。linux


       開發JNI程序會受到系統環境的限制,由於用C/C++語言寫出來的代碼或模塊,編譯過程中要依賴當前操做系統環境所提供的一些庫函數,並和本地庫連接在一塊兒。並且編譯後生成的二進制代碼只能在本地操做系統環境下運行,由於不一樣的操做系統環境,有本身的本地庫和CPU指令集,並且各個平臺對標準C/C++的規範和標準庫函數實現方式也有所區別。這就形成使用了JNI接口的JAVA程序,再也不像之前那樣自由的跨平臺。若是要實現跨平臺,就必須將本地代碼在不一樣的操做系統平臺下編譯出相應的動態庫。windows


JNI開發流程主要分爲如下6步:eclipse

一、編寫聲明瞭native方法的Java類jvm

二、將Java源代碼編譯成class字節碼文件函數

三、用javah -jni命令生成.h頭文件(javah是jdk自帶的一個命令,-jni參數表示將class中用native聲明的函數生成jni規則的函數)工具

四、用本地代碼實現.h頭文件中的函數spa

五、將本地代碼編譯成動態庫(windows:*.dll,linux/unix:*.so,mac os x:*.jnilib操作系統

六、拷貝動態庫至 java.library.path 本地庫搜索目錄下,並運行Java程序.net



經過上面的介紹,相信你們對JNI及開發流程有了一個總體的認識,下面經過一個HelloWorld的示例,再深刻了解JNI開發的各個環節及注意事項。

PS:本人的開發環境爲Mac os x 10.10.1 ,Eclipse 3.8(Juno),若是在其它操做系統下開發也是同樣,只需將本地代碼編譯成當前操做系統所支持的動態庫便可。

這個案例用命令行的方式介紹開發流程,這樣你們對JNI開發流程的印象會更加深入,後面的案例都採用eclipse+cdt來開發。

第一步、並新建一個HelloWorld.java源文件

package com.study.jnilearn;

public class HelloWorld {
	
	public static native String sayHello(String name); 	// 1.聲明這是一個native函數,由本地代碼實現

	public static void main(String[] args) {
		String text = sayHello("yangxin");	// 3.調用本地函數
		System.out.println(text);
	}
	
	static {
		System.loadLibrary("HelloWorld");	// 2.加載實現了native函數的動態庫,只須要寫動態庫的名字
	}

}
第二步、用javac命令將.java源文件編譯成.class字節碼文件

注意:HelloWorld放在com.study.jnilearn包下面

javac src/com/study/jnilearn/HelloWorld.java -d ./bin
-d 表示將編譯後的class文件放到指定的目錄下,這裏我把它放到和src同級的bin目錄下

第三步、用javah -jni命令,根據class字節碼文件生成.h頭文件-jni參數是可選的

javah -jni -classpath ./bin -d ./jni com.study.jnilearn.HelloWorld

默認生成的.h頭文件名爲:com_study_jnilearn_HelloWorld.h(包名+類名.h),也能夠經過-o參數指定生成頭文件名稱:

javah -jni -classpath ./bin -o HelloWorld.h com.study.jnilearn.HelloWorld
參數說明:

-classpath :類搜索路徑,這裏表示從當前的bin目錄下查找

-d :將生成的頭文件放到當前的jni目錄下

-o : 指定生成的頭文件名稱,默認以類全路徑名生成(包名+類名.h)

注意:-d和-o只能使用其中一個參數。


第四步、用本地代碼實現.h頭文件中的函數

com_study_jnilearn_HelloWorld.h:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_study_jnilearn_HelloWorld */

#ifndef _Included_com_study_jnilearn_HelloWorld
#define _Included_com_study_jnilearn_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_study_jnilearn_HelloWorld
 * Method:    sayHello
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_study_jnilearn_HelloWorld_sayHello
  (JNIEnv *, jclass, jstring);

#ifdef __cplusplus
}
#endif
#endif

HelloWorld.c:

// HelloWorld.c

#include "com_study_jnilearn_HelloWorld.h"

#ifdef __cplusplus
extern "C"
{
#endif

/*
 * Class:     com_study_jnilearn_HelloWorld
 * Method:    sayHello
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_study_jnilearn_HelloWorld_sayHello(
		JNIEnv *env, jclass cls, jstring j_str)
{
	const char *c_str = NULL;
	char buff[128] = { 0 };
	c_str = (*env)->GetStringUTFChars(env, j_str, NULL);
	if (c_str == NULL)
	{
		printf("out of memory.\n");
		return NULL;
	}
	(*env)->ReleaseStringUTFChars(env, j_str, c_str);
	printf("Java Str:%s\n", c_str);
	sprintf(buff, "hello %s", c_str);
	return (*env)->NewStringUTF(env, buff);
}

#ifdef __cplusplus
}
#endif
第五步、將C/C++代碼編譯成本地動態庫文件

      動態庫文件名命名規則:lib+動態庫文件名+後綴(操做系統不同,後綴名也不同)如:

      Mac OS X : libHelloWorld.jnilib

      Windows :HelloWorld.dll(不須要lib前綴)

      Linux/Unix:libHelloWorld.so

1> Mac OS X

gcc -dynamiclib -o /Users/yangxin/Library/Java/Extensions/libHelloWorld.jnilib jni/HelloWorld.c -framework JavaVM -I/$JAVA_HOME/include -I/$JAVA_HOME/include/darwin
個人$JAVA_HOME目錄在:/Library/Java/JavaVirtualMachines/jdk1.7.0_21.jdk/Contents/Home
參數選項說明:

-dynamiclib:表示編譯成動態連接庫

-o:指定動態連接庫編譯後生成的路徑及文件名

-framework JavaVM -I:編譯JNI須要用到JVM的頭文件(jni.h),第一個目錄是平臺無關的,第二個目錄是與操做系統平臺相關的頭文件

2> Windows(以Windows7下VS2012爲例)

開始菜單-->全部程序-->Microsoft Visual Studio 2012-->打開VS2012 X64本機工具命令提示,用cl命令編譯成dll動態庫:

cl -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -LD HelloWorld.c -FeHelloWorld.dll 

參數選項說明:

-I :   和mac os x同樣,包含編譯JNI必要的頭文件

-LD:標識將指定的文件編譯成動態連接庫

-Fe:指定編譯後生成的動態連接庫的路徑及文件名



3> Linux/Unix

gcc -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -fPIC -shared HelloWorld.c -o libHelloWorld.so
參數說明:

-I:          包含編譯JNI必要的頭文件

-fPIC:    編譯成與位置無關的獨立代碼

-shared:編譯成動態庫

-o:         指定編譯後動態庫生成的路徑和文件名


第六步、運行Java程序

       Java在調用native(本地)方法以前,須要先加載動態庫。若是在未加載動態以前就調用native方法,會拋出找不到動態連接庫文件的異常。以下所示:

Exception in thread "main" java.lang.UnsatisfiedLinkError: com.study.jnilearn.HelloWorld.sayHello(Ljava/lang/String;)Ljava/lang/String;
	at com.study.jnilearn.HelloWorld.sayHello(Native Method)
	at com.study.jnilearn.HelloWorld.main(HelloWorld.java:9)

通常在類的靜態(static)代碼塊中加載動態庫最合適,由於在建立類的實例時,類會被ClassLoader先加載到虛擬機,隨後立馬調用類的static靜態代碼塊。這時再去調用native方法就萬無一失了。加載動態庫的兩種方式:

System.loadLibrary("HelloWorld");
System.load("/Users/yangxin/Desktop/libHelloWorld.jnilib");
方式1:只須要指定動態庫的名字便可,不須要加lib前綴,也不要加.so、.dll和.jnilib後綴

方式2:指定動態庫的絕對路徑名,須要加上前綴和後綴

若是使用方式1,java會去java.library.path系統屬性指定的目錄下查找動態庫文件,若是沒有找到會拋出java.lang.UnsatisfiedLinkError異常。

Exception in thread "main" java.lang.UnsatisfiedLinkError: no HelloWorld2 in java.library.path
	at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1860)
	at java.lang.Runtime.loadLibrary0(Runtime.java:845)
	at java.lang.System.loadLibrary(System.java:1084)
	at com.study.jnilearn.HelloWorld.<clinit>(HelloWorld.java:13)
你們從異常中能夠看出來,他是在java.library.path中查找該名稱對應的動態庫,若是在mac下找libHelloWorld.jnilib文件,linux下找libHelloWorld.so文件,windows下找libHelloWorld.dll文件,能夠經過調用System.getProperties("java.library.path")方法獲取查找的目錄列表,下面是我本機mac os x 系統下的查找目錄:

String libraryDirs = System.getProperties("java.library.path");
System.out.println(libraryDirs);
// 輸出結果以下:
/Users/yangxin/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.
有兩種方式可讓java從java.library.path找到動態連接庫文件,聰明的你應該已經想到了。

方式1:將動態連接庫拷貝到java.library.path目錄下

方式2:給jvm添加「-Djava.library.path=動態連接庫搜索目錄」參數,指定系統屬性java.library.path的值

java -Djava.library.path=/Users/yangxin/Desktop

Linux/Unix環境下能夠經過設置LD_LIBRARY_PATH環境變量,指定庫的搜索目錄。


費了那麼大勁,終於能夠運行寫好的Java程序了,結果以下:

yangxin-MacBook-Pro:JNILearn yangxin$ java -classpath ./bin com.study.jnilearn.HelloWorld
Java Str:yangxin
hello yangxin

若是沒有將動態庫拷貝到本地庫搜索目錄下,執行java命令,可經過添加系統屬性java.library.path來指定動態庫的目錄,以下所示:

yangxin-MacBook-Pro:JNILearn yangxin$ java -Djava.library.path=/Users/yangxin/Desktop -classpath ./bin com.study.jnilearn.HelloWorld
Java Str:yangxin
hello yangxin
相關文章
相關標籤/搜索