Accompagnement commercial :
JavaCPP fournit un accès efficace au C++ natif dans Java, un peu comme la façon dont certains compilateurs C/C++ interagissent avec le langage assembleur. Pas besoin d'inventer de nouveaux langages comme avec SWIG, SIP, C++/CLI, Cython ou RPython. Au lieu de cela, à l’instar de ce que cppyy s’efforce de faire pour Python, il exploite les similitudes syntaxiques et sémantiques entre Java et C++. Sous le capot, il utilise JNI, il fonctionne donc avec toutes les implémentations de Java SE, en plus d'Android, Avian et RoboVM (instructions).
Plus précisément, par rapport aux approches ci-dessus ou ailleurs (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, Panama Native, etc.), il mappe naturellement et efficacement de nombreuses fonctionnalités communes offertes par le langage C++ et souvent considérées comme problématiques, notamment les opérateurs surchargés, les modèles de classes et de fonctions, les rappels via des pointeurs de fonction, les objets de fonction (alias foncteurs), fonctions virtuelles et pointeurs de fonctions membres, définitions de structures imbriquées, arguments de longueur variable, espaces de noms imbriqués, grandes structures de données contenant des cycles arbitraires, héritage virtuel et multiple, passage/retour par valeur/référence/chaîne/vecteur, unions anonymes, champs de bits, exceptions, des destructeurs et des pointeurs partagés ou uniques (via un essai avec des ressources ou un garbage collection) et des commentaires sur la documentation. Évidemment, prendre en charge correctement l'ensemble du C++ nécessiterait plus de travail (même si l'on pourrait discuter de la netteté intrinsèque du C++), mais nous le publions ici comme preuve de concept.
À titre d'exemple, nous l'avons déjà utilisé pour produire des interfaces complètes pour OpenCV, FFmpeg, libdc1394, PGR FlyCapture, OpenKinect, videoInput, ARToolKitPlus, Leptonica, Tesseract, GSL, LLVM, HDF5, MKL, CUDA, Caffe, MXNet, TensorFlow. , API système et autres dans le cadre du sous-projet JavaCPP Presets, démontrant également les premiers capacités d'analyse des fichiers d'en-tête C/C++ qui montrent des résultats prometteurs et utiles.
N'hésitez pas à poser des questions sur la liste de diffusion ou sur le forum de discussion si vous rencontrez des problèmes avec le logiciel ! Je suis sûr que c'est loin d'être parfait...
Les archives contenant des fichiers JAR sont disponibles sous forme de versions.
Nous pouvons également tout télécharger et installer automatiquement avec :
Maven (à l'intérieur du fichier pom.xml
)
<dépendance> <groupId>org.bytedeco</groupId> <artifactId>javacpp</artifactId> <version>1.5.11</version> </dépendance>
Gradle (à l'intérieur du fichier build.gradle.kts
ou build.gradle
)
dépendances { mise en œuvre ("org.bytedeco:javacpp:1.5.11") }
Leiningen (à l'intérieur du fichier project.clj
)
:dépendances [ [org.bytedeco/javacpp "1.5.11"] ]
sbt (à l'intérieur du fichier build.sbt
)
libraryDependencies += "org.bytedeco" % "javacpp" % "1.5.11"
Une autre option disponible pour les utilisateurs de Gradle est Gradle JavaCPP, et de même pour les utilisateurs de Scala, il existe SBT-JavaCPP.
Pour utiliser JavaCPP, vous devrez télécharger et installer le logiciel suivant :
Une implémentation de Java SE 7 ou version ultérieure :
OpenJDK http://openjdk.java.net/install/ ou
Oracle JDK http://www.oracle.com/technetwork/java/javase/downloads/ ou
IBM JDK http://www.ibm.com/developerworks/java/jdk/
Un compilateur C++, à partir duquel ceux-ci ont été testés :
https://docs.microsoft.com/en-us/cpp/build/walkthrough-compiling-a-native-cpp-program-on-the-command-line
Pour Windows x86 et x64 http://mingw-w64.org/
Compilateur GNU C/C++ (Linux, etc.) http://gcc.gnu.org/
LLVM Clang (Mac OS X, etc.) http://clang.llvm.org/
Compilateur Microsoft C/C++, faisant partie de Visual Studio https://visualstudio.microsoft.com/
Pour produire des fichiers binaires pour Android 4.0 ou plus récent, vous devrez également installer :
Android NDK r7 ou version ultérieure http://developer.android.com/ndk/downloads/
Et de la même manière pour cibler iOS, vous devrez installer soit :
Gluon VM http://gluonhq.com/products/mobile/vm/ ou
RoboVM 1.x ou version ultérieure http://robovm.mobidevelop.com/downloads/
Pour modifier le code source, veuillez noter que les fichiers du projet ont été créés pour :
Maven 3.x http://maven.apache.org/download.html
Enfin, comme nous avons affaire à du code natif, les bugs peuvent facilement faire planter la machine virtuelle. Heureusement, la VM HotSpot fournit quelques outils pour nous aider à déboguer dans ces circonstances :
Guide de dépannage pour Java SE avec HotSpot VM
http://docs.oracle.com/javase/7/docs/webnotes/tsg/TSG-VM/html/
http://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/
Pour comprendre comment JavaCPP est censé être utilisé, il faut d'abord jeter un œil aux recettes de mappage pour les bibliothèques C/C++, mais un aperçu de haut niveau de l'architecture de base est également disponible pour comprendre la situation dans son ensemble. Le référentiel des préréglages JavaCPP fournit en outre des exemples complexes que nous pouvons utiliser comme modèles, mais il comprend également une page wiki sur la façon de créer de nouveaux préréglages qui explique leur structure en détail ainsi qu'un exemple de projet petit mais complet à partir duquel on peut commencer à expérimenter. avec.
Pour implémenter des méthodes native
, JavaCPP génère le code approprié pour JNI et le transmet au compilateur C++ pour créer une bibliothèque native. À aucun moment nous n’avons besoin de nous salir les mains avec JNI, les makefiles ou d’autres outils natifs. La chose importante à réaliser ici est que, même si nous effectuons toute la personnalisation dans le langage Java à l'aide d'annotations, JavaCPP produit un code qui n'a aucune surcharge par rapport aux fonctions JNI codées manuellement (vérifiez les fichiers .cpp générés pour vous en convaincre). De plus, au moment de l'exécution, la méthode Loader.load()
charge automatiquement les bibliothèques natives à partir des ressources Java, qui ont été placées dans le bon répertoire par le processus de construction. Ils peuvent même être archivés dans un fichier JAR, cela ne change rien. Les utilisateurs n'ont tout simplement pas besoin de savoir comment faire en sorte que le système charge les fichiers. Ces caractéristiques rendent JavaCPP adapté soit
accéder aux API natives,
en utilisant des types C++ complexes,
optimiser les performances du code, ou
créer des fonctions de rappel.
En plus des quelques exemples fournis ci-dessous, pour en savoir plus sur l'utilisation des fonctionnalités de cet outil, veuillez vous référer aux recettes de mappage pour les bibliothèques C/C++ ainsi qu'au code source des préréglages JavaCPP pour des exemples. Pour plus d'informations sur l'API elle-même, on peut se référer à la documentation générée par Javadoc.
Bien entendu, tout cela fonctionne également avec le langage Scala, mais pour rendre le processus encore plus fluide, il ne devrait pas être trop difficile d'ajouter la prise en charge des "propriétés natives", de sorte que des déclarations comme @native var
puissent générer un getter natif. et les méthodes de définition...
Le cas d'utilisation le plus courant implique l'accès à une bibliothèque native écrite pour C++, par exemple, dans un fichier nommé NativeLibrary.h
contenant cette classe C++ :
#include <string>namespace NativeLibrary { class NativeClass { public: const std::string& get_property() { return property; } void set_property(const std::string& property) { this->property = property; } propriété std::string ; } ; }
Pour faire le travail avec JavaCPP, nous pouvons facilement définir une classe Java telle que celle-ci, bien que l'on puisse utiliser l' Parser
pour la produire à partir du fichier d'en-tête, comme le démontre le sous-projet JavaCPP Presets, en suivant les principes décrits dans les recettes de mappage. pour les bibliothèques C/C++ :
import org.bytedeco.javacpp.*;import org.bytedeco.javacpp.annotation.*;@Platform(include="NativeLibrary.h")@Namespace("NativeLibrary")classe publique NativeLibrary { classe statique publique NativeClass extends Pointer { static { Loader.load(); } public NativeClass() { allouer(); } private native void allouer(); // pour appeler les fonctions getter et setter public native @StdString String get_property(); public native void set_property (propriété String); // pour accéder directement à la variable membre public native @StdString String property(); propriété publique native void (propriété String); } public static void main(String[] args) { // Les objets pointeurs alloués en Java sont libérés une fois qu'ils deviennent inaccessibles, // mais les destructeurs C++ peuvent toujours être appelés en temps opportun avec Pointer.deallocate() NativeClass l = new NativeClass (); l.set_property("Bonjour tout le monde !"); System.out.println(l.property()); } }
Après avoir compilé le code source Java de la manière habituelle, nous devons également construire en utilisant JavaCPP avant de l'exécuter, ou nous pouvons le laisser tout faire comme suit :
$ java -jar javacpp.jar NativeLibrary.java -exec Bonjour le monde!
Pour démontrer sa relative facilité d'utilisation même face à des types de données complexes, imaginez que nous ayons une fonction C++ qui prend un vector<vector<void*> >
comme argument. Pour prendre en charge ce type, nous pourrions définir une classe simple comme celle-ci :
import org.bytedeco.javacpp.*;import org.bytedeco.javacpp.annotation.*;@Platform(include="<vecteur>")public class VectorTest { @Name("std::vector<std::vector<void *> >") classe statique publique PointerVectorVector extends Pointer { static { Loader.load(); } public PointerVectorVector() { allocate(); } public PointerVectorVector(long n) { allocate(n); } public PointerVectorVector(Pointeur p) { super(p); } // this = (vecteur<vecteur<void*> >*)p privé natif 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); public natif @StdVector PointerPointer at(long n); taille longue native publique (); public native @Cast("bool") booléen vide(); redimensionnement du vide natif public (long n); public natif @Index taille longue (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, Pointer p); // (*ceci)[i][j] = p } public static void main(String[] args) { PointerVectorVector v = new PointerVectorVector(13); v.resize(0, 42); // v[0].resize(42) Pointeur p = new Pointer() { { adresse = 0xDEADBEEFL; } } ; v.put(0, 0, p); // v[0][0] = p PointerVectorVector v2 = new PointerVectorVector().put(v); Pointeur p2 = v2.get(0).get(); // p2 = *(&v[0][0]) System.out.println(v2.size() + " " + v2.size(0) + " " + p2); v2.at(42); } }
L'exécution de ce programme à l'aide de cette commande produit le résultat suivant :
$ java -jar javacpp.jar VectorTest.java -exec 13 42 org.bytedeco.javacpp.Pointer[address=0xdeadbeef,position=0,limit=0,capacity=0,deallocator=null] Exception dans le thread "main" java.lang.RuntimeException : vector::_M_range_check: __n (qui vaut 42) >= this->size() (qui vaut 13) à VectorTest$PointerVectorVector.at (méthode native) sur VectorTest.main(VectorTest.java:44)
D'autres fois, nous souhaiterons peut-être coder en C++ (y compris CUDA) pour des raisons de performances. Supposons que notre profileur ait identifié qu'une méthode nommée Processor.process()
prenait 90 % du temps d'exécution du programme :
public class Processor { public static void process (java.nio.Buffer buffer, int size) { System.out.println("Traitement en Java..."); //... } public static void main(String[] args) {process(null, 0); } }
Après plusieurs jours de travail acharné et de sueur, les ingénieurs ont trouvé quelques astuces et ont réussi à faire chuter ce ratio à 80 %, mais vous savez, les managers n'étaient toujours pas satisfaits. Nous pourrions donc essayer de le réécrire en C++ (ou même en langage assembleur d'ailleurs via l'assembleur en ligne) et placer la fonction résultante dans un fichier nommé par exemple Processor.h
:
#include <iostream>processus vide statique en ligne (void *buffer, int size) { std::cout << "Traitement en C++..." << std::endl; //...}
Après avoir ajusté le code source Java pour obtenir quelque chose comme ceci :
importer org.bytedeco.javacpp.*;importer org.bytedeco.javacpp.annotation.*;@Platform(include="Processor.h")public class Processeur { static { Loader.load(); } Processus vide natif statique public (tampon java.nio.Buffer, taille int); public static void main(String[] args) {processus(null, 0); } }
Il serait alors compilé et exécuté comme ceci :
$ java -jar javacpp.jar Processeur.java -exec Traitement en C++...
Certaines applications nécessitent également un moyen de rappeler la JVM à partir de C/C++. JavaCPP fournit donc un moyen simple de définir des rappels personnalisés, sous forme de pointeurs de fonction, d'objets de fonction ou de fonctions virtuelles. Bien qu'il existe des frameworks, qui sont sans doute plus difficiles à utiliser, tels que Jace, JunC++ion, JCC, jni.hpp ou Scapix, qui peuvent mapper des API Java complètes vers C++, puisque l'appel d'une méthode Java à partir du code natif prend au moins un ordre de grandeur plus de temps que l'inverse, cela n'a pas beaucoup de sens à mon avis d'exporter comme c'est le cas d'une API conçue pour être utilisée en Java. Néanmoins, supposons que nous souhaitions effectuer certaines opérations en Java, en prévoyant de les envelopper dans une fonction nommée foo()
qui appelle une méthode à l'intérieur de la classe Foo
, nous pouvons écrire le code suivant dans un fichier nommé foo.cpp
, en prenant soin d'initialiser le JVM si nécessaire avec JavaCPP_init()
ou par tout autre moyen :
#include <iostream>#include "jniFoo.h"int main() { JavaCPP_init(0, NULL); essayez { foo(6, 7); } catch (std::exception &e) { std :: cout << e.what() << std :: endl; } JavaCPP_uninit(); }
Nous pouvons ensuite déclarer cette fonction à une méthode call()
ou apply()
définie dans un FunctionPointer
comme suit :
importer org.bytedeco.javacpp.*;importer org.bytedeco.javacpp.annotation.*;@Platform(include="<algorithm>")@Namespace("std")public class Foo { static { Loader.load(); } public static class Callback extends FunctionPointer { // Loader.load() et allocate() sont requis uniquement lors de la création explicite d'une instance static { Loader.load(); } protégé Callback() { allouer(); } private native void allouer(); public @Name("foo") appel booléen (int a, int b) lève une exception { throw new Exception("bar " + a * b); } } // Nous pouvons également passer (ou obtenir) un FunctionPointer comme argument à (ou renvoyer une valeur depuis) d'autres fonctions public static native void stable_sort(IntPointer first, IntPointer last, Callback compare); // Et pour le transmettre (ou l'obtenir) en tant qu'objet fonction C++, annotez avec @ByVal ou @ByRef public static native void sort (IntPointer en premier, IntPointer en dernier, @ByVal Callback compare) ; }
Étant donné que les fonctions ont également des pointeurs, nous pouvons utiliser les instances FunctionPointer
en conséquence, d'une manière similaire au type FunPtr
de Haskell FFI, mais où tout objet java.lang.Throwable
lancé est traduit en std::exception
. La construction et l'exécution de cet exemple de code avec ces commandes sous Linux x86_64 produisent le résultat attendu :
$ 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 : barre 42
Dans cet exemple, l'objet FunctionPointer
est créé implicitement, mais pour appeler un pointeur de fonction natif, nous pourrions en définir un qui contient à la place une méthode native call()/apply()
et créer une instance explicitement. Une telle classe peut également être étendue en Java pour créer des rappels, et comme tout autre objet Pointer
normal, doit être allouée avec une méthode native void allocate()
, alors n'oubliez pas de vous accrocher aux références en Java , car celles-ci seront récupérées. . En bonus, FunctionPointer.call()/apply()
correspond en fait à un operator()
surchargé d'un objet fonction C++ que l'on peut transmettre à d'autres fonctions en annotant les paramètres avec @ByVal
ou @ByRef
, comme avec le sort()
fonction dans l’exemple ci-dessus.
Il est également possible de faire la même chose avec des fonctions virtuelles, qu'elles soient « pures » ou non. Considérons la classe C++ suivante définie dans un fichier nommé Foo.h
:
#include <stdio.h>class Foo {public : int n ; Foo(int n) : n(n) { } virtual ~Foo() { } virtual void bar() { printf("Rappel en C++ (n == %d)n", n); } };void rappel(Foo *foo) { foo->bar(); }
La fonction Foo::bar()
peut être remplacée en Java si nous déclarons la méthode dans la classe homologue comme native
ou abstract
et l'annotons avec @Virtual
, par exemple :
importer org.bytedeco.javacpp.*;importer org.bytedeco.javacpp.annotation.*;@Platform(include="Foo.h")public class VirtualFoo { static { Loader.load(); } classe statique publique Foo extends Pointer { static { Loader.load(); } public Foo(int n) { allouer(n); } allocation vide native privée (int n); @NoOffset public native int n(); public natif Foo n(int n); @Barre vide native publique virtuelle (); } public static native 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("Rappel en Java (n == " + n() + ")"); } } ; foo.bar(); foo2.bar(); rappel(foo); rappel(foo2); } }
Ce qui donne ce que l’on supposerait naturellement :
$ java -jar javacpp.jar VirtualFoo.java -exec Rappel en C++ (n == 13) Rappel en Java (n == 42) Rappel en C++ (n == 13) Rappel en Java (n == 42)
Le plus simple à utiliser est Avian compilé avec les bibliothèques de classes OpenJDK, alors commençons par cela. Après avoir créé et construit un programme comme décrit ci-dessus, sans aucune autre modification, nous pouvons l'exécuter directement avec cette commande :
$ /path/to/avian-dynamic -Xbootclasspath/a:javacpp.jar <MainClass>
Cependant, dans le cas d’Android, nous devons faire un peu plus de travail. Pour le système de build en ligne de commande basé sur Ant, dans le répertoire du projet :
Copiez le fichier javacpp.jar
dans le sous-répertoire libs/
, et
Exécutez cette commande pour produire les fichiers de bibliothèque *.so
dans 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/to/<arm-linux-androideabi-g++|i686-linux-android-g++> -d libs/<armeabi|x86>/
Pour que tout soit automatique, nous pouvons également insérer cette commande dans le fichier build.xml
. Alternativement, pour l'intégration avec Android Studio, nous pouvons utiliser Gradle JavaCPP.
De même pour RoboVM, en supposant que les classes compilées se trouvent dans le sous-répertoire classes
:
Copiez le fichier javacpp.jar
dans le répertoire du projet, et
Exécutez les commandes suivantes pour produire le fichier binaire natif :
$ java -jar javacpp.jar -cp classes/ -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>
Et au lieu de Loader.load()
, la bibliothèque doit être chargée avec System.load("lib.o")
, dans ce cas, et peut ne pas être nécessaire du tout.
Responsable du projet : Samuel Audet samuel.audet at
gmail.com
Site développeur : https://github.com/bytedeco/javacpp
Groupe de discussion : http://groups.google.com/group/javacpp-project