1. 程式人生 > >Mac下Java的native方法以及JNI呼叫C語言

Mac下Java的native方法以及JNI呼叫C語言

1.native方法

最近在讀JDK NIO的原始碼的時候發現了很多的native方法,這些方法都只給出了native修飾的定義,並沒有給出實現體,並且也沒有實現體,形式上和介面很類似。但是這些其實是特定的native方法,那麼什麼是native方法呢?下面給出以下示例:

//sun.nio.ch;包裡面的IOUtil工具類下面有一些native方法
static native boolean randomBytes(byte[] var0);

    static native long makePipe(boolean var0);

    static native boolean drain(int
var0) throws IOException; static native void configureBlocking(FileDescriptor var0, boolean var1) throws IOException; static native int fdVal(FileDescriptor var0); static native void setfdVal(FileDescriptor var0, int var1); static native int iovMax(); static native int fdLimit(); static
native void initIDs(); //static程式碼段,這裡載入JNI呼叫的C/C++程式碼 static { Util.load(); IOV_MAX = iovMax(); }

native方法稱為本地方法。在java源程式中以關鍵字“native”宣告,不提供函式體。其實現使用C/C++語言在另外的檔案中編寫,編寫的規則遵循Java本地介面的規範Java native Interface(簡稱JNI)。簡而言就是Java中宣告的可呼叫的使用C/C++實現的方法。

識別符號native可以與所有其它的java識別符號連用,但是abstract除外。這是合理的,因為native暗示這些方法是有實現體的,只不過這些實現體是非java的,但是abstract卻顯然的指明這些方法無實現體。native與其它java識別符號連用時,其意義同非Native Method並無差別,比如native static表明這個方法可以在不產生類的例項時直接呼叫,這非常方便,比如當你想用一個native method去呼叫一個C的類庫時。上面的第三個方法用到了native synchronized,JVM在進入這個方法的實現體之前會執行同步鎖機制(就像java的多執行緒。)

一個native method方法可以返回任何java型別,包括非基本型別,而且同樣可以進行異常控制。這些方法的實現體可以制一個異常並且將其丟擲,這一點與java的方法非常相似。當一個native method接收到一些非基本型別時如Object或一個整型陣列時,這個方法可以訪問這非些基本型的內部,但是這將使這個native方法依賴於你所訪問的java類的實現。有一點要牢牢記住:我們可以在一個native method的本地實現中訪問所有的java特性,但是這要依賴於你所訪問的java特性的實現,而且這樣做遠遠不如在java語言中使用那些特性方便和容易。

native method的存在並不會對其他類呼叫這些本地方法產生任何影響,實際上呼叫這些方法的其他類甚至不知道它所呼叫的是一個本地方法。JVM將控制呼叫本地方法的所有細節。需要注意當我們將一個本地方法宣告為final的情況。用java實現的方法體在被編譯時可能會因為內聯而產生效率上的提升。但是一個native final方法是否也能獲得這樣的好處卻是值得懷疑的,但是這只是一個程式碼優化方面的問題,對功能實現沒有影響。

如果一個含有本地方法的類被繼承,子類會繼承這個本地方法並且可以用java語言重寫這個方法(這個似乎看起來有些奇怪),同樣的如果一個本地方法被final標識,它被繼承後不能被重寫。

本地方法非常有用,因為它有效地擴充了JVM .事實上,我們所寫的java程式碼已經用到了本地方法,在sun的java的併發(多執行緒)的機制實現中,許多與作業系統的接觸點都用到了本地方法,這得java程式能夠超越java執行時的界限。有了本地方法,java程式可以做任何應用層次的任務。

2. JNI實現native方法(Java呼叫C語言的實現)

JNI的實現步驟如下:
1. 編寫帶有native宣告的方法的Java類
2. 使用javac命令編譯編寫的Java類
3. 使用java -jni **來生成字尾名為.h的標頭檔案
4. 使用其他語言(C、C++)實現本地方法
5. 將本地方法編寫的檔案生成動態連結庫

下面給出一個最簡單的

1. 編寫含有native方法的類:

public class HelloWorld{
    public native void hello();

    static {
        //設定查詢路徑為當前專案路徑
        System.setProperty("java.library.path", ".");
        //載入動態庫的名稱
        System.loadLibrary("hello");
    }

    public static void main(String[] args){
        new HelloWorld().hello();
    }
}

2.編譯程式碼:

$javac HelloWorld.java

這時就會生成HelloWorld.class檔案

3. 使用java -jni **來生成字尾名為.h的標頭檔案

根據上面的Java程式碼,呼叫jni命令

$javah -jni HelloWorld

這就會生成HelloWorld.h檔案,這個檔案裡面的內容是不能被修改的。下面我們看一下這個檔案內容:

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

#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloWorld
 * Method:    hello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloWorld_hello
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

4. 使用其他語言(C、C++)實現本地方法

這裡建立HelloWorldImpl.c原始檔實現功能:

#include "jni.h"
#include "HelloWorld.h"
#include <stdio.h>
JNIEXPORT void JNICALL Java_HelloWorld_hello(JNIEnv *env,jobject obj){
    printf("Hello World!\n");
    return;
}

5. 將本地方法編寫的檔案生成動態連結庫

使用命令生成動態連結庫:

$gcc -dynamiclib -I /Library/Java/JavaVirtualMachines/jdk1.7.0_72.jdk/Contents/Home/include/ HelloWorldImpl.c -o libhello.jnilib

後面的那個libhello.jnilib就是生成的動態連結庫;但是執行這個命令時報錯了,顯示:

In file included from HelloWorldImpl.c:1:
/Library/Java/JavaVirtualMachines/jdk1.7.0_72.jdk/Contents/Home/include/jni.h:45:10: fatal error:
      'jni_md.h' file not found
#include "jni_md.h"
         ^
1 error generated.

提示jni_md.h這個檔案找不到;我們執行下面的命令拷貝一份:
¥sudo cp /Library/Java/JavaVirtualMachines/jdk1.7.0_72.jdk/Contents/Home/include/darwin/jni_md.h /Library/Java/JavaVirtualMachines/jdk1.7.0_72.jdk/Contents/Home/include

後面在執行生成動態連結庫的命令就成功了,下面圖示最後的檔案列表:
這裡寫圖片描述

5.最後執行可執行檔案

$ java HelloWorld
得到執行結果。
這裡寫圖片描述

6. 注意:

我們使用gcc生成的動態連結庫;在Mac下的字尾是固定的,就是.jnilib。此外我們生成的動態連結庫必須是以lib開頭的,而且我們載入動態庫時需要去掉前面的lib.比如上面的例子,生成的是libhello.jnilib,後面呼叫載入動態庫時使用的是hello這個動態庫名。

.