2011-05-11 228 views
49

如何從Java調用c函數。 似乎c是基於編譯器的。從Java調用c函數

我想從Java調用Windows的C函數,也可以調用Java的GCC函數 。

有沒有參考?

+0

你可能想看看JNI(Java本地接口)。 – 2011-05-11 11:13:21

+1

我會從這些http://www.google.co.uk/search?q=JNI+tutorial – 2011-05-11 11:14:07

+0

開始。非常感謝。 – Wen 2011-05-11 13:53:09

回答

54

看看Java Native Interface: Getting Started

2.1概述

[...]編寫一個簡單的Java應用程序調用C函數打印 的 「Hello World!」。該過程包含以下步驟:

創建聲明本地方法的類(HelloWorld.java)。使用 javac編譯HelloWorld源文件,產生 文件HelloWorld.class。 javac編譯器隨JDK或Java 2 SDK發行版一起提供。使用javah -jni生成包含本機方法 實現的函數原型的C頭文件 (HelloWorld.h)。 javah工具隨JDK或Java 2 SDK 版本提供。編寫本地 方法的C實現(HelloWorld.c)。將C實現編譯爲本地庫,從而創建 Hello-World.dlllibHello-World.so。使用主機環境中可用的C編譯器和鏈接器 。運行HelloWorld程序,使用java運行時解釋器 。類文件(HelloWorld.class) 和本機庫(HelloWorld.dlllibHelloWorld.so)在運行時加載 。本章的其餘部分將詳細說明 中的這些步驟。

2.2寫在Java下面的程序編程語言 聲明本地方法

你開始。該程序定義了一個名爲HelloWorld的類,其中包含一個 本地方法print。

class HelloWorld { 
    private native void print(); 

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

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

HelloWorld類定義以print native方法的聲明開始。接下來是一個主要方法, 實例化Hello-World類並調用此實例的打印本地方法 。類定義的最後一部分是一個靜態的 初始化程序,用於加載包含打印本機方法的 實現的本機庫。

本地方法 (如print)的聲明與Java 編程語言中常規方法的聲明之間有兩點區別。本機方法聲明必須包含 本機修飾符。本地修飾符表示此方法是以另一種語言實現的 。此外,原生方法聲明 以分號(語句結束符符號 )結尾,因爲 類本身沒有針對本地方法的實現。我們將在一個單獨的C文件中實現打印方法。

在調用本地方法打印之前,必須加載 實現打印的本地庫。在這種情況下,我們在HelloWorld類的靜態初始化程序中加載本地 庫。在 調用HelloWorld類中的任何方法之前,Java 虛擬機自動運行靜態初始化程序,從而確保在調用打印本機方法之前加載了 本機庫。

我們定義一個能夠運行HelloWorld類的主要方法。 Hello-World.main調用本地方法打印的方式與調用常規方法的 相同。

System.loadLibrary需要一個庫名稱,找到一個與該名稱對應的本地庫,並將本地庫加載到 應用程序中。我們將在後面的 書中討論確切的加載過程。現在簡單地記住,爲了使 System.loadLibrary("HelloWorld")成功,我們需要在Win32上創建 本地庫,稱爲HelloWorld.dll,或者在 Solaris上創建libHelloWorld.so

2.3編譯HelloWorld類

您已經定義了HelloWorld類後,保存的源代碼在 文件名爲HelloWorld.java。然後用自帶的JDK或Java 2 SDK版本的 javac編譯器編譯源文件:

javac HelloWorld.java 

這個命令會生成一個HelloWorld.class 文件在當前目錄中。

2.4創建的本地方法的頭文件

下一步,我們將使用javah工具來生成JNI風格的頭文件 實現C中的本地方法時有用,您可以在Hello-World運行 javah類如下:

javah -jni HelloWorld 

頭文件的名稱是類名 具有「.h」追加到它的結束。上面顯示的命令 會生成一個名爲HelloWorld.h的文件。我們不會在這裏列出生成的 頭文件。所述 頭文件的最重要的部分是函數原型Java_HelloWorld_print,這 是C函數實現該方法HelloWorld.print:

JNIEXPORT void JNICALL Java_HelloWorld_print (JNIEnv *, jobject); 

忽略JNIEXPORTJNICALL宏現在。您可能已注意到 即使本機方法的相應聲明接受 無參數,本機方法的C實現也接受兩個參數 。每種本地方法 實現的第一個參數是一個JNIEnv接口指針。第二個參數是對HelloWorld對象本身的引用 (有點像C++中的「this」 指針)。我們將在本書稍後討論如何使用JNIEnv接口 指針和jobject參數,但是這個簡單的 示例忽略了這兩個參數。

2.5編寫本地方法實現

通過javah產生的JNI風格的頭文件可以幫助你編寫C或本地方法 C++實現。您編寫的函數 必須遵循生成的頭文件中指定的原型。您 可以實現在C文件中HelloWorld.cHello-World.print方法 如下:

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

JNIEXPORT void JNICALL Java_HelloWorld_print(JNIEnv *env, jobject obj) { 
    printf("Hello World!\n"); 
    return; 
} 

這個本地方法的實現很簡單。它使用printf函數來顯示字符串「Hello World!」然後返回。如前所述,兩個參數,JNIEnv指針和對象的引用都被忽略。

C程序包括三個頭文件:

jni.h - 該頭文件提供的信息的本機代碼需要 調用JNI函數。在編寫本地方法時,您必須始終在您的C或C++源文件中包含此文件。 stdio.h - 上面的代碼 也包含stdio.h,因爲它使用printf 函數。 HelloWorld.h - 使用 javah生成的頭文件。它包括用於Java_HelloWorld_print 函數的C/C++原型。 2.6編譯C源代碼,並創建一個本地庫

請記住,當你在 HelloWorld.java文件創建的HelloWorld類,包括你行的代碼,加載本地 庫到程序:

System.loadLibrary("HelloWorld"); 

現在編寫了所有必需的C代碼 ,您需要編譯Hello-World.c並構建此本機 庫。

不同的操作系統支持不同的方法來構建本地 庫。在Solaris上,下面的命令來建立所謂libHello-World.so共享庫 :

cc -G -I/java/include -I/java/include/solaris HelloWorld.c -o libHelloWorld.so 

-G選項指示C編譯器生成一個共享庫,而不是常規的Solaris 可執行文件。由於本書中頁寬的限制,我們將命令行分成兩行。您需要在一行中鍵入命令 ,或將該命令放在腳本文件中。上Win32,將 以下命令生成一個動態鏈接庫(DLL)HelloWorld.dll 使用Microsoft Visual C++編譯:

cl -Ic:\java\include -Ic:\java\include\win32 -MD -LD HelloWorld.c -FeHelloWorld.dll 

-MD選項確保HelloWorld.dllWin32多線程C庫鏈接。 -LD選項指示C編譯器生成DLL而不是常規的Win32可執行文件。當然,在Solaris和Win32上, 都需要放入包含自己的設備的包含路徑。

2.7運行程序

在這一點上,你必須準備好運行程序的兩個組成部分。 類文件(HelloWorld.class)調用本地方法,並且 本地庫(Hello-World.dll)實現本機方法。

因爲HelloWorld類包含自己的主要方法,您可以按以下運行 Solaris或Win32的程序:

java HelloWorld 

你應該看到下面的輸出:

Hello World! 

這是重要的是要正確設置您的本機庫路徑 以使程序運行。本機庫路徑是當加載 本機庫時Java虛擬機搜索的目錄列表 。如果您沒有本地庫路徑設置 做正確,那麼您將看到類似以下內容的錯誤:

java.lang.UnsatisfiedLinkError: no HelloWorld in library path 
     at java.lang.Runtime.loadLibrary(Runtime.java) 
     at java.lang.System.loadLibrary(System.java) 
     at HelloWorld.main(HelloWorld.java) 

確保本機庫駐留在本地庫路徑的目錄之一。 如果您在Solaris系統上運行,則使用環境變量LD_LIBRARY_PATH 定義本機庫路徑。使 確信它包含包含文件的目錄的名稱。如果libHelloWorld.so文件是在當前 目錄,你可以發出在標準 殼(SH)或KornShell(KSH)建立正確的LD_LIBRARY_PATH 環境變量以下兩條命令:

LD_LIBRARY_PATH=. 
export LD_LIBRARY_PATH 

等效在 的是C shell(csh或tcsh)命令如下:

setenv LD_LIBRARY_PATH . 

如果您在Windows 95或 Windows NT的計算機上運行,​​確保HelloWorld.dll是在T他目前在 目錄下,或者在PATH環境下列出的目錄 變量中。

在Java 2 SDK中1。2版本中,你也可以指定Java命令行上的本地庫 路徑爲系統屬性如下:

java -Djava.library.path=. HelloWorld 

的「-D」命令行選項 設置一個Java平臺的系統性能。將java.library.path 屬性設置爲「.」會指示Java虛擬機在當前目錄中搜索 本機庫。

+0

Mac用戶可以忽略solaris命令。這個博客幫助我完成了這個答案幫助開始的東西:http://nerdposts.blogspot.com/2010/10/jni-mac-os-x-simple-sample.html – cloudsurfin 2015-07-15 01:31:34

11

簡而言之,只要確保加載了包含函數定義的相關庫,加載遵循JNI規範的庫幷包裝來自第一個庫的目標函數,公開Java類中的本地方法以及你應該很好走。

我建議對生JNI,因爲它包含了很多樣板代碼,如果你開始打包 C庫,你最終會詛咒自己。盡一切可能在開始時隨意涉足JNI,但在實際工作中使用類似JNA的東西。

+0

JNA和JNI一樣快嗎? – Pacerier 2017-06-17 22:01:59

0

JNI - Java本地接口

爲了從Java調用C函數,你需要使用JNI

3

您的選項包括:

Java本地接口
見:https://en.wikipedia.org/wiki/Java_Native_Interface

報價:

JNI允許程序員編寫本地方法來處理情況當應用程序不能完全用Java編程語言編寫時,例如當標準Java類庫不支持特定於平臺的功能或程序庫

Java本機訪問

見:https://en.wikipedia.org/wiki/Java_Native_Access

報價:

Java本機Access是一個社區開發的庫,它可以讓Java程序輕鬆訪問本地共享庫,而無需使用Java Nat ive接口。

JNR-FFI

見:https://github.com/jnr/jnr-ffi

報價:

JNR-FFI是加載本地庫一個Java庫,而無需手動編寫JNI代碼,或使用諸如SWIG之類的工具。

+0

所以JNR diff JNA是? – Pacerier 2017-06-17 21:55:32

0

對於低於

「CL -Ic使兼容64位的DLL刪除 「-MD」 從語句選項:\ java的\包括-Ic:\ java \ include \ win32 -MD -LD HelloWorld.c -FeHelloWorld.dll「

0

我得到了解決這個問題的方法。您需要確保的是,您正在使用64位C++編譯器編譯代碼,以調用在64位JRE上運行的Java函數。除此之外,我們需要將創建的dll文件的路徑保存在「環境變量」下的「路徑」中。

1

如果您使用的是Windows和MinGW GCC,你可能需要額外的標誌,如果你在LIB越來越UnsatisfiedLinkError具體方法:

gcc -D_JNI_IMPLEMENTATION_ -Wl,--kill-at -I"%JAVA_HOME%"\include -I"%JAVA_HOME%"\include\win32 BestCode.c -shared -o BestCode.dll 
0

首先要確保加載您的本機庫或.dll文件在類路徑通過產權java.library.path設置路徑

然後使用System.loadLibrary()

Do not use .dll extension at the end.