商業支援:
JavaCPP 提供了對 Java 內部本機 C++ 的高效訪問,這與某些 C/C++ 編譯器與彙編語言互動的方式沒有什麼不同。無需發明新語言,例如 SWIG、SIP、C++/CLI、Cython 或 RPython。相反,與 cppyy 為 Python 所做的事情類似,它利用了 Java 和 C++ 之間的語法和語義相似性。在底層,它使用 JNI,因此除了 Android、Avian 和 RoboVM(指令)之外,它也適用於 Java SE 的所有實作。
更具體地說,與上述或其他地方的方法(CableSwig、JNIGeneratorApp、cxxwrap、JNIWrapper、Platform Invoke、GlueGen、LWJGL Generator、JNIDirect、ctypes、JNA、JNIEasy、JniMarshall、JNative、JNIDirect、ctypes、JNA、JNIEasy、JniMarshall、JNative、JNIDirect、JV/J 、 CFFI、fficxx、CppSharp、cgo、pybind11、rust-bindgen、Panama Native 等),它自然而有效地映射了C++ 語言提供的許多常見功能,並且經常被認為是有問題的,包括重載運算符、類別和函數模板、透過函數指標、函數物件(又稱函子)、虛擬函數和成員函數指標、巢狀結構定義、可變長度參數、巢狀命名空間、包含任意循環的大型資料結構、虛擬和多重繼承、按值/引用/字串傳遞/返迴向量、匿名聯合、位元字段、異常、析構函數和共享或唯一指標(透過 try-with-resources 或垃圾收集)以及文件註釋。顯然,整齊地支援整個 C++ 需要更多的工作(儘管人們可能會爭論 C++ 內在的整潔性),但我們在這裡發布它作為概念證明。
舉個例子,我們已經使用它產生了 OpenCV、FFmpeg、libdc1394、PGR FlyCapture、OpenKinect、videoInput、ARToolKitPlus、Leptonica、Tesseract、GSL、LLVM、HDF5、MKL、CUDA、Caffe、MXNet、TensorFlowAPI 的完整介面系統以及其他作為JavaCPP 預設子專案一部分的內容,也示範了C/C++ 頭檔的早期解析功能,這些功能顯示了有希望且有用的結果。
如果您在使用該軟體時遇到任何問題,請隨時在郵件清單或論壇上提問!我確信它遠非完美......
包含 JAR 檔案的存檔可作為版本提供。
我們也可以使用以下命令自動下載和安裝所有內容:
Maven(在pom.xml
檔內)
<依賴關係> <groupId>org.bytedeco</groupId> <artifactId>javacpp</artifactId> <版本>1.5.11</版本> </依賴>
Gradle(在build.gradle.kts
或build.gradle
檔案內)
依賴項{ 實作(“org.bytedeco:javacpp:1.5.11”) }
Leiningen(在project.clj
檔案內)
:依賴關係[ [org.bytedeco/javacpp“1.5.11”] ]
sbt(在build.sbt
檔案內)
庫依賴項 += "org.bytedeco" % "javacpp" % "1.5.11"
Gradle 使用者可以使用的另一個選項是 Gradle JavaCPP,同樣,Scala 使用者可以使用 SBT-JavaCPP。
要使用JavaCPP,您需要下載並安裝以下軟體:
Java SE 7 或更高版本的實作:
OpenJDK http://openjdk.java.net/install/ 或
Oracle JDK http://www.oracle.com/technetwork/java/javase/downloads/ 或
IBM JDK http://www.ibm.com/developerworks/java/jdk/
一個 C++ 編譯器,其中已經過測試:
https://docs.microsoft.com/en-us/cpp/build/walkthrough-compiling-a-native-cpp-program-on-the-command-line
對於 Windows x86 和 x64 http://mingw-w64.org/
GNU C/C++ 編譯器(Linux 等)http://gcc.gnu.org/
LLVM Clang(Mac OS X 等)http://clang.llvm.org/
Microsoft C/C++ 編譯器,Visual Studio 的一部份 https://visualstudio.microsoft.com/
要產生適用於 Android 4.0 或更高版本的二進位文件,您還必須安裝:
Android NDK r7 或更高版本 http://developer.android.com/ndk/downloads/
與目標 iOS 類似,您需要安裝:
Gluon VM http://gluonhq.com/products/mobile/vm/ 或
RoboVM 1.x 或更高版本 http://robovm.mobidevelop.com/downloads/
若要修改原始程式碼,請注意專案文件是為以下目的建立的:
Maven 3.x http://maven.apache.org/download.html
最後,由於我們處理的是本機程式碼,錯誤很容易使虛擬機器崩潰。幸運的是,HotSpot VM 提供了一些工具來幫助我們在這些情況下進行調試:
使用 HotSpot VM 的 Java SE 故障排除指南
http://docs.oracle.com/javase/7/docs/webnotes/tsg/TSG-VM/html/
http://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/
要了解如何使用 JavaCPP,應該先查看 C/C++ 程式庫的映射方法,但也可以對基本架構進行高階概述,以了解更大的情況。 JavaCPP 預設的儲存庫進一步提供了我們可以用作模板的複雜範例,但它還包括一個關於如何建立新預設的wiki 頁面,該頁面詳細解釋了它們的結構,以及一個小而完整的範例項目,人們可以從中開始試驗和。
為了實作native
方法,JavaCPP 為 JNI 產生適當的程式碼,並將其傳遞給 C++ 編譯器以建立本機程式庫。我們永遠不需要親自使用 JNI、makefile 或其他本機工具。這裡要認識到的重要一點是,雖然我們使用註釋在 Java 語言中進行所有自定義,但與手動編碼的 JNI 函數相比,JavaCPP 生成的程式碼開銷為零(驗證生成的.cpp 檔案以說服自己) 。此外,在執行時, Loader.load()
方法會自動從 Java 資源載入本機程式庫,這些資源由建置過程放置在正確的目錄中。它們甚至可以存檔在 JAR 檔案中,這不會改變任何內容。使用者根本不需要弄清楚如何讓系統載入檔案。這些特性使得 JavaCPP 適用於
存取本機 API,
使用複雜的 C++ 類型,
優化程式碼效能,或者
建立回調函數。
除了下面提供的幾個範例之外,要了解有關如何使用此工具的功能的更多信息,請參閱 C/C++ 庫的映射食譜以及 JavaCPP 預設的源代碼範例。有關 API 本身的更多信息,可以參考 Javadoc 產生的文檔。
當然,這一切也適用於 Scala 語言,但為了使過程更加順利,添加對「本機屬性」的支援應該不會太難,這樣像@native var
這樣的聲明就可以生成本機 getter和設定方法. ..
最常見的用例涉及存取一些為 C++ 編寫的本機程式庫,例如,在包含此 C++ 類別的名為NativeLibrary.h
的檔案中:
#include <string>命名空間 NativeLibrary { class NativeClass { public: const std::string& get_property() { return property; } } void set_property(const std::string& property) { this->property = property; } std::string 屬性; }; }
為了使用 JavaCPP 完成工作,我們可以輕鬆定義一個像這樣的 Java 類別——儘管可以使用Parser
從頭文件中生成它,如 JavaCPP Presets 子項目所示,遵循映射食譜中概述的原則對於C/C++ 庫:
導入 org.bytedeco.javacpp.*;導入 org.bytedeco.javacpp.annotation.*;@Platform(include="NativeLibrary.h")@Namespace("NativeLibrary")public class NativeLibrary { public static class NativeClass extends Pointer { static { 載入器.load(); } 公用 NativeClass() { allocate(); } 私有本機無效分配(); // 呼叫 getter 和 setter 函數 public native @StdString String get_property();公用本機無效set_property(字串屬性); // 直接存取成員變數 public native @StdString String property(); 公用本機無效屬性(字串屬性); } public static void main(String[] args) { // 在Java 中分配的指針對像一旦無法存取就會被釋放, // 但C++ 析構函數仍然可以透過Pointer.deallocate() 及時呼叫NativeClass l = new NativeClass (); l.set_property("你好世界!"); System.out.println(l.property()); } }
以通常的方式編譯Java原始碼後,我們還需要在執行之前使用JavaCPP進行構建,或者我們可以讓它做所有事情,如下所示:
$ java -jar javacpp.jar NativeLibrary.java -exec 你好世界!
為了證明即使面對複雜的資料類型它也相對易於使用,假設我們有一個 C++ 函數,它接受一個vector<vector<void*> >
作為參數。為了支援這種類型,我們可以定義一個簡單的類,如下所示:
導入 org.bytedeco.javacpp.*;導入 org.bytedeco.javacpp.annotation.*;@Platform(include="<vector>")public class VectorTest { @Name("std::vector<std::vector<void *> >") public static class PointerVectorVector extends Pointer { static { Loader.load(); } 公共 PointerVectorVector() { allocate(); } 公用 PointerVectorVector(long n) { 分配(n); } 公用 PointerVectorVector(指標 p) { super(p); } // this = (vector<vector<void*> >*)p private native void allocate(); // this = new vector<vector<void*> >() private native void allocate(long n); // this = new vector<vector<void*> >(n) @Name("operator=") public native @ByRef PointerVectorVector put(@ByRef PointerVectorVector x); @Name("operator[]") public native @StdVector PointerPointer get(long n); 公用本機@StdVector PointerPointer at(long n); 公共本機長尺寸(); 公共本機@Cast(「布爾」)布爾空(); 公共本機無效調整大小(長n); 公用本機@Index long size(long i); // return (*this)[i].size() public native @Index @Cast("bool") boolean empty(long i); // return (*this)[i].empty() public native @Index void resize(long i, long n); // (*this)[i].resize(n) public native @Index Pointer get(long i, long j); // return (*this)[i][j] public native void put(long i, long j, 指標 p); // (*this)[i][j] = p } public static void main(String[] args) { PointerVectorVector v = new PointerVectorVector(13); v.調整大小(0, 42); // v[0].resize(42) 指標 p = new Pointer() { { 位址 = 0xDEADBEEFL; } }; v.put(0, 0, p); // v[0][0] = p PointerVectorVector v2 = new PointerVectorVector().put(v); 指標 p2 = v2.get(0).get(); // p2 = *(&v[0][0]) System.out.println(v2.size() + " " + v2.size(0) + " " + p2); v2.at(42); } }
使用此命令執行程式會產生以下輸出:
$ java -jar javacpp.jar VectorTest.java -exec 13 42 org.bytedeco.javacpp.Pointer[位址=0xdeadbeef,位置=0,限制=0,容量=0,deallocator=null] 執行緒「main」中的例外 java.lang.RuntimeException: vector::_M_range_check: __n (即 42) >= this->size() (即 13) 在 VectorTest$PointerVectorVector.at(本機方法) 在 VectorTest.main(VectorTest.java:44)
其他時候,出於效能原因,我們可能希望使用 C++(包括 CUDA)進行編碼。假設我們的分析器發現名為Processor.process()
方法佔用了程式 90% 的執行時間:
public class Processor { public static void process(java.nio.Buffer buffer, int size) { System.out.println("Java 中的處理..."); // ... } public static void main(String[] args) { 程式(null, 0); } }
經過多天的努力和汗水,工程師們想出了一些竅門,成功地將這個比例降到了80%,但你知道,管理者們仍然不滿意。因此,我們可以嘗試用 C++(甚至透過內聯彙編器使用組合語言)來重寫它,並將產生的函數放入名為Processor.h
的檔案中:
#include <iostream>static inline void process(void *buffer, int size) { std::cout << "用 C++ 處理..." << std::endl; // ...}
將Java原始碼調整為如下所示:
導入 org.bytedeco.javacpp.*;導入 org.bytedeco.javacpp.annotation.*;@Platform(include="Processor.h")public class Processor { static { Loader.load(); } public static native void process(java.nio.Buffer buffer, int size); 公共靜態無效主(字串[] args){過程(null,0); } }
然後它會像這樣編譯和執行:
$ java -jar javacpp.jar Processor.java -exec 用 C++ 處理...
有些應用程式還需要一種從 C/C++ 回調 JVM 的方法,因此 JavaCPP 提供了一種簡單的方法來定義自訂回調,無論是函數指標、函數物件還是虛擬函數。儘管存在可以將完整 Java API 映射到 C++ 的框架(例如 Jace、JunC++ion、JCC、jni.hpp 或 Scapix),但它們可能更難使用,因為從本機程式碼呼叫 Java 方法至少需要比相反的時間要長一個數量級,在我看來,像設計用於Java 中使用的API 那樣導出並沒有多大意義。儘管如此,假設我們想要在 Java 中執行一些操作,並計劃將其包裝到一個名為foo()
的函數中,該函數調用類別Foo
中的某個方法,我們可以在名為foo.cpp
的文件中編寫以下程式碼,並注意初始化JVM(如有必要)可使用JavaCPP_init()
或任何其他方式:
#include <iostream>#include "jniFoo.h"int main() { JavaCPP_init(0, NULL); 試 { foo(6, 7); } catch (std::異常 &e) { std::cout << e.what() << std::endl; JavaCPP_uninit(); }
然後我們可以將該函數宣告為FunctionPointer
中定義的call()
或apply()
方法,如下所示:
導入org.bytedeco.javacpp.*;導入org.bytedeco.javacpp.annotation.*;@Platform(include="<algorithm>")@Namespace("std")public class Foo { static { Loader.load(); } } public static class Callback extends FunctionPointer { // 僅在明確建立實例時才需要 Loader.load() 和 allocate() static { Loader.load(); } 受保護的回呼() { 分配(); } 私有本機無效分配(); public @Name("foo") boolean call(int a, int b) throws Exception { 拋出新的例外(“欄”+ a * b); } // 我們也可以將 FunctionPointer 作為參數傳遞給其他函數(或從其他函數傳回值) public static native void stable_sort(IntPointer first, IntPointer last, Callback Compare); // 要將其作為 C++ 函數物件傳遞(或取得),請使用 @ByVal 或 @ByRef 進行註解 public static native void sort(IntPointer first, IntPointer last, @ByVal Callback Compare); }
由於函數也有指針,我們可以相應地使用FunctionPointer
實例,其方式類似於 Haskell FFI 的FunPtr
類型,但拋出的任何java.lang.Throwable
物件都會轉換為std::exception
。在 Linux x86_64 下使用這些命令建置並執行此範例程式碼會產生預期的輸出:
$ java -jar javacpp.jar Foo.java -header $ g++ -I/usr/lib/jvm/java/include/ -I/usr/lib/jvm/java/include/linux/ foo.cpp linux-x86_64/libjniFoo.so -o foo $ ./foo java.lang.Exception:欄 42
在此範例中, FunctionPointer
物件是隱式建立的,但要呼叫本機函數指針,我們可以定義一個包含native call()/apply()
方法的對象,並明確建立一個實例。這樣的類別也可以在 Java 中擴展以建立回調,並且像任何其他普通Pointer
物件一樣,必須使用native void allocate()
方法進行分配,因此請記住保留 Java 中的引用,因為這些引用將被垃圾收集。作為獎勵, FunctionPointer.call()/apply()
實際上映射到 C++ 函數物件的重載運算子operator()
,我們可以透過使用@ByVal
或@ByRef
註解參數將其傳遞給其他函數,就像sort()
一樣上面例子中的函數。
也可以對虛擬函數執行相同的操作,無論是否為「純」。考慮在名為Foo.h
的檔案中定義的以下 C++ 類別:
#include <stdio.h>class Foo {public: int n; Foo(int n) : n(n) { } virtual ~Foo() { } virtual void bar() { printf("C++ 中的回呼 (n == %d)n", n); } };無效回呼(Foo *foo) { foo->bar(); }
如果我們將同級類別中的方法宣告為本native
或abstract
並使用@Virtual
進行註釋,則可以在 Java 中重寫函數Foo::bar()
例如:
導入 org.bytedeco.javacpp.*;導入 org.bytedeco.javacpp.annotation.*;@Platform(include="Foo.h")public class VirtualFoo { static { Loader.load(); } public static class Foo extends Pointer { static { Loader.load(); } 公用 Foo(int n) { 分配(n); } 私有本機無效分配(int n); @NoOffset 公用本機 int n();公用本機 Foo n(int n); @Virtual public native void bar(); } 公用靜態本機無效回呼(Foo foo); 公共靜態無效主(字串[] args){ Foo foo = new Foo(13); Foo foo2 = new Foo(42) { public void bar() { System.out.println("Java 中的回呼 (n == " + n() + ")"); } }; foo.bar(); foo2.bar(); 回調(foo); 回調(foo2); } }
它輸出人們自然會假設的內容:
$ java -jar javacpp.jar VirtualFoo.java -exec C++ 中的回呼 (n == 13) Java 中的回呼(n == 42) C++ 中的回呼 (n == 13) Java 中的回呼(n == 42)
最容易使用的是使用 OpenJDK 類別庫編譯的 Avian,所以讓我們從它開始。如上所述創建並建置程式後,無需進一步修改,我們可以直接使用以下命令執行它:
$ /path/to/avian-dynamic -Xbootclasspath/a:javacpp.jar <MainClass>
然而,對於 Android 來說,我們還需要做更多的工作。對於基於Ant的命令列建置系統,在專案目錄中:
將javacpp.jar
檔複製到libs/
子目錄中,然後
執行此命令以在libs/armeabi/
中產生*.so
庫檔案:
$ java -jar libs/javacpp.jar -classpath bin/ -classpath bin/classes/ > -properties <android-arm|android-x86> -Dplatform.root=/path/to/android-ndk/ > -Dplatform .compiler=/path/to/<arm-linux-androideabi-g++|i686-linux-android-g++> -d libs/<armeabi|x86>/
為了讓一切自動化,我們也可以將該指令插入build.xml
檔案中。或者,為了與 Android Studio 集成,我們可以使用 Gradle JavaCPP。
對於 RoboVM 類似,假設編譯的類別位於classes
子目錄中:
將javacpp.jar
檔複製到專案目錄中,並
執行以下命令來生成本機二進位:
$ java -jar javacpp.jar -cp 類別/ -properties <ios-arm|ios-x86> -o lib $ /path/to/robovm -arch <thumbv7|x86> -os ios -cp javacpp.jar:classes/ -libs classes/<ios-arm|ios-x86>/lib.o <MainClass>
在這種情況下,應該使用System.load("lib.o")
來載入函式庫,而不是Loader.load()
,而且可能根本不需要。
專案負責人:Samuel Audet gmail.com at
samuel.audet
開發者網址:https://github.com/bytedeco/javacpp
討論群組:http://groups.google.com/group/javacpp-project