JavaCPP技術使用經驗總結

developerguy發表於2016-11-25

本文是對 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 特性的一個有用的子集,適合開發跨平臺、自包容的應用程式。它實現非常快速而且體積小,主要特性包括如下四點:

  1. 類似於 HotSpot JVM 的 JIT 編譯器,支援快速方法執行;
  2. 採用 JVM 的複製演算法,即將現有的記憶體空間分為兩快,每次只使用其中一塊,在垃圾回收時將正在使用的記憶體中的存活物件複製到未被使用的記憶體塊中,之後,清除正在使用的記憶體塊中的所有物件,交換兩個記憶體的角色,完成垃圾回收。這樣可以確保記憶體回收過程中記憶體暫停服務的時間較短,並且記憶體的使用空間侷限性較小;
  3. JVM 記憶體區域裡面的本地棧快速分配,沒有同步開銷;
  4. 作業系統訊號量方式解決了空指標問題,避免了不必要的分支。
  • 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/

 


相關文章