本文是对 JNI 技术的一个补充方法,提出了替换 JNI、JNA 的一种开源技术。首先对 JavaCPP 技术进行简单介绍及对应于其他现有方案的介绍、对比。接下来,通过一个简单的示例让大家了解 JavaCPP 的工作原理。然后,介绍了 JavaCPP presets 子项目,最后通过若干个针对 presets 的示例来让大家了解如何使用它,本文主要提出了替换 JNI 的一种编程实现方式。
JavaCPP 简介
JavaCPP 是一个开源库,它提供了在 Java 中高效访问本地 C++的方法。采用 JNI 技术实现,所以支持所有 Java 实现包括 Android 系统,Avian 和 RoboVM。
- Android
一种基于 Linux 的自由及开放源代码的操作系统,主要使用于移动设备,如智能手机和平板电脑,由 Google 公司和开放手机联盟领导及开发。
- Avian
Avian 是一个轻量级的 Java 虚拟机和类库,提供了 Java 特性的一个有用的子集,适合开发跨平台、自包容的应用程序。它实现非常快速而且体积小,主要特性包括如下四点:
- 类似于 HotSpot JVM 的 JIT 编译器,支持快速方法执行;
- 采用 JVM 的复制算法,即将现有的内存空间分为两快,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。这样可以确保内存回收过程中内存暂停服务的时间较短,并且内存的使用空间局限性较小;
- JVM 内存区域里面的本地栈快速分配,没有同步开销;
- 操作系统信号量方式解决了空指针问题,避免了不必要的分支。
- RoboVM
RoboVM 编译器可以将 Java 字节码翻译成 ARM 或者 x86 平台上的原生代码,应用可直接在 CPU 上运行,无需其他解释器或者虚拟机。RoboVM 同时包含一个 Java 到 Objective-C 的桥,可像其他 Java 对象一样来使用 Objective-C 对象。大多数 UIKit 已经支持,而且将会支持更多的框架。
总的来说,JavaCPP 提供了一系列的 Annotation 将 Java 代码映射到 C++代码,并使用一个可执行的 jar 包将 C++代码转化为可以从 JVM 内调用的动态链接库文件。
与其他技术相比,特性总结如下表 1 所示。
表 1. 类似技术介绍或特点
技术名称 | 技术介绍 |
---|---|
CableSwig | 用于针对 Tcl 和 Python 语言创建接口 |
JNIGeneratorApp | 所有用于 SWT 的 C 代码都是通过它来创建的 |
cxxwrap | 用于生成针对 C++的 Java JNI 包、HTML 文档、用户手册 |
JNIWrapper | 商业版本,可以帮助实现 Java 和本地代码之间的无缝结合 |
Platform Invoke | 微软发布的一个工具 |
GlueGen | 针对 C 语言的一个工具,帮助生成 JNI 代码 |
LWJGL Generator | JNI 代码生成器 |
ctypes | 针对 Python 的接口代码生成器 |
JNA | JNA(Java Native Access)提供一组 Java 工具类用于在运行期动态访问系统本地库(native library:如 Window 的 dll)而不需要编写任何 Native/JNI 代码。开发人员只要在一个 Java 接口中描述目标 native library 的函数与结构,JNA 将自动实现 Java 接口到 native function 的映射。 |
JNIEasy | 替换 JNA 的一种技术 |
JNative | Windows 版本的库 (DLL),提供了 JNI 代码生成 |
fficxx | 针对 haskell 模型的代码生成器,主要生成 C 语言 |
JavaCPP | 更加自然高效,它支持大部分的 C++语法特性。目前已经能成功封装 OpenCV, FFmpeg, libdc1394, PGR FlyCapture, OpenKinect, videoInput, and ARToolKitPlus。除此之外,它还能直接把 C/C++的头文件转化成 Java 类,能自动生成 JNI 代码,编译成本地库,开发人员无需编写繁琐的 C++、JNI 代码,从而提高开发效率。 |
JavaCPP 示例
为了调用本地方法,JavaCPP 生成了对应的 JNI 代码,并且把这些代码输入到 C++编译器,用来构建本地库。使用了 Annotations 特性的 Java 代码在运行时会自动调用 Loader.load() 方法从 Java 资源里载入本地库,这里指的资源是工程构建过程中配置好的。
我们先来演示一个例子,这是一个简单的注入/读出方法,类似于 JavaBean 的工作方式。清单 1 所示的 LegacyLibrary.h 包含了 C++类。
清单 1. LegacyLibrary.h
#include <string> namespace LegacyLibrary { class LegacyClass { public: const std::string& get_property() { return property; } void set_property(const std::string& property) { this->property = property; } std::string property; }; }
接下来定义一个 Java 类,驱动 JavaCPP 来完成调用 C++代码。
清单 2. LegacyLibrary.java
import org.bytedeco.javacpp.*; import org.bytedeco.javacpp.annotation.*; @Platform(include="LegacyLibrary.h") @Namespace("LegacyLibrary") public class LegacyLibrary { public static class LegacyClass extends Pointer { static { Loader.load(); } public LegacyClass() { allocate(); } private native void allocate(); // to call the getter and setter functions public native @StdString String get_property(); public native void set_property(String property); // to access the member variable directly public native @StdString String property(); public native void property(String property); } public static void main(String[] args) { // Pointer objects allocated in Java get deallocated once they become unreachable, // but C++ destructors can still be called in a timely fashion with Pointer.deallocate() LegacyClass l = new LegacyClass(); l.set_property("Hello World!"); System.out.println(l.property()); } }
以上两个类放在一个目录下面,接下来运行一系列编译指令,如清单 3 所示。
清单 3. 运行命令
$ javac -cp javacpp.jar LegacyLibrary.java $ java -jar javacpp.jar LegacyLibrary $ java -cp javacpp.jar LegacyLibrary Hello World!
我们看到清单 3 最后运行输出了一行“Hello World!”,这是 LegacyLibrary 类里面定义好的,通过一个 setter 方法注入字符串,getter 方法读出字符串。
我们可以看到文件夹里面内容的变化,刚开始的时候只有.h、.java 两个文件,清单 3 所示的 3 个命令运行过后,生成了 class 文件及本地方法 (native method) 对应的.so 文件。
http://www.ibm.com/developerworks/cn/java/j-lo-cpp/