利用IDEA進行JNI開發:生成Windows平臺下的dll檔案

柴月和岐月發表於2017-09-15

 

由於站在巨人的肩膀上,學習過程中沒遇到什麼問題,所以此篇文章僅作為個人補充的內容。

閱讀此篇文章前,請先站在巨人的肩膀上IntelliJ IDEA平臺下JNI程式設計(一)—HelloWorld篇

總結下流程:

1.編寫包含本地方法的類

2.使用javah獲得標頭檔案(這裡不太瞭解的,可以看下小生我的IDEA開發,Jni中javah使用方式的探索

3.使用標頭檔案編寫.c原始檔

4.使用GCC或其他編譯工具(甚至你用VC,eclipse都行啊)將.c原始檔編譯連線為dll檔案。

以下是GCC的使用引數

內容摘自GCC與MinGW的簡單安裝與使用

簡單的GCC用法
總的來說,gcc應該是一個編譯器。但整套的gcc環境並不是由gcc構成的,它是由多個包所組成的,這些包的互相作用產生了gcc的開發環境。其中,有一些包是你開發應用程式所必備的基本包,離開這些包你將無法正常使用gcc。

gcc開發環境包括如下幾大包:

binary 基本包 提供基本的彙編器,聯結器等
gcc 基本包 各種語言的編譯器,包括C,C++,Ada,Java等
Win32api,mingwi-runtime/glibc 基本包 系統函式庫
make/automake 需要包 管理專案編譯的程式
gdb 附加包 除錯程式

一. 常用編譯命令選項
假設源程式檔名為test.c。

1. 無選項編譯連結
用法:#gcc test.c
作用:將test.c預處理、彙編、編譯並連結形成可執行檔案。這裡未指定輸出檔案,預設輸出為a.out。編譯成功後可以看到生成了一個a.out的檔案。在命令列輸入./a.out 執行程式。./表示在當前目錄,a.out為可執行程式檔名。

2. 選項 -o
用法:#gcc test.c -o test
作用:將test.c預處理、彙編、編譯並連結形成可執行檔案test。-o選項用來指定輸出檔案的檔名。輸入./test執行程式。

3. 選項 -E <大寫,注意>
用法:#gcc -E test.c -o test.i
作用:將test.c預處理輸出test.i檔案。

4. 選項 -S <大寫,注意>
用法:#gcc -S test.i
作用:將預處理輸出檔案test.i彙編成test.s檔案。

5. 選項 -c
用法:#gcc -c test.s
作用:將彙編輸出檔案test.s編譯輸出test.o檔案。

6. 無選項鍊接
用法:#gcc test.o -o test
作用:將編譯輸出檔案test.o連結成最終可執行檔案test。輸入./test執行程式。

7. 選項-O
用法:#gcc -O1 test.c -o test
作用:使用編譯優化級別1編譯程式。級別為1~3,級別越大優化效果越好,但編譯時間越長。輸入./test執行程式。

二. 多原始檔的編譯方法

如果有多個原始檔,基本上有兩種編譯方法:
[假設有兩個原始檔為test.c和testfun.c]

1. 多個檔案一起編譯
用法:#gcc testfun.c test.c -o test
作用:將testfun.c和test.c分別編譯後連結成test可執行檔案。
2. 分別編譯各個原始檔,之後對編譯後輸出的目標檔案連結。
用法:
#gcc -c testfun.c //將testfun.c編譯成testfun.o
#gcc -c test.c //將test.c編譯成test.o
#gcc testfun.o test.o -o test //將testfun.o和test.o連結成test

以上兩種方法相比較,第一中方法編譯時需要所有檔案重新編譯,而第二種方法可以只重新編譯修改的檔案,未修改的檔案不用重新編譯。

三. gcc的常用編譯引數

同VC,TC等編譯器不同,gcc其實是可以很方便的在提示符下編譯程式的。gcc在提示符下編譯程式,並沒有如同VC那樣的冗長而晦澀的編譯引數。相反,卻有著比VC更靈活且簡短的引數。

不得不承認,不懂gcc編譯引數的人,確實會損失一些gcc的強大功能。所以,我下面簡單介紹一下gcc的一些基本編譯引數。這裡,我以C編譯器為例。

注意:gcc的編譯引數是區分大小寫的。

編譯二進位制程式碼
gcc -c yours.c -o yours.o
使用這段指令,gcc將會把yours.c編譯成yours.o的二進位制程式碼。其中,yours.o就類似於VC,TC中的.obj文件。


編譯最簡單的小程式
gcc -o yours yours.c
通過這條指令,gcc將會把yours.c原始碼編譯成名為yours的可執行程式。當然,您也可以將yours.c改成我們剛才介紹的yours.o檔案。這樣,gcc將使用編譯剛才編譯好的二進位制文件來連結程式。這裡,格式的特點是,-o 後面是一串檔案列表,第一個引數是所編譯程式的檔名,從第二個開始,就是您編譯和連線該可執行程式所需要的二進位制文件或者原始碼。


編譯時將自己的標頭檔案目錄設為預設標頭檔案目錄
gcc -I”Your_Include_Files_Document_Path” -c yours.c -o yours.o
這條指令中的-I引數將會把Your_Include_Files_Document_Path新增到你預設的標頭檔案目錄中。這樣您將可以使用 #include <your_include.h>來匯入標頭檔案。


編譯時使用自己的靜態庫存放目錄
gcc -L”Your_Lib_Files_Document_Path” -o yours yours.o
這條指令將會讓gcc在連線時除了在預設Lib存放目錄中搜尋指定的靜態庫以外,還會在Your_Lib_Files_Document_Path中搜尋。


編譯時使用靜態連線庫
gcc -lyour_lib -o yours yours.o
這條指令將會讓gcc在連線時把 libyour_lib.a中您所用到的函式連線到可執行程式中。此處注意,gcc所使用的靜態連線庫是lib*.a格式的。在連線時,只且僅需要提供*的內容就可以了。


編譯時使用優化
gcc -O2 -c yours.c -o yours.o
使用優化方式編譯程式,其中除了-O2以外,還有-O3 -O1等等。他們代表不同的優化等級。最常用的,是-O2優化。當然,還有針對特殊CPU的優化,這裡就不介紹了。


編譯時顯示所有錯誤和警告資訊
gcc -Wall -c yours.c -o yours.o
gcc在預設情況下,將對一些如變數申請未使用這樣的問題或者申請了沒有給予初始值的問題忽略。但是,如果使用了-Wall引數,編輯器將列出所有的警告資訊。這樣,您就可以知道您的程式碼中有多少可能會在其他作業系統下出錯的地方了。(用這個指令看看你的程式碼有多少地方寫的不怎麼合適。)


編譯連線時,加入除錯程式碼
gcc -g -o yours yours.c
正如同VC有debug編譯模式一樣,gcc也有debug模式。新增了-g 引數編譯的可執行程式比普通程式略為大一些,其中新增了一些除錯程式碼。這些程式碼將被gdb所支援。


連線時縮小程式碼體積
gcc -s -o yours yours.o
因為有人說Visual-MinGW生成的程式碼小,於是研究了一下它的編譯引數,發現release模式的編譯引數就加了這一項。貌似編譯後的程式碼的確縮小了很多。

反彙編
gcc -S yours.c
用這個指令能把C語言變成組合語言,不過不是常見的Intel語法,而是AT&T語法。兩者的語法有很大的區別。


獲得幫助
gcc --help
這條指令從意思上就能看出,獲得gcc的幫助資訊。如果您有什麼特殊需要,也許這個指令能幫上點小忙。


總結:

gcc的編譯引數是可以組合起來的,如:

gcc yours.c -o yours -Wall -s -O2

以下是MinGW的使用引數

內容摘自CodeBlocks綜合設定

“由於近期有專案要用到JNI,但用CodeBlocks生成的dll一直不能被Java呼叫,而Vs、Vc卻可以,後來經過一番折騰,終於弄好了。原來JNI想要VC風格的函式宣告,但是CodeBlocks預設的GCC編譯器生成的是GCC風格的,所以需要用編譯選項修改生成風格、指定連結引數,才能使生成的dll可以被Java成功呼叫。否則會報錯誤”

網上有兩種引數模式,都測試在codeblocks,都能夠生成供Java呼叫的dll

1:-Wl,--kill-at
2:-Wl,--add-stdcall-alias
stdcall與_cdecl是兩種不同的函式呼叫約定,區別在函式引數入棧的順序,由呼叫函式還是被呼叫函式將引數彈出棧,以及產生函式修飾名的方法。
jni使用的dll庫函式預設使用stdcall呼叫約定,stdcall的呼叫約定意味著:
1.引數從右向左壓入堆疊。
2.函式自身修改堆疊。
3.函式名自動加前導的下劃線,後面緊跟一個@符號,其後緊跟著引數的尺寸。gcc編譯的時候可能不使用stdcall,百度說gcc預設是_cdecl約定,加上這個連結引數就可以了。

(所以說,MinGW似乎並沒有預設其中一個,測試的時候未指定報錯:error: ld returned 1 exit status)

-shared:使其生成動態庫

-static:使其生成靜態庫

動態庫和靜態庫區別:

靜態庫在程式編譯時會被連線到目的碼中,程式執行時將不再需要該靜態庫。
動態庫在程式編譯時並不會被連線到目的碼中,而是在程式執行是才被載入,因此在程式執行時還需要動態庫存在。
當靜態庫和動態庫同名時, gcc命令將優先使用動態庫。

相關文章