之前应公司要求,利用java开发一个门禁系统,其要求是用封装好的DLL(C++写的)来做为接口,用java去调用该DLL库中暴露出来的接口,已完成门禁系统的逻辑开发。
对于我这样的实习僧来说实为当头一棒,从来没有接触过java还能跨平台,我了个ca,于是乎在内心几近崩溃的条件下,开始查资料,垒代码,最终算是完成了要求,现在分享如下,望需要的小伙伴们拾到干货。
首先,企业级开发吗,虽然对我这实习的不太实用,但是多少还要遵守流程的,首先是查资料。Java跨平台 有很多种方法,如JNI,Jawin,Jacob,JNative等,但是后者皆是以JNI为基础进行的开发,所以说JNI是最底层的,最强大的,读者想知道其他几种方法可以起官方文档查看。
知道了用JNI方式进行开发后,我们接下来就要了解JNI并进行一些简单的实现,比如打印我们的老朋友HelloWorld:
JNI -- Java Native Interface
1.简介.
JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了。
sun相关文档:http://java.sun.com/j2se/1.5.0/docs/guide/jni/spec/jniTOC.html
2.简单实例.
Java调用c++的dll实现在console端打印“Hello World !!!”
3.步骤.
①编写Java类,用该类将DLL对外提供的函数服务进行声明,其中的Java方法均声明为native,其方法签名可以自定义,不用实现函数体。实现如下:
<span style="font-size:18px;">public class HelloWorld { public native void show(); static { System.loadLibrary("hello"); } public static void main(String[] args) { TestJNI t = new TestJNI(); t.show(); } }</span>
②win+r打开cmd,cd 到java的到工程目录下的TestJNI.java文件处,执行
Javac TestJNI.java
Javah TestJNI
可以看到,在该文件目录下生成两连个文件,TestJNI.classes和TestJNI.h,其中TestJNI.h用来在c中调用。
TestJNI.h文件内容如下
<span style="font-size:18px;">/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class HelloWorld */ #ifndef _Included_HelloWorld #define _Included_HelloWorld #ifdef __cplusplus extern "C" { #endif /* * Class: HelloWorld * Method: show * Signature: ()V */ JNIEXPORT void JNICALL Java_HelloWorld_show (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif</span>
这里JNIEXPORT和JNICALL都是JNI的关键字,表示此函数是要被JNI调用的。而jint、jstring是以JNI为中介使JAVA中的数据类型与C/C++中的数据类型之间的一种中间类型。jint可以直接当做int使用,但是jstring不能和char *等同,需要做一定的转换。函数的名称是JAVA_再加上java程序的package路径再加函数名组成的。参数中,我们也只需要关心在JAVA程序中存在的参数,至于JNIEnv*和jclass我们一般没有必要去碰它。
④实现c/c++头文件HelloWorld.h的头文件 HelloWorld.cpp如下:(自己先建个win32的工程,就是可以生成dll的工程,不同编译器创建dll工程的方式不同,这个大家自己问度娘)
<span style="font-size:18px;">#include "jni.h" #include "TestJNI.h" #include <stdio.h> JNIEXPORT void JNICALL Java_TestJNI_show (JNIEnv *env, jobject obj) { printf("Hello World !!!"); return; }</span>
⑤编译生成hello.dll动态链接库
⑥在java编译器中,选择File->Import->FileSystem->Broswer->dll文件所在目录->勾选要导入的dll文件。
⑦运行 java HelloWorld(在编译器或者cmd下都可以)。
⑧如果在编译器下运行(STS为例),则Console中会出现Hello World!!!、
简单说,我们的流程就是在java中先建立java文件,用
导入要调用的dll包,dll包创建过程如上。<span style="font-size:18px;">static { System.loadLibrary("hello"); }</span>
简单的我们了解了。helloworld我也打印了,接下来进行比较高端的,其实也并不高端,就是你想调用的函数(如上的show()函数)里面有参数了,而且万一参数是指针,那怎么办,java中可没有指针,这里,大家先要了解JNI中的数据类型了。表如下(摘下来的):
Java类型 | 本地类型 | JNI中定义的别名 |
int | long | jint |
long | _int64 | jlong |
byte | signed char | jbyte |
boolean | unsigned char | jboolean |
char | unsigned short | jchar |
short | short | jshort |
float | float | jfloat |
double | double | jdouble |
Object | _jobject* | jobject |
方法签名包括两部分:参数类型和返回值类型;具体的格式:(参数1类型签名参数2类型签名)返回值类型签名。下面是java类型和年名类型的对照的一个表
Java类型 | 对应的签名 |
boolean | Z |
byte | B |
char | C |
shrot | S |
int | I |
long | L |
float | F |
double | D |
void | V |
Object | L用/分割包的完整类名; Ljava/lang/String; |
Array | [签名 [I [Ljava/lang/String; |
基本知识知道的差不多啦,接下来就得搞一下复杂的东西啦,在这里,我以工作中遇到的复杂类以及解决办法为例子,给大家直接上实例,我想,如果已经琢磨过了很久JNI的人,看过我这些例子之后就能明白很多问题,废话不多说,直接上例子:
首先。我们有一个需要调用的第三方DLL我们只能通过文档等手段了解到这个DLL的信息,比如接口,函数名,参数,返回值,功能。那么我们需要用JAVA去掉用,首先要查看DLL的函数名,这里推荐一些软件,在度娘中搜索DLL函数查看器,就可以找到很多可以查DLL函数名的软件,比如我用某款软件查到的函数名如下:
序数 函数名 地址 参数量
------------------------------------------------------------------
1 _PDB_AddDataBase@4 06761E20 1
2 _PDB_ChangeFeature@16 06761FD0 4
3 _PDB_DeleteDataBase@4 06761D40 1
4 _PDB_DeleteFeature@8 06761980 2
5 _PDB_MatchFeature@28 06761A70 7
6 _PDB_ResetDataBase@4 06761B70 1
7 _PDB_StoreFeature@12 06761860 3
8 _PFD_AgrRecogImg@12 06763480 3
9 _PFD_BlinkRecogImg@12 06763B60 3
10 _PFD_DirectRecogImg@12 06763910 3
11 _PFD_Exit@0 06761650 0
12 _PFD_FaceRecog@16 06763230 4
13 _PFD_FeatureMatching@16 067617A0 4
14 _PFD_GetFeature@16 067620D0 4
15 _PFD_GetFeatureByFaceInfo@24 06762C90 6
16 _PFD_GetFeatureByManual@20 06762560 5
17 _PFD_Init@4 06761330 1
18 _PFD_SmileRecogImg@12 067636B0 3
怎么样,巨恶心吧,不过不要紧,我们一点点分析,我通过找资料,明白了,这种方式定义的函数名是遵循__stdcall方式来进行引用的,这里__stdcall大家需要记住,当然了,还有很多别的引用方式,大家可以度娘。
接下来我们给出这个DLL文档中给出的一小撮结构体定义以及用到这些结构体作为参数的函数:
typedef struct _pfd_face_position {
short conf;
short rect_l;
short rect_r;
short rect_t;
short rect_b;
short eye_lx;
short eye_ly;
short eye_rx;
short eye_ry;
} PFD_FACE_POSITION;
typedef struct _pfd_detect_info{
PFD_FACE_POSITION faceInfo;
short ageConf;
short genConf;
short age;
short gen;
short smile;
short pitch;
short yaw;
short roll;
short lb;
short rb;
short flen;
}PFD_DETECT_INFO;
typedef struct _pfd_face_detect {
short num;
PFD_DETECT_INFO info[PFD_MAX_FACE_NUM];
} PFD_FACE_DETECT;
/*
DLL_API int __stdcall PFD_FaceRecog(unsigned char * bmpData,PFD_FACE_DETECT* faceInfo,int faceInfoFlag,short faceRote);
DLL_API int __stdcall PFD_GetFeature(unsigned char * bmpData,PFD_FACE_DETECT* faceInfo,unsigned char ** feature,short faceRote);......*/
知道了结构体的内容后,和DLL中函数的声明以后,我们就到重点了,路漫漫其修远兮啊、、、PFD_FaceRecog()这个函数的参数有unsigned char*,结构体指针,这怎么调用?java中并没有这种数据类型啊。
这里我们用到了封装的思想,即把一个复杂的DLL封装成一个参数简单,易于调用的DLL,比如我们可以将其封装成java中都有的数据类型,以方便调用。那我们就得想怎么变成java中的数据类型,在这里我是这样对PFD_FaceRecog()这个函数中的参数做变化的。我在java中做了这样的函数:
public native int FaceRecog(byte[] bmpData, PFD_FACE_DETECT faceInfo, int faceInfoFlag, short faceRote);
即,将unsigned char*->byte[],结构体指针->java中的类
所以按照上面helloworld的方式,我就做了这样的java类:
public class getFunction {
public native int FaceRecog(byte[] bmpData, PFD_FACE_DETECT faceInfo, int faceInfoFlag, short faceRote);
public native int GetFeature(byte[] bmpData,PFD_FACE_DETECT faceInfo,byte[] feature,short faceRote);
/*......*/
static {
System.loadLibrary("test");
}
这里,我那个作为封装dll的dll的名为test.dll;
有了这个java文件后,我们就参照上方的HelloWorld的步骤,创建一个空的dll文件,来做为封装文件。我就直接把我做的对PFD_FaceRrecog()函数和GetFeature()函数的封装给大家,参数比较典型,大家可以对照,看看我是如何在这个里面进行java和c++参数类型转换的,如下(这里面凡是碰到你没见过的函数或执行方式,就去问度娘,度娘解释的很多,很简单):
#include<windows.h>
#include<jni.h>
#include"face.h"
#include"eval.h"
typedef int(__stdcall *FaceRecog)(unsigned char *, PFD_FACE_DETECT*, int, short);
JNIEXPORT jint JNICALL Java_getFunction_FaceRecog(JNIEnv *env, jobject,
jbyteArray bmpData, jobject faceInfo, jint faceInfoFlag, jshort faceRote) {
//获取java中实例类PFD_FACE_POSITION
jclass jcpfp = env->FindClass("PFD_FACE_POSITION");
//short conf
jfieldID jfconf = env->GetFieldID(jcpfp, "conf", "S");
//short rect_l
jfieldID jfrect_l = env->GetFieldID(jcpfp, "rect_l", "S");
//short rect_r
jfieldID jfrect_r = env->GetFieldID(jcpfp, "rect_r", "S");
//short rect_t
jfieldID jfrect_t = env->GetFieldID(jcpfp, "rect_t", "S");
//short rect_b
jfieldID jfrect_b = env->GetFieldID(jcpfp, "rect_b", "S");
//short eye_lx
jfieldID jfeye_lx = env->GetFieldID(jcpfp, "eye_lx", "S");
//short eye_ly
jfieldID jfeye_ly = env->GetFieldID(jcpfp, "eye_ly", "S");
//short eye_rx
jfieldID jfeye_rx = env->GetFieldID(jcpfp, "eye_rx", "S");
//short eye_ry
jfieldID jfeye_ry = env->GetFieldID(jcpfp, "eye_ry", "S");
//获取java中实例类PFD_DETECT_INFO
jclass jcpdi = env->FindClass("PFD_DETECT_INFO");
//PFD_FACE_POSITION faceInfo
jfieldID jfpfp = env->GetFieldID(jcpdi, "faceInfo", "LPFD_FACE_POSITION;");
//short ageConf
jfieldID jfageConf = env->GetFieldID(jcpdi, "ageConf", "S");
//short genConf
jfieldID jfgenConf = env->GetFieldID(jcpdi, "genConf","S");
//short age
jfieldID jfage = env->GetFieldID(jcpdi, "age","S");
//short gen
jfieldID jfgen = env->GetFieldID(jcpdi, "gen","S");
//short smile
jfieldID jfsmile = env->GetFieldID(jcpdi, "smile", "S");
//short pitch
jfieldID jfpitch = env->GetFieldID(jcpdi, "pitch","S");
//short yaw
jfieldID jfyaw = env->GetFieldID(jcpdi, "yaw", "S");
//short roll
jfieldID jfroll = env->GetFieldID(jcpdi, "roll", "S");
//short lb
jfieldID jflb = env->GetFieldID(jcpdi, "lb", "S");
//short rb
jfieldID jfrb = env->GetFieldID(jcpdi, "rb","S");
//short flen
jfieldID jfflen = env->GetFieldID(jcpdi, "flen", "S");
//获取java中实例类PFD_FACE_DETECT
jclass jcpfd = env->FindClass("PFD_FACE_DETECT");
//short num
jfieldID jfnum = env->GetFieldID(jcpfd, "num", "S");//short->S
//PFD_DETECT_INFO[] info
jfieldID jfpdi = env->GetFieldID(jcpfd, "info", "[LPFD_DETECT_INFO;");
PFD_FACE_DETECT pfd;//c结构体
//获取实例中的num值给c结构体的num值
pfd.num = env->GetShortField(faceInfo, jfnum);
//获取实例中的PFD_DETECT_INFO类的值给c结构体的PFD_DETECT_INFO值
//jobjectArray joinfo = (jobjectArray)env->GetObjectField(faceInfo, jfpdi);
//int infolen = env->GetArrayLength(joinfo);
jobject joinfo = env->GetObjectField(faceInfo, jfpdi);
//PFD_DETECT_INFO *info1 =(PFD_DETECT_INFO *)env->GetObjectArrayElement(joinfo, 0);
//java中的PFD_DETECT_INFO[]对象数组 --> c中的PFD_DETECT_INFO[]结构体数组,需要上面的注释吗?还是直接像这句话这样。
//memcpy(pfd.info, joinfo, infolen);
pfd.info->ageConf = env->GetShortField(joinfo, jfageConf);
pfd.info->genConf = env->GetShortField(joinfo, jfgenConf);
pfd.info->age = env->GetShortField(joinfo, jfage);
pfd.info->gen = env->GetShortField(joinfo, jfgen);
pfd.info->smile = env->GetShortField(joinfo, jfsmile);
pfd.info->pitch = env->GetShortField(joinfo, jfpitch);
pfd.info->yaw = env->GetShortField(joinfo, jfyaw);
pfd.info->roll = env->GetShortField(joinfo, jfroll);
pfd.info->lb = env->GetShortField(joinfo, jflb);
pfd.info->rb = env->GetShortField(joinfo, jfrb);
pfd.info->flen = env->GetShortField(joinfo, jfflen);
//获取PFD_FACE_POSITION对象
jobject jopfp = env->GetObjectField(joinfo, jfpfp);
pfd.info->faceInfo.conf = env->GetShortField(jopfp, jfconf);
pfd.info->faceInfo.rect_l = env->GetShortField(jopfp, jfrect_l);
pfd.info->faceInfo.rect_r = env->GetShortField(jopfp, jfrect_r);
pfd.info->faceInfo.rect_t = env->GetShortField(jopfp, jfrect_t);
pfd.info->faceInfo.rect_b = env->GetShortField(jopfp, jfrect_b);
pfd.info->faceInfo.eye_lx = env->GetShortField(jopfp, jfeye_lx);
pfd.info->faceInfo.eye_ly = env->GetShortField(jopfp, jfeye_ly);
pfd.info->faceInfo.eye_rx = env->GetShortField(jopfp, jfeye_rx);
pfd.info->faceInfo.eye_ry = env->GetShortField(jopfp, jfeye_ry);
HINSTANCE hDLL = LoadLibrary("disanfang.dll");
if(!hDLL) {
printf("cannot get DLL!");
}
int k;
FaceRecog fr = (FaceRecog) GetProcAddress(hDLL, "_PFD_FaceRecog@16");
if(fr) {
unsigned char *bmpData1 = (unsigned char*)env->GetByteArrayElements(bmpData, 0);
k = fr(bmpData1, &pfd, faceInfoFlag, faceRote);
printf("get FaceRecog success\n");
return k;
} else {
printf("get FaceRecog failed\n");
return 0;
}
}
#include<windows.h>
#include<jni.h>
#include"face.h"
#include"eval.h"
typedef int(__stdcall *GetFeature)(unsigned char *, PFD_FACE_DETECT*, unsigned char *, short);
JNIEXPORT jint JNICALL Java_getFunction_GetFeature(JNIEnv *env, jobject,
jbyteArray bmpData, jobject faceInfo, jstring feature, jshort faceRote) {
//获取java中实例类PFD_FACE_POSITION
jclass jcpfp = env->FindClass("PFD_FACE_POSITION");
//short conf
jfieldID jfconf = env->GetFieldID(jcpfp, "conf", "S");
//short rect_l
jfieldID jfrect_l = env->GetFieldID(jcpfp, "rect_l", "S");
//short rect_r
jfieldID jfrect_r = env->GetFieldID(jcpfp, "rect_r", "S");
//short rect_t
jfieldID jfrect_t = env->GetFieldID(jcpfp, "rect_t", "S");
//short rect_b
jfieldID jfrect_b = env->GetFieldID(jcpfp, "rect_b", "S");
//short eye_lx
jfieldID jfeye_lx = env->GetFieldID(jcpfp, "eye_lx", "S");
//short eye_ly
jfieldID jfeye_ly = env->GetFieldID(jcpfp, "eye_ly", "S");
//short eye_rx
jfieldID jfeye_rx = env->GetFieldID(jcpfp, "eye_rx", "S");
//short eye_ry
jfieldID jfeye_ry = env->GetFieldID(jcpfp, "eye_ry", "S");
//获取java中实例类PFD_DETECT_INFO
jclass jcpdi = env->FindClass("PFD_DETECT_INFO");
//PFD_FACE_POSITION faceInfo
jfieldID jfpfp = env->GetFieldID(jcpdi, "faceInfo", "LPFD_FACE_POSITION;");
//short ageConf
jfieldID jfageConf = env->GetFieldID(jcpdi, "ageConf", "S");
//short genConf
jfieldID jfgenConf = env->GetFieldID(jcpdi, "genConf","S");
//short age
jfieldID jfage = env->GetFieldID(jcpdi, "age","S");
//short gen
jfieldID jfgen = env->GetFieldID(jcpdi, "gen","S");
//short smile
jfieldID jfsmile = env->GetFieldID(jcpdi, "smile", "S");
//short pitch
jfieldID jfpitch = env->GetFieldID(jcpdi, "pitch","S");
//short yaw
jfieldID jfyaw = env->GetFieldID(jcpdi, "yaw", "S");
//short roll
jfieldID jfroll = env->GetFieldID(jcpdi, "roll", "S");
//short lb
jfieldID jflb = env->GetFieldID(jcpdi, "lb", "S");
//short rb
jfieldID jfrb = env->GetFieldID(jcpdi, "rb","S");
//short flen
jfieldID jfflen = env->GetFieldID(jcpdi, "flen", "S");
//获取java中实例类PFD_FACE_DETECT
jclass jcpfd = env->FindClass("PFD_FACE_DETECT");
//short num
jfieldID jfnum = env->GetFieldID(jcpfd, "num", "S");//short->S
//PFD_DETECT_INFO[] info
jfieldID jfpdi = env->GetFieldID(jcpfd, "info", "[LPFD_DETECT_INFO;");
PFD_FACE_DETECT pfd;//c结构体
//获取实例中的num值给c结构体的num值
pfd.num = env->GetShortField(faceInfo, jfnum);
//获取实例中的PFD_DETECT_INFO类的值给c结构体的PFD_DETECT_INFO值
//jobjectArray joinfo = (jobjectArray)env->GetObjectField(faceInfo, jfpdi);
//int infolen = env->GetArrayLength(joinfo);
jobject joinfo = env->GetObjectField(faceInfo, jfpdi);
//PFD_DETECT_INFO *info1 =(PFD_DETECT_INFO *)env->GetObjectArrayElement(joinfo, 0);
//java中的PFD_DETECT_INFO[]对象数组 --> c中的PFD_DETECT_INFO[]结构体数组,需要上面的注释吗?还是直接像这句话这样。
//memcpy(pfd.info, joinfo, infolen);
pfd.info->ageConf = env->GetShortField(joinfo, jfageConf);
pfd.info->genConf = env->GetShortField(joinfo, jfgenConf);
pfd.info->age = env->GetShortField(joinfo, jfage);
pfd.info->gen = env->GetShortField(joinfo, jfgen);
pfd.info->smile = env->GetShortField(joinfo, jfsmile);
pfd.info->pitch = env->GetShortField(joinfo, jfpitch);
pfd.info->yaw = env->GetShortField(joinfo, jfyaw);
pfd.info->roll = env->GetShortField(joinfo, jfroll);
pfd.info->lb = env->GetShortField(joinfo, jflb);
pfd.info->rb = env->GetShortField(joinfo, jfrb);
pfd.info->flen = env->GetShortField(joinfo, jfflen);
//获取PFD_FACE_POSITION对象
jobject jopfp = env->GetObjectField(joinfo, jfpfp);
pfd.info->faceInfo.conf = env->GetShortField(jopfp, jfconf);
pfd.info->faceInfo.rect_l = env->GetShortField(jopfp, jfrect_l);
pfd.info->faceInfo.rect_r = env->GetShortField(jopfp, jfrect_r);
pfd.info->faceInfo.rect_t = env->GetShortField(jopfp, jfrect_t);
pfd.info->faceInfo.rect_b = env->GetShortField(jopfp, jfrect_b);
pfd.info->faceInfo.eye_lx = env->GetShortField(jopfp, jfeye_lx);
pfd.info->faceInfo.eye_ly = env->GetShortField(jopfp, jfeye_ly);
pfd.info->faceInfo.eye_rx = env->GetShortField(jopfp, jfeye_rx);
pfd.info->faceInfo.eye_ry = env->GetShortField(jopfp, jfeye_ry);
HINSTANCE hDLL = LoadLibrary("EVAL_x86_Accuracy.dll");
if(!hDLL) {
printf("cannot get EVAL_x86_Accuracy.dll!");
}
int k=0;
GetFeature gf = (GetFeature) GetProcAddress(hDLL, "_PFD_GetFeature@16");
if(gf) {
unsigned char *bmpData1 = (unsigned char*)env->GetByteArrayElements(bmpData,0);
unsigned char *feature1 = (unsigned char*)env->GetStringUTFChars(feature,0);
k = gf(bmpData1, &pfd, feature1, faceRote);
env->ReleaseStringUTFChars(feature, (const char*)feature1);
printf("get GetFeature success\n");
return k;
} else {
printf("get GetFeature failed\n");
return 0;
}
}
这里面其实是很有逻辑的,你只要明白了FindClass和GetFieldID就能很好的做出来这样的转换。之后就简单了,我们运行,然后把得出的dll带如我们那个java文件的工程里,记住,要把那个disanfang.dll也一起导进去,然后我们运行java文件,这里我们知道第三方的DLL文件的文档中给出,PFD_FaceRecog()函数返回的是整数,没种整数代表返回的状态,-1代表异常结束,0代表正常结束,-2验证失败,我们运行后发现,console显示-2,先不管结果对不对,有结果说明我们的数据流已经走通了,证明这一系列的步骤是正确的,之所以是验证失败,是因为我的这个dll是有安全保护的,需要插上key才会成功运行与成功验证。
以上即为笔者用JAVA调用第三方dll的过程,虽然过程艰辛,但是最后得出正确结果的时候内心还是异常欢喜的,只要我们认真的去分析,去思考,去实践,去阅览,那没有什么问题可以难倒我们。