Suporte comercial:
JavaCPP fornece acesso eficiente ao C++ nativo dentro de Java, não muito diferente da forma como alguns compiladores C/C++ interagem com a linguagem assembly. Não há necessidade de inventar novas linguagens, como SWIG, SIP, C++/CLI, Cython ou RPython. Em vez disso, semelhante ao que cppyy se esforça para fazer com Python, ele explora as semelhanças sintáticas e semânticas entre Java e C++. Internamente, ele usa JNI, portanto funciona com todas as implementações de Java SE, além de Android, Avian e RoboVM (instruções).
Mais especificamente, quando comparado com as abordagens acima ou em outros lugares (CableSwig, JNIGeneratorApp, cxxwrap, JNIWrapper, Platform Invoke, GlueGen, LWJGL Generator, JNIDirect, ctypes, JNA, JNIEasy, JniMarshall, JNative, J/Invoke, HawtJNI, JNR, BridJ, CFFI, ffixx, CppSharp, cgo, pybind11, ferrugem-bindgen, Panama Native, etc.), ele mapeia de forma natural e eficiente muitos recursos comuns oferecidos pela linguagem C++ e muitas vezes considerados problemáticos, incluindo operadores sobrecarregados, modelos de classes e funções, retornos de chamada por meio de ponteiros de função, objetos de função (também conhecidos como functores ), funções virtuais e ponteiros de função de membro, definições de estruturas aninhadas, argumentos de comprimento variável, namespaces aninhados, grandes estruturas de dados contendo ciclos arbitrários, herança virtual e múltipla, passagem/retorno por valor/referência/string/vetor, uniões anônimas, campos de bits, exceções, destruidores e ponteiros compartilhados ou exclusivos (por meio de tentativa com recursos ou coleta de lixo) e comentários de documentação. Obviamente, apoiar perfeitamente todo o C++ exigiria mais trabalho (embora se possa argumentar sobre a limpeza intrínseca do C++), mas estamos divulgando-o aqui como uma prova de conceito.
Como exemplo, já o utilizamos para produzir interfaces completas para OpenCV, FFmpeg, libdc1394, PGR FlyCapture, OpenKinect, videoInput, ARToolKitPlus, Leptonica, Tesseract, GSL, LLVM, HDF5, MKL, CUDA, Caffe, MXNet, TensorFlow , APIs de sistema e outros como parte do subprojeto JavaCPP Presets, também demonstrando recursos de análise de arquivos de cabeçalho C/C++ que mostram resultados promissores e úteis.
Sinta-se à vontade para fazer perguntas na lista de discussão ou no fórum de discussão se encontrar algum problema com o software! Tenho certeza que está longe de ser perfeito...
Arquivos contendo arquivos JAR estão disponíveis como versões.
Também podemos baixar e instalar tudo automaticamente com:
Maven (dentro do arquivo pom.xml
)
<dependência> <groupId>org.bytedeco</groupId> <artifactId>javacpp</artifactId> <versão>1.5.11</versão> </dependency>
Gradle (dentro do arquivo build.gradle.kts
ou build.gradle
)
dependências { implementação("org.bytedeco:javacpp:1.5.11") }
Leiningen (dentro do arquivo project.clj
)
:dependências [ [org.bytedeco/javacpp "1.5.11"] ]
sbt (dentro do arquivo build.sbt
)
bibliotecaDependencies += "org.bytedeco" % "javacpp" % "1.5.11"
Outra opção disponível para usuários do Gradle é o Gradle JavaCPP e, da mesma forma, para usuários do Scala existe o SBT-JavaCPP.
Para usar JavaCPP, você precisará baixar e instalar o seguinte software:
Uma implementação do Java SE 7 ou mais recente:
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/
Um compilador C++, do qual foram testados:
https://docs.microsoft.com/en-us/cpp/build/walkthrough-compiling-a-native-cpp-program-on-the-command-line
Para Windows x86 e 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 do Visual Studio https://visualstudio.microsoft.com/
Para produzir arquivos binários para Android 4.0 ou mais recente, você também precisará instalar:
Android NDK r7 ou mais recente http://developer.android.com/ndk/downloads/
E da mesma forma que para iOS de destino, você precisará instalar:
Gluon VM http://gluonhq.com/products/mobile/vm/ ou
RoboVM 1.x ou mais recente http://robovm.mobidevelop.com/downloads/
Para modificar o código-fonte, observe que os arquivos do projeto foram criados para:
Maven 3.x http://maven.apache.org/download.html
Finalmente, por estarmos lidando com código nativo, bugs podem facilmente travar a máquina virtual. Felizmente, o HotSpot VM fornece algumas ferramentas para nos ajudar a depurar nessas circunstâncias:
Guia de solução de problemas para Java SE com 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 entender como o JavaCPP deve ser usado, deve-se primeiro dar uma olhada nas Receitas de Mapeamento para Bibliotecas C/C++, mas uma visão geral de alto nível da Arquitetura Básica também está disponível para entender o panorama geral. O repositório dos Presets JavaCPP fornece ainda exemplos complexos que podemos usar como modelos, mas também inclui uma página wiki sobre como criar novos Presets que explica sua estrutura em detalhes, juntamente com um projeto de amostra pequeno, mas completo, a partir do qual se pode começar a experimentar. com.
Para implementar métodos native
, JavaCPP gera código apropriado para JNI e o passa para o compilador C++ para construir uma biblioteca nativa. Em nenhum momento precisamos sujar as mãos com JNI, makefiles ou outras ferramentas nativas. O importante a ser percebido aqui é que, embora façamos toda a customização dentro da linguagem Java usando anotações, o JavaCPP produz código que tem sobrecarga zero em comparação com funções JNI codificadas manualmente (verifique os arquivos .cpp gerados para se convencer). Além disso, em tempo de execução, o método Loader.load()
carrega automaticamente as bibliotecas nativas dos recursos Java, que foram colocadas no diretório correto pelo processo de construção. Eles podem até ser arquivados em um arquivo JAR, isso não muda nada. Os usuários simplesmente não precisam descobrir como fazer o sistema carregar os arquivos. Essas características tornam o JavaCPP adequado para qualquer
acessando APIs nativas,
usando tipos complexos de C++,
otimizando o desempenho do código ou
criando funções de retorno de chamada.
Além dos poucos exemplos fornecidos abaixo, para saber mais sobre como usar os recursos desta ferramenta, consulte as Receitas de mapeamento para bibliotecas C/C++, bem como o código-fonte das predefinições JavaCPP para obter exemplos. Para mais informações sobre a própria API, pode-se consultar a documentação gerada pelo Javadoc.
É claro que tudo isso também funciona com a linguagem Scala, mas para tornar o processo ainda mais suave, não deve ser muito difícil adicionar suporte para "propriedades nativas", de modo que declarações como @native var
possam gerar getter nativo e métodos setter ...
O caso de uso mais comum envolve acessar alguma biblioteca nativa escrita para C++, por exemplo, dentro de um arquivo chamado NativeLibrary.h
contendo esta 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; } propriedade std::string; }; }
Para realizar o trabalho com JavaCPP, podemos facilmente definir uma classe Java como esta - embora seja possível usar o Parser
para produzi-la a partir do arquivo de cabeçalho, conforme demonstrado pelo subprojeto JavaCPP Presets, seguindo os princípios descritos nas Receitas de Mapeamento para bibliotecas 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 {Carregador.load(); } public NativeClass() { alocar(); } private nativo void alocar(); // para chamar as funções getter e setter public native @StdString String get_property(); público nativo void set_property (propriedade String); // para acessar a variável de membro diretamente public native @StdString String property(); propriedade pública nativa void (propriedade String); } public static void main(String[] args) { // Objetos ponteiro alocados em Java são desalocados quando se tornam inacessíveis, // mas destruidores C++ ainda podem ser chamados em tempo hábil com Pointer.deallocate() NativeClass l = new NativeClass (); l.set_property("Olá mundo!"); System.out.println(l.property()); } }
Depois de compilar o código-fonte Java da maneira usual, também precisamos construir usando JavaCPP antes de executá-lo, ou podemos deixá-lo fazer tudo da seguinte maneira:
$ java -jar javacpp.jar NativeLibrary.java -exec Olá mundo!
Para demonstrar sua relativa facilidade de uso mesmo diante de tipos de dados complexos, imagine que tivéssemos uma função C++ que usasse um vector<vector<void*> >
como argumento. Para dar suporte a esse tipo, poderíamos definir uma classe básica como esta:
import org.bytedeco.javacpp.*;import org.bytedeco.javacpp.annotation.*;@Platform(include="<vector>")public class VectorTest { @Name("std::vector<std::vector<void *> >") classe estática pública PointerVectorVector estende Ponteiro { static { Loader.load(); } public PointerVectorVector() { alocar(); } public PointerVectorVector(longo n) { alocar(n); } public PointerVectorVector(Ponteiro p) { super(p); } // this = (vector<vector<void*> >*)p private nativo void allocate(); // this = new vector<vector<void*> >() private nativo void allocate(long n); // this = new vector<vector<void*> >(n) @Name("operator=") public native @ByRef PointerVectorVector put(@ByRef PointerVectorVector x); @Name("operador[]") public nativo @StdVector PointerPointer get(long n); público nativo @StdVector PointerPointer em (n longo); tamanho longo nativo público(); público nativo @Cast("bool") boolean vazio(); redimensionamento de vazio nativo público (n longo); público nativo @Index tamanho longo (i longo); // return (*this)[i].size() public native @Index @Cast("bool") boolean vazio(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); // (*isto)[i][j] = p } public static void main(String[] args) { PointerVectorVector v = new PointerVectorVector(13); redimensionar(0, 42); // v[0].resize(42) Ponteiro p = new Pointer() { { endereço = 0xDEADBEEFL; } }; v.put(0, 0, p); // v[0][0] = p PointerVectorVector v2 = new PointerVectorVector().put(v); Ponteiro p2 = v2.get(0).get(); // p2 = *(&v[0][0]) System.out.println(v2.size() + " " + v2.size(0) + " " + p2); v2.at(42); } }
A execução desse programa usando este comando produz a seguinte saída:
$ java -jar javacpp.jar VectorTest.java -exec 13 42 org.bytedeco.javacpp.Pointer[address=0xdeadbeef,position=0,limit=0,capacity=0,deallocator=null] Exceção no thread "main" java.lang.RuntimeException: vector::_M_range_check: __n (que é 42) >= this->size() (que é 13) em VectorTest$PointerVectorVector.at (método nativo) em VectorTest.main(VectorTest.java:44)
Outras vezes, podemos desejar codificar em C++ (incluindo CUDA) por motivos de desempenho. Suponha que nosso criador de perfil tenha identificado que um método chamado Processor.process()
consumiu 90% do tempo de execução do programa:
classe pública Processador { public static void process(java.nio.Buffer buffer, tamanho int) { System.out.println("Processando em Java..."); // ... } public static void main(String[] args) { process(null, 0); } }
Depois de muitos dias de trabalho duro e suor, os engenheiros descobriram alguns truques e conseguiram fazer com que essa proporção caísse para 80%, mas você sabe, os gerentes ainda não estavam satisfeitos. Portanto, poderíamos tentar reescrevê-lo em C++ (ou mesmo em linguagem assembly por meio do assembler embutido) e colocar a função resultante em um arquivo chamado say Processor.h
:
#include <iostream>static inline void process(void *buffer, int size) { std::cout << "Processando em C++..." << std::endl; // ...}
Depois de ajustar o código-fonte Java para algo assim:
import org.bytedeco.javacpp.*;import org.bytedeco.javacpp.annotation.*;@Platform(include="Processor.h")public class Processador { static { Loader.load(); } processo de void nativo estático público (buffer java.nio.Buffer, tamanho int); public static void main(String[] args) { processo(null, 0); } }
Ele seria então compilado e executado assim:
$ java -jar javacpp.jar Processador.java -exec Processando em C++...
Alguns aplicativos também exigem uma maneira de retornar a chamada para a JVM a partir de C/C++, portanto, o JavaCPP fornece uma maneira simples de definir retornos de chamada personalizados, seja como ponteiros de função, objetos de função ou funções virtuais. Embora existam estruturas, que são indiscutivelmente mais difíceis de usar, como Jace, JunC++ion, JCC, jni.hpp ou Scapix que podem mapear APIs Java completas para C++, já que invocar um método Java a partir de código nativo leva pelo menos um ordem de grandeza mais tempo do que o contrário, na minha opinião não faz muito sentido exportar como é uma API que foi projetada para ser usada em Java. Porém, suponha que queiramos realizar algumas operações em Java, planejando envolvê-las em uma função chamada foo()
que chama algum método dentro da classe Foo
, podemos escrever o seguinte código em um arquivo chamado foo.cpp
, tomando cuidado para inicializar o JVM, se necessário, com JavaCPP_init()
ou por qualquer outro meio:
#include <iostream>#include "jniFoo.h"int main() { JavaCPP_init(0, NULL); tente {foo(6, 7); } catch (std::exception &e) { std::cout << e.what() << std::endl; } JavaCPP_uninit(); }
Podemos então declarar essa função para um método call()
ou apply()
definido em um FunctionPointer
da seguinte forma:
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() e allocate() são necessários apenas ao criar explicitamente uma instância static { Loader.load(); } retorno de chamada protegido() { alocar(); } private nativo void alocar(); public @Name("foo") boolean call(int a, int b) lança exceção { lançar nova Exceção("barra " + a * b); } } // Também podemos passar (ou obter) um FunctionPointer como argumento para (ou retornar valor de) outras funções public static native void stable_sort(IntPointer first, IntPointer last, Callback compare); // E para passá-lo (ou obtê-lo) como um objeto de função C++, anote com @ByVal ou @ByRef public static native void sort(IntPointer first, IntPointer last, @ByVal Callback compare); }
Como as funções também possuem ponteiros, podemos usar instâncias FunctionPointer
de acordo, de maneira semelhante ao tipo FunPtr
de Haskell FFI, mas onde qualquer objeto java.lang.Throwable
lançado é traduzido para std::exception
. Construir e executar este código de amostra com estes comandos no Linux x86_64 produz a saída esperada:
$ 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: barra 42
Neste exemplo, o objeto FunctionPointer
é criado implicitamente, mas para chamar um ponteiro de função nativo, poderíamos definir um que contenha um método native call()/apply()
e criar uma instância explicitamente. Essa classe também pode ser estendida em Java para criar retornos de chamada e, como qualquer outro objeto Pointer
normal, deve ser alocada com um método native void allocate()
, portanto, lembre-se de manter as referências em Java , pois elas serão coletadas como lixo . Como bônus, FunctionPointer.call()/apply()
mapeia de fato para um operator()
sobrecarregado de um objeto de função C++ que podemos passar para outras funções anotando parâmetros com @ByVal
ou @ByRef
, como acontece com sort()
função no exemplo acima.
Também é possível fazer o mesmo com funções virtuais, sejam elas “puras” ou não. Considere a seguinte classe C++ definida em um arquivo chamado Foo.h
:
#include <stdio.h>class Foo {público: int n; Foo(int n) : n(n) { } virtual ~Foo() { } virtual void bar() { printf("Retorno de chamada em C++ (n == %d)n", n); } };void retorno de chamada(Foo *foo) { foo->barra(); }
A função Foo::bar()
pode ser substituída em Java se declararmos o método na classe peer como native
ou abstract
e anotá-lo com @Virtual
, por exemplo:
import org.bytedeco.javacpp.*;import org.bytedeco.javacpp.annotation.*;@Platform(include="Foo.h")public class VirtualFoo { static { Loader.load(); } public static class Foo estende Ponteiro { static { Loader.load(); } public Foo(int n) { alocar(n); } private nativo void alocar(int n); @NoOffset public nativo int n(); público nativo Foo n(int n); @Virtual public nativa void bar(); } retorno de chamada void nativo estático público (Foo foo); public static void main(String[] args) { Foo foo = new Foo(13); Foo foo2 = new Foo(42) { public void bar() { System.out.println("Retorno de chamada em Java (n == " + n() + ")"); } }; foo.bar(); foo2.bar(); retorno de chamada(foo); retorno de chamada(foo2); } }
O que resulta no que se assumiria naturalmente:
$ java -jar javacpp.jar VirtualFoo.java -exec Retorno de chamada em C++ (n == 13) Retorno de chamada em Java (n == 42) Retorno de chamada em C++ (n == 13) Retorno de chamada em Java (n == 42)
O mais fácil de começar a trabalhar é o Avian compilado com bibliotecas de classes OpenJDK, então vamos começar com isso. Depois de criar e construir um programa conforme descrito acima, sem quaisquer modificações adicionais, podemos executá-lo diretamente com este comando:
$ /caminho/para/avian-dynamic -Xbootclasspath/a:javacpp.jar <MainClass>
Porém, no caso do Android, precisamos trabalhar um pouco mais. Para o sistema de construção de linha de comando baseado em Ant, dentro do diretório do projeto:
Copie o arquivo javacpp.jar
no subdiretório libs/
e
Execute este comando para produzir os arquivos da biblioteca *.so
em 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=/caminho/para/<arm-linux-androideabi-g++|i686-linux-android-g++> -d libs/<armeabi|x86>/
Para tornar tudo automático, também podemos inserir esse comando no arquivo build.xml
. Alternativamente, para integração com Android Studio, podemos usar Gradle JavaCPP.
Da mesma forma para RoboVM, assumindo que as classes compiladas estão no subdiretório classes
:
Copie o arquivo javacpp.jar
no diretório do projeto e
Execute os seguintes comandos para produzir o arquivo binário nativo:
$ java -jar javacpp.jar -cp classes/ -properties <ios-arm|ios-x86> -o lib $ /caminho/para/robovm -arch <thumbv7|x86> -os ios -cp javacpp.jar:classes/ -libs classes/<ios-arm|ios-x86>/lib.o <MainClass>
E em vez de Loader.load()
, a biblioteca deve ser carregada com System.load("lib.o")
, neste caso, e pode não ser necessária.
Líder do projeto: Samuel Audet samuel.audet at
gmail.com
Site do desenvolvedor: https://github.com/bytedeco/javacpp
Grupo de discussão: http://groups.google.com/group/javacpp-project