最近由於搞畢業設計的需要,使用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檔案添加到標頭檔,另外還要把jdk中include檔案夾下的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);//返回用name和buffer中的資訊,中間用”|”分割開
}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);
}
}
本文來自CSDN部落格,轉載請標明出處:http://blog.csdn.net/rohsuton/archive/2008/12/29/3637272.aspx#1571539