1.介绍jna
优点
JNA可以让你像调用一般java方法一样直接调用本地方法。就和直接执行本地方法差不多,而且调用本地方法还不用额外的其他处理或者配置什么的,也不需要多余的引用或者编码,使用很方便。
与jni对比
在java JDk也有调用动态库的技术:jni,相对jna来说,jna功能给强大,效率更高,但是入门门槛高,它要求调用的函数库是符合jni调用规范的。如果你要调用的函数库不符合jni规范,对不起,你得自己用C写一个符合jni调用规范的函数库,在里面调用要你要使用的函数,然后你再去调用这个jni规范的函数库,实现你的功能。这样的话开发量更大,而且开发难度也更大(要求你会C),但使用jna的话就问题就简单很多了,我不需要你的函数符合jni规范,直接可以调用原生函数库,不需要编写任何Native/JNI代码。
使用jna(jni)的两个缺点:
2.与java数据类型的映射
跨平台、跨语言调用的最大难点,就是不同语言之间数据类型不一致造成的问题。绝大部分跨平台调用的失败,都是这个问题造成的。
JNA使用的数据类型是Java的数据类型。而原生函数中使用的数据类型是原生函数的编程语言使用的数据类型。可能是C,Delphi,汇编等语言的数据类型。因此,不一致是在所难免的。
JNA提供了Java和原生代码的类型映射。
和操作系统数据类型的对应表
Java 类型 | C 类型 | 原生表现 |
boolean | int | 32位整数 (可定制) |
byte | char | 8位整数 |
char | wchar_t | 平台依赖 |
short | short | 16位整数 |
int | int | 32位整数 |
long | long long, __int64 | 64位整数 |
float | float | 32位浮点数 |
double | double | 64位浮点数 |
| pointer | 平台依赖(32或 64位指针) |
<T>[] (基本类型的数组) | pointer | 32或 64位指针(参数/返回值) |
支持常见的数据类型的映射
Java 类型 | C 类型 | 原生表现 |
char* | /0结束的数组 (native encoding or | |
wchar_t* | /0结束的数组(unicode) | |
char** | /0结束的数组的数组 | |
wchar_t** | /0结束的宽字符数组的数组 | |
struct* | 指向结构体的指针 (参数或返回值) (或者明确指定是结构体指针) | |
union | 等同于结构体 | |
struct[] | 结构体的数组,邻接内存 | |
<T> (*fp)() | Java函数指针或原生函数指针 | |
varies | 依赖于定义 | |
long | 平台依赖(32或64位整数) | |
pointer | 和 |
尽量使用基本、简单的数据类型;
尽量少跨平台、跨语言传递数据!
3.使用
dll、so函数库区别
dll、so函数库都可以通过jna来调用,dll与so函数库的区别是:dll是C编译windows环境下使用的函数库,而so是在linux等环境下使用的函数库,他们调用的方法都是一样的。
下面是一个调用dll的例子:
函数库libHsMacAPI.h头文件(相对于函数库api)
函数名称: GenZAK
功能描述: 用于生成MACKey
参数说明: sMacKey 存放生成的MACKEY,长度为16位
返回值: 无
void GenZAK(char * sMacKey);
*********************************************************************
函数名称: GenMAC
功能描述: 用于生成MAC
参数说明:
sMacBuf 需要进行MAC的数据流
sMac 生成的MAC,最小为16字节
sMacKey 对MAC加密密钥(加密过的KEY)
返回值: 无
**********************************************************************
void GenMAC(char * sMacBuf, char * sMac, char * sMacKey);
函数名称: MACVerify
功能描述: 用于检查MAC
参数说明:
sMacBuf 需要进行MAC的数据流
sMac 生成的MAC,最小为16字节
sMacKey 对MAC加密密钥(加密过的KEY)
返回值:
=0:校验成功
!=0: 校验不成功
**********************************************************************
int MACVerify(char * sMacBuf, char * sMac, char * sMacKey);
分析:
通过头文件我们可以看到:
1.在GenZAK方法中sMacKey是出参,相当于指针(Pointer),我们将一个指针传入,然后它给我们给我们的指针赋值,在java中我们使用对应的Pointer的子类Memory进行传值。
2.在GenMAC方法中sMacBuf 是入参,char*对应的是java中的String;sMac是出参,对应Memory,sMacKey是入参;对应java中是String,但我们之前定义了为Memory类型,这里我们就不将其转化为String了,还是直接用Memory类型。
3.在MACVerify方法中sMacBuf、sMacKey、sMac都是入参,因为我们之前定义sMacKey、sMac都是Memory类型,所以还是使用Memory类型,sMacBuf还是String类型。
入参使用与java对应的类型即可,出参都需使用指针类。
实现:
需要下载jna架包
1.编写jna接口代码(接口里面的方法名要跟C的接口方法名一致)
<p>package demo;</p><p>import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;</p><p>
/**
* 接口里面的方法名要跟C的接口方法名一致
* 调用linux so(动态库) 获取mac
* @since JDK1.6
*/
public interface LibHsMacAPIJNA extends Library{
/*
然后开始我们的java接口
loadLibrary第一个参数就是你的dll名字
第二个就是当前接口的.class类型
*/
//线程安全 (加载lib使用了绝对路径 切割去掉了路径前面的"/")
public LibHsMacAPIJNA INATANCE = (LibHsMacAPIJNA) Native.synchronizedLibrary((LibHsMacAPIJNA)Native.loadLibrary(LibHsMacAPIJNA.class.getResource("/libHsMacAPI.dll").getPath().substring(1), LibHsMacAPIJNA.class)); </p><p> /*********************************************************************
函数名称: GenMAC
功能描述: 用于生成MAC
参数说明:
sMacBuf 需要进行MAC的数据流
sMac 生成的MAC,最小为16字节
sMacKey 对MAC加密密钥(加密过的KEY)
返回值: 无
**********************************************************************/
public void GenMAC(String sMacBuf, Pointer sMac, String sMacKey);
/*
函数名称: GenZAK
功能描述: 用于生成MACKey
参数说明: sMacKey 存放生成的MACKEY,长度为16位
返回值: 无
**********************************************************************/
public void GenZAK(Pointer sMacKey);
/*********************************************************************
函数名称: MACVerify
功能描述: 用于检查MAC
参数说明:
sMacBuf 需要进行MAC的数据流
sMac 生成的MAC,最小为16字节
sMacKey 对MAC加密密钥(加密过的KEY)
返回值:
=0:校验成功
!=0: 校验不成功
**********************************************************************/
public int MACVerify(String sMacBuf, String sMac, String sMacKey);
}</p>
Native.loadLibrary()加载函数库文件时,可以直接填写函数库名称(前缀lib可以去掉,后缀也可以去掉),不过前提是这个函数库必须放在系统或者java的函数库目录下;也可以放在任意位置,使用绝对路径。
2.调用函数库代码
package demo;
import com.sun.jna.Memory;
import com.sun.jna.Pointer;
/**
* 此类功能描述
* @version 2015-6-1 下午4:13:03
* @since JDK1.6
*/
public class LibHsMacAPITest {
public static void main(String[] args) throws Exception {
LibHsMacAPITest.generateMAC();
}
/**
* 生成MAC
* @param method
* @param databuf
* @param datalen
* @param key
* @return
* @throws Exception
*/
public static void generateMAC() throws Exception{
//获取key
Pointer sMacKey = new Memory(100);
LibHsMacAPIJNA.INATANCE.GenZAK(sMacKey);
String key = new String(sMacKey.getByteArray(0, 100));
System.out.println("key:" + key);
//加密
Pointer sMac = new Memory(16);
String sMacBuf = new String("1111");
LibHsMacAPIJNA.INATANCE.GenMAC(sMacBuf, sMac, key);
String mac = new String(sMac.getByteArray(0, 16),"utf-8");
System.out.println(mac);
//检验加密是否正确
int flag = LibHsMacAPIJNA.INATANCE.MACVerify(sMacBuf, mac, key);
System.out.println("检验结果:" + (flag == 0 ? "成功":"失败"));
}
}
4.使用jna注意
注意事项
在非web工程时,将需要调用的函数库文件放置在工程目录下是OK的;但在web工程中,放置在工程目录下是no OK的,因为项目发布时会将函数库也进行编译,造成函数库损坏。故在工程中使用jna,建议放在外部文件中,别放工程目录下。
加载函数库时,使用的绝对路径中不能有中文!