商用サポート:
JavaCPP は、一部の C/C++ コンパイラーがアセンブリ言語と対話する方法と同様に、Java 内のネイティブ 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、J/Invoke、HawtJNI、JNR、BridJ、 CFFI、fficxx、CppSharp、cgo、 pybind11、rust-bindgen、パナマ ネイティブなど)、オーバーロードされた演算子、クラスと関数のテンプレート、関数ポインターを介したコールバック、関数オブジェクト(ファンクターとも呼ばれる)など、C++ 言語によって提供され、問題があると考えられる多くの共通機能を自然かつ効率的にマッピングします。 )、仮想関数とメンバー関数ポインター、ネストされた構造体定義、可変長引数、ネストされた名前空間、任意のサイクルを含む大きなデータ構造、仮想継承と多重継承、受け渡し/返却値/参照/文字列/ベクトル、匿名共用体、ビット フィールド、例外、デストラクター、共有ポインターまたは一意のポインター (リソースの試行またはガベージ コレクション経由)、およびドキュメントのコメントによって。明らかに、C++ 全体をきちんとサポートするにはさらに多くの作業が必要になります (ただし、C++ の本質的なきちんと性について議論する人もいるでしょう)。しかし、概念実証としてここでリリースします。
好例として、私たちはすでにこれを使用して、OpenCV、FFmpeg、libdc1394、PGR FlyCapture、OpenKinect、videoInput、ARToolKitPlus、Leptonica、Tesseract、GSL、LLVM、HDF5、MKL、CUDA、Caffe、MXNet、TensorFlow への完全なインターフェイスを作成しています。 、システム API など、JavaCPP Presets サブプロジェクトの一部として、また、有望で有用な結果を示す 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") }
ライニンゲン ( project.clj
ファイル内)
:依存関係 [ [org.bytedeco/javacpp "1.5.11"] 】
sbt ( build.sbt
ファイル内)
libraryDependency += "org.bytedeco" % "javacpp" % "1.5.11"
Gradle ユーザーが利用できるもう 1 つのオプションは 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 言語内で行う一方で、JavaCPP は手動でコード化された JNI 関数と比較してオーバーヘッドがゼロのコードを生成するということです (生成された .cpp ファイルを確認して納得してください)。さらに、実行時に、 Loader.load()
メソッドは、ビルド プロセスによって適切なディレクトリに配置された Java リソースからネイティブ ライブラリを自動的にロードします。 JAR ファイルにアーカイブすることもできますが、何も変わりません。ユーザーは、システムにファイルをロードさせる方法を理解する必要はありません。これらの特性により、JavaCPP はどちらの用途にも適しています。
ネイティブ API へのアクセス、
複雑な C++ 型を使用し、
コードのパフォーマンスを最適化する、または
コールバック関数を作成します。
以下に示すいくつかの例に加えて、このツールの機能の使用方法について詳しくは、C/C++ ライブラリのマッピング レシピと JavaCPP プリセットのソース コードの例を参照してください。 API 自体の詳細については、Javadoc によって生成されたドキュメントを参照してください。
当然のことながら、これはすべて Scala 言語でも同様に機能しますが、プロセスをさらにスムーズにするために、 @native var
のような宣言でネイティブ ゲッターを生成できるように、「ネイティブ プロパティ」のサポートを追加するのはそれほど難しいことではありません。そしてセッターメソッド...
最も一般的な使用例には、C++ 用に作成されたネイティブ ライブラリ (たとえば、次の C++ クラスを含むNativeLibrary.h
という名前のファイル内) へのアクセスが含まれます。
#include <string>namespace NativeLibrary { class NativeClass { public: const std::string& get_property() { return プロパティ; } void set_property(const std::string& property) { this->property = プロパティ; } std::string プロパティ; }; }
JavaCPP でジョブを実行するには、このような Java クラスを簡単に定義できます。ただし、マッピング レシピで概説されている原則に従って、JavaCPP Presets サブプロジェクトで示されているように、 Parser
を使用してヘッダー ファイルからクラスを生成することもできます。 C/C++ ライブラリの場合:
import org.bytedeco.javacpp.*;import org.bytedeco.javacpp.annotation.*;@Platform(include="NativeLibrary.h")@Namespace("NativeLibrary")public class NativeLibrary { public static class NativeClass extends Pointer { static { Loader.load(); public NativeClass() { assign();プライベートネイティブ void assign(); // ゲッター関数とセッター関数を呼び出すため publicnative @StdString String get_property(); public Native void set_property(String プロパティ); // メンバー変数に直接アクセスするには public ネイティブ @StdString String property(); パブリックネイティブ void プロパティ(文字列プロパティ); } public static void main(String[] args) { // Java で割り当てられたポインタ オブジェクトは、到達不能になると割り当てが解除されます。 // ただし、C++ デストラクタは Pointer.deallocate() を使用して適切なタイミングで呼び出すことができます。 NativeClass l = new NativeClass (); l.set_property("Hello World!"); System.out.println(l.property()); } }
通常の方法で Java ソース コードをコンパイルした後、実行する前に JavaCPP を使用してビルドする必要もあります。または、次のようにすべてを JavaCPP に実行させることもできます。
$ java -jar javacpp.jar NativeLibrary.java -exec 「こんにちは世界」
複雑なデータ型であっても比較的使いやすいことを示すために、 vector<vector<void*> >
引数として受け取る C++ 関数があると想像してください。その型をサポートするには、次のような必要最低限のクラスを定義できます。
import org.bytedeco.javacpp.*;import 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(); public PointerVectorVector() { 割り当て(); public PointerVectorVector(long n) { assign(n); public PointerVectorVector(Pointer p) { super(p); } // this = (vector<vector<void*> >*)p privateネイティブ void assign(); // this = new Vector<vector<void*> >() privateネイティブ void assign(long n); // this = new Vector<vector<void*> >(n) @Name("operator=") publicネイティブ @ByRef PointerVectorVector put(@ByRef PointerVectorVector x); @Name("operator[]") パブリック ネイティブ @StdVector PointerPointer get(long n); パブリックネイティブ @StdVector PointerPointer at(long n); パブリックネイティブロングサイズ(); パブリックネイティブ @Cast("bool") boolean empty(); publicnativevoidsize(longn); パブリックネイティブ @Index long size(long i); // return (*this)[i].size() publicネイティブ @Index @Cast("bool") boolean empty(long i); // return (*this)[i].empty() publicnative@Index voidsize(long i, long n); // (*this)[i].resize(n) public ネイティブ @Index Pointer get(long i, long j); // return (*this)[i][j] publicネイティブ void put(long i, long j, Pointer p); // (*this)[i][j] = p public static void main(String[] args) { PointerVectorVector v = new PointerVectorVector(13); v.resize(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.Bufferbuffer, int size) { System.out.println("Java での処理..."); // ... public static void main(String[] args) { process(null, 0); } }
何日も汗を流した後、エンジニアはいくつかのハックを見つけ出し、その比率を 80% まで下げることができました。しかし、マネージャーはまだ満足していませんでした。したがって、これを C++ (またはインライン アセンブラを介したアセンブリ言語) で書き直して、結果として得られた関数をProcessor.h
という名前のファイルに配置することもできます。
#include <iostream>static inline void process(void *buffer, int size) { std::cout << "C++ で処理しています..." << std::endl; // ...}
Java ソース コードを次のように調整した後、次のようになります。
import org.bytedeco.javacpp.*;import org.bytedeco.javacpp.annotation.*;@Platform(include="Processor.h")public class Processor { static { Loader.load(); public staticative void process(java.nio.Buffer バッファ, int サイズ); public static void main(String[] args) { process(null, 0); } }
次に、次のようにコンパイルして実行します。
$ java -jar javacpp.jar プロセッサ.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); を試してください。 } キャッチ (std::例外 &e) { std::cout << e.what() << std::endl; JavaCPP_uninit(); }
次に、次のようにFunctionPointer
で定義されたcall()
またはapply()
メソッドに対してその関数を宣言できます。
import org.bytedeco.javacpp.*;import org.bytedeco.javacpp.annotation.*;@Platform(include="<algorithm>")@Namespace("std")public class Foo { static { Loader.load(); public static class Callback extends FunctionPointer { // Loader.load() と assign() はインスタンスを明示的に作成する場合にのみ必要です static { Loader.load(); protected Callback() { assign();プライベートネイティブ void assign(); public @Name("foo") boolean call(int a, int b) は例外をスローします { throw new Exception("bar " + a * b); } } // FunctionPointer を引数として他の関数に渡す(または取得する)こともできます(または他の関数からの戻り値) public staticative voidsteady_sort(IntPointer first, IntPointer last, Callback Compare); // C++ 関数オブジェクトとして渡す (または取得する) には、@ByVal または @ByRef で注釈を付けます public staticative void sort(IntPointer first, IntPointer last, @ByVal Callback Compare); }
関数にもポインターがあるため、Haskell FFI のFunPtr
型と同様の方法でFunctionPointer
インスタンスを使用できますが、スローされた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()
にマップされます。このオブジェクトは、 sort()
と同様に、パラメーターに@ByVal
または@ByRef
の注釈を付けることで、他の関数に渡すことができます。上の例の関数。
「純粋」かどうかに関係なく、仮想関数でも同じことを行うことも可能です。 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); } };void callback(Foo *foo) { foo->bar(); }
関数Foo::bar()
は、ピア クラスでメソッドをnative
またはabstract
として宣言し、それに@Virtual
の注釈を付けると、Java でオーバーライドできます。次に例を示します。
import org.bytedeco.javacpp.*;import org.bytedeco.javacpp.annotation.*;@Platform(include="Foo.h")public class VirtualFoo { static { Loader.load(); public static class Foo extends Pointer { static { Loader.load(); public Foo(int n) { 割り当て(n);プライベートネイティブ void assign(int n); @NoOffset publicnativeintn();パブリックネイティブ Foo n(int n); @Virtual publicnative void bar(); public staticnative void callback(Foo foo); public static void main(String[] 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 <メインクラス>
ただし、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/ -libsclasses/<ios-arm|ios-x86>/lib.o <MainClass>
この場合、ライブラリはLoader.load()
の代わりにSystem.load("lib.o")
でロードされる必要がありますが、まったく必要ない場合もあります。
プロジェクトリーダー: Samuel Audet samuel.audet at
gmail.com)
開発者サイト: https://github.com/bytedeco/javacpp
ディスカッション グループ: http://groups.google.com/group/javacpp-project