B/S架构下applet,JNI实现对客户端硬件的读取
声明:本文只是提供了大体的思路,文中所涉及到的代码,不保证都能无错误运行
前言
项目要求连接读卡器读写IC卡。由于项目是用PHP开发,所以刚开始设计的是用PHP连接读卡器(几个人还都没察觉有什么问题,汗)。
顺便说一下PHP如何读取硬件.
第一种:开发PHP标准的extension,此种方法开发难度较高,但是后期维护很容易。
第二种:写个COM组件注册到系统里,然后PHP调用COM组件。此种方法实现比较容易,但是维护较复杂,需要用regsvr32注册COM组件。
最后讨论决定用第二种比较简单的方法(到这里还是没察觉任何问题。汗)
路子选好了,说干就干,热火朝天的忙了两天,用VB写了个COM组件,然后注册到系统里.很显然功能实现了(欣喜若狂,但是还未察觉任何不妥).于是打包交给组长,组长试了试,也没什么问题。都以为就这么完事了。孰料第二天,组长突然提出了一个很严重的问题,PHP是在服务器端运行的,而读卡器是连接在客户端的。之前测试的客户端服务器端都在一台电脑上面,真正项目部署是不可能这样.瞬间就石化了,屁颠屁颠忙了两天,忙的跟真的一样
可是项目还是要做的,生活还是要过的,赶鸭子也是得上架的。苦思冥想十几分钟.既然是客户端读取硬件,必须用浏览器的插件实现,于是瞬间想到了老本行Java里面的applet.觉得这是一条可以走通的路,没办法,摸着石头过河也得过啊。一步一步走吧.
最简单的applet
第一步,首先弄明白applet怎么实现(说实话,以前真没写过applet).网上查了些资料,编码也不是很难.代码大致如下(由于是在家写的,代码在公司,所以只能凭记忆大概写点了)
//Java代码
//包含包的部分省略,记不得了
class ICCARD extend Applet{
public void paint(Graphics g)
g.drawString("Hello Applet",50,50);
}
//HTML代码
<html>
<body>
<applet id="applet" code="ICCARD.class" width="500" height="500"/>
</body>
</html>
记得要javac ICCARD.java.然后把html和class放在同一目录下,浏览器打开,根据你的安全设置,会有不同提示,一般IE默认会提示是不安装Activex控件,同意即可,Firefox则没有提示,直接就运行了(个人还是比较喜欢Firefox的,尤其里面的firebug).
applet和JavaScript合体
大多数情况下,我们的Applet都不可能只有一个方法,所以如何执行我们想要的方法呢,换句话说就是如何和JavaScript交互呢。
//Javascript Code
var applet = document.getElementById("applet");
applet.hello();
其实Javascript和Applet交互就这么简单(注意,这里是JavaScript调用Applet,不是Applet调用JS.关于Applet中调用JS,可自己搜索相关资料).
如果hello()有返回值的话,JS还可以接受返回值。
最简单的JNI
第一个主要模块Applet已经基本实现,按照我们的设想,applet下载到客户端执行,然后读取客户端的dll.貌似行的通.所以接下来的关键问题就是Java 如何读取dll,换句话说就是Java如何和其他语言协同工作,很明显,JNI闪亮登场。接下来主要就是解决JNI的问题,所以我们暂且抛开applet的环境,用最最简单的方法调试JNI.
//Java Code
public class Hello{//必须要先把我们的dll库load进来,我们的native方法才能运行
static{
System.loadLiberary("Hello");
}
public native void hello();
}
Java要做的就这么多,具体的实现就交给C++了(我用的是VC6.0)
第一步生成class文件 javac Hello.java
第二部生成C++引用的头文件javah Hello.生成文件内容如下
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Hello */
#ifndef _Included_Hello
#define _Included_Hello
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: Hello
* Method: hello
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_Hello_hello
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
这里我们只关心那个方法,把方法copy到下面的c++source file里面
第三部具体的C++代码实现.
新建dll工程->新建c++ source file
//c++ code
#include <stdio.h>
#include <jni.h>
#include <Hello.h>//从Hello.h拷贝来的
JNIEXPORT void JNICALL Java_Hello_hello
(JNIEnv *, jobject){
printf("Hello C++");
}
不用怀疑c++代码就这么简单,但是jni.h和Hello.h的路径要设置正确,编译器才能找到这两个文件
jni.h在jdk/include下
jni.h里面还会包含一个jni_md.h,在jdk/include/win32目录下
Hello.h就随便你放了
把以上提到的路径加到C++的库目录下.工具->选项->目录.
然后编译dll,就会生成我们需要的dll文件.
这个dll的文件名,就是我们在java代码中System.loadLiberary("Hello")里面的这个参数,大小写无所谓,后缀名不需要,会自动识别。
System.loadLiberary()这个方法会默认搜索几个目录,把dll放在正确的目录下,JVM就能自动加载了.然后我们的native就能运行了。
java Hello,控制台输入Hello C++;
这里只是提供了一个JNI最简单的实现,更详细的JNI编程,如参数传递等请参考http://www.docin.com/p-46800196.html
Applet和JNI合体
上面我们的JNI是没有在applet环境下运行的,下面就要把Applet和JNI结合起来了。
//Java code
import java.applet.Applet;
public Hello extend Applet{
//从Applet继承
public void init(){
System.loadLiberary("Hello");
}
public native String hello();
}
//C++ code
#include <stdio.h>
#include <jni.h>
#include <Hello.h>
//从Hello.h拷贝来的
JNIEXPORT jstring JNICALL Java_Hello_hello (JNIEnv *env, jobject){
return env->NewStringUTF("Hello JavaScript");
}
这里涉及到了传参的问题,详细内容请参考上面给出的网址。
//JS Code
var applet = document.getElementById("applet");
alert(applet.hello());
这里几个主要的代码就这样,至于其他次要的工作,由于时间有限,就不再一一赘述.
到了这里我们发现,由于Applet是在客户端运行的,那么我们如何把我们的dll也放到客户端呢.
首先,试试把class文件和dll打包一起发到客户端行不行呢,jar -cvf Hello.jar *.class *.dll.注意,打包的话<applet>标签里面就要加个archive属性,archive="Hello.jar".
打开浏览器,结果失败了,同志们就不要再试了。
那么最简单的方法,就是手动拷贝到客户端(至于自动用Applet下载到客户端,我没有尝试过,有兴趣可以试试).把我们的dll拷贝到system32下.(由于我们项目的客户端比较少,比较单一,所以还不至于被骂).
再打开浏览器运行,如果前面步骤都没问题的话,浏览器应该会报access denied错误.紧要关头,突现离奇错误,为虾米呢?请听下文分解.
applet数字签名
其实动动脚指头就能想得通了,为了安全,applet不可能具有肆无忌惮的权限,想读就读,想写就写,那样网络世界就乱套了。applet默认的安全机制是阻止对客户端的任何操作的(必须阻止).那我们就要想办法允许applet有读写权限。又接着去网上查了许多资料,发现有的人说可以读写,有的人说不能(我的心凉了一半啊,都进行到这一步了,我容易吗我),但是不管怎样,只要还有希望,就要继续努力.按照网友们给出的方法,对applet的jar包签名.
首先把.class都打成jar包:jar -cvf iccard.jar *.class
然后用jdk/bin目录下的keytool生成.store文件:keytool -genkey -keystore iccard.store -alias Mission -validity 999.这样会提示你输入一些信息,最后时候确认即可.
最后用jdk/bin下的jarsigner给jar文件签名 jarsigner -verbose -keystore iccard.store iccard.jar Mission.会提示你输入密码,然后会打印出签名的过程(verbose参数的作用).
关于applet签名的过程,可以去百度“applet签名”,由于以上只凭记忆写的,所以难免有不准的地方。
好了,这下我们的jar包可厉害了,它已经不是一般的jar包了,它是一个签过名的jar包,一个具有对客户端读写权限的jar包.好了,现在让我们满怀期待的打开浏览器,嗯,IE提示是否允许Active,允许,然后就跳出窗口让我们验证签名,允许。。然后,OMG,顺利执行了,然后,然后就没有然后了。
总结:
虽然功能可以实现,可以跨浏览器,但是缺点也是比较多的。
1.首先,要把我们的dll拷贝到客户端。
2.其次,客户端还要装JRE
由于我们的客户端用IC读卡器的比较少,所以这至少还是一个行之有效的方案,如果面向客户比较多的话,我觉得可一开发BHO(浏览器帮助对象).这已经不是本文讨论范围.