技術轉載:Jni學習一:瞭解Jni

轉自:http://www.iteye.com/topic/295776

通過本博文,可以大體瞭解Jni是一個什麼東西,其中包含了一些對java基本數據類型的操作實例,對於Jni中如何使用Java更復雜的數據類型,將在下一章節總結。

 

 

 

JNI一直以來都很少去關注,但卻是我心中的一個結,最近這幾天剛好手頭有點時間,因此抽空看了一下這方面的東西,整理了一份文檔,JNI技術的出現主要是基於三個方面的應用需求:

 

1. 解決性能問題
Java具有平臺無關性,這使人們在開發企業級應用的時候總是把它作爲主要候選方案之一,但是性能方面的因素又大大削弱了它的競爭力。爲此,提高Java的性能就顯得十分重要。Sun公司及Java的支持者們爲提高Java的運行速度已經做出了許多努力,其中大多數集中在程序設計的方法和模式選擇方面。由於算法和設計模式的優化是通用的,對Java有效的優化算法和設計模式,對其他編譯語言也基本同樣適用,因此不能從根本上改變Java程序與編譯型語言在執行效率方面的差異。由此,於是人們開始引入JIT(Just In Time,及時編譯)的概念。它的基本原理是:首先通過Java編譯器把Java源代碼編譯成平臺無關的二進制字節碼。然後在Java程序真正執行之前,系統通過JIT編譯器把Java的字節碼編譯爲本地化機器碼。最後,系統執行本地化機器碼,節省了對字節碼進行解釋的時間。這樣做的優點是大大提高了Java程序的性能,縮短了加載程序的時間;同時,由於編譯的結果並不在程序運行間保存,因此也節約了存儲空間。缺點是由於JIT編譯器對所有的代碼都想優化,因此同樣也佔用了很多時間。

 

動態優化技術是提高Java性能的另一個嘗試。該技術試圖通過把Java源程序直接編譯成機器碼,以充分利用Java動態編譯和靜態編譯技術來提高Java的性能。該方法把輸入的Java源碼或字節碼轉換爲經過高度優化的可執行代碼和動態庫 (Windows中的. dll文件或Unix中的. so文件)。該技術能大大提高程序的性能,但卻破壞了Java的可移植性。

JNI(Java Native Interface, Java本地化方法)技術由此閃亮登場。因爲採用JNI技術只是針對一些嚴重影響Java性能的代碼段,該部分可能只佔源程序的極少部分,所以幾乎可以不考慮該部分代碼在主流平臺之間移植的工作量。同時,也不必過分擔心類型匹配問題,我們完全可以控制代碼不出現這種錯誤。此外,也不必擔心安全控制問題,因爲Java安全模型已擴展爲允許非系統類加載和調用本地方法。根據Java規範,從JDK 1. 2開始,FindClass將設法找到與當前的本地方法關聯的類加載器。如果平臺相關代碼屬於一個系統類,則無需涉及任何類加載器; 否則,將調用適當的類加載器來加載和鏈接已命名的類。換句話說,如果在Java程序中直接調用C/C++語言產生的機器碼,該部分代碼的安全性就由Java虛擬機控制。

 

2. 解決本機平臺接口調用問題
JAVA以其跨平臺的特性深受人們喜愛,而又正由於它的跨平臺的目的,使得它和本地機器的各種內部聯繫變得很少,約束了它的功能。解決JAVA對本地操作的一種方法就是JNI。JAVA通過JNI調用本地方法,而本地方法是以庫文件的形式存放的(在WINDOWS平臺上是DLL文件形式,在UNIX機器上是SO文件形式)。通過調用本地的庫文件的內部方法,使JAVA可以實現和本地機器的緊密聯繫,調用系統級的各接口方法。

 

3. 嵌入式開發應用
“一次編程,到處使用”的Java軟件概念原本就是針對網上嵌入式小設備提出的,幾經周折,目前SUN公司已推出了J2ME(Java 2 P1atform Micro Edition)針對信息家電的Java版本,其技術日趨成熟,開始投入使用。SUN公司Java虛擬機(JVM)技術的有序開放,使得Java軟件真正實現跨平臺運行,即Java應用小程序能夠在帶有JVM的任何硬軟件系統上執行。加上Java語言本身所具有的安全性、可靠性和可移植性等特點,對實現瘦身上網的信息家電等網絡設備十分有利,同時對嵌入式設備特別是上網設備軟件編程技術產生了很大的影響。也正是由於JNI解決了本機平臺接口調用問題,於是JNI在嵌入式開發領域也是如火如荼。

 

不失直觀性,我們首先寫一個JNI小例子:

public class HelloJni {
    public native void displayHelloJni();

    static {
        System.loadLibrary("helloJni");
    }

    public static void main(String[] args) {
        //System.out.println(System.getProperty("java.library.path"));
        new HelloJni().displayHelloJni();
    }
}

 

在class文件生成的相應目錄執行命令如下:
----------------------------------------------------
E:\projects\jni\target\classes>javah HelloJni
----------------------------------------------------

 

得到C++文件HelloJni.h

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

#ifndef _Included_HelloJni
#define _Included_HelloJni
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloJni
 * Method:    displayHelloJni
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloJni_displayHelloJni
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

 

JNI函數名稱分爲三部分:首先是Java關鍵字,供Java虛擬機識別;然後是調用者類名稱(全限定的類名,其中用下劃線代替名稱分隔符);最後是對應的方法名稱,各段名稱之間用下劃線分割。


JNI函數的參數也由三部分組成:首先是JNIEnv *,是一個指向JNI運行環境的指針;第二個參數隨本地方法是靜態還是非靜態而有所不同一一非靜態本地方法的第二個參數是對對象的引用,而靜態本地方法的第二個參數是對其Java類的引用;其餘的參數對應通常Java方法的參數,參數類型需要根據一定規則進行映射。

 

 

編寫C++文件HelloJni.h的實現類,我是比較常用VC6.0來生成dll文件(helloJni.dll)的

#include <jni.h>
#include "HelloJni.h"
#include <stdio.h>

JNIEXPORT void JNICALL 
Java_HelloJni_displayHelloJni(JNIEnv *env, jobject obj) 
{
    printf("Hello Dynamic Link Library has been calling!\n");
    printf("Java_HelloJni_displayHelloJni method has been executed!\n");
    return;
}

 

其實此時,我們的工程目前還暫時不能生成我們想要的 helloJni.dll 文件,問題就出在了“#include <jni.h>”。由於VC6.0裏沒有我們需要的“jni.h”文件,因此就需要手動加入到VC6.0的環境中去。在JAVA_HOME路徑下我們可以找到include文件夾,其中就可以找到我們需要的“jni.h”文件。爲了避免以後麻煩起見,將所有的C++文件全部拿出來,放在“%CPP_HOME%\VC98\Include”路徑下。然後將工程進行打包就可以得到我們需要的“helloJni.dll”文件了。

 

 將helloJni.dll文件放置於工程classes目錄,執行命令如下:
-----------------------------------------------
E:\projects\jni\target\classes>java HelloJni
-----------------------------------------------

運行結果如下:
-----------------------------------------------------------------
Hello Dynamic Link Library has been calling!
Java_HelloJni_displayHelloJni method has been executed!
-----------------------------------------------------------------


但是要想在eclipse中運行helloJni.dll文件,就需要將文件拷貝到工程的根目錄,或者將其放在諸如C:\WINDOWS\system32;C:\WINDOWS;等目錄下。因爲,eclipse在運行helloJni.dll文件時首先會去在當前根目錄找,如果找不到則在path上去找,因此你還可以爲了方便管理生成的dll文件,將所有工程中的dll文件都放到一個特定的目錄,然後將該目錄加入到你的本地path環境變量中去,這樣每次只需要將生成的dll文件放入path目錄下就可以訪問了。注,如果需要加環境變量最好在加好以後重新啓動一下eclipse,確保eclipse能夠加載到最新的path環境。

 

接下來,對小例子進行重構:
1. 新增一個基礎類

 

package org.danlley.jni.test;

public class BaseClass {

    public BaseClass(String arg) {
        loadLibrary(arg);
    }

    private static void loadLibrary(String arg) {
        System.loadLibrary(arg);
    }
}

 

 2. 定義新類繼承基礎類

package org.danlley.jni.test;

public class HelloJniTest extends BaseClass {
    public HelloJniTest(String arg){
        super(arg);
    }
    public native void displayHelloJni();
}

 

3. 編寫調用類

package org.danlley.jni.test;

public class RunMain {
    public static void main(String[] args) {
        new HelloJniTest("helloJniTest").displayHelloJni();
    }
}

 此次,將dll文件定義爲:helloJniTest.dll。

 

執行結果:
------------------------------------------------------------------------------------
Java_org_danlley_jni_test_HelloJniTest_displayHelloJni has been called!
------------------------------------------------------------------------------------

 

例子相當簡單,沒有傳入參數,也沒有返回值,那麼是不是可以讓本地方法返回一些參數,同時又可以傳入數據進行處理,並把處理結果返回給方法的調用者呢,先拿基本類型開刀。接下來對 HelloJniTest 繼續進行改造:新增兩個本地方法,如下:

package org.danlley.jni.test;

public class HelloJniTest extends BaseClass {
    public HelloJniTest(String arg){
        super(arg);
    }
    public native void displayHelloJni();
    
    public native int getDynamicIntDataNoParam();
    
    public native int getDynamicIntData(int i);
}

 

重新生成org_danlley_jni_test_HelloJniTest.h文件,並改寫其實現類org_danlley_jni_test_HelloJniTest.cpp如下:

// org_danlley_jni_test_HelloJniTest.cpp: implementation of the org_danlley_jni_test_HelloJniTest class.
//
//////////////////////////////////////////////////////////////////////

#include "org_danlley_jni_test_HelloJniTest.h"
#include <jni.h>
#include <stdio.h>

JNIEXPORT void JNICALL 
Java_org_danlley_jni_test_HelloJniTest_displayHelloJni(JNIEnv *env, jobject obj) 
{
    printf("Java_org_danlley_jni_test_HelloJniTest_displayHelloJni has been called!\n");
    return;
}

JNIEXPORT jint JNICALL 
Java_org_danlley_jni_test_HelloJniTest_getDynamicIntDataNoParam(JNIEnv *env, jobject obj) 
{
    return 65535;
}

JNIEXPORT jint JNICALL 
Java_org_danlley_jni_test_HelloJniTest_getDynamicIntData(JNIEnv *env, jobject obj, jint i)
{
    i*=i;
    return i;
}

 

修改 RunMain 類:

package org.danlley.jni.test;

public class RunMain {
    public static void main(String[] args) {
        HelloJniTest tester=new HelloJniTest("helloJniTest");
        tester.displayHelloJni();
        int i=tester.getDynamicIntDataNoParam();
        System.out.println("tester.getDynamicIntDataNoParam()="+i);
        int j=tester.getDynamicIntData(100);
        System.out.println("tester.getDynamicIntData(100)="+j);
    }
}

 

運行RunMain:

-----------------------------------------------------------------------
tester.getDynamicIntDataNoParam()=65535
tester.getDynamicIntData(100)=10000
Java_org_danlley_jni_test_HelloJniTest_displayHelloJni has been called!
-----------------------------------------------------------------------

OK,一切正常。

 

還是不過癮,簡單對象可以處理了,如果是一個java對象,還可以處理嗎,答案是當然可以,接下來我們來繼續對 helloJniTest 類進行改造。新增一個方法如下:

package org.danlley.jni.test;

public class HelloJniTest extends BaseClass {
    public HelloJniTest(String arg){
        super(arg);
    }
    public native void displayHelloJni();
    
    public native int getDynamicIntDataNoParam();
    
    public native int getDynamicIntData(int i);
    
    public native String getDynamicStringData(String arg);
}

 

重新生成org_danlley_jni_test_HelloJniTest.h文件:

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

#ifndef _Included_org_danlley_jni_test_HelloJniTest
#define _Included_org_danlley_jni_test_HelloJniTest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     org_danlley_jni_test_HelloJniTest
 * Method:    displayHelloJni
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_org_danlley_jni_test_HelloJniTest_displayHelloJni
  (JNIEnv *, jobject);

/*
 * Class:     org_danlley_jni_test_HelloJniTest
 * Method:    getDynamicIntDataNoParam
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicIntDataNoParam
  (JNIEnv *, jobject);

/*
 * Class:     org_danlley_jni_test_HelloJniTest
 * Method:    getDynamicIntData
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicIntData
  (JNIEnv *, jobject, jint);

/*
 * Class:     org_danlley_jni_test_HelloJniTest
 * Method:    getDynamicStringData
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicStringData
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

 

改寫org_danlley_jni_test_HelloJniTest.cpp文件:

// org_danlley_jni_test_HelloJniTest.cpp: implementation of the org_danlley_jni_test_HelloJniTest class.
//
//////////////////////////////////////////////////////////////////////

#include "org_danlley_jni_test_HelloJniTest.h"
#include <jni.h>
#include <stdio.h>

JNIEXPORT void JNICALL 
Java_org_danlley_jni_test_HelloJniTest_displayHelloJni(JNIEnv *env, jobject obj) 
{
    printf("Java_org_danlley_jni_test_HelloJniTest_displayHelloJni has been called!\n");
    return;
}

JNIEXPORT jint JNICALL 
Java_org_danlley_jni_test_HelloJniTest_getDynamicIntDataNoParam(JNIEnv *env, jobject obj) 
{
    return 65535;
}

JNIEXPORT jint JNICALL 
Java_org_danlley_jni_test_HelloJniTest_getDynamicIntData(JNIEnv *env, jobject obj, jint i)
{
	i*=i;
    return i;
}

JNIEXPORT jstring JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicStringData
(JNIEnv *env, jobject obj, jstring arg){
	//Get the native string from javaString
    const char *nativeString = env->GetStringUTFChars(arg, 0);
    printf("%s", nativeString);
    //DON'T FORGET THIS LINE!!!
    env->ReleaseStringUTFChars(arg, nativeString);
	return arg;
}

  

 重新對C++工程打包成dll文件,運行結果:
---------------------------------------------------------------------------
tester.getDynamicIntDataNoParam()=65535
tester.getDynamicIntData(100)=10000
tester.getDynamicStringData=My first String test
Java_org_danlley_jni_test_HelloJniTest_displayHelloJni has been called!
My first String test
---------------------------------------------------------------------------

我們不僅把Java的一個String對象成功的傳給了dll,而且還將處理後的結果返回了出來。

 

但是總覺得還是不夠,那我們就再來個比較複雜的對象把,我們這次將一個整形數組通過java傳給dll,看看是不是也可以處理,繼續還是對 helloJniTest 類進行改造,新增一個方法:

package org.danlley.jni.test;

public class HelloJniTest extends BaseClass {
    public HelloJniTest(String arg){
        super(arg);
    }
    public native void displayHelloJni();
    
    public native int getDynamicIntDataNoParam();
    
    public native int getDynamicIntData(int i);
    
    public native String getDynamicStringData(String arg);
    
    public native int[] getDynamicArrayData(int[] args);
}

 

重新生成org_danlley_jni_test_HelloJniTest.h文件

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

#ifndef _Included_org_danlley_jni_test_HelloJniTest
#define _Included_org_danlley_jni_test_HelloJniTest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     org_danlley_jni_test_HelloJniTest
 * Method:    displayHelloJni
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_org_danlley_jni_test_HelloJniTest_displayHelloJni
  (JNIEnv *, jobject);

/*
 * Class:     org_danlley_jni_test_HelloJniTest
 * Method:    getDynamicIntDataNoParam
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicIntDataNoParam
  (JNIEnv *, jobject);

/*
 * Class:     org_danlley_jni_test_HelloJniTest
 * Method:    getDynamicIntData
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicIntData
  (JNIEnv *, jobject, jint);

/*
 * Class:     org_danlley_jni_test_HelloJniTest
 * Method:    getDynamicStringData
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicStringData
  (JNIEnv *, jobject, jstring);

/*
 * Class:     org_danlley_jni_test_HelloJniTest
 * Method:    getDynamicArrayData
 * Signature: ([I)[I
 */
JNIEXPORT jintArray JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicArrayData
  (JNIEnv *, jobject, jintArray);

#ifdef __cplusplus
}
#endif
#endif

 

改寫org_danlley_jni_test_HelloJniTest.cpp文件:

// org_danlley_jni_test_HelloJniTest.cpp: implementation of the org_danlley_jni_test_HelloJniTest class.
//
//////////////////////////////////////////////////////////////////////

#include "org_danlley_jni_test_HelloJniTest.h"
#include <jni.h>
#include <stdio.h>

JNIEXPORT void JNICALL 
Java_org_danlley_jni_test_HelloJniTest_displayHelloJni(JNIEnv *env, jobject obj) 
{
    printf("Java_org_danlley_jni_test_HelloJniTest_displayHelloJni has been called!\n");
    return;
}

JNIEXPORT jint JNICALL 
Java_org_danlley_jni_test_HelloJniTest_getDynamicIntDataNoParam(JNIEnv *env, jobject obj) 
{
    return 65535;
}

JNIEXPORT jint JNICALL 
Java_org_danlley_jni_test_HelloJniTest_getDynamicIntData(JNIEnv *env, jobject obj, jint i)
{
	i*=i;
    return i;
}

JNIEXPORT jstring JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicStringData
(JNIEnv *env, jobject obj, jstring arg){
	//Get the native string from javaString
    const char *nativeString = env->GetStringUTFChars(arg, 0);
    printf("%s", nativeString);
    //DON'T FORGET THIS LINE!!!
    env->ReleaseStringUTFChars(arg, nativeString);
	return arg;
}


JNIEXPORT jintArray JNICALL Java_org_danlley_jni_test_HelloJniTest_getDynamicArrayData
(JNIEnv *env, jobject  obj, jintArray args){
    jint buf[10];
    jint i;
    env->GetIntArrayRegion(args, 0, 10, buf);
    jint j=0;
    for (i = 0; i < 10; i++) {
        j=buf[i];
        j*=j;
		buf[i]=j;
    }
	env->SetIntArrayRegion(args, 0, 10, buf);
    return args;
}

 

改寫RunMain:

package org.danlley.jni.test;

public class RunMain {
    public static void main(String[] args) {
        HelloJniTest tester = new HelloJniTest("helloJniTest");
        tester.displayHelloJni();
        int i = tester.getDynamicIntDataNoParam();
        System.out.println("tester.getDynamicIntDataNoParam()=" + i);
        int j = tester.getDynamicIntData(100);
        System.out.println("tester.getDynamicIntData(100)=" + j);
        String str = tester.getDynamicStringData("My first String test");
        System.out.println("tester.getDynamicStringData=" + str);
        int[] args_int = new int[10];
        for (int ii = 0; ii < 10; ii++) {
            args_int[ii] = ii;
        }
        int[] args_arr = tester.getDynamicArrayData(args_int);
        for (int ii = 0; ii < 10; ii++) {
            System.out.println(args_arr[ii]);
        }
    }
}

 

運行結果:
--------------------------------------------------------------------------------
tester.getDynamicIntDataNoParam()=65535
tester.getDynamicIntData(100)=10000
tester.getDynamicStringData=My first String test
0
1
4
9
16
25
36
49
64
81
Java_org_danlley_jni_test_HelloJniTest_displayHelloJni has been called!
My first String test
--------------------------------------------------------------------------------

 

 

 

另附加一個自定義的對象的操作方法:

 

代碼:

package com.boao;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class JniString3Activity extends Activity {
 /** Called when the activity is first created. */
 public static final String libName = "JniString3";
 TextView textView;
 Date date;

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
  textView = (TextView) findViewById(R.id.text);
  date = new Date();
  date.string = "boao";
  getString(date);
  textView.setText(date.string);
 }

 public native void getString(Date date);

 static {
  System.loadLibrary(libName);
 }
}

 

package com.boao;

public class Date {

 public String string;
}


LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog


LOCAL_MODULE    := JniString3
LOCAL_SRC_FILES := \
JavaCalu.c \


include $(BUILD_SHARED_LIBRARY)

 

#include <string.h>
#include <android/log.h>
#include <jni.h>

/**
 * 對傳遞進來的字符串先輸出,然後在對其進行修改,最後設置屬性
 */
void Java_com_boao_JniString3Activity_getString(JNIEnv* env,jobject thiz,jobject date)
{
 jclass date_calss;
 jfieldID date_string_f;
 jstring jstr;
 const char *str;

 //得到Date的class類型
 date_calss = (*env)->GetObjectClass(env,date);
 //得到Date對象的string屬性的屬性ID
 date_string_f = (*env)->GetFieldID(env,date_calss,"string","Ljava/lang/String;");
 //得到Date對象的string屬性的屬性值
 jstr = (*env)->GetObjectField(env,date,date_string_f);
  //從jstring類型取得c語言環境下的char*類型
 str = (*env)->GetStringUTFChars(env,jstr,0);
 //釋放jni分配的內存
 (*env)->ReleaseStringUTFChars(env,jstr,str);
 jstr = (*env)->NewStringUTF(env,"hao");
 //給Date對象的string屬性重新賦值
 (*env)->SetObjectField(env,date,date_string_f,jstr);
 //return;
}

 

 

 

  

參考資料:

http://en.wikipedia.org/wiki/Java_Native_Interface
http://www.yesky.com/20011004/199789.shtml
http://www.21ic.com/news/html/63/show13260.htm
http://java.ccidnet.com/art/297/20060228/439729_1.html
http://www.blogjava.net/soft/archive/2006/11/13/posoft.html
http://www.blogjava.net/soft/archive/2006/11/13/80788.html
http://www.blogjava.net/soft/archive/2006/11/13/80789.html 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章