JNA 之 初识(上)
JNA(Java Native Access)框架是一个开源的Java框架,是SUN公司主导开发的,建立在经典的JNI的基础之上的一个框架。使用JNI调用共享类库(.dll/.so文件)是非常麻烦的事情,既需要编写java代码,又要编写C语言的代理方法,这其中需要很多数据类型的转换,是让人非常头痛。JNA框架就是为了解决这些问题和繁琐的事情而开发的,它提供一组Java工具类用于在运行期动态访问系统本地共享类库而不需要编写任何Native/JNI代码。开发人员只要在一个java接口中描述目标native library的函数与结构,JNA将自动实现Java接口到native function的映射,大大降低了Java调用本体共享库的开发难度。JNA与.NET平台上的P/Invoke机制一样简单和方便。
你只需要下载一个jar包,就可以使用JNA的强大功能方便地调用动态链接库中的C函数。下载地址是:https://github.com/twall/jna
JNA调用本地的库函数
假设有一个动态链接库: CnblogsJna.dll。里面有这样一个函数:
void sayHello(char * name){ printf("C Code Start...\n"); printf("Hello! Mr %s.\n",name); printf("C Code End.\n"); }
此函数接收一个代表姓名的字符指针,然后在控制台上输出几句字符串。
为了调用这个函数,使用JNA,我们需要编写下面的JAVA代码:
1、接口ICnblogsJna.java
import com.sun.jna.Library; import com.sun.jna.Native; /** * @author BCH)王国成 */ public interface ICnblogsJna extends Library { // 接口实例 ICnblogsJna INSTANCE = (ICnblogsJna) Native.loadLibrary("CnblogsJna",ICnblogsJna.class); // 与C代码映射的函数 public void sayHello(String name); }
注意:接口需要继承制JNA的Library接口;
接口内部需要一个公共静态常量INSTANCE, 通过这个常量,就可以获得这个接口的实例,从而使用接口的方法。也就是调用动态链接库CnblogsJna.dll中的sayHello函数了。
如果使用JNI,你需要使用System.loadLibrary方法,来加载我们专为JNI编写的动态链接库,这个动态链接库实际上是我们真正需要的动态链接库的代理。使用JNA是,需要用JNA类库的Native类的loadLibrary函数,是直接把我们需要的动态链接库载入进来。使用JNA,我们不需要编写作为代理的动态链接库,不需要编写一行原生代码。
Native类的loadLibrary方法有两个参数:第一个参数是.dll或者.so文件的名字,但不带后缀名。这符合JNI的规范,因为带了后缀名就不可以跨操作系统平台了。第二个参数是本接口的Class类型,JNA通过这个Class类型,根据指定的dll/.so文件,动态创建接口的实例。
2、调用动态链接库文件中函数的Java方法():
/** * @author BCH)王国成 */ public class CnblogsJna { /** * 入口函数 * @param args */ public static void main(String[] args) { // 调用动态链库中的sayHello函数 ICnblogsJna.INSTANCE.sayHello("Wanggc/王国成"); } }
方法很简单,就像调用Java自己的函数一样。执行输出结果如下:
和原生代码的类型映射
跨平台,跨语言调用的难点,就是不同语言之间数据类型不一致造成的,JNA也不例外,要想跨平台调用,数据类型转换是个无法回避的问题。JNA提供了Java和原生代码的类型映射。
Java和C数据类型的对应表如下:
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位浮点数 |
Buffer/Pointer |
pointer |
平台依赖(32或 64位指针) |
<T>[] (基本类型的数组) |
pointer/array |
32或 64位指针(参数/返回值) 邻接内存(结构体成员) |
String |
char* |
/0结束的数组 (native encoding or jna.encoding) |
WString |
wchar_t* |
/0结束的数组(unicode) |
String[] |
char** |
/0结束的数组的数组 |
WString[] |
wchar_t** |
/0结束的宽字符数组的数组 |
Structure |
struct*/struct |
指向结构体的指针 (参数或返回值) (或者明确指定是结构体指针) |
Union |
union |
等同于结构体 |
Structure[] |
struct[] |
结构体的数组,邻接内存 |
Callback |
<T> (*fp)() |
Java函数指针或原生函数指针 |
NativeMapped |
varies |
依赖于定义 |
NativeLong |
long |
平台依赖(32或64位整数) |
PointerType |
pointer |
和 Pointer相同 |
由于跨平台和跨语言尤其自身无法克服的确定,所以尽量少跨平台、跨语言传递数据。如果必须这样做,也尽量使用简单的数据类型。如果有复杂的数据类型需要在Java和原生函数中传递,那么我们就必须在Java中模拟这种复杂的原生类型。这将大大增加实现的难度,甚至无法实现。如果在Java和原生函数间存在大量的数据传递,一方面,会损失程序的性能;另一方面会造成内存碎片,Java调用原生函数时,会把数据固定在内存中,这样原生函数才可以访问这些Java数据。这些数据,JVM的GC不能管理,就会造成内存碎片。