1. JNA 中 byte [] 类型映射问题
在之前的文章中,我们知道 JNA 中,char *
和 char
类型都可以映射为 byte[]
类型, 通常来说也就是 byte[]
具有通用性。
在实际开发中,我们通常使用 "string".toBytes()
来进行转化。如下示例:
// 代码 1-1 ,某驱动函数原型
int NewKey(char *room,char *gate,char *stime,char *guestname,char *guestid, char *lift, int overflag, int Breakfast, long *cardno,char * track1,char * track2);
按照之前的映射为 Java 方法,
// 代码 1-2 , 映射为 Java 方法
int NewKey(byte [] room,byte [] gate,byte [] stime,byte [] guestname,byte [] guestid, byte [] lift, int overflag, int Breakfast, NativeLongByReference cardno,byte [] track1,byte [] track2);
此时,如果我们调用的参数如下,
// 代码 1-3 ,NewKey 方法调用
int result = NewKey("010101".getBytes(),"00","201810231200201810241200".getBytes(), "".getBytes(),"".getBytes(),1, nativeLongByReference, null,null);
看上去是没有问题的, 我们满怀信心的调用,却发现调用始终失败。于是我们对调用的 dll 动态链接库再用 C 封装一层,用 java 代码间接调用原始 dll。 打印日志发现,调用原始dll的入参如下,
// 代码 1-4 , 调用 原始 dll 入参日志
Adell_NewKey (010101,00,2018102312002018102412002c� � ,,,1, nullptr ,nullptr)
很明显的发现,在某个参数后面多了一串乱码导致调用失败。此时,我们可以用一个蹩脚的方法暂时先解决,那就是把 byte []
类型改为 String
, 如下所示,
// 代码 1-4 ,参数从 byte[] 改为 String后,从 dll 入参日志
// java method invoke
NewKey("010101".getBytes(),"00","201810231200201810241200", "".getBytes(),"".getBytes(),1, nativeLongByReference, null,null);
// parameters log from dll method signature
NewKey (010101,00,201810231200201810241200,,,1, nullptr ,nullptr)
问题似乎这样简单的解决的了, 但我们还没明白为什么使用 jdk 中的原生 toBytes()
会在结尾加上其他字节?只能求助社区。
2. 真相大白
在 Issues on byte [] when invoke by JNA 这个帖子中, 感谢 JNA 的作者 Matthias Bläsing 耐心回答。
在 C 中的 char 数组是以一个 NULL
字符作为结束中止标识, 使用 jdk 中的 toBytes()
并不会在byte 数组后加结束符,导致读取参数数组越界,强制结束。所以在getBytes()
转为 byte 数组后我们需要
人为在结尾加上0
来作为中止字符,当然 Matthias Bläsing 也给出了 JNA 解决方法 , Native#toByteArray
做了部分重写。
我们查看下 JNA 对转 byte 数组是怎么处理的,
// 代码 1-5 , Native#toByteArray 源码
/**
* @param s The string
* @return A NUL-terminated byte buffer equivalent to the given String,
* using the given encoding.
* @see #getBytes(String, String)
*/
public static byte[] toByteArray(String s, String encoding) {
byte[] bytes = getBytes(s, encoding);
byte[] buf = new byte[bytes.length+1];
System.arraycopy(bytes, 0, buf, 0, bytes.length);
return buf;
}
拷贝 byte 数组,并且在 byte 数组结尾加上 0
作为终止符。
3. char ** 映射
char **
指针引用映射 JNA 类型为 StringArray
, 而不是 String 。
4. 一些建议
通常情况下 ,我们的 dll 动态链接库文件都放在 resources
资源目录下, 当然也建议放在项目的根目录下。如下文件目录结构,
根目录
|
│ external.lib
│ pom.xml
└─src
├─main
│ ├─java
│ │ └─InvokerDLL
│ │ InvokerDLL.java
│ │
│ └─resources
│ external.dll