JNA 技术解密
JNA 工作原理 JNA 是建立在 JNI 技术基础之上的一个 Java 类库,它使您可以方便地使用 java 直接访问动态链接库中的函数。
原来使用 JNI ,你必须手工用 C 写一个动态链接库,在 C 语言中映射 Java 的数据类型。
JNA 中,它提供了一个动态的 C 语言编写的转发器,可以自动实现 Java 和 C 的数据类型映射。你不再需要编写 C 动态链接库。
当然,这也意味着,使用 JNA 技术比使用 JNI 技术调用动态链接库会有些微的性能损失。可能速度会降低几倍。但影响不大。
JNA 技术难点
1 ,当前路径是在项目下,而不是 bin 输出目录下。
2 ,数据结构的对应关系:
Java—C 和操作系统数据类型的对应表
Java Type | C Type | Native Representation |
boolean | int | 32-bit integer (customizable) |
byte | char | 8-bit integer |
char | wchar_t | platform-dependent |
short | short | 16-bit integer |
int | int | 32-bit integer |
long | long long, __int64 | 64-bit integer |
float | float | 32-bit floating point |
double | double | 64-bit floating point |
pointer | platform-dependent (32- or 64-bit pointer to memory) | |
<T>[] (array of primitive type) | pointer | 32- or 64-bit pointer to memory (argument/return) |
除了上面的类型, JNA 还支持常见的数据类型的映射。 | ||
char* | NUL-terminated array (native encoding or | |
wchar_t* | NUL-terminated array (unicode) | |
char** | NULL-terminated array of C strings | |
wchar_t** | NULL-terminated array of wide C strings | |
struct* | pointer to struct (argument or return) ( | |
union | same as | |
struct[] | array of structs, contiguous in memory | |
<T> (*fp)() | function pointer (Java or native) | |
varies | depends on definition | |
long | platform-dependent (32- or 64-bit integer) | |
pointer | same as |
JNA 编程过程
JNA 把一个 dll/.so 文件看做是一个 Java 接口。
Dll 是 C 函数的集合、容器,这正和接口的概念吻合。
我们定义这样一个接口,
public interface TestDll1 extends Library {
/**
* 当前路径是在项目下,而不是 bin 输出目录下。
*/
TestDll1 INSTANCE = (TestDll1)Native.loadLibrary("TestDll1", TestDll1.class);
public void say(WString value);
}
如果 dll 是以 stdcall 方式输出函数,那么就继承 StdCallLibrary 。否则就继承默认的 Library 接口。
接口内部需要一个公共静态常量: instance 。
TestDll1 INSTANCE = (TestDll1)Native.loadLibrary("TestDll1", TestDll1.class);
通过这个常量,就可以获得这个接口的实例,从而使用接口的方法。也就是调用外部 dll 的函数!
注意:
1 , Native.loadLibrary() 函数有 2 个参数:
1 , dll 或者 .so 文件的名字,但不带后缀名。这符合 JNI 的规范,因为带了后缀名就不可以跨操作系统平台了。
搜索 dll 的路径是:
1 )项目的根路径
2 )操作系统的全局路径、
3 ) path 指定的路径。
2 ,第二个参数是本接口的 Class 类型。
JNA 通过这个 Class 类型,根据指定的 dll/.so 文件,动态创建接口的实例。
2 ,接口中你只需要定义你需要的函数或者公共变量,不需要的可以不定义。
public void say(WString value);
参数和返回值的类型,应该和 dll 中的 C 函数的类型一致。
这是 JNA ,甚至所有跨平台调用的难点。
这里, C 语言的函数参数是: wchar_t * 。
JNA 中对应的Java 类型是WStirng 。
所有跨平台、跨语言调用的难点
有过跨语言、跨平台开发的程序员都知道,跨平台、语言调用的难点,就是不同语言之间数据类型不一致造成的问题。绝大部分跨平台调用的失败,都是这个问题造成的。
关于这一点,不论何种语言,何种技术方案,都无法解决这个问题。
这需要程序员的仔细开发和设计。这是程序员的责任。
常见的跨平台调用有:
1 , Java 调用 C 语言编写的 dll 、 .so 动态链接库中的函数。
2 , .NET 通过 P/Invoke 调用 C 语言编写的 dll 、 .so 动态链接库中的函数。
3 ,通过 WEBService ,在 C,C++,Java,.NET 等种种语言间调用。
WebService 传递的是 xml 格式的数据。
即使是强大的 P/Invoke 或者 WebService ,在遇到复杂的数据类型和大数据量的传递时,还是会碰到很大的困难。
因为,一种语言的复杂的数据类型,很难用另一种语言来表示。这就是跨平台调用问题的本质。
如, WEBService 调用中,很多语言,如 Java , .NET 都有自动实现的 Java/.NET 类型和 XML 类型之间的映射的类库或者工具。
但是,在现实的编程环境中,如果类型非常复杂,那么这些自动转换工具常常力不从心。
要么 Object-XML 映射错误。
要么映射掉大量的内存。
因此,我个人对这些 Object-XML 映射框架相当不感冒。
我现在使用 WEBService ,都是直接手工使用 xml 处理工具提取 xml 中的数据构建对象。或者反过来,手工根据 Object 中的属性值构建 xml 数据。
Java 和 C 语言之间的调用问题,也是如此。
Java 要调用 C 语言的函数,那么就必须严格按照 C 语言要求的内存数量提供 Java 格式的数据。要用 Java 的数据类型完美模拟 C 语言的数据类型。
JNA 已经提供了大量的类型匹配 C 语言的数据类型。
JNI 还是不能废
我们已经见识了 JNA 的强大。 JNI 和它相比是多么的简陋啊!
但是,有些需求还是必须求助于 JNI 。
JNA 是建立在 JNI 技术基础之上的一个框架。
使用 JNI 技术,不仅可以实现 Java 访问 C 函数,也可以实现 C 语言调用 Java 代码。
而 JNA 只能实现 Java 访问 C 函数,作为一个 Java 框架,自然不能实现 C 语言调用 Java 代码。此时,你还是需要使用 JNI 技术。
JNI 是 JNA 的基础。是 Java 和 C 互操作的技术基础。