这里主要用的方法是JNI。在网上查资料时看到很多人说用JNI非常的复杂,不仅要看很多的文档,而且要非常熟悉C/C++编程。恐怕有很多人在看到诸如此类的评论时已经决定绕道用其他方法了。但是,假如你要实现的功能并不复杂(简单的参数传递,获取返回值等等),我还是支持使用这个方法的。
Java Native Interface,简称JNI,是Java平台的一部分,可用于让Java和其他语言编写的代码进行交互。下面是从网上摘取的JNI工作示意图。
图1 JNI的工作模式
下面就举具体的例子说明一下使用步骤:
先说明一下我们要达到的目的:
假设我们现在有一个第三方dll叫做ThirdPartyDll.dll,我们想调用其中的realfunction方法,该方法的接口在ThirdParty.h中给出,如下:
// The ThirdPartyDll.dll contains the following function.
// Name:
// realfunction
// Function:
// Turn a double type array to an int type array.
// Parameters:
// nDoubleArray: a double type array needed to cutoff.
// nSize: the size of nDoubleArray.
// nPrint: print the original array if nPrint == true.
// Return Value:
// This function will allocate a new int type array inside for storing the result.
// So, DO NOT forget to release the space after using the result!
// (Of course, we'd better not to design a function like this when coding:))
// ----------------------------------------------------------------------------------
int* realfunction(double* nDoubleArray, unsigned int nSize, bool nPrint);
我们现在要尝试根据这个dll和这个函数接口,在java程序中通过一个中介dll调用该函数。
1) 编写一个类,声明native方法
public class CallThirdParty {
public native int[] CallThirdPartyDll(double[] arg_DoubleArray,
int arg_SizeofArray,
boolean arg_print);
static
{
System.loadLibrary("MediumDll");
}
}
上面是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头文件,内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class CallThirdParty */
#ifndef _Included_CallThirdParty
#define _Included_CallThirdParty
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: CallThirdParty
* Method: CallThirdPartyDll
* Signature: ([DIZ)[I
*/
JNIEXPORT jintArray JNICALL Java_CallThirdParty_CallThirdPartyDll
(JNIEnv *, jobject, jdoubleArray, jint, jboolean);
#ifdef __cplusplus
}
#endif
#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); 这个方法了。先贴代码再慢慢解释吧。
#include "CallThirdParty.h"
#include "ThirdParty.h"
#include <iostream>
#include <Windows.h>
using namespace std;
#ifdef __cplusplus
extern "C" {
#endif
typedef int* (*ThirdPartyFunc)(double*, unsigned int, bool);
JNIEXPORT jintArray JNICALL Java_CallThirdParty_CallThirdPartyDll (JNIEnv *env, jobject _obj, jdoubleArray _arg_doublearray, jint _arg_int, jboolean _arg_boolean)
{
HMODULE dlh = NULL;
ThirdPartyFunc thirdPartyFunc;
if (!(dlh=LoadLibrary("ThirdPartyDll.dll")))
{
printf("LoadLibrary() failed: %d\n", GetLastError());
}
if (!(thirdPartyFunc = (ThirdPartyFunc)GetProcAddress(dlh, "realfunction")))
{
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 = (*thirdPartyFunc)(m_doublearray, m_int, m_boolean); /* actual function call */
jintArray result = NULL;
if (ret)
{
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添加相应目录:
图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工程下
具体目录需要根据调试器进行配置(可以参考上次的文章“在Java程序中加载Native/Dynamic DLL”),或者直接利用system.load("absolute path"),直接指定绝对路径,
5)编写测试java程序,调用dll库。
以下为测试程序,Test.java:
public class Test {
public static void main(String[] args) {
double doubleArray[] = {1.1, 2.5, 5,2};
CallThirdParty callThirdParty = new CallThirdParty();
int cutOffArray[] = callThirdParty.CallThirdPartyDll(doubleArray, 3, true);
for (int i = 0; i < cutOffArray.length; ++i)
System.out.println(cutOffArray[i]);
}
}
运行,控制台输出结果:
Value of 0: 1.1
Value of 1: 2.5
Value of 2: 5
1
2
5
到此,java调用第三方dll就基本完成了。
本文也主要是介绍大概的操作流程,至于具体应该使用哪些API就只有去研究官方文档了。
另外还有一些需要注意的问题,比如64位的程序去调用32位的dll会报错(需要32位的Eclipse和32位的JRE才能调用32位dll)啊等等...这些都是细节问题了。