本文是對 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/