java 調用第三方dll學習心得

最近由於搞畢業設計的需要,使用J2EE做一個實驗預約系統,其中涉及到一卡通和IC卡讀卡器,弄來一個刷卡機,廠商只提供了一個用C編寫的Windows動態鏈接庫SmartCom411SFJ.dll,我需要使用java程序調用這個dll文件來獲取一卡通中的信息。其實我用到的函數很簡單,這有三個:

串口初始化函數:int IniCom(int ComPort,int BaudRate)

讀卡信息函數:int ReadPersonalInfo(int ComPort,unsigned char *Name,unsigned char *buffer)

關閉串口函數:int CloseCom(int ComPort)

上網查過很多資料,得知可以使用JNI調用本地DLL文件,網上也有很多朋友提出類似問題,但是看了很多網友的回答結果還是不能解決自己遇到的問題,好多回答都是從別人的網頁上copy一些代碼,沒有講述如何調用第三方的DLL文件,打開很多網頁發現裏面講的內容都是一樣的,甚至還有很多代碼貼出來都是錯誤的,根本就沒有對提問者的問題做出回答,真正要找的解決辦法卻很難。

我開始也嘗試網上說的辦法來做:

1. java編寫一個類,類中使用System.LoadLibrary方法調用動態鏈接庫,同時聲明動態鏈接庫中個各個方法。

2. 然後用javac編譯成class文件,再用javah生成.h文件。

3. 編寫一個C/C++程序,生成java可以直接調用的DLL文件。

4. 把生成的DLL文件何java文件放在一塊,重新運行開始寫的java程序。

但是問題是很多C中使用的數據類型在java中不能使用,如unsigned char *HANDLE等,如何轉換呢?我覺得這是很常用的啊,怎麼很少有人回答這種問題呢?也許是我的搜索能力太差了吧,呵呵!

通過幾天的努力我終於把問題解決了,我把在編寫過程中遇到的一寫問題列出來,雖然我的程序有點簡單,想跟大家分享一下,希望與我一樣困惑的朋友能夠用得上。

1. JAVA程序中,首先聲明java要調用的庫名稱,庫的擴展名字可以不用寫出來,該庫名稱不是商家提供的庫,名字可以隨便去,最好不要和商家提供的庫名稱一樣,否則會出錯。還需要對將要調用的方法做本地聲明,使用關鍵字native,只需聲明不要具體實現,方法名和參數不需要和商家提供的庫中方法一樣,況且一些C參數類型也沒辦法使用java語言表示。例如我的程序SmartCard.java內容如下:

public class SmartCard{

static{

System.loadLibrary("SmartCard");//後面使用C/C++編寫的JAVA能直接調用的庫

}

//java中需要用到的本地方法聲明,從安全上考慮最好把它設成私有

private native int iniCom(int ComPort,int BaudRate);

private native int closeCom(int ComPort);

private native String readPersonalInfo(int ComPort);

//外部類能調用的方法

public int iniComTemp(int ComPort,int BaudRate){

return this.iniCom(ComPort,BaudRate);

}

public int closeComTemp(int ComPort){

return this.closeCom(ComPort);

}

public String readPersonalInfoTemp(int ComPort){

return this.readPersonalInfo(ComPort);

}

}

2. 使用javac SmartCard編譯生成CLASS文件,再調用javah SmartCard生成C/C++的頭文件

比如我的程序生成的.h文件內容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */

#include <jni.h>

/* Header for class SmartCard */

#ifndef _Included_SmartCard

#define _Included_SmartCard

#ifdef __cplusplus

extern "C" {

#endif

/*

* Class: SmartCard

* Method: iniCom

* Signature: (II)I

*/

JNIEXPORT jint JNICALL Java_SmartCard_iniCom

(JNIEnv *, jobject, jint, jint);

/*

* Class: SmartCard

* Method: closeCom

* Signature: (I)I

*/

JNIEXPORT jint JNICALL Java_SmartCard_closeCom

(JNIEnv *, jobject, jint);

/*

* Class: SmartCard

* Method: readPersonalInfo

* Signature: (I)Ljava/lang/String;

*/

JNIEXPORT jstring JNICALL Java_SmartCard_readPersonalInfo

(JNIEnv *, jobject, jint);

#ifdef __cplusplus

}

#endif

#endif

3. C/C++中所需做的工作,對於已生成的.h文件,C/C++所需要做的就是把它的各個方法具體實現,然後連接成庫文件即可,在方法實現過程中需要用到商家提供的第三方DLL文件,以及轉化數據類型。編寫是需要把剛纔生成的.h文件添加到頭文件,另外還要把jdkinclude文件夾下的jni.h以及include/ win32下的jni_md.h添加到編譯器中的include中,或者何源文件放在一起,又是會提示jnih找不到,這是你可以把使用javah生成的.h文件中的<jni.h>改成“jni.h”。類型轉換及如何調用商家提供的庫可分析一下代碼,要注意的是這裏使用的DLL文件不能與開始java中使用的DLL文件同名。

#include "stdafx.h"

#include "windows.h"

#include "string.h"

#include "SmartCard.h"//該頭文件須被包含進來

typedef int (_stdcall *INICOM)(int ComPort,int BaudRate);//參數需要何商家提供的DLL文件中方法的參數一致

typedef int (_stdcall *CLOSECOM)(int ComPort);

typedef int (_stdcall *IDREAD)(int ComPort,unsigned char *Name,unsigned char *buffer);

HINSTANCE dllHandle;

int result;

//初始化串口方法實現

JNIEXPORT jint JNICALL Java_SmartCard_iniCom(JNIEnv *env, jobject jo, jint ComPort, jint BaudRate){

INICOM pIniCom;

dllHandle = LoadLibrary("SmartCom411SFJ.dll");//商家提供的庫文件

pIniCom = (INICOM)GetProcAddress(dllHandle,"IniCom");//尋找商家提供庫中對應的方法名

result = pIniCom(ComPort,BaudRate);

FreeLibrary(dllHandle);

return result;

}

//關閉串口方法實現

JNIEXPORT jint JNICALL Java_SmartCard_closeCom(JNIEnv *env, jobject jo, jint ComPort){

CLOSECOM pCloseCom;

dllHandle = LoadLibrary("SmartCom411SFJ.dll");

pCloseCom = (CLOSECOM)GetProcAddress(dllHandle,"CloseCom");

result = pCloseCom(ComPort);

FreeLibrary(dllHandle);

return result;

}

JNIEXPORT jstring JNICALL Java_SmartCard_readPersonalInfo(JNIEnv *env, jobject jo, jint ComPort){

IDREAD pIdRead;

unsigned char name[8]="",*na=name;

unsigned char buffer[20]="put card on it",*buf=buffer;

char splitLetter[]="|";

jstring jstr;

dllHandle = LoadLibrary("SmartCom411SFJ.dll");

pIdRead = (IDREAD)GetProcAddress(dllHandle,"ReadPersonalInfo");

result = pIdRead(ComPort,name,buffer);

char resultStr[29]="",*reTemp=resultStr;

if(result==0){

for(int i=0;i<8;i++){

*(reTemp+i)=*(na+i);

}

*(reTemp+8)=splitLetter[0];

for(i=9;i<29;i++){

*(reTemp+i)=*(buf+i);

}

jstr=env->NewStringUTF(resultStr);//返回用namebuffer中的信息,中間用”|”分割開

}else if(result==-6){

jstr=env->NewStringUTF("-6");

}else if(result==1){

jstr=env->NewStringUTF("1");

}else if(result==16){

jstr=env->NewStringUTF("16");

}else{

jstr=env->NewStringUTF("-2");

}

FreeLibrary(dllHandle);

return jstr;

}

4. 編譯生成庫文件,生成的庫文件名需和第一步中的庫名一致。在需要使用商家提供的庫中方法的java類中調用第一步中聲明的對應方法即可。我編寫的一個測試類如下:

public class test{

public static void main(String[] args){

SmartCard sc = new SmartCard();

int i = sc.iniComTemp(2,0);

int j = sc.closeComTemp(2);

String str = sc.readPersonalInfoTemp(2);

System.out.println(i);

System.out.println(j);

System.out.println(str);

}

}

發佈了24 篇原創文章 · 獲贊 50 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章