問題背景:之前的JNI編程都是基於Android的NDK工具,產生so檔案供android端調用,參見:http://blog.csdn.net/yanzi1225627/article/details/8525720 現在的目標是用eclipse CDT MinGW編寫C++檔案產生PC上可用的動態連結程式庫dll,供純Java調用。本以為很簡單,可沒想到折騰到半夜兩點沒搞定,原因是很多參考文獻資料太老了。好吧,大年初一搞了兩個小時終於拿下。下面是詳細步驟:
準備工作:
將C:\Program Files\Java\jdk1.7.0_45\include路徑下的jni.h和C:\Program Files\Java\jdk1.7.0_45\include\win32路徑下的jni_md.h拷貝到MinGW下的include路徑下,否則會出現找不到#include<jni.h>及不認關鍵字:JNIEXPORT JNICALL JNIEnv的情況。參考文獻中將這兩個路徑直接添加到了C++工程屬性裡的General---Paths and Symbols---GNU C++的include欄,如:
而實際上是沒有必要的,只需按上面的拷貝兩個h檔案到相應位置即可!
1、建立一個java project,包名為org.yanzi.learnjni,主類為LearnJNI,即帶有main函數的類。為了使代碼結構有條理性,再建立一個包:org.yanzi.mylib,建立一個類JNILib.java.代碼如下:
package org.yanzi.mylib;public class JNILib {static{System.loadLibrary("");}public static native void jniPrint(String str);}
我們在這個類裡將本地庫載入進來,由於本地庫還麼有產生,所有System.loadLibrary()函數裡的參數暫時不寫。最關鍵的是下面那句話,聲明了jni裡的函數原型,輸入一個String然後再jni裡列印出來。
2、然後我們在cmd裡利用javah產生與JNILib.java裡jniPrint()函數相對應的JNI的聲明。cmd裡進到所在工程目錄的src檔案夾下:E:\WorkSpaces\Eclipse_Java\LearnJNI\src. 輸入命令:javah org.yanzi.mylib.JNILib
注意:一定要在src檔案夾下輸入javah,只有這樣後面的org.yanzi.mylib.JNILib(包名 + 類名)路徑才能對的上。
重新整理工程,就會看到產生的.h檔案:
3、建立一個C++工程,如:
注意這個C++工程的名字就是未來產生的dll的名字libXXX.dll。這一點跟ndk不同,ndk是通過mk檔案指定動態連結程式庫的名字的。然後點擊next,再建立一個src檔案夾,不是必須的,僅僅是為了讓程式更加規整.然後將剛才產生的org_yanzi_mylib_JNILib.h拷貝到這個src檔案夾下,再建立一個cpp檔案。之後這個.h檔案在java工程就麼有作用了,刪除掉也是可以的,不過為了告訴Java調用的人介面是什麼,這個h檔案就要保留下。為了統一,cpp檔案取名為:org_yanzi_mylib_JNILib.cpp.
原來產生的.h檔案裡沒有形參,加形參後函數體為:
JNIEXPORT void JNICALL Java_org_yanzi_mylib_JNILib_jniPrint
(JNIEnv *env, jclass jthis, jstring str);
org_yanzi_mylib_JNILib.h檔案的內容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>/* Header for class org_yanzi_mylib_JNILib */#ifndef _Included_org_yanzi_mylib_JNILib#define _Included_org_yanzi_mylib_JNILib#ifdef __cplusplusextern "C" {#endif/* * Class: org_yanzi_mylib_JNILib * Method: jniPrint * Signature: (Ljava/lang/String;)V */JNIEXPORT void JNICALL Java_org_yanzi_mylib_JNILib_jniPrint (JNIEnv *env, jclass jthis, jstring str);#ifdef __cplusplus}#endif#endiforg_yanzi_mylib_JNILib.cpp檔案的內容如下:
/* * org_yanzi_mylib_JNILib.cpp * * Created on: 2014-2-1 * Author: Administrator */#include "org_yanzi_mylib_JNILib.h"#include <iostream>using namespace std;JNIEXPORT void JNICALL Java_org_yanzi_mylib_JNILib_jniPrint (JNIEnv *env, jclass jthis, jstring str){jboolean iscopy = false;const char *charData = env->GetStringUTFChars(str, &iscopy);cout << "Hello, this is from JNI(dll)" <<endl;cout<<"The data from java is:"<<charData << endl;env->ReleaseStringUTFChars(str, charData);}[
關鍵一步]選中工程,按alt+enter,在Build----Settings----Tool Settings-----MinGW C++ Linker目錄欄下的Miscellaneous選項下,在linker flags處填入:-Wl,--add-stdcall-alias
然後點擊編譯,在Debug目錄下產生libMyJNILib.dll,libXXX.dll名字可以發現XXX就是我們起的C++的工程名字.
4、產生dll完畢後,C++的就告一段落了。在java工程裡建立一個檔案夾libs,該檔案夾路徑跟src在同一級目錄。將產生的dll拷貝到libs檔案夾。
5、[關鍵一步]在System.loadLibrary()函數裡寫入參數:libMyJNILib,注意而不是MyJNILib,一定是全名,此處和ndk-build產生so不同。JNILib.java代碼如下:
package org.yanzi.mylib;public class JNILib {static{System.loadLibrary("libMyJNILib");}public static native void jniPrint(String str);}
LearnJNI.java代碼如下:
package org.yanzi.learnjni;import org.yanzi.mylib.JNILib;public class LearnJNI {/** * @param args */public static void main(String[] args) {// TODO Auto-generated method stubJNILib.jniPrint("123456");}}
此刻,點擊run會報錯如下,java.lang.UnsatisfiedLinkError錯誤:
Exception in thread "main" java.lang.UnsatisfiedLinkError: no libMyJNILib in java.library.pathat java.lang.ClassLoader.loadLibrary(ClassLoader.java:1886)at java.lang.Runtime.loadLibrary0(Runtime.java:849)at java.lang.System.loadLibrary(System.java:1088)at org.yanzi.mylib.JNILib.<clinit>(JNILib.java:5)at org.yanzi.learnjni.LearnJNI.main(LearnJNI.java:12)
所以還需要下面重要一步。
6、[關鍵一步]選中工程,依次點擊run---run configurations---LearnJNI,在點擊Arguments,在Vm arguments處填入如下:-Djava.library.path="${workspace_loc}\LearnJNI\libs;${env_var:PATH}"
注意:上面這句話一點都不能錯,其中LearnJNI是java的工程的名字。兩頭的引號不要少,另外裡面是\,因為這是windows下。
經過這些後,點擊run,久違的列印出現了:
另外,在static{
System.loadLibrary("libMyJNILib");
}裡可以加上一句:
System.out.println(System.getProperty("java.library.path"));列印path的所有路徑。整體代碼如下:
package org.yanzi.mylib;public class JNILib {static{System.out.println(System.getProperty("java.library.path"));System.loadLibrary("libMyJNILib");}public static native void jniPrint(String str);}
總結,網上常見的誤解之處:1、將mingw裡的bin檔案夾下的mingw32-make.exe改名為make.exe,這一步完全是多餘的!2、在C++的cpp和h檔案裡,將函數的申明(Java關鍵字前面)加個底線_. 如JNIEXPORT void JNICALL _Java_org_yanzi_mylib_JNILib_jniPrint
(JNIEnv *env, jclass jthis, jstring str) 事實證明這一步是多餘的!3、在-Djava.library.path的配置裡一定要帶“”,有的教程上寫的是: .;./libs,經過列印path印證,這完全就是扯淡!本博文裡的配置才是正確的。4、有的博文說C++的代碼裡才cpp的名字必須和.h名字一樣,這也是扯淡。5、還有的說產生的h檔案原封不動的拷貝到cpp檔案裡,這樣做當然是可以的。但是保留h檔案,在cpp檔案裡include進來也是可以的,推薦這樣做,這會讓程式顯得規整些。
參考:http://jingyan.baidu.com/article/9c69d48f53575d13c9024ec1.html
---------------------本文系原創,轉載請註明作者:yanzi1225627
代碼下載(C++和Java的):http://download.csdn.net/detail/yanzi1225627/6893971