Dukungan komersial:
JavaCPP menyediakan akses efisien ke C++ asli di dalam Java, tidak berbeda dengan cara beberapa kompiler C/C++ berinteraksi dengan bahasa assembly. Tidak perlu menemukan bahasa baru seperti SWIG, SIP, C++/CLI, Cython, atau RPython. Sebaliknya, mirip dengan apa yang cppyy coba lakukan untuk Python, ia mengeksploitasi kesamaan sintaksis dan semantik antara Java dan C++. Pada dasarnya, ia menggunakan JNI, sehingga berfungsi dengan semua implementasi Java SE, selain Android, Avian, dan RoboVM (instruksi).
Lebih khusus lagi, jika dibandingkan dengan pendekatan di atas atau di tempat lain (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, karat-bindgen, Panama Native, dll.), ia memetakan secara alami dan efisien banyak fitur umum yang disediakan oleh bahasa C++ dan sering dianggap bermasalah, termasuk operator yang kelebihan beban, templat kelas dan fungsi, panggilan balik melalui penunjuk fungsi, objek fungsi (alias fungsi), fungsi virtual dan penunjuk fungsi anggota, definisi struktur bersarang, argumen panjang variabel, ruang nama bersarang, struktur data besar yang berisi siklus arbitrer, pewarisan virtual dan berganda, penerusan/pengembalian berdasarkan nilai/referensi/string/vektor, gabungan anonim, bidang bit, pengecualian, destruktor, dan petunjuk bersama atau unik (melalui sumber daya coba-dengan-atau pengumpulan sampah), dan komentar dokumentasi. Tentu saja, mendukung keseluruhan C++ dengan rapi akan membutuhkan lebih banyak pekerjaan (walaupun orang dapat berdebat tentang kerapian intrinsik C++), namun kami merilisnya di sini sebagai bukti konsep.
Sebagai contoh, kami telah menggunakannya untuk menghasilkan antarmuka lengkap ke OpenCV, FFmpeg, libdc1394, PGR FlyCapture, OpenKinect, videoInput, ARToolKitPlus, Leptonica, Tesseract, GSL, LLVM, HDF5, MKL, CUDA, Caffe, MXNet, TensorFlow , API Sistem, dan lainnya juga sebagai bagian dari subproyek JavaCPP Presets mendemonstrasikan kemampuan parsing awal file header C/C++ yang menunjukkan hasil yang menjanjikan dan bermanfaat.
Silakan mengajukan pertanyaan di milis atau forum diskusi jika Anda mengalami masalah dengan perangkat lunak! Saya yakin ini jauh dari sempurna...
Arsip yang berisi file JAR tersedia sebagai rilis.
Kami juga dapat mengunduh dan menginstal semuanya secara otomatis dengan:
Maven (di dalam file pom.xml
)
<ketergantungan> <groupId>org.bytedeco</groupId> <artifactId>javacpp</artifactId> <versi>1.5.11</versi> </ketergantungan>
Gradle (di dalam file build.gradle.kts
atau build.gradle
)
ketergantungan { implementasi("org.bytedeco:javacpp:1.5.11") }
Leiningen (di dalam file project.clj
)
:ketergantungan [ [org.bytedeco/javacpp "1.5.11"] ]
sbt (di dalam file build.sbt
)
perpustakaanDependensi += "org.bytedeco" % "javacpp" % "1.5.11"
Opsi lain yang tersedia untuk pengguna Gradle adalah Gradle JavaCPP, dan demikian pula untuk pengguna Scala, ada SBT-JavaCPP.
Untuk menggunakan JavaCPP, Anda perlu mengunduh dan menginstal perangkat lunak berikut:
Implementasi Java SE 7 atau yang lebih baru:
OpenJDK http://openjdk.java.net/install/ atau
Oracle JDK http://www.oracle.com/technetwork/java/javase/downloads/ atau
IBM JDK http://www.ibm.com/developerworks/java/jdk/
Kompiler C++, yang telah diuji:
https://docs.microsoft.com/en-us/cpp/build/walkthrough-compiling-a-native-cpp-program-on-the-command-line
Untuk Windows x86 dan x64 http://mingw-w64.org/
Kompiler GNU C/C++ (Linux, dll.) http://gcc.gnu.org/
LLVM Dentang (Mac OS X, dll.) http://clang.llvm.org/
Kompiler Microsoft C/C++, bagian dari Visual Studio https://visualstudio.microsoft.com/
Untuk menghasilkan file biner untuk Android 4.0 atau lebih baru, Anda juga harus menginstal:
Android NDK r7 atau lebih baru http://developer.android.com/ndk/downloads/
Demikian pula untuk menargetkan iOS, Anda perlu menginstal:
Gluon VM http://gluonhq.com/products/mobile/vm/ atau
RoboVM 1.x atau lebih baru http://robovm.mobidevelop.com/downloads/
Untuk mengubah kode sumber, harap perhatikan bahwa file proyek dibuat untuk:
Maven 3.x http://maven.apache.org/download.html
Terakhir, karena kita berurusan dengan kode asli, bug dapat dengan mudah membuat mesin virtual mogok. Untungnya, VM HotSpot menyediakan beberapa alat untuk membantu kami melakukan debug dalam kondisi seperti ini:
Panduan Mengatasi Masalah untuk Java SE dengan HotSpot VM
http://docs.Oracle.com/javase/7/docs/webnotes/tsg/TSG-VM/html/
http://docs.Oracle.com/javase/8/docs/technotes/guides/troubleshoot/
Untuk memahami bagaimana JavaCPP dimaksudkan untuk digunakan, pertama-tama kita harus melihat Resep Pemetaan untuk Perpustakaan C/C++, namun gambaran umum tingkat tinggi tentang Arsitektur Dasar juga tersedia untuk memahami gambaran yang lebih besar. Repositori Preset JavaCPP selanjutnya memberikan contoh kompleks yang dapat kita gunakan sebagai templat, tetapi juga mencakup halaman wiki tentang cara Membuat Preset Baru yang menjelaskan strukturnya secara rinci bersama dengan contoh proyek kecil namun lengkap yang dapat digunakan untuk mulai bereksperimen. dengan.
Untuk mengimplementasikan metode native
, JavaCPP menghasilkan kode yang sesuai untuk JNI, dan meneruskannya ke kompiler C++ untuk membangun perpustakaan asli. Kita tidak perlu mengotori tangan kita dengan JNI, makefile, atau alat asli lainnya. Hal penting yang harus disadari di sini adalah, meskipun kami melakukan semua penyesuaian dalam bahasa Java menggunakan anotasi, JavaCPP menghasilkan kode yang tidak memiliki overhead dibandingkan dengan fungsi JNI yang dikodekan secara manual (verifikasi file .cpp yang dihasilkan untuk meyakinkan diri Anda sendiri). Selain itu, saat runtime, metode Loader.load()
secara otomatis memuat pustaka asli dari sumber daya Java, yang ditempatkan di direktori yang tepat melalui proses pembangunan. Mereka bahkan dapat diarsipkan dalam file JAR, tidak mengubah apa pun. Pengguna tidak perlu memikirkan cara membuat sistem memuat file. Karakteristik ini membuat JavaCPP cocok untuk keduanya
mengakses API asli,
menggunakan tipe C++ yang kompleks,
mengoptimalkan kinerja kode, atau
membuat fungsi panggilan balik.
Selain beberapa contoh yang diberikan di bawah ini, untuk mempelajari lebih lanjut tentang cara menggunakan fitur alat ini, silakan lihat Resep Pemetaan untuk Perpustakaan C/C++ serta kode sumber Preset JavaCPP untuk contohnya. Untuk informasi lebih lanjut tentang API itu sendiri, seseorang dapat merujuk pada dokumentasi yang dihasilkan oleh Javadoc.
Tentu saja, semua ini juga dapat digunakan dengan bahasa Scala, namun untuk membuat prosesnya lebih lancar, tidak akan terlalu sulit untuk menambahkan dukungan untuk "properti asli", sehingga deklarasi seperti @native var
dapat menghasilkan pengambil asli dan metode penyetel...
Kasus penggunaan yang paling umum melibatkan pengaksesan beberapa pustaka asli yang ditulis untuk C++, misalnya, di dalam file bernama NativeLibrary.h
yang berisi kelas C++ ini:
#include <string>namespace NativeLibrary { class NativeClass { public: const std::string& get_property() { mengembalikan properti; } void set_property(const std::string& properti) { ini->properti = properti; } std::properti string; }; }
Untuk menyelesaikan pekerjaan dengan JavaCPP, kita dapat dengan mudah mendefinisikan kelas Java seperti ini--walaupun kita dapat menggunakan Parser
untuk memproduksinya dari file header seperti yang ditunjukkan oleh subproyek JavaCPP Presets, mengikuti prinsip-prinsip yang diuraikan dalam Resep Pemetaan untuk Perpustakaan 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.beban(); } public NativeClass() { mengalokasikan(); } pribadi asli void mengalokasikan(); // untuk memanggil fungsi pengambil dan penyetel public native @StdString String get_property(); public native void set_property(String properti); // untuk mengakses variabel anggota secara langsung public native @StdString String property(); properti void asli publik (properti string); } public static void main(String[] args) { // Objek pointer yang dialokasikan di Java akan dibatalkan alokasinya ketika objek tersebut tidak dapat dijangkau, // namun destruktor C++ masih dapat dipanggil secara tepat waktu dengan Pointer.deallocation() NativeClass l = new NativeClass (); l.set_property("Halo Dunia!"); Sistem.keluar.println(l.property()); } }
Setelah mengkompilasi kode sumber Java dengan cara biasa, kita juga perlu membangunnya menggunakan JavaCPP sebelum mengeksekusinya, atau kita dapat membiarkannya melakukan semuanya sebagai berikut:
$ java -jar javacpp.jar NativeLibrary.java -exec Halo Dunia!
Untuk mendemonstrasikan kemudahan penggunaannya bahkan dalam menghadapi tipe data yang kompleks, bayangkan kita memiliki fungsi C++ yang menggunakan vector<vector<void*> >
sebagai argumen. Untuk mendukung tipe tersebut, kita dapat mendefinisikan kelas sederhana seperti ini:
import org.bytedeco.javacpp.*;import org.bytedeco.javacpp.annotation.*;@Platform(include="<vector>")public class VectorTest { @Name("std::vector<std::vector<void *> >") kelas statis publik PointerVectorVector extends Pointer { static { Loader.load(); } publik PointerVectorVector() { mengalokasikan(); } public PointerVectorVector(panjang n) { mengalokasikan(n); } publik PointerVectorVector(Penunjuk p) { super(p); } // ini = (vektor<vektor<void*> >*)p private native void mengalokasikan(); // ini = vektor baru<vektor<void*> >() private native void mengalokasikan(long n); // ini = vektor baru<vector<void*> >(n) @Name("operator=") public native @ByRef PointerVectorVector put(@ByRef PointerVectorVector x); @Name("operator[]") publik asli @StdVector PointerPointer get(long n); @StdVector PointerPointer asli publik di(panjang n); ukuran panjang asli publik(); publik asli @Cast("bool") boolean kosong(); pengubahan ukuran kekosongan asli publik (panjang n); publik asli @Index ukuran panjang (panjang i); // return (*this)[i].size() public native @Index @Cast("bool") boolean kosong(long i); // return (*this)[i].empty() public native @Index void resize(panjang i, panjang n); // (*ini)[i].resize(n) public native @Index Pointer get(long i, long j); // kembalikan (*ini)[i][j] public native void put(panjang i, panjang j, Penunjuk p); // (*ini)[i][j] = hal } public static void main(String[] args) { PointerVectorVector v = new PointerVectorVector(13); v.mengubah ukuran(0, 42); // v[0].resize(42) Penunjuk p = penunjuk baru() { { alamat = 0xDEADBEEFL; } }; v.menempatkan(0, 0, p); // v[0][0] = p PointerVectorVector v2 = PointerVectorVector().put(v); Penunjuk p2 = v2.get(0).get(); // p2 = *(&v[0][0]) Sistem.keluar.println(v2.ukuran() + " " + v2.ukuran(0) + " " + p2); v2.at(42); } }
Mengeksekusi program tersebut menggunakan perintah ini menghasilkan output berikut:
$ java -jar javacpp.jar VectorTest.java -exec 13 42 org.bytedeco.javacpp.Pointer[address=0xdeadbeef,position=0,limit=0,capacity=0,deallocator=null] Pengecualian di thread "utama" java.lang.RuntimeException: vector::_M_range_check: __n (yaitu 42) >= this->size() (yaitu 13) di VectorTest$PointerVectorVector.at(Metode Asli) di VectorTest.main(VectorTest.java:44)
Di lain waktu, kami mungkin ingin membuat kode dalam C++ (termasuk CUDA) untuk alasan kinerja. Misalkan profiler kita telah mengidentifikasi bahwa metode bernama Processor.process()
menghabiskan 90% waktu eksekusi program:
Prosesor kelas publik { public static void process(java.nio.Buffer buffer, int size) { System.out.println("Memproses di Java..."); // ... } public static void main(String[] args) { proses(null, 0); } }
Setelah berhari-hari bekerja keras dan berkeringat, para insinyur menemukan beberapa peretasan dan berhasil membuat rasio tersebut turun hingga 80%, namun tahukah Anda, para manajer masih belum puas. Jadi, kita dapat mencoba menulis ulang dalam C++ (atau bahkan bahasa assembly melalui assembler inline) dan menempatkan fungsi yang dihasilkan dalam file bernama say Processor.h
:
#include <iostream>proses kekosongan inline statis(void *buffer, ukuran int) { std::cout << "Memproses dalam C++..." << std::endl; // ...}
Setelah menyesuaikan kode sumber Java menjadi seperti ini:
import org.bytedeco.javacpp.*;import org.bytedeco.javacpp.annotation.*;@Platform(include="Processor.h")public class Processor { static { Loader.load(); } proses kekosongan asli statis publik (java.nio.Buffer buffer, ukuran int); public static void main(String[] args) { proses(null, 0); } }
Kemudian akan dikompilasi dan dijalankan seperti ini:
$ java -jar javacpp.jar Prosesor.java -exec Memproses dalam C++...
Beberapa aplikasi juga memerlukan cara untuk memanggil kembali ke JVM dari C/C++, sehingga JavaCPP menyediakan cara sederhana untuk mendefinisikan callback khusus, baik sebagai penunjuk fungsi, objek fungsi, atau fungsi virtual. Meskipun ada kerangka kerja, yang bisa dibilang lebih sulit untuk digunakan, seperti Jace, JunC++ion, JCC, jni.hpp, atau Scapix yang dapat memetakan Java API lengkap ke C++, karena menjalankan metode Java dari kode asli memerlukan setidaknya waktu urutan besarnya lebih banyak waktu daripada sebaliknya, menurut saya tidak masuk akal untuk mengekspor karena API yang dirancang untuk digunakan di Java. Namun demikian, misalkan kita ingin melakukan beberapa operasi di Java, berencana untuk menggabungkannya ke dalam fungsi bernama foo()
yang memanggil beberapa metode di dalam kelas Foo
, kita dapat menulis kode berikut dalam file bernama foo.cpp
, berhati-hatilah saat menginisialisasi JVM jika perlu dengan JavaCPP_init()
atau dengan cara lain apa pun:
#include <iostream>#include "jniFoo.h"int main() { JavaCPP_init(0, NULL); coba { foo(6, 7); } tangkapan (std::pengecualian &e) { std::cout << e.apa() << std::endl; } JavaCPP_uninit(); }
Kita kemudian dapat mendeklarasikan fungsi tersebut ke metode call()
atau apply()
yang didefinisikan dalam FunctionPointer
sebagai berikut:
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() dan mengalokasikan() diperlukan hanya ketika secara eksplisit membuat instance static { Loader.load(); } Panggilan Balik yang dilindungi() { alokasi(); } pribadi asli void mengalokasikan(); publik @Nama("foo") panggilan boolean(int a, int b) melempar Pengecualian { melempar Pengecualian baru("bar " + a * b); } } // Kita juga bisa meneruskan (atau mendapatkan) FunctionPointer sebagai argumen ke (atau mengembalikan nilai dari) fungsi lain public static native void stable_sort(IntPointer dulu, IntPointer terakhir, Callback bandingkan); // Dan untuk meneruskan (atau mendapatkannya) sebagai objek fungsi C++, beri anotasi dengan @ByVal atau @ByRef public static native void sort (IntPointer dulu, IntPointer terakhir, @ByVal Callback bandingkan); }
Karena fungsi juga memiliki pointer, kita dapat menggunakan instance FunctionPointer
yang sesuai, dengan cara yang mirip dengan tipe FunPtr
dari Haskell FFI, tetapi objek java.lang.Throwable
yang dilempar akan diterjemahkan ke std::exception
. Membangun dan menjalankan kode contoh ini dengan perintah berikut di Linux x86_64 menghasilkan keluaran yang diharapkan:
$ 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.Pengecualian: bilah 42
Dalam contoh ini, objek FunctionPointer
dibuat secara implisit, namun untuk memanggil penunjuk fungsi asli, kita dapat mendefinisikan penunjuk fungsi yang berisi metode native call()/apply()
, dan membuat sebuah instance secara eksplisit. Kelas seperti itu juga dapat diperluas di Java untuk membuat callback, dan seperti objek Pointer
normal lainnya, harus dialokasikan dengan metode native void allocate()
, jadi harap ingat untuk menyimpan referensi di Java , karena itu akan mengumpulkan sampah . Sebagai bonus, FunctionPointer.call()/apply()
sebenarnya memetakan ke operator()
yang kelebihan beban dari objek fungsi C++ yang dapat kita teruskan ke fungsi lain dengan memberi anotasi pada parameter dengan @ByVal
atau @ByRef
, seperti halnya sort()
fungsi pada contoh di atas.
Hal yang sama juga dapat dilakukan dengan fungsi virtual, baik "murni" atau tidak. Pertimbangkan kelas C++ berikut yang didefinisikan dalam file bernama Foo.h
:
#include <stdio.h>kelas Foo {publik: int n; Foo(int n): n(n) { } virtual ~Foo() { } virtual void bar() { printf("Panggilan balik dalam C++ (n == %d)n", n); } }; batalkan panggilan balik(Foo *foo) { foo->bar(); }
Fungsi Foo::bar()
dapat ditimpa di Java jika kita mendeklarasikan metode di kelas rekan sebagai native
atau abstract
dan membubuhi keterangan dengan @Virtual
, misalnya:
import org.bytedeco.javacpp.*;import org.bytedeco.javacpp.annotation.*;@Platform(include="Foo.h")public class VirtualFoo { static { Loader.load(); } kelas statis publik Foo extends Pointer { static { Loader.load(); } public Foo(int n) { mengalokasikan(n); } alokasi kekosongan asli pribadi (int n); @NoOffset int asli publik n(); publik asli Foo n(int n); @Vid bar asli publik virtual(); } panggilan balik batal asli statis publik (Foo foo); public static void main(String[] args) { Foo foo = new Foo(13); Foo foo2 = new Foo(42) { public void bar() { System.out.println("Panggilan balik di Java (n == " + n() + ")"); } }; foo.bar(); foo2.bar(); panggilan balik(foo); panggilan balik(foo2); } }
Yang menghasilkan apa yang secara alami diasumsikan:
$ java -jar javacpp.jar VirtualFoo.java -exec Panggilan balik dalam C++ (n == 13) Panggilan balik di Java (n == 42) Panggilan balik dalam C++ (n == 13) Panggilan balik di Java (n == 42)
Yang paling mudah untuk digunakan adalah Avian yang dikompilasi dengan perpustakaan kelas OpenJDK, jadi mari kita mulai dengan itu. Setelah membuat dan membangun program seperti yang dijelaskan di atas, tanpa modifikasi lebih lanjut, kita bisa langsung mengeksekusinya dengan perintah ini:
$ /path/ke/avian-dynamic -Xbootclasspath/a:javacpp.jar <Kelas Utama>
Namun, dalam kasus Android, kita perlu melakukan lebih banyak pekerjaan. Untuk sistem pembangunan baris perintah berdasarkan Ant, di dalam direktori proyek:
Salin file javacpp.jar
ke subdirektori libs/
, dan
Jalankan perintah ini untuk menghasilkan file perpustakaan *.so
di libs/armeabi/
:
$ java -jar libs/javacpp.jar -classpath bin/ -classpath bin/classes/ > -properties <android-arm|android-x86> -Dplatform.root=/path/to/android-ndk/ > -Dplatform .compiler=/path/ke/<arm-linux-androideabi-g++|i686-linux-android-g++> -d libs/<armeabi|x86>/
Untuk membuat semuanya otomatis, kita juga dapat memasukkan perintah itu ke dalam file build.xml
. Alternatifnya, untuk integrasi dengan Android Studio, kita bisa menggunakan Gradle JavaCPP.
Demikian pula untuk RoboVM, dengan asumsi bahwa kelas yang dikompilasi berada di subdirektori classes
:
Salin file javacpp.jar
ke direktori proyek, dan
Jalankan perintah berikut untuk menghasilkan file biner asli:
$ java -jar javacpp.jar -kelas cp/ -properti <ios-arm|ios-x86> -o lib $ /path/ke/robovm -arch <thumbv7|x86> -os ios -cp javacpp.jar:classes/ -libs class/<ios-arm|ios-x86>/lib.o <MainClass>
Dan alih-alih Loader.load()
, perpustakaan harus dimuat dengan System.load("lib.o")
, dalam hal ini, dan mungkin tidak diperlukan sama sekali.
Pimpinan proyek: Samuel Audet samuel.audet at
gmail.com
Situs pengembang: https://github.com/bytedeco/javacpp
Grup diskusi: http://groups.google.com/group/javacpp-project