Soporte comercial:
JavaCPP proporciona acceso eficiente a C++ nativo dentro de Java, de manera similar a la forma en que algunos compiladores de C/C++ interactúan con el lenguaje ensamblador. No es necesario inventar nuevos lenguajes como SWIG, SIP, C++/CLI, Cython o RPython. En cambio, de manera similar a lo que cppyy se esfuerza por hacer para Python, explota las similitudes sintácticas y semánticas entre Java y C++. En su interior, utiliza JNI, por lo que funciona con todas las implementaciones de Java SE, además de Android, Avian y RoboVM (instrucciones).
Más específicamente, en comparación con los enfoques anteriores o de otros lugares (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.), mapea de forma natural y eficiente muchas características comunes que ofrece el lenguaje C++ y que a menudo se consideran problemáticas, incluidos operadores sobrecargados, plantillas de clases y funciones, devoluciones de llamadas a través de punteros de función, objetos de función (también conocidos como functores), funciones virtuales y punteros de funciones miembro, definiciones de estructuras anidadas, argumentos de longitud variable, espacios de nombres anidados, grandes estructuras de datos que contienen ciclos arbitrarios, herencia virtual y múltiple, paso/retorno por valor/referencia/cadena/vector, uniones anónimas, campos de bits, excepciones, destructores y punteros compartidos o únicos (a través de prueba con recursos o recolección de basura) y comentarios de documentación. Obviamente, soportar claramente todo C++ requeriría más trabajo (aunque se podría discutir sobre la pulcritud intrínseca de C++), pero lo publicamos aquí como una prueba de concepto.
Como ejemplo, ya lo hemos utilizado para producir interfaces completas para OpenCV, FFmpeg, libdc1394, PGR FlyCapture, OpenKinect, videoInput, ARToolKitPlus, Leptonica, Tesseract, GSL, LLVM, HDF5, MKL, CUDA, Caffe, MXNet, TensorFlow. , API del sistema y otros como parte del subproyecto JavaCPP Presets, que también demuestra capacidades de análisis temprano de archivos de encabezado C/C++ que muestran resultados prometedores y útiles.
¡No dude en hacer preguntas en la lista de correo o en el foro de discusión si tiene algún problema con el software! Estoy seguro de que está lejos de ser perfecto...
Los archivos que contienen archivos JAR están disponibles como versiones.
También podremos tener todo descargado e instalado automáticamente con:
Maven (dentro del archivo pom.xml
)
<dependencia> <groupId>org.bytedeco</groupId> <artifactId>javacpp</artifactId> <versión>1.5.11</versión> </dependencia>
Gradle (dentro del archivo build.gradle.kts
o build.gradle
)
dependencias { implementación("org.bytedeco:javacpp:1.5.11") }
Leiningen (dentro del archivo project.clj
)
:dependencias [ [org.bytedeco/javacpp "1.5.11"] ]
sbt (dentro del archivo build.sbt
)
bibliotecaDependencies += "org.bytedeco" % "javacpp" % "1.5.11"
Otra opción disponible para los usuarios de Gradle es Gradle JavaCPP y, de manera similar, para los usuarios de Scala existe SBT-JavaCPP.
Para utilizar JavaCPP, deberá descargar e instalar el siguiente software:
Una implementación de Java SE 7 o posterior:
OpenJDK http://openjdk.java.net/install/ o
Oracle JDK http://www.oracle.com/technetwork/java/javase/downloads/ o
IBM JDK http://www.ibm.com/developerworks/java/jdk/
Un compilador de C++, del cual se han probado estos:
https://docs.microsoft.com/en-us/cpp/build/walkthrough-compiling-a-native-cpp-program-on-the-command-line
Para Windows x86 y x64 http://mingw-w64.org/
Compilador GNU C/C++ (Linux, etc.) http://gcc.gnu.org/
LLVM Clang (Mac OS X, etc.) http://clang.llvm.org/
Compilador Microsoft C/C++, parte de Visual Studio https://visualstudio.microsoft.com/
Para generar archivos binarios para Android 4.0 o posterior, también deberás instalar:
Android NDK r7 o posterior http://developer.android.com/ndk/downloads/
Y de manera similar para apuntar a iOS, necesitarás instalar:
Gluon VM http://gluonhq.com/products/mobile/vm/ o
RoboVM 1.x o posterior http://robovm.mobidevelop.com/downloads/
Para modificar el código fuente, tenga en cuenta que los archivos del proyecto fueron creados para:
Maven 3.x http://maven.apache.org/download.html
Finalmente, debido a que estamos tratando con código nativo, los errores pueden bloquear fácilmente la máquina virtual. Afortunadamente, HotSpot VM proporciona algunas herramientas para ayudarnos a depurar en esas circunstancias:
Guía de solución de problemas para Java SE con HotSpot VM
http://docs.oracle.com/javase/7/docs/webnotes/tsg/TSG-VM/html/
http://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/
Para comprender cómo se debe utilizar JavaCPP, primero se deben echar un vistazo a las recetas de mapeo para bibliotecas C/C++, pero también está disponible una descripción general de alto nivel de la arquitectura básica para comprender el panorama más amplio. El repositorio de ajustes preestablecidos de JavaCPP proporciona además ejemplos complejos que podemos usar como plantillas, pero también incluye una página wiki sobre cómo crear nuevos ajustes preestablecidos que explica su estructura en detalle junto con un pequeño pero completo proyecto de muestra desde el cual se puede comenzar a experimentar. con.
Para implementar métodos native
, JavaCPP genera código apropiado para JNI y lo pasa al compilador de C++ para crear una biblioteca nativa. En ningún momento necesitamos ensuciarnos las manos con JNI, archivos MAKE u otras herramientas nativas. Lo importante a tener en cuenta aquí es que, si bien realizamos toda la personalización dentro del lenguaje Java mediante anotaciones, JavaCPP produce código que no tiene sobrecarga en comparación con las funciones JNI codificadas manualmente (verifique los archivos .cpp generados para convencerse usted mismo). Además, en tiempo de ejecución, el método Loader.load()
carga automáticamente las bibliotecas nativas de los recursos de Java, que fueron colocados en el directorio correcto durante el proceso de construcción. Incluso se pueden archivar en un archivo JAR, no cambia nada. Los usuarios simplemente no necesitan descubrir cómo hacer que el sistema cargue los archivos. Estas características hacen que JavaCPP sea adecuado para
acceder a API nativas,
utilizando tipos complejos de C++,
optimizar el rendimiento del código, o
creando funciones de devolución de llamada.
Además de los pocos ejemplos proporcionados a continuación, para obtener más información sobre cómo utilizar las funciones de esta herramienta, consulte las Recetas de mapeo para bibliotecas C/C++, así como el código fuente de los ajustes preestablecidos de JavaCPP para ver ejemplos. Para obtener más información sobre la API en sí, se puede consultar la documentación generada por Javadoc.
Por supuesto, todo esto también funciona con el lenguaje Scala, pero para que el proceso sea aún más fluido, no debería ser demasiado difícil agregar soporte para "propiedades nativas", de modo que declaraciones como @native var
puedan generar un captador nativo. y métodos de establecimiento...
El caso de uso más común implica acceder a alguna biblioteca nativa escrita para C++, por ejemplo, dentro de un archivo llamado NativeLibrary.h
que contiene esta clase de C++:
#include <string>espacio de nombres NativeLibrary { clase NativeClass { public: const std::string& get_property() { return propiedad; } void set_property(const std::string& propiedad) { this->property = propiedad; } std::propiedad de cadena; }; }
Para realizar el trabajo con JavaCPP, podemos definir fácilmente una clase Java como esta, aunque se podría usar el Parser
para producirla a partir del archivo de encabezado, como lo demuestra el subproyecto JavaCPP Presets, siguiendo los principios descritos en las Recetas de mapeo. para bibliotecas C/C++:
importar org.bytedeco.javacpp.*;importar org.bytedeco.javacpp.annotation.*;@Platform(include="NativeLibrary.h")@Namespace("NativeLibrary")clase pública NativeLibrary { clase pública estática NativeClass extiende puntero { estático { Cargador.carga(); } public NativeClass() { asignar(); } asignación nula nativa privada(); // para llamar a las funciones getter y setter public native @StdString String get_property(); set_property público nativo vacío (propiedad de cadena); // para acceder a la variable miembro directamente public native @StdString String property(); propiedad pública nativa nula (propiedad de cadena); } public static void main(String[] args) { // Los objetos puntero asignados en Java se desasignan una vez que se vuelven inalcanzables, // pero los destructores de C++ aún se pueden llamar de manera oportuna con Pointer.deallocate() NativeClass l = new NativeClass (); l.set_property("¡Hola mundo!"); System.out.println(l.property()); } }
Después de compilar el código fuente de Java de la manera habitual, también necesitamos compilarlo usando JavaCPP antes de ejecutarlo, o podemos dejar que haga todo de la siguiente manera:
$ java -jar javacpp.jar NativeLibrary.java -exec ¡Hola Mundo!
Para demostrar su relativa facilidad de uso incluso frente a tipos de datos complejos, imagine que tenemos una función de C++ que toma un vector<vector<void*> >
como argumento. Para admitir ese tipo, podríamos definir una clase básica como esta:
importar org.bytedeco.javacpp.*;importar org.bytedeco.javacpp.annotation.*;@Platform(include="<vector>")public class VectorTest { @Name("std::vector<std::vector<void *> >") clase estática pública PointerVectorVector extiende Puntero { estático { Loader.load(); } público PointerVectorVector() { asignar(); } public PointerVectorVector(long n) { asignar(n); } public PointerVectorVector(Puntero p) { super(p); } // this = (vector<vector<void*> >*)p asignación de vacío nativa privada(); // esto = nuevo vector<vector<void*> >() asignación de vacío nativo privado (long n); // esto = nuevo vector<vector<void*> >(n) @Name("operator=") público nativo @ByRef PointerVectorVector put(@ByRef PointerVectorVector x); @Name("operador[]") público nativo @StdVector PointerPointer get(long n); PointerPointer @StdVector nativo público en (long n); tamaño largo nativo público(); público nativo @Cast("bool") booleano vacío(); cambio de tamaño del vacío nativo público (n largo); público nativo @Index tamaño largo (i largo); // return (*this)[i].size() público nativo @Index @Cast("bool") boolean vacío(long i); // return (*this)[i].empty() público nativo @Index void resize(long i, long n); // (*this)[i].resize(n) puntero @Index público nativo get(long i, long j); // return (*this)[i][j] público nativo void put(long i, long j, Pointer p); // (*esto)[i][j] = p } public static void main(String[] args) { PointerVectorVector v = new PointerVectorVector(13); v.resize(0, 42); // v[0].resize(42) Puntero p = nuevo puntero() { { dirección = 0xDEADBEEFL; } }; v.put(0, 0, p); // v[0][0] = p PointerVectorVector v2 = nuevo PointerVectorVector().put(v); Puntero p2 = v2.get(0).get(); // p2 = *(&v[0][0]) System.out.println(v2.size() + " " + v2.size(0) + " " + p2); v2.at(42); } }
La ejecución de ese programa usando este comando produce el siguiente resultado:
$ java -jar javacpp.jar VectorTest.java -exec 13 42 org.bytedeco.javacpp.Pointer[dirección=0xdeadbeef,posición=0,límite=0,capacidad=0,deallocator=null] Excepción en el hilo "principal" java.lang.RuntimeException: vector::_M_range_check: __n (que es 42) >= this->size() (que es 13) en VectorTest$PointerVectorVector.at(Método nativo) en VectorTest.main (VectorTest.java:44)
En otras ocasiones, es posible que deseemos codificar en C++ (incluido CUDA) por motivos de rendimiento. Supongamos que nuestro generador de perfiles hubiera identificado que un método llamado Processor.process()
tomó el 90% del tiempo de ejecución del programa:
Procesador de clase pública { proceso vacío estático público (búfer java.nio.Buffer, tamaño int) { System.out.println ("Procesando en Java ..."); //... } public static void main(String[] args) { proceso(nulo, 0); } }
Después de muchos días de arduo trabajo y sudor, los ingenieros descubrieron algunos trucos y lograron que esa proporción cayera al 80%, pero ya sabes, los gerentes todavía no estaban satisfechos. Entonces, podríamos intentar reescribirlo en C++ (o incluso en lenguaje ensamblador a través del ensamblador en línea) y colocar la función resultante en un archivo llamado, por ejemplo, Processor.h
:
#include <iostream>proceso vacío en línea estático (void *buffer, tamaño int) { std::cout << "Procesando en C++..." << std::endl; //...}
Después de ajustar el código fuente de Java a algo como esto:
importar org.bytedeco.javacpp.*;importar org.bytedeco.javacpp.annotation.*;@Platform(include="Processor.h")public class Procesador { estático { Loader.load(); } proceso público estático nativo vacío (búfer java.nio.Buffer, tamaño int); public static void main(String[] args) { proceso(nulo, 0); } }
Luego se compilaría y ejecutaría así:
$ java -jar javacpp.jar Procesador.java -exec Procesando en C++...
Algunas aplicaciones también requieren una forma de volver a llamar a la JVM desde C/C++, por lo que JavaCPP proporciona una forma sencilla de definir devoluciones de llamada personalizadas, ya sea como punteros de función, objetos de función o funciones virtuales. Aunque existen marcos, que posiblemente sean más difíciles de usar, como Jace, JunC++ion, JCC, jni.hpp o Scapix, que pueden asignar API de Java completas a C++, ya que invocar un método Java desde código nativo requiere al menos un tiempo. orden de magnitud más tiempo que al revés, en mi opinión no tiene mucho sentido exportar como es una API que fue diseñada para usarse en Java. Sin embargo, supongamos que queremos realizar algunas operaciones en Java y planeamos incluirlas en una función llamada foo()
que llama a algún método dentro de la clase Foo
, podemos escribir el siguiente código en un archivo llamado foo.cpp
, teniendo cuidado de inicializar el JVM si es necesario con JavaCPP_init()
o por cualquier otro medio:
#include <iostream>#include "jniFoo.h"int main() { JavaCPP_init(0, NULL); prueba { foo(6, 7); } captura (std::excepción &e) { std::cout << e.what() << std::endl; } JavaCPP_uninit(); }
Luego podemos declarar esa función en un método call()
o apply()
definido en un FunctionPointer
de la siguiente manera:
importar org.bytedeco.javacpp.*;importar org.bytedeco.javacpp.annotation.*;@Platform(include="<algorithm>")@Namespace("std")public class Foo { static { Loader.load(); } public static class Callback extends FunctionPointer { // Loader.load() y allocate() son necesarios solo cuando se crea explícitamente una instancia static { Loader.load(); } devolución de llamada protegida() { asignar(); } asignación nula nativa privada(); public @Name ("foo") llamada booleana (int a, int b) lanza una excepción { lanzar nueva excepción ("barra" + a * b); } } // También podemos pasar (u obtener) un FunctionPointer como argumento (o valor de retorno de) otras funciones public static native void stable_sort(IntPointer primero, IntPointer último, comparación de devolución de llamada); // Y para pasarlo (u obtenerlo) como un objeto de función C++, anotar con @ByVal o @ByRef public static native void sort(IntPointer primero, IntPointer último, @ByVal Callback compare); }
Dado que las funciones también tienen punteros, podemos usar instancias FunctionPointer
en consecuencia, de manera similar al tipo FunPtr
de Haskell FFI, pero donde cualquier objeto java.lang.Throwable
lanzado se traduce a std::exception
. La compilación y ejecución de este código de muestra con estos comandos en Linux x86_64 produce el resultado esperado:
$ java -jar javacpp.jar Foo.java -encabezado $ 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: barra 42
En este ejemplo, el objeto FunctionPointer
se crea implícitamente, pero para llamar a un puntero de función nativo, podríamos definir uno que contenga un método native call()/apply()
y crear una instancia explícitamente. Esta clase también se puede extender en Java para crear devoluciones de llamada y, como cualquier otro objeto Pointer
normal, debe asignarse con un método native void allocate()
, así que recuerde conservar las referencias en Java , ya que se recolectarán basura. . Como beneficio adicional, FunctionPointer.call()/apply()
se asigna de hecho a un operator()
sobrecargado de un objeto de función C++ que podemos pasar a otras funciones anotando parámetros con @ByVal
o @ByRef
, como con sort()
función en el ejemplo anterior.
También es posible hacer lo mismo con funciones virtuales, sean "puras" o no. Considere la siguiente clase C++ definida en un archivo llamado Foo.h
:
#include <stdio.h>class Foo {public: int n; Foo(int n) : n(n) { } virtual ~Foo() { } virtual void bar() { printf("Devolución de llamada en C++ (n == %d)n", n); } }; devolución de llamada nula (Foo *foo) { foo->bar(); }
La función Foo::bar()
se puede anular en Java si declaramos el método en la clase par como native
o abstract
y lo anotamos con @Virtual
, por ejemplo:
importar org.bytedeco.javacpp.*;importar org.bytedeco.javacpp.annotation.*;@Platform(include="Foo.h")public class VirtualFoo { static { Loader.load(); } clase estática pública Foo extiende Puntero {estático { Loader.load(); } public Foo(int n) { asignar(n); } asignación nula nativa privada (int n); @NoOffset público nativo int n(); público nativo Foo n (int n); @Barra vacía nativa pública virtual(); } devolución de llamada nula nativa estática pública (Foo foo); public static void main(String[] args) { Foo foo = new Foo(13); Foo foo2 = new Foo(42) { public void bar() { System.out.println("Devolución de llamada en Java (n == " + n() + ")"); } }; foo.bar(); foo2.bar(); devolución de llamada (foo); devolución de llamada (foo2); } }
Lo que produce lo que uno asumiría naturalmente:
$ java -jar javacpp.jar VirtualFoo.java -exec Devolución de llamada en C++ (n == 13) Devolución de llamada en Java (n == 42) Devolución de llamada en C++ (n == 13) Devolución de llamada en Java (n == 42)
El más fácil de usar es Avian compilado con bibliotecas de clases OpenJDK, así que comencemos con eso. Después de crear y construir un programa como se describe arriba, sin más modificaciones, podemos ejecutarlo directamente con este comando:
$ /ruta/a/avian-dynamic -Xbootclasspath/a:javacpp.jar <Clase principal>
Sin embargo, en el caso de Android, necesitamos trabajar un poco más. Para el sistema de compilación de línea de comando basado en Ant, dentro del directorio del proyecto:
Copie el archivo javacpp.jar
en el subdirectorio libs/
y
Ejecute este comando para producir los archivos de biblioteca *.so
en 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=/ruta/a/<arm-linux-androideabi-g++|i686-linux-android-g++> -d libs/<armeabi|x86>/
Para que todo sea automático, también podemos insertar ese comando en el archivo build.xml
. Alternativamente, para la integración con Android Studio, podemos usar Gradle JavaCPP.
De manera similar para RoboVM, asumiendo que las clases compiladas están en el subdirectorio classes
:
Copie el archivo javacpp.jar
en el directorio del proyecto y
Ejecute los siguientes comandos para generar el archivo binario nativo:
$ java -jar javacpp.jar -cp clases/ -properties <ios-arm|ios-x86> -o lib $ /ruta/a/robovm -arch <thumbv7|x86> -os ios -cp javacpp.jar:classes/ -libs classs/<ios-arm|ios-x86>/lib.o <MainClass>
Y en lugar de Loader.load()
, la biblioteca debería cargarse con System.load("lib.o")
, en este caso, y es posible que no sea necesaria en absoluto.
Líder del proyecto: Samuel Audet samuel.audet at
gmail.com
Sitio del desarrollador: https://github.com/bytedeco/javacpp
Grupo de discusión: http://groups.google.com/group/javacpp-project