深入浅出 JNAJNAJNAJNA————快速调用原生函数
By 沈东良(网名:良少)
Blog:
http://blog.csdn.net/shendl
2009/7/20
本文原名《使用 JNA 方便地调用原生函数》发表于 2009 年 3 月的“程序员”杂 志 上 。
感谢程序员杂志的许可,使这篇文章能够成为免费的电子版,发布于网络上。
程序员杂志发表此文时,略有裁剪,因此本文比程序员上的文章内容更多。
JNA 的 API 参考手册和最新版本的 pdf 文档,可以在如下地址下载:
http://code.google.com/p/shendl/downloads/list
目录
深入浅出 JNA—快速调用原生函数................................................................................................1
为什么需要 JNA............................................................................................................................... 2
JNA 介绍........................................................................................................................................... 2
JNA 实现原理................................................................................................................................... 2
JNA 调用原生函数................................................................................................................... 3
例子 1 使用 JNA 调用原生函数................................................................................3
使用 JNA 调用原生函数的模式.......................................................................................3
Java 和原生代码的类型映射...........................................................................................................4
Java—C 和操作系统数据类型的对应表................................................................................. 4
JNA 支持常见的数据类型的映射........................................................................................... 4
跨平台、跨语言调用原则:...................................................................................................5
JNA 模拟结构体............................................................................................................................... 5
例 2 使用 JNA 调用使用 Struct 的 C 函数..........................................................................5
Structure 说明.................................................................................................................. 6
JNA 模拟复杂结构体............................................................................................................... 7
例 3 结构体内部可以包含结构体对象的数组......................................................... 7
例 4 结构体内部可以包含结构体对象的指针的数组............................................. 7
原生代码调用 Java 代码..................................................................................................................8
例 5 通过回调函数实现原生代码调用 Java 代码............................................................ 8
JNA 回调函数说明................................................................................................................... 9
JNA 模拟指针................................................................................................................................... 9
例 6 使用 PointerByReference 模拟指向指针的指针..................................................... 11
例 7 使用 Pointer 和 PointerByReference 模拟指针..................................................... 11
Pointer 类详解....................................................................................................................... 12
结语................................................................................................................................................ 13
为什么需要 JNAJNAJNAJNA
和许多解释执行的语言一样,Java 提供了调用原生函数的机制,以加强 Java 平台的能
力。Java™ Native Interface (JNI)就是 Java 调用原生函数的机制。
事实上,很多 Java 核心代码内部就是使用 JNI 实现的。这些 Java 功能实际上是通过原
生函数提供的。
但是,使用 JNI 对 Java 开发者来说简直是一场噩梦。
如果你已经有了原生函数,使用 JNI,你必须使用 C 语言再编写一个动态链接库,这个
动态链接库的唯一功能就是使用 Java 能够理解的 C 代码来调用目标原生函数。
这个没什么实际用途的动态链接库的编写过程令人沮丧。同时编写 Java 和 C 代码使开
发难度大大增加。
因此,在 Java 开发社区中,人们一直都视 JNI 为禁地,轻易不愿涉足。
缺少原生函数的协助使 Java 的使用范围大大缩小。
反观.NET 阵营,其 P/Invoke 技术调用原生函数非常方便,不需要编写一行 C 代码,只
需要写 Annotation 就可以快速调用原生函数。因此,与硬件有关的很多开发领域都被.NET
所占据。
JNAJNAJNAJNA 介绍
JNA(Java Native Access)框架是一个开源的 Java 框架,是 SUN 公司主导开发的,建立在
经典的 JNI 的基础之上的一个框架。
JNA 项目地址:https://jna.dev.java.net/
JNA 使 Java 调用原生函数就像.NET 上的 P/Invoke 一样方便、快捷。
JNA 的功能和 P/Invoke 类似,但编写方法与 P/Invoke 截 然 不 同 。JNA 没有使用 Annotation,
而是通过编写一般的 Java 代码来实现。
P/Invoke 是.NET 平台的机制。而 JNA 是 Java 平台上的一个开源类库,和其他类库没有
什么区别。只需要在 classpath 下加入 jna.jar 包,就可以使用 JNA。
JNA 使 Java 平台可以方便地调用原生函数,这大大扩展了 Java 平台的整合能力。
JNAJNAJNAJNA 实现原理
JNI 是 Java 调用原生函数唯一的机制。JNA 也是建立在 JNI 技术之上的。它简化了 Java
调用原生函数的过程。
JNA 提供了一个动态的 C 语言编写的转发器,可以自动实现 Java 和 C 的数据类型映射。
你不再需要编写那个烦人的 C 动态链接库。
当然,这也意味着,使用 JNA 技术比使用 JNI 技术调用动态链接库会有些微的性能损失 。
可能速度会降低几倍。但对于绝大部分项目来说,影响不大。
JNAJNAJNAJNA 调用原生函数
让我们先看一个 JNA 调用原生函数的例子。
例子 1111 使用 JNAJNAJNAJNA 调用原生函数
假设我们有一个动态链接库,发布了这样一个 C 函数:
void say(wchar_t* pValue){
std::wcout.imbue(std::locale("chs"));
std::wcout<
载入进来。使用 JNA,我们不需要编写作为代理的动态链接库,不需要编写一行原生代码。
上面的 JNA 代码使用了单例,接口的静态变量返回的是接口的唯一实例,这个 Java 对
象是 JNA 通过反射动态创建的。通过这个对象,我们可以调用动态链接库发布的函数。
JavaJavaJavaJava 和原生代码的类型映射
跨平台、跨语言调用的最大难点,就是不同语言之间数据类型不一致造成的问题。绝大
部分跨平台调用的失败,都是这个问题造成的。
JNA 使用的数据类型是 Java 的数据类型。而原生函数中使用的数据类型是原生函数的编
程语言使用的数据类型。可能是 C,Delphi,汇编等语言的数据类型。因此,不一致是在所难免
的。
JNA 提供了 Java 和原生代码的类型映射。
JavaJavaJavaJava————CCCC 和操作系统数据类型的对应表
JavaJavaJavaJava 类型
boolean
byte
char
short
int
long
float
double
Buffer
Pointer
[] (基本类型的数组)
CCCC 类型
int
char
wchar_t
short
int
原生表现
32 位整数 (可定制)
8 位整数
平台依赖
16 位整数
32 位整数
long long, __int64
64 位整数
float
double
pointer
pointer
array
32 位浮点数
64 位浮点数
平台依赖(32 或 64 位指针)
32 或 64 位指针(参数/返回值)
邻接内存(结构体成员)
JNAJNAJNAJNA 支持常见的数据类型的映射
JavaJavaJavaJava 类型
CCCC 类型 原生表现
String
char*
\0 结束的数组 (native encoding or jna.encoding)
WString
String[]
wchar_t* \0 结束的数组(unicode)
char**
\0 结束的数组的数组
WString[]
wchar_t** \0 结束的宽字符数组的数组
Structure
struct*
struct
指向结构体的指针 (参数或返回值) (或者明确指定是结构体指
针)
结构体(结构体的成员) (或者明确指定是结构体)
Union
union
等同于结构体
Structure[]
struct[] 结构体的数组,邻接内存
Callback
(*fp)() Java 函数指针或原生函数指针
NativeMapped varies
NativeLong
PointerType
依赖于定义
平台依赖(32 或 64 位整数)
long
pointer 和 Pointer 相同
跨平台、跨语言调用原则:
尽量使用基本、简单的数据类型;
尽量少跨平台、跨语言传递数据!
如果有复杂的数据类型需要在 Java 和原生函数中传递,那么我们就必须在 Java 中模拟
大量复杂的原生类型。这将大大增加实现的难度,甚至无法实现。
如果在 Java 和原生函数间存在大量的数据传递,那么一方面,性能会有很大的损失。
更为重要的是,Java 调用原生函数时,会把数据固定在内存中,这样原生函数才可以访问这
些 Java 数据。这些数据,JVM 的 GC 不能管理,会造成内存碎片。
如果在你需要调用的动态链接库中,有复杂的数据类型和庞大的跨平台数据传递。那么
你应该另外写一些原生函数,把需要传递的数据类型简化,把需要传递的数据量简化。
JNAJNAJNAJNA 模拟结构体
在原生代码中,结构体是经常使用的复杂数据类型。这里我们研究一下怎样使用 JNA
模拟结构体。
Struct
例 2222 使用 JNAJNAJNAJNA 调用使用 Struct
Struct 的 CCCC 函数
Struct
假设我们现在有这样一个 C 语言结构体
struct UserStruct{
long id;
wchar_t* name;
int age;
};
使用上述结构体的函数
#define MYLIBAPI extern
"C"
__declspec( dllexport )
MYLIBAPI void sayUser(UserStruct* pUserStruct);
对应的 Java 程序中,在例 1 的 接口中添加下列代码:
extends
class
static
public
public
static
class
extends
static class
public static
public
class UserStruct extends
extends Structure{
public
public
public
public NativeLong id;
public
public
public
public WString name;
public intintintint age;
public
public
public
public
static
public
static
public
public
static
static
implements
implements
implements
implements Structure.ByReference { }
extends
class
static
public
implements
public
static
class
extends
implements
extends UserStruct implements
class ByValue extends
static class
public static
public
implements
Structure.ByValue
ByReference
class
class
class
class
extends
extends
extends
extends
UserStruct
{ }
}
public
public
public
public void
voidvoidvoid sayUser(UserStruct.ByReference struct);
Java 中的调用代码:
UserStruct userStruct=newnewnewnew UserStruct ();
userStruct.id=newnewnewnew NativeLong(100);
userStruct.age=30;
userStruct.name=newnewnewnew WString("奥巴马");
TestDll1.INSTANCE.sayUser(userStruct);
Structure
Structure
Structure
Structure 说明
现在,我们就在 Java 中实现了对 C 语言的结构体的模拟。
这里,我们继承了 Structure 类,用这个类来模拟 C 语言的结构体。
必须注意,Structure 子类中的公共字段的顺序,必须与 C 语言中的结构的顺序一致。
否则会报错!
因为,Java 调用动态链接库中的 C 函数,实际上就是一段内存作为函数的参数传递给 C
函数。
动态链接库以为这个参数就是 C 语言传过来的参数。
同时,C 语言的结构体是一个严格的规范,它定义了内存的次序。因此,JNA 中模拟的
结构体的变量顺序绝对不能错。
如果一个 Struct 有 2 个 int 变量。 Int a, int b
如果 JNA 中的次序和 C 中的次序相反,那么不会报错,但是数据将会被传递到错误的
字段中去。
Structure 类代表了一个原生结构体。当 Structure 对象作为一个函数的参数或者返回
值传递时,它代表结构体指针。当它被用在另一个结构体内部作为一个字段时,它代表结构
体本身。
另外,Structure 类有两个内部接口 Structure.ByReference 和 Structure.ByValue。这两个接
口仅仅是标记,如果一个类实现 Structure.ByReference 接口,就表示这个类代表结构体指针 。
如果一个类实现 Structure.ByValue 接口,就表示这个类代表结构体本身。
使用这两个接口的实现类,可以明确定义我们的 Structure 实例表示的是结构体的指针
还是结构体本身。
上面的例子中,由于 Structure 实例作为函数的参数使用,因此是结构体指针。所以这
里直接使用了 UserStruct userStruct=newnewnewnew UserStruct ();
也可以使用 UserStruct userStruct=newnewnewnew UserStruct.ByReference ();
明确指出 userStruct 对象是结构体指针而不是结构体本身。
JNAJNAJNAJNA 模拟复杂结构体
C 语言最主要的数据类型就是结构体。结构体可以内部可以嵌套结构体,这使它可以模
拟任何类型的对象。
JNA 也可以模拟这类复杂的结构体。
例 3333
结构体内部可以包含结构体对象的数组
struct CompanyStruct{
long id;
wchar_t*
name;
UserStruct
users[100];
int count;
};
JNA 中可以这样模拟:
extends
class
static
public
public
static
class
extends
class CompanyStruct extends
static class
public static
public
extends Structure{
UserStruct.ByValue[]
users=newnewnewnew
public
public
public
public NativeLong id;
public
public
public
public WString name;
public
public
public
public
UserStruct.ByValue[100];
public
public intintintint count;
public
public
}
这里,必须给 users 字段赋值,否则不会分配 100 个 UserStruct 结构体的内存,这样JNA
中的内存大小和原生代码中结构体的内存大小不一致,调用就会失败。
例 4444
结构体内部可以包含结构体对象的指针的数组
struct CompanyStruct2{
long id;
wchar_t*
name;
UserStruct*
users[100];
int count;
};
JNA 中可以这样模拟:
extends
class
static
public
public
static
class
extends
class CompanyStruct2 extends
static class
public static
public
extends Structure{
public
public
public
public NativeLong id;
public
public
public
public WString name;
public
public
public
public
UserStruct.ByReference[100];
UserStruct.ByReference[]
users=newnewnewnew
public
public intintintint count;
public
public
}
测试代码:
CompanyStruct2.ByReference
CompanyStruct2.ByReference();
companyStruct2.id=newnewnewnew NativeLong(2);
companyStruct2.name=newnewnewnew WString("Yahoo");
companyStruct2.count=10;
UserStruct.ByReference
companyStruct2=newnewnewnew
pUserStruct=newnewnewnew
UserStruct.ByReference();
pUserStruct.id=newnewnewnew NativeLong(90);
pUserStruct.age=99;
pUserStruct.name=newnewnewnew WString("杨致远");
// pUserStruct.write();
forforforfor(intintintint i=0;i