1. 程式人生 > >[2014.1.31] Eclipse、MinGW、JNI編寫C++生成dll, Java端呼叫的完整示例(附java.lang.UnsatisfiedLinkError解決方法)

[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

拷貝到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 __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進來也是可以的,推薦這樣做,這會讓程式顯得規整些。