Java調用C/C++編寫的第三方dll動態鏈接庫
最近在用weka做一個數據挖掘相關的項目,不得不說,weka還是一個不錯的開放源代碼庫,提供了很多最常用的分類和聚類算法。
在我的項目中要用到一個聚類算法,Affinity Propagation(AP),由多倫多大學的Brendan J. Frey發表于2007年。相比其他的聚類算法,AP算法的聚類結果更加準確。
在AP的官方網站公布了AP算法的動態鏈接庫,我的目標就是實現在Java工程中調用這個動態鏈接庫。
在網上查了資料,發現,如果僅僅是想調用Windows的Native API還是比較省事的,這里我主要針對第三方dll的調用。
下面進入正題。
這里主要用的方法是JNI。在網上查資料時看到很多人說用JNI非常的復雜,不僅要看很多的文檔,而且要非常熟悉C/C++編程。恐怕有很多人在看到諸如此類的評論時已經決定繞道用其他方法了。但是,假如你要實現的功能并不復雜(簡單的參數傳遞,獲取返回值等等),我還是支持使用這個方法的。
Java Native Interface,簡稱JNI,是Java平臺的一部分,可用于讓Java和其他語言編寫的代碼進行交互。下面是從網上摘取的JNI工作示意圖。
下面就舉具體的例子說明一下使用步驟:
1) 編寫一個類,聲明native方法
- public class APCluster {
- public native int[] CallAPClusterDll( int arg_Int,
- double[] arg_DoubleArray,
- boolean arg_boolean);
- static
- {
- System.loadLibrary("APClusterDllMedium");
- }
- }
上面是APCluster.java文件,定義了一個APCluster類,其中有一個方法CallAPClusterDll(),需要傳遞三種不同類型的參數,并且返回一個整型數組。
注意,這里只需要聲明這個方法,并不需要實現,具體實現就在APClusterDllMedium中。
APClusterDllMedium就像中介一樣,Java通過調用這個中介Dll中的CallAPClusterDll方法,間接調用真正的第三方Dll。
2)編譯生成.h文件
第一步:
javac APCluster.java 生成APCluster.class
第二步:
javah APCluster 生成APCluster.h頭文件,內容如下:
- /* DO NOT EDIT THIS FILE - it is machine generated */
- #include <jni.h>
- /* Header for class APCluster */
- #ifndef _Included_APCluster
- #define _Included_APCluster
- #ifdef __cplusplus
- extern "C" {
- #endif10 /*
- * Class: APCluster
- * Method: CallAPClusterDll
- * Signature: (I[DZ)[I
- */
- JNIEXPORT jintArray JNICALL Java_APCluster_CallAPClusterDll
- (JNIEnv *, jobject, jint, jdoubleArray, jboolean);
- #ifdef __cplusplus
- }
- #endif21
- #endif
注意,APCluster.h這個頭文件的內容是不能修改的,否則JNI會找不到相對應的CallAPClusterDll()的實現。
3)創建C/C++工程,實現CallAPClusterDll()方法。
創建一個C/C++工程,工程名為APClusterDllMedium(其實,生成的dll名為APClusterDllMedium即可),導入APCluster.h這個頭文件,并創建一個CPP文件,實現.h文件中的方法。
由于我創建的工程是win32控制臺程序,所以最后默認生成的是.exe文件,所以還要做一步工程屬性修改,讓它生成.dll后綴文件。
打開Project Property ->General,做以下修改:
下面就是實現 JNIEXPORT jintArray JNICALL Java_APCluster_CallAPClusterDll (JNIEnv *, jobject, jint, jdoubleArray, jboolean); 這個方法了。先貼代碼再慢慢解釋吧。
- #include "APCluster.h"
- #include <stdio.h>
- #include <windows.h>
- #ifdef __cplusplus
- extern "C" {
- #endif
- typedef int* (__stdcall *APCLUSTER32)(double*, unsigned int, bool);
- JNIEXPORT jintArray JNICALL Java_APCluster_CallAPClusterDll
- (JNIEnv *env, jobject _obj, jint _arg_int, jdoubleArray _arg_doublearray, jboolean _arg_boolean)
- {
- HMODULE dlh = NULL;
- APCLUSTER32 apcluster32;
- if (!(dlh=LoadLibrary("apclusterwin.dll"))) //第三方DLL位置
- {
- printf("LoadLibrary() failed: %d\n", GetLastError());
- }
- if (!(apcluster32 = (APCLUSTER32)GetProcAddress(dlh, "apcluster32"))) //具體調用apcluster32方法
- {
- printf("GetProcAddress() failed: %d\n", GetLastError());
- }
- int m_int = _arg_int; //類型轉換
- double* m_doublearray = env->GetDoubleArrayElements(_arg_doublearray, NULL);
- bool m_boolean = _arg_boolean;
- int* ret = (*apcluster32)(m_doublearray, m_int, m_boolean); /* actual function call */
- jintArray result = env->NewIntArray(_arg_int);
- env->SetIntArrayRegion(result, 0, _arg_int, (const jint*)ret);
- FreeLibrary(dlh); /* unload DLL and free memory */
- if(ret)
- {
- free(ret);
- }
- return result;
- }
- #ifdef __cplusplus
- }
- #endif
a)首先為了#include <jni.h>,必須添加JNI所在的目錄。
打開Project Property -> C/C++ -> General -> Additional Include Directories添加相應目錄:
b)在APCluster.h文件中自動生成的函數,只標識了函數參數類型,為了引用這些參數,自己起一個相應的名字:
JNIEXPORT jintArray JNICALL Java_APCluster_CallAPClusterDll
(JNIEnv *env, jobject _obj, jint _arg_int, jdoubleArray _arg_doublearray, jboolean _arg_boolean) ......
c)聲明函數指針,就是你要調用的第三方dll中函數的類型。
d)LoadLibrary,導入真正的第三方Dll,并找到要調用的方法的函數地址。
把這個函數地址賦值給函數指針,接下來就可以通過這個函數指針調用真正的apcluster函數了!
e)類型轉換:
讀讀jni.h文件就知道jdouble和double其實是一個東西,jboolean就是unsigned char類型,jni.h中是這么聲明的:
- typedef unsigned char jboolean;
- typedef unsigned short jchar;
- typedef short jshort;
- typedef float jfloat;
- typedef double jdouble;
但是數組類型就沒有這么簡單,獲取數組要使用類型相對應的env->GetTypeArrayElement(jTypeArray...)。
最后,要返回一個jint類型的數組,就要新創建一個此類型的數組,再為其賦值:
- jintArray result = env->NewIntArray(_arg_int);
- env->SetIntArrayRegion(result, 0, _arg_int, (const jint*)ret);
其中,_arg_int代表的是創建數組的長度。
最后return result。
4)Build這個工程。
Build,生成相應的APCluster.dll文件,將這個dll放到java工程目錄下。
5)編寫測試java程序,調用dll庫。
以下為測試程序,Test.java:
- public class Test
- {
- public static void main(String[] args)
- {
- double arg_doublearray[] = {0.1, 0.2, 0.3};
- int arg_int = 3;
- boolean arg_boolean = true;
- int[] result = new APCluster().CallAPClusterDll(arg_int, arg_doublearray, arg_boolean);
- .....
- }
- }
到此,java調用第三方dll就基本完成了。
本文也主要是介紹大概的操作流程,至于具體應該使用哪些API就只有去研究官方文檔了。
另外還有一些需要注意的問題,比如64位的程序去調用32位的dll會報錯啊等等...這些都是細節問題了。
最后,個人認為,自己動手實踐還是很重要,網上都說這個復雜那個難,但是至于難還是不難,還是要實踐了才知道...不能不去嘗試...
原文鏈接:http://www.cnblogs.com/AnnieKim/archive/2012/01/01/2309567.html
【編輯推薦】