IntelliJ IDEA平臺下JNI編程(一)—HelloWorld篇

轉載請注明出處:【huachao1001的簡書:http://www.lxweimin.com/users/0a7e42698e4b/latest_articles】

JNI(Java Native Interface),出于學習JNI的目的,為了能夠更方便快速地運行程序。本文的是在IDEA中進行,而不在AndroidStudio,這樣能夠對NDK的工作過程有個更深刻的認識,同時也能對JNI的原理有更深的理解。雖然本文是HelloWorld篇,但是其中涉及到很多內容。博主將遇到的坑都記錄下來了,希望能夠幫到大家。這篇文章可能是2016年的最后一篇文章了,接下來JNI相關系列文章明年推出,歡迎大家關注。

1. 搭建GCC編譯環境

既然使用的了JNI,那就不可避免地需要將C/C++文件編譯成dll(windows)so(Linux)文件。因為我是在Windows平臺下開發,可以有如下選擇:

  1. 使用VC(或VS)編譯成dll
  2. 使用GCC編譯成dll

因為開發Android應用肯定是需要編譯成Linux平臺的so文件,因此,為了后面開發Android程序的兼容,使用GCC編譯器比較好。而Windows平臺下的GCC又可以有如下選擇:

  1. 使用MinGW
  2. 使用Cygwin

這里我選擇了MinGW,不管選擇哪個,只要能讓本地有GCC編譯環境即可。

注意:搭建GCC編譯環境時,一定要選擇正確的GCC編譯版本(32位和64位)。如果你本地安裝的JDK是64位的,那么選擇64位GCC,否則選擇32位。這是為了使得編譯后的庫文件跟JVM的位一致,否則后面JVM無法調用dll(或so)。

1.1 安裝MinGW

安裝MinGW的方法很多,可以前往https://sourceforge.net/projects/mingw/files/MinGW/Base/gcc/ 中自己選擇需要的包。老實說,我也不是很清楚哪些包需要下載,沒花時間研究,感興趣的自己研究一些各個包的功能。不過這里有個安裝教程https://github.com/cpluspluscom/ChessPlusPlus/wiki/MinGW-Build-Tutorial按照這里的方法安裝就可以。

另外,針對64位的,這里https://nuwen.net/mingw.html提供了完整的壓縮包,直接下載https://nuwen.net/files/mingw/mingw-14.1.exe運行。如下:

指定解壓路徑

其本質也是解壓縮,把需要的MinGW庫和程序解壓到指定目錄。如果懶的去看英文,我這里將我解壓后的重新壓縮了下,大家去下載并且解壓即可直接使用。

鏈接: https://pan.baidu.com/s/1slpQrrJ
密碼: fykw

解壓完成后,剛才指定的解壓目錄中的bin加入到path環境變量中。例如上面圖中解壓到E:\MinGw,那么應當將E:\MinGw\bin加入到環境變量path中。注意,請確保bin目錄確實在E:\MinGw中,如果不在,可能在更深一層目錄中,自行確定bin的目錄。

做完后,打開控制臺,輸入:gcc -v,如下:

C:\Users\NetLab\Desktop>gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=e:/mingw/bin/../libexec/gcc/x86_64-w64-mingw32/6.3.0/lto-wrapper.exe
Target: x86_64-w64-mingw32
Configured with: ../src/configure --enable-languages=c,c++ --build=x86_64-w64-mingw32 --host=x86_64-w64-mingw32 --target=x86_64-w64-mingw32 --disable-multilib --prefix=/c/temp/gcc/dest --with-sysroot=/c/temp/gcc/dest --disable-libstdcxx-pch --disable-nls --disable-shared --disable-win32-registry --enable-checking=release --with-tune=haswell
Thread model: win32
gcc version 6.3.0 (GCC)

2. 開始編碼

2.1 編寫Java文件

新建一個Java Project,創建包com.huachao.java,如下:

創建項目

在包com.huachao.java下編寫HelloJNI類:

package com.huachao.java;

/**
 * Created by HuaChao on 2016/12/29.
 */
public class HelloJNI {
    static {
        // hello.dll (Windows) or libhello.so (Unixes)
        System.loadLibrary("hello");
    }

    private native void sayHello();

    public static void main(String[] args) {

        new HelloJNI().sayHello();  // invoke the native method
    }

}

函數System.loadLibrary()是加載dll(windows)或so(Linux)庫,只需名稱即可,無需加入文件名后綴(.dll或.so)。native關鍵字將函數sayHello()聲明為本地函數,由C/C++實現。具體的實現就在hello.dll(Windows平臺)或hello.so(Linux平臺)中

2.2 生成JNI頭文件

2.2.1 手動輸入javah指令

JNI生成頭文件是通過JDK中提供的javah來完成,javah在 {JDKHome}/bin目錄中。用法如下:

javah -jni -classpath (搜尋類目錄) -d (輸出目錄) (類名)

例如,將E:\Porject\out\com\huachao\java目錄中的HelloJNI.class生成頭文件,并放入到E:\Project\jni中:

javah -jni -classpath  E:\Porject\out\com\huachao\java -d E:\Project\jni  com.huachao.java.HelloJNI.java

需要注意的是,使用javah來生成頭文件(.h)時,-classpath指定的是編譯后的java文件(.class)的目錄,而不是源文件(.java)的目錄,因此在使用javah指令之前,先build一下項目(或直接運行一下)。此時會生稱out目錄,所有編譯后的文件都會存放在這個目錄中。

out目錄

接下來,直接在IDEA的Terminal窗口運行javah:

執行javah指令

此時在jni目錄中生成了頭文件com_huachao_java_HelloJNI.h

自動生成頭文件

內容如下:

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

#ifndef _Included_com_huachao_java_HelloJNI
#define _Included_com_huachao_java_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_huachao_java_HelloJNI
 * Method:    sayHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_huachao_java_HelloJNI_sayHello
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

接下來我們只需實現Java_com_huachao_java_HelloJNI_sayHello(JNIEnv *, jobject)即可。仔細觀察就會發現這個函數名稱是有規律的,即Java_<包>_<類名>_<函數名>,JNIEXPORT和JNICALL這兩個宏定義暫時不用管。JNIEnv 和jobject后面系列文章會詳細介紹,這里暫時不理會。

2.2.2 一鍵生成頭文件

在2.2.1小節中,介紹了輸入javah生成頭文件方法。但是如果目錄層次很深,或者是有多個需要生成頭文件的class文件,這工作量太大了,當然你可以通過寫個小程序來實現。但是這里有個更便捷的方法。點擊File>Settings>Tools>External Tools

External Tools

添加一個先的External Tools:

添加一個先的External Tools

在HelloJNI.java文件中點擊右鍵>External Tools>Generate Header File

點擊生成頭文件

點擊生成,可以看到Terminal窗口會自動運行指令。跟2.2.1小節的指令一模一樣。

3. 編寫C文件并編譯成dll(或so)文件

3.1 手動輸入命令生成

在jni目錄中新建HelloJNI.c文件,如下:

新建C文件

編輯HelloJNI.c如下:

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

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

接下來就是使用GCC對HelloJNI.c編譯,在Terminal窗口輸入如下:

E:\workspace\StudyJNI>gcc -c jni/HelloJNI.c
jni/HelloJNI.c:1:17: fatal error: jni.h: No such file or directory
 #include <jni.h>

發現報錯,找不到jni.h頭文件,將JDK目錄中的include目錄加入,即為:

E:\workspace\StudyJNI>gcc -c -I"E:\JDK\include" jni/HelloJNI.c
In file included from jni/HelloJNI.c:1:0:
E:\JDK\include/jni.h:45:20: fatal error: jni_md.h: No such file or directory
compilation terminated.

又報找不到jni_md.h錯誤,繼續將JDK目錄中的include/win32加入,即:

gcc -c -I"E:\JDK\include" -I"E:\JDK\include\win32" jni/HelloJNI.c

完成編譯。此時在項目中會生成HelloJNI.o文件。接下來是將HelloJNI.o轉為HelloJNI.dll,即轉為windows平臺下的動態鏈接庫。在Terminal中輸入如下:

gcc -Wl,--add-stdcall-alias -shared -o hello.dll HelloJNI.o

此時項目目錄中生成了hello.dll文件:

生成dll

3.2 一鍵生成dll

有了前面使用External Tools一鍵生成頭文件的經驗后,我們可以將編譯成dll的過程命令也加入到External Tools中。前面將c文件編譯鏈接成dll文件分了2個命令,這里我們直接通過一個命令來完成:

gcc -Wl,--add-stdcall-alias -I"E:\JDK\include" -I"E:\JDK\include\win32" -shared -o ./lib/hello.dll ./jni/HelloJNI.c

這樣就將c文件編譯成了dll,在這里把生成的dll文件加入到了lib目錄中,而不是像之前那直接放到項目底下。因此在java.library.path應該指定目錄為lib。

有了上面的命令后,可以很輕松的加入到External Tools中了。按照前面的方法,點擊File>Settings>Tools>External Tools>+,輸入內容如下:

name:Generate DLL
Program:<GCC路徑>
Parameters:-Wl,--add-stdcall-alias -I"$JDKPath$\include" -I"$JDKPath$\include\win32" -shared -o ./lib/$FileNameWithoutExtension$.dll ./jni/$FileNameWithoutExtension$.c
Working Directory:$ProjectFileDir$

添加External Tools

在HelloJNI.c中點擊右鍵,選擇External Tools>Generate DLL。此時,在lib目錄中會得到dll文件。

4. 運行

運行HelloJNI.java類后,如下:

運行結果

5 可能出現的錯誤

5.1 java.library.path找不到dll的錯誤

此時,點擊直接運行HelloJNI.java類時,依然還會有錯誤:

Exception in thread "main" java.lang.UnsatisfiedLinkError: no hello in java.library.path
    at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1867)
    at java.lang.Runtime.loadLibrary0(Runtime.java:870)
    at java.lang.System.loadLibrary(System.java:1122)
    at com.huachao.java.HelloJNI.<clinit>(HelloJNI.java:8)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:264)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:123)

Process finished with exit code 1

即找不到我們生成的dll文件。因為在Windows中JVM的java.library.path屬性即為環境變量Path指定的目錄,而我們生成的dll并未放入到Path指定的任何一個目錄中,因此我們需要告訴JVM,dll文件在哪個目錄中。點擊Run > Edit Configurations...,如下:

修改Edit Configurations

在VM options中加入java.library.path,指定dll(或so)文件所在的目錄,比如本文中dll放在項目目錄中的lib中,如下:

-Djava.library.path=E:\workspace\StudyJNI\lib

如下圖所示:

修改VM options

5.2 無法識別__int64類型錯誤

錯誤如下:

 error: unknown type name '__int64'
 typedef __int64 jlong;
         ^

出現這個錯誤的人一般是使用Cygwin GCC的人,這是因為Cygwin GCC不認識__int64類型,找到<JDK_HOME>/include/win32/jni_md.h,找到typedef __int64 jlong;并修改為:

#ifdef __GNUC__
typedef long long jlong;
#else
typedef __int64 jlong;
#endif

或者是編譯時將__int64加入,如下:

> gcc-3 -D __int64="long long" -mno-cygwin -Wl,--add-stdcall-alias 
  -I"<JAVA_HOME>\include" -I"<JAVA_HOME>\include\win32" -shared -o hello.dll HelloJNI.c

5.3 64-bit mode not compiled

錯誤如下:

HelloJNI.c:1:0: sorry, unimplemented: 64-bit mode not compiled in
 #include <jni.h>

出現這個錯誤是因為,JDK版本是64位,而GCC編譯器編譯出的dll(或so)是32位,只需換個64位版本的GCC即可。

參考資料

https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaNativeInterface.html#zz-3.
http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,836評論 6 540
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,275評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,904評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,633評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,368評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,736評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,740評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,919評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,481評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,235評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,427評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,968評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,656評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,055評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,348評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,160評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,380評論 2 379

推薦閱讀更多精彩內容