最近在用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的工作模式
下面就举具体的例子说明一下使用步骤:
1 public class APCluster { 2 3 public native int[] CallAPClusterDll( int arg_Int, 4 double[] arg_DoubleArray, 5 boolean arg_boolean); 6 static 7 { 8 System.loadLibrary("APClusterDllMedium"); 9 } 10 }
上面是APCluster.java文件,定义了一个APCluster类,其中有一个方法CallAPClusterDll(),需要传递三种不同类型的参数,并且返回一个整型数组。
注意,这里只需要声明这个方法,并不需要实现,具体实现就在APClusterDllMedium中。
APClusterDllMedium就像中介一样,Java通过调用这个中介Dll中的CallAPClusterDll方法,间接调用真正的第三方Dll。
第一步:
javac APCluster.java 生成APCluster.class
第二步:
javah APCluster 生成APCluster.h头文件,内容如下:
1 /* DO NOT EDIT THIS FILE - it is machine generated */ 2 #include <jni.h> 3 /* Header for class APCluster */ 4 5 #ifndef _Included_APCluster 6 #define _Included_APCluster 7 #ifdef __cplusplus 8 extern "C" { 9 #endif 10 /* 11 * Class: APCluster 12 * Method: CallAPClusterDll 13 * Signature: (I[DZ)[I 14 */ 15 JNIEXPORT jintArray JNICALL Java_APCluster_CallAPClusterDll 16 (JNIEnv *, jobject, jint, jdoubleArray, jboolean); 17 18 #ifdef __cplusplus 19 } 20 #endif 21 #endif
注意,APCluster.h这个头文件的内容是不能修改的,否则JNI会找不到相对应的CallAPClusterDll()的实现。
创建一个C/C++工程,工程名为APClusterDllMedium(其实,生成的dll名为APClusterDllMedium即可),导入APCluster.h这个头文件,并创建一个CPP文件,实现.h文件中的方法。
图2 新建工程结构
由于我创建的工程是win32控制台程序,所以最后默认生成的是.exe文件,所以还要做一步工程属性修改,让它生成.dll后缀文件。
打开Project Property ->General,做以下修改:
图3 修改工程属性
下面就是实现JNIEXPORT jintArray JNICALL Java_APCluster_CallAPClusterDll(JNIEnv *, jobject, jint, jdoubleArray, jboolean);这个方法了。先贴代码再慢慢解释吧。
1 #include "APCluster.h" 2 #include <stdio.h> 3 #include <windows.h> 4 5 #ifdef __cplusplus 6 extern "C" { 7 #endif 8 9 typedef int* (__stdcall *APCLUSTER32)(double*, unsigned int, bool); 10 11 JNIEXPORT jintArray JNICALL Java_APCluster_CallAPClusterDll 12 (JNIEnv *env, jobject _obj, jint _arg_int, jdoubleArray _arg_doublearray, jboolean _arg_boolean) 13 { 14 HMODULE dlh = NULL; 15 APCLUSTER32 apcluster32; 16 17 if (!(dlh=LoadLibrary("apclusterwin.dll"))) //第三方DLL位置 18 { 19 printf("LoadLibrary() failed: %d\n", GetLastError()); 20 } 21 if (!(apcluster32 = (APCLUSTER32)GetProcAddress(dlh, "apcluster32"))) //具体调用apcluster32方法 22 { 23 printf("GetProcAddress() failed: %d\n", GetLastError()); 24 } 25 26 int m_int = _arg_int; //类型转换 27 double* m_doublearray = env->GetDoubleArrayElements(_arg_doublearray, NULL); 28 bool m_boolean = _arg_boolean; 29 30 int* ret = (*apcluster32)(m_doublearray, m_int, m_boolean); /* actual function call */ 31 32 jintArray result = env->NewIntArray(_arg_int); 33 env->SetIntArrayRegion(result, 0, _arg_int, (const jint*)ret); 34 35 FreeLibrary(dlh); /* unload DLL and free memory */ 36 if(ret) 37 { 38 free(ret); 39 } 40 41 return result; 42 } 43 44 #ifdef __cplusplus 45 } 46 #endif
a)首先为了#include <jni.h>,必须添加JNI所在的目录。
打开Project Property -> C/C++ -> General -> Additional Include Directories添加相应目录:
图4 添加JNI目录
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中是这么声明的:
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。
Build,生成相应的APCluster.dll文件,将这个dll放到java工程目录下。
图5 将生成的dll放到java工程下
以下为测试程序,Test.java:
1 public class Test 2 { 3 public static void main(String[] args) 4 { 5 double arg_doublearray[] = {0.1, 0.2, 0.3}; 6 int arg_int = 3; 7 boolean arg_boolean = true; 8 9 int[] result = new APCluster().CallAPClusterDll(arg_int, arg_doublearray, arg_boolean); 10 ..... 11 } 12 }
到此,java调用第三方dll就基本完成了。
本文也主要是介绍大概的操作流程,至于具体应该使用哪些API就只有去研究官方文档了。
另外还有一些需要注意的问题,比如64位的程序去调用32位的dll会报错啊等等...这些都是细节问题了。
最后,个人认为,自己动手实践还是很重要,网上都说这个复杂那个难,但是至于难还是不难,还是要实践了才知道...不能不去尝试...
(完)