[2014.1.31] Eclipse、MinGW、JNI編寫C++生成dll, Java端呼叫的完整示例(附java.lang.UnsatisfiedLinkError解決方法)
問題背景:之前的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
而實際上是沒有必要的,只需按上面的拷貝兩個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 __cplusplus
extern "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
#endif
org_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 stub
JNILib.jniPrint("123456");
}
}
此刻,點選run會報錯如下,java.lang.UnsatisfiedLinkError錯誤:
Exception in thread "main" java.lang.UnsatisfiedLinkError: no libMyJNILib in java.library.path
at 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進來也是可以的,推薦這樣做,這會讓程式顯得規整些。