最近在开发j2ee的项目中,需要读取面部识别考勤终端机中的数据,厂商提供二次开发需要的就是一个开发指南说明书和2个dll文件:HwDevComm.dll、HDCP_Utils.dll。其中重要的就是HwDevComm.dll。
用到的函数有3个:
1、 执行各种命令
int HwDev_Execute( char * pDevInfoBuf, unsigned long nDevInfoLen,
char * pSendBuf, unsigned long nSendLen,
char ** pRecvBuf, unsigned long * pRecvLen,
FuncTotalDoneTp pFuncTotalDone)
2、 释放存放返回数据的内存
void HwDev_Finish(char **pRecvBuf)
3、 服务器端启动或者关闭对特定端口的监听服务,用来接收考勤机主动上传的信息
int HwDev_Server(int nSwith, char *pServerInfoBuf,
unsigned long nServerInfoLen,
FuncProcessData pFuncProcessData)
Java是使用jni来调用本地dll文件的
通过上网查找和自己的摸索,现就自己在开发过程中遇到的问题和解决方法总结如下(下面仅以HwDev_Execute 方法的实现为例):
开发环境:
我的开发环境是:操作系统:window7
开发语言:java、vc++
java开发环境:myeclipse6.0、jdk1.6
vc++开发环境:visual studio 2008
第一步:java中的准备工作
1、 在myeclipse中创建一个java项目,新建一个类Face,
这个类可以看作是对上面的3个方法的封装类。具体的实现如下所示:
public class Face {
static {
// 装载库文件face.dll,不包括库文件的扩展名
// face.dll必须是在java.library.path这一jvm变量所指向的路径下,
//该路径可以通过System.getProperty("java.library.path");来获得
System.loadLibrary("face");
}
private native String HwDev_Execute(String sDevInfoBuf, long lDevInfoLen, String sSendBuf, long lSendLen, String[] spRecvBuf, long[] lpRecvLen);
//对外接口
public String HwDev_Execute_temp(String sDevInfoBuf, long lDevInfoLen, String sSendBuf, long lSendLen, String[] spRecvBuf, long[] lpRecvLen) {
return this.HwDev_Execute(sDevInfoBuf, lDevInfoLen, sSendBuf, lSendLen, spRecvBuf, lpRecvLen);
}
}
说明:java中使用System.loadLibrary("face")来装载本地库文件,我们将要在下面步骤中生成face.dll文件,java就是通过调用生成的face.dll文件来获得考勤机上的数据。
用native修饰的方法是本地方法,java中不提供方法的实现。
2、 用javac编译生成class文件(如果是用myeclipse等IDE开发就会自动编译生成class文件),再用javah命令生成.h的头文件。具体的执行如下(在cmd下):
javac Face.java
javah Face
此时,就会在class文件的路径下生成名为Face.h的头文件,头文件的内容如下所示:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Face */
#ifndef _Included_Face
#define _Included_Face
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: Face
* Method: HwDev_Execute
* Signature: (Ljava/lang/String;JLjava/lang/String;J[Ljava/lang/String;[J)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_Face_HwDev_1Execute
(JNIEnv *, jobject, jstring, jlong, jstring, jlong, jobjectArray, jlongArray);
#ifdef __cplusplus
}
#endif
#endif
第二步:vc++的准备工作
1、 打开visual Studio 2008,新建一个项目,选择Win32—>win32项目,项目名称为:face。如下图所示:
点击确定,然后点击下一步,进入应用程序设置的页面,如下图所示:
在应用程序类型下选择”DLL”,在附加选项中选中”导出符号”,点击完成。
生成的项目的目录结构如下图所示:
2、 将Face.h中的内容复制到vc++项目的face.h的中。在face.h 的头文件中包含了jni.h头文件,所以需要将jdk安装目录下include文件夹下的jni.h头文件和include\win32文件夹下的jawt_md.h和jni_md.h头文件拷贝到visual studio的安装目录下vc\include文件夹下。我的路径是:C:\Program Files\Microsoft Visual Studio 9.0\VC\include
也可以把jdk下的那三个头文件拷贝到vc++的项目目录下,我的项目目录路径是:
C:\Users\wangzw\Documents\Visual Studio 2008\Projects\face\face,如果是拷贝到了项目的路径下的话,就需要将face.h头文件中的#include <jni.h>改为#include “jni.h”
3、 在face.cpp中加入如下代码:
#include "stdafx.h"
#include "face.h"
#include <tchar.h>
#include <iostream>
using namespace std;
//参数需要和商家提供的DLL文件中方法的参数一致
typedef int (CALLBACK FuncTotalDoneTp)( unsigned long nTotal, unsigned long nDone );
//参数需要和商家提供的DLL文件中方法的参数一致
typedef int (_stdcall *EXECUTE)(char * pDevInfoBuf,unsigned long nDevInfoLen,char * pSendBuf,unsigned longnSendLen,char ** pRecvBuf,unsigned long * pRecvLen,FuncTotalDoneTp pFuncTotalDone);
//参数需要和商家提供的DLL文件中方法的参数一致
typedef int (_stdcall *FINISH)(char **pRecvBuf);
HINSTANCE dllHandle;
int result;
//将jstring 转换为 char*
char* jstringTostring(JNIEnv* env, jstring jstr)
{
char* rtn = NULL;
jclass clsstring = env->FindClass("java/lang/String");
jstring strencode = env->NewStringUTF("utf-8");
jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barr= (jbyteArray)env->CallObjectMethod(jstr, mid, strencode);
jsize alen = env->GetArrayLength(barr);
jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
if (alen > 0)
{
rtn = (char*)malloc(alen + 1);
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
env->ReleaseByteArrayElements(barr, ba, 0);
return rtn;
}
//将char* 转换为 jstring
jstring stoJstring(JNIEnv* env, const char* pat)
{
jclass strClass = env->FindClass("Ljava/lang/String;");
jmethodID ctorID = env->GetMethodID(strClass, "<init>", "([BLjava/lang/String;)V");
jbyteArray bytes = env->NewByteArray(strlen(pat));
env->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte*)pat);
jstring encoding = env->NewStringUTF("utf-8");
return (jstring)env->NewObject(strClass, ctorID, bytes, encoding);
}
//实现方法
JNIEXPORT jstring JNICALL Java_Face_HwDev_1Execute
(JNIEnv * env, jobject obj, jstring sDevInfoBuf, jlong lDevInfoLen, jstring sSendBuf, jlong lSendLen, jobjectArray spRecvBuf, jlongArray lpRecvLen){
dllHandle = LoadLibrary(_T("HwDevComm.dll"));//商家提供的库文件
EXECUTE pExecute = (EXECUTE)GetProcAddress(dllHandle,"HwDev_Execute");//寻找商家提供库中对应的方法名
FINISH pFinish = (FINISH)GetProcAddress(dllHandle,"HwDev_Finish");//寻找商家提供库中对应的方法名
const char* strDeviceInfo1 = (*env).GetStringUTFChars(sDevInfoBuf, 0);
const char* strCmd1 = (*env).GetStringUTFChars(sSendBuf, 0);
char* strDeviceInfo = jstringTostring(env,sDevInfoBuf);
char* strCmd = jstringTostring(env,sSendBuf);
unsigned long nStrDeviceInfoLen = strlen(strDeviceInfo);
unsigned long nStrCmd = strlen(strCmd);
char* pResult = NULL;
unsigned long iResult = 0;
//执行商家提供的方法
result = pExecute(strDeviceInfo,nStrDeviceInfoLen,strCmd,nStrCmd,&pResult,&iResult,0);
printf( "String1 = [%s]\n", pResult );
jstring returnStr = (*env).NewStringUTF(pResult);
//释放资源
//在使用完你所转换之后的对象之后,需要显示调用ReleaseStringUTFChars方法,让JVM释放转换成UTF-8的string的对象的空间,如果不显示的调用的话,JVM中会一直保存该对象,不会被垃圾回收器回收,因此就会导致内存溢出
(*env).ReleaseStringUTFChars(sDevInfoBuf, strDeviceInfo1);
(*env).ReleaseStringUTFChars(sSendBuf, strCmd1);
pFinish(&pResult);
//释放指定的动态链接库
FreeLibrary(dllHandle);
return returnStr;
}
说明:代码具体的实现涉及到jni中的类型和vc++中类型的对应转换的操作,商家提供的dll中的函数参数是c/c++中的类型,而通过java方法传进来的参数通过jni的处理转变为java和c/c++之间的一种过渡类型,比如jstring等,此时,就需要我们调用jni中的对应方法来转换该类型,具体需参照jni文档。
4、 生成dll文件
这时,就会在工程下的Release文件夹下生成face.dll。我的是在:
C:\Users\wangzw\Documents\Visual Studio 2008\Projects\face\Release路径下。
注:在解决方案配置中如果选择Debug,则会在工程下的Debug文件夹下生成face.dll
5、 将dll文件放到java项目中
如果生成face.dll的时候,解决方案配置选择的是Debug,则还需要把
C:\Program Files\Microsoft Visual Studio 9.0\VC\redist\Debug_NonRedist\x86文件夹下的
否则,就会报下面的错误:
Exception in thread "main" java.lang.UnsatisfiedLinkError: D:\workspace\zc_project\face\face.dll: 应用程序无法启动,因为应用程序的并行配置不正确。有关详细信息,请参阅应用程序事件日志,或使用命令行 sxstrace.exe 工具。
at java.lang.ClassLoader$NativeLibrary.load(Native Method)
at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1751)
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1676)
at java.lang.Runtime.loadLibrary0(Runtime.java:823)
at java.lang.System.loadLibrary(System.java:1030)
at Face.<clinit>(Face.java:6)
at Client.main(Client.java:5)
在没有安装Visual Studio的机子上运行也有可能出现上面的错误,原因可能是因为机子上没有安装Microsoft Visual C++ 2008 Redistributable -86这款软件,安装了它就可以在没有安装Visual Studio的机子上运行C++程序了。
6、 在java项目中调用dll
写一个测试类,调用Face类中的HwDev_Execute_temp方法,传入相应的参数,如下所示:
public class Client {
public static void main(String[] args) {
Face face = new Face();
String sDevInfoBuf = "DeviceInfo( dev_id = \"1\" comm_type = \"ip\" ip_address = \"192.168.0.2\" password = \"123456\")";
int lDevInfoLen = sDevInfoBuf.length();
String sSendBuf = "GetEmployee(id=\"2\")";
int lSendLen = sSendBuf.length();
String str = face.HwDev_Execute_temp(sDevInfoBuf, lDevInfoLen, sSendBuf, lSendLen, null, null);
System.out.println("*******"+str);
}
}
注:在运行的时候,有可能出现如下的错误:
# An unexpected error has been detected by Java Runtime Environment:
#
# EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x6d895268, pid=7944, tid=7880
#
# Java VM: Java HotSpot(TM) Client VM (1.6.0-b105 mixed mode, sharing)
# Problematic frame:
# V [jvm.dll+0xd5268]
#
# If you would like to submit a bug report, please visit:
# http://java.sun.com/webapps/bugreport/crash.jsp
该错误很有可能是由于自己写的dll文件有问题,就需要仔细检查生产dll文件的c++代码是否有问题,特别是在资源的释放问题上。