Java調用C/C++編寫的第三方dll動態鏈接庫(非native API)--- JNI


注:2013年6月6日,我對該博文進行了修改,增加了源代碼以及更直觀詳細的講解。如果需要代碼,可以到文章最后給出的鏈接進行下載。

 

最近在用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   JNI的工作模式

下面就舉具體的例子說明一下使用步驟

先說明一下我們要達到的目的

假設我們現在有一個第三方dll叫做ThirdPartyDll.dll,我們想調用其中的realfunction方法,該方法的接口在ThirdParty.h中給出,如下:

 1 // The ThirdPartyDll.dll contains the following function.
 2 // Name: 
 3 //   realfunction
 4 // Function:
 5 //   Turn a double type array to an int type array.
 6 // Parameters:
 7 //   nDoubleArray: a double type array needed to cutoff.
 8 //   nSize: the size of nDoubleArray.
 9 //   nPrint: print the original array if nPrint == true.
10 // Return Value:
11 //   This function will allocate a new int type array inside for storing the result.
12 //   So, DO NOT forget to release the space after using the result! 
13 //   (Of course, we'd better not to design a function like this when coding:))
14 // ----------------------------------------------------------------------------------
15 
16 int* realfunction(double* nDoubleArray, unsigned int nSize, bool nPrint);

我們現在要嘗試根據這個dll和這個函數接口,在java程序中通過一個中介dll調用該函數。

1) 編寫一個類,聲明native方法

 1 public class CallThirdParty {
 2       
 3       public native int[] CallThirdPartyDll(double[]    arg_DoubleArray,
 4                                             int         arg_SizeofArray,
 5                                             boolean     arg_print);
 6       static
 7       {
 8           System.loadLibrary("MediumDll");
 9       }
10 }

上面是CallThirdParty.java文件,定義了一個CallThirdParty類,其中有一個方法CallThirdPartyDll(),需要傳遞三種不同類型的參數,並且返回一個整型數組。

注意,這里只需要聲明這個方法,並不需要實現,具體實現就在MediumDll中。

MediumDll就像中介一樣,Java通過調用這個中介Dll中的CallThirdPartyDll方法,間接調用真正的第三方Dll。

2)編譯生成.h文件

cmd到CallThirdParty.java目錄下,執行以下幾個命令,生成.h文件。(需要設定java環境變量)

第一步:

javac  CallThirdParty.java 生成CallThirdParty.class

第二步:

javah  CallThirdParty 生成CallThirdParty.h頭文件,內容如下:

 
        
 1 /* DO NOT EDIT THIS FILE - it is machine generated */
 2 #include <jni.h>
 3 /* Header for class CallThirdParty */
 4 
 5 #ifndef _Included_CallThirdParty
 6 #define _Included_CallThirdParty
 7 #ifdef __cplusplus
 8 extern "C" {
 9 #endif
10 /*
11  * Class:     CallThirdParty
12  * Method:    CallThirdPartyDll
13  * Signature: ([DIZ)[I
14  */
15 JNIEXPORT jintArray JNICALL Java_CallThirdParty_CallThirdPartyDll
16   (JNIEnv *, jobject, jdoubleArray, jint, jboolean);
17 
18 #ifdef __cplusplus
19 }
20 #endif
21 #endif

注意,CallThirdParty.h這個頭文件的內容是不能修改的,否則JNI會找不到相對應的CallThirdPartyDll()的實現。

3)創建C/C++工程,實現CallThirdPartyDll()方法。

創建一個C/C++工程,工程名為MediumDll(其實,生成的dll名為MediumDll即可),導入上一步生成的CallThirdParty.h這個頭文件以及官方給出的接口文件ThirdParty.h,並創建一個CPP文件,實現CallThirdParty.h文件中的方法。

        圖2   新建工程結構

由於我默認創建的工程是win32控制台程序並且最后生成的是.exe文件,所以還要做一步工程屬性修改,讓它生成.dll后綴文件。

打開Project Property ->General,做以下修改:

                                  圖3   修改工程屬性

下面就是實現 JNIEXPORT jintArray JNICALL Java_CallThirdParty_CallThirdPartyDll (JNIEnv *, jobject, jdoubleArray, jint, jboolean); 這個方法了。先貼代碼再慢慢解釋吧。

 1 #include "CallThirdParty.h"
 2 #include "ThirdParty.h"
 3 #include <iostream>
 4 #include <Windows.h>
 5 using namespace std;
 6 
 7 #ifdef __cplusplus
 8 extern "C" {
 9 #endif
10 
11 typedef int* (*ThirdPartyFunc)(double*, unsigned int, bool);
12 
13 JNIEXPORT jintArray JNICALL Java_CallThirdParty_CallThirdPartyDll (JNIEnv *env, jobject _obj, jdoubleArray _arg_doublearray, jint _arg_int, jboolean _arg_boolean)
14 {
15     HMODULE dlh = NULL;
16     ThirdPartyFunc thirdPartyFunc;
17 
18     if (!(dlh=LoadLibrary("ThirdPartyDll.dll")))      
19     {
20     printf("LoadLibrary() failed: %d\n", GetLastError()); 
21     }
22     if (!(thirdPartyFunc = (ThirdPartyFunc)GetProcAddress(dlh, "realfunction")))  
23     {
24         printf("GetProcAddress() failed: %d\n", GetLastError()); 
25     }
26 
27     int        m_int = _arg_int;  
28     double*    m_doublearray = env->GetDoubleArrayElements(_arg_doublearray, NULL);
29     bool       m_boolean = _arg_boolean;
30 
31     int* ret = (*thirdPartyFunc)(m_doublearray, m_int, m_boolean); /* actual function call */
32     
33     jintArray result = NULL;
34     if (ret)
35     {
36         result = env->NewIntArray(_arg_int);
37         env->SetIntArrayRegion(result, 0, _arg_int, (const jint*)ret);
38     }
39 
40     FreeLibrary(dlh); /* unload DLL and free memory */
41     if(ret) 
42     {
43         free(ret); 
44     }
45 
46     return result;
47 }
48 
49 #ifdef __cplusplus
50 }
51 #endif

a)首先為了#include <jni.h>,必須添加JNI所在的目錄。

打開Project Property -> C/C++ -> General -> Additional Include Directories添加相應目錄:

                                                         圖4   添加JNI目錄

b)在CallThirdParty.h文件中自動生成的函數,只標識了函數參數類型,為了引用這些參數,自己起一個相應的名字:

JNIEXPORT jintArray JNICALL Java_CallThirdParty_CallThirdPartyDll
(JNIEnv *env, jobject _obj, jint _arg_int, jdoubleArray _arg_doublearray, jboolean _arg_boolean) ......

c)聲明函數指針,就是你要調用的第三方dll中函數的類型。

d)LoadLibrary,導入真正的第三方Dll,並找到要調用的方法的函數地址

把這個函數地址賦值給函數指針,接下來就可以通過這個函數指針調用真正的realfunction函數了!

e)類型轉換:

讀讀jni.h文件就知道jdouble和double其實是一個東西,jboolean就是unsigned char類型,jni.h中是這么聲明的:

1 typedef unsigned char   jboolean;
2 typedef unsigned short  jchar;
3 typedef short           jshort;
4 typedef float           jfloat;
5 typedef double          jdouble;

但是數組類型就沒有這么簡單,獲取數組要使用類型相對應的env->GetTypeArrayElement(jTypeArray...)。

最后,要返回一個jint類型的數組,就要新創建一個此類型的數組,再為其賦值:

1 jintArray result = env->NewIntArray(_arg_int);
2 env->SetIntArrayRegion(result, 0, _arg_int, (const jint*)ret);

其中,_arg_int代表的是創建數組的長度。

最后return result。

4)Build這個工程。

Build,生成相應的MediumDll.dll文件,並將其與真正要調用的第三方動態鏈接庫ThirdPartyDll.dll放到java工程目錄下。

               圖5   將生成的dll放到java工程下

5)編寫測試java程序,調用dll庫。

以下為測試程序,Test.java:

 1 public class Test {
 2      public static void main(String[] args) {
 3          
 4          double doubleArray[] = {1.1, 2.5, 5,2};
 5          
 6          CallThirdParty callThirdParty = new CallThirdParty();
 7          int cutOffArray[] = callThirdParty.CallThirdPartyDll(doubleArray, 3, true);
 8          
 9          for (int i = 0; i < cutOffArray.length; ++i)
10              System.out.println(cutOffArray[i]);
11      }
12 }

運行,控制台輸出結果:

Value of 0: 1.1
Value of 1: 2.5
Value of 2: 5
1
2
5

到此,java調用第三方dll就基本完成了。

本文也主要是介紹大概的操作流程,至於具體應該使用哪些API就只有去研究官方文檔了。

另外還有一些需要注意的問題,比如64位的程序去調用32位的dll會報錯啊等等...這些都是細節問題了。

最后,個人認為,自己動手實踐還是很重要,網上都說這個復雜那個難,但是至於難還是不難,還是要實踐了才知道...不能不去嘗試...

-----------------------------------------------

最后的最后,附上相關代碼的鏈接:https://github.com/AnnieKim/ForMyBlog/tree/master/20120101

其中,projects文件夾下是工程文件,在eclipse和vs2008下進行,如果可以直接運行那就最好了。

另外,source文件夾下是所有的相關代碼,包括第三方dll和生成的Mediumdll。

如果遇到什么問題,歡迎討論。

(完)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM