Коммерческая поддержка:
JavaCPP обеспечивает эффективный доступ к собственному C++ внутри Java, мало чем отличаясь от того, как некоторые компиляторы C/C++ взаимодействуют с языком ассемблера. Нет необходимости изобретать новые языки, такие как SWIG, SIP, C++/CLI, Cython или RPython. Вместо этого, подобно тому, что cppyy пытается сделать для Python, он использует синтаксическое и семантическое сходство между Java и C++. Под капотом он использует JNI, поэтому работает со всеми реализациями Java SE, помимо Android, Avian и RoboVM (инструкции).
Более конкретно, по сравнению с подходами, описанными выше или где-либо еще (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 и т. д.), он естественным и эффективным образом отображает многие общие функции, предоставляемые языком C++ и часто считающиеся проблематичными, включая перегруженные операторы, шаблоны классов и функций, обратные вызовы через указатели на функции, объекты функций ( также известные как функторы), виртуальные функции и указатели на функции-члены, определения вложенных структур, аргументы переменной длины, вложенные пространства имен, большие структуры данных, содержащие произвольные циклы, виртуальное и множественное наследование, передачу/возврат по значение/ссылка/строка/вектор, анонимные объединения, битовые поля, исключения, деструкторы и общие или уникальные указатели (через попытку с ресурсами или сборку мусора), а также комментарии к документации. Очевидно, что для аккуратной поддержки всего C++ потребуется дополнительная работа (хотя можно поспорить о внутренней аккуратности C++), но мы публикуем его здесь в качестве доказательства концепции.
Например, мы уже использовали его для создания полных интерфейсов для OpenCV, FFmpeg, libdc1394, PGR FlyCapture, OpenKinect, videoInput, ARToolKitPlus, Leptonica, Tesseract, GSL, LLVM, HDF5, MKL, CUDA, Caffe, MXNet, TensorFlow. , системные API и другие как часть пресетов JavaCPP. подпроект, также демонстрирующий возможности раннего анализа заголовочных файлов 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
)
LibraryDependities += "org.bytedeco" % "javacpp" % "1.5.11"
Другой вариант, доступный пользователям Gradle, — 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 предоставляет некоторые инструменты, которые помогут нам отладить в таких обстоятельствах:
Руководство по устранению неполадок для Java SE с HotSpot VM
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 дополнительно предоставляет сложные примеры, которые мы можем использовать в качестве шаблонов, а также включает вики-страницу о том, как создавать новые пресеты, где подробно объясняется их структура, а также небольшой, но полный пример проекта, с которого можно начать экспериментировать. с.
Для реализации native
методов JavaCPP генерирует соответствующий код для JNI и передает его компилятору C++ для создания собственной библиотеки. Ни в коем случае нам не нужно пачкать руки JNI, make-файлами или другими собственными инструментами. Здесь важно понимать, что, хотя мы выполняем всю настройку внутри языка Java с помощью аннотаций, JavaCPP создает код, который имеет нулевые накладные расходы по сравнению с функциями JNI, написанными вручную (проверьте сгенерированные файлы .cpp, чтобы убедиться в этом). Более того, во время выполнения метод Loader.load()
автоматически загружает собственные библиотеки из ресурсов Java, которые были помещены в правильный каталог в процессе сборки. Их можно даже заархивировать в JAR-файл, это ничего не меняет. Пользователям просто не нужно придумывать, как заставить систему загружать файлы. Эти характеристики делают JavaCPP подходящим для любого
доступ к собственным API,
использование сложных типов C++,
оптимизация производительности кода или
создание функций обратного вызова.
В дополнение к нескольким примерам, приведенным ниже, чтобы узнать больше о том, как использовать функции этого инструмента, обратитесь к рецептам сопоставления для библиотек C/C++, а также к исходному коду предустановок JavaCPP для примеров. Для получения дополнительной информации о самом API можно обратиться к документации, созданной Javadoc.
Разумеется, все это работает и с языком Scala, но чтобы сделать процесс еще более плавным, не должно быть слишком сложно добавить поддержку «родных свойств», чтобы объявления типа @native var
могли генерировать собственный метод получения. и методы установки...
Наиболее распространенный вариант использования включает доступ к некоторой собственной библиотеке, написанной для C++, например, внутри файла с именем NativeLibrary.h
содержащего этот класс 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; } свойство std::string; }; }
Чтобы выполнить работу с помощью JavaCPP, мы можем легко определить класс Java, подобный этому, хотя можно использовать Parser
для его создания из файла заголовка, как показано в подпроекте «Предустановки JavaCPP», следуя принципам, изложенным в рецептах сопоставления. для библиотек C/C++:
import org.bytedeco.javacpp.*;import org.bytedeco.javacpp.annotation.*;@Platform(include="NativeLibrary.h")@Namespace("NativeLibrary")public class NativeLibrary { public static class NativeClass расширяет Pointer { static { Загрузчик.загрузка(); } Общественный NativeClass () {выделить (); } Частный собственный void allocate(); // для вызова функций получения и установки public own @StdString String get_property(); общедоступный собственный void set_property (свойство String); // для прямого доступа к переменной-члену public own @StdString String property(); общедоступное собственное свойство void (свойство String); } public static void main(String[] args) { // Объекты-указатели, выделенные в Java, освобождаются, как только они становятся недоступными, // но деструкторы C++ по-прежнему могут быть своевременно вызваны с помощью Pointer.deallocate() NativeClass l = new NativeClass (); l.set_property("Привет, мир!"); System.out.println(l.property()); } }
После компиляции исходного кода Java обычным способом нам также необходимо выполнить сборку с использованием JavaCPP перед его выполнением, или мы можем позволить ему сделать все следующим образом:
$ java -jar javacpp.jar NativeLibrary.java -exec Привет, мир!
Чтобы продемонстрировать относительную простоту использования даже при работе со сложными типами данных, представьте, что у нас есть функция C++, которая принимает в качестве аргумента vector<vector<void*> >
. Для поддержки этого типа мы могли бы определить простой класс следующим образом:
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 расширяет Pointer { static { Loader.load(); } Общественный PointerVectorVector () {выделить (); } Общественный PointerVectorVector (длинный n) {выделить (n); } Общественный PointerVectorVector (Указатель р) {супер (р); } // this = (vector<vector<void*> >*)p Private Native void allocate(); // this = new Vector<vector<void*> >() Private Native void allocate(long n); // this = новый вектор<vector<void*> >(n) @Name("operator=") public own @ByRef PointerVectorVector put(@ByRef PointerVectorVector x); @Name("operator[]") общедоступный собственный @StdVector PointerPointer get(long n); общедоступный собственный @StdVector PointerPointer at (long n); общедоступный собственный длинный размер(); общедоступный собственный @Cast("bool") boolean пустой(); общедоступный собственный void resize (long n); общедоступный собственный @Index длинный размер (длинный i); // возвращаем (*this)[i].size() public own @Index @Cast("bool") boolean пустой(long i); // return (*this)[i].empty() public own @Index void resize(long i, long n); // (*this)[i].resize(n) public own @Index Pointer get(long i, long j); // return (*this)[i][j] public own void put(long i, long j, Pointer p); // (*это)[i][j] = p } Public static void main(String[] args) { PointerVectorVector v = новый PointerVectorVector(13); v.resize(0, 42); // v[0].resize(42) Указатель p = новый Pointer() { { адрес = 0xDEADBEEFL; } }; v.put(0, 0, р); // v[0][0] = p PointerVectorVector v2 = новый 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] Исключение в потоке «основной» java.lang.RuntimeException: вектор::_M_range_check: __n (42) >= this->size() (13) в VectorTest$PointerVectorVector.at (собственный метод) в VectorTest.main(VectorTest.java:44)
В других случаях нам может потребоваться писать код на C++ (включая CUDA) из соображений производительности. Предположим, наш профилировщик определил, что метод с именем Processor.process()
занимает 90% времени выполнения программы:
общедоступный класс Processor { public static voidprocess(java.nio.Bufferuffer, int size) {System.out.println("Обработка в Java..."); // ... } Public static void main(String[] args) {process(null, 0); } }
После многих дней напряженной работы и пота инженеры нашли некоторые хитрости и сумели снизить это соотношение до 80%, но вы знаете, менеджеры все равно были недовольны. Итак, мы могли бы попытаться переписать его на C++ (или даже на языке ассемблера, если на то пошло, через встроенный ассемблер) и поместить полученную функцию в файл с именем, скажем, Processor.h
:
#include <iostream>static встроенный процесс void (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(); } Публичный статический собственный процесс void (буфер java.nio.Buffer, размер int); public static void main(String[] args) {process(null, 0); } }
Затем он будет компилироваться и выполняться следующим образом:
$ java -jar javacpp.jar Processor.java -exec Обработка на C++...
Некоторым приложениям также требуется способ обратного вызова в JVM из C/C++, поэтому JavaCPP предоставляет простой способ определения пользовательских обратных вызовов в виде указателей на функции, объектов функций или виртуальных функций. Хотя существуют платформы, которые, возможно, сложнее использовать, такие как Jace, JunC++ion, JCC, jni.hpp или Scapix, которые могут отображать полные API Java в C++, поскольку вызов метода Java из собственного кода требует как минимум на порядок больше времени, чем наоборот, на мой взгляд, не имеет особого смысла экспортировать, поскольку это API, который был разработан для использования в Java. Тем не менее, предположим, что мы хотим выполнить некоторые операции на Java и планируем обернуть это в функцию с именем foo()
, которая вызывает некоторый метод внутри класса Foo
. Мы можем написать следующий код в файле с именем foo.cpp
, позаботившись об инициализации JVM, если необходимо, с помощью JavaCPP_init()
или любым другим способом:
#include <iostream>#include "jniFoo.h"int main() { JavaCPP_init(0, NULL); попробуйте { foo(6, 7); } catch (std::Exception &e) { std::cout << e.what() << std::endl; } JavaCPP_uninit(); }
Затем мы можем объявить эту функцию в методе call()
или apply()
, определенном в FunctionPointer
следующим образом:
import org.bytedeco.javacpp.*;import org.bytedeco.javacpp.annotation.*;@Platform(include="<algorithm>")@Namespace("std")public class Foo { static { Loader.load(); } public static class Callback расширяет FunctionPointer { // Loader.load() и allocate() требуются только при явном создании экземпляра static { Loader.load(); } protected Callback() { allocate(); } Частный собственный void allocate(); public @Name("foo") boolean call(int a, int b) выдает исключение { throw new Exception("bar" + a * b); } } // Мы также можем передать (или получить) FunctionPointer в качестве аргумента (или вернуть значение) другим функциям public static own voidtable_sort(IntPointer first, IntPointer Last, Callback Compare); // И чтобы передать (или получить) его как объект функции C++, добавьте аннотацию с помощью @ByVal или @ByRef public static own void sort(IntPointer first, IntPointer Last, @ByVal Callback Compare); }
Поскольку функции также имеют указатели, мы можем использовать экземпляры FunctionPointer
соответственно, аналогично типу FunPtr
Haskell FFI, но где любой созданный объект 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 $ ./фу java.lang.Исключение: бар 42
В этом примере объект FunctionPointer
создается неявно, но для вызова собственного указателя на функцию мы могли бы определить тот, который вместо этого содержит native call()/apply()
, и создать экземпляр явно. Такой класс также может быть расширен в Java для создания обратных вызовов, и, как и любой другой обычный объект Pointer
, он должен быть выделен с помощью native void allocate()
, поэтому не забывайте сохранять ссылки в Java , так как они будут собирать мусор. . В качестве бонуса FunctionPointer.call()/apply()
фактически отображается в перегруженный operator()
объекта функции C++, который мы можем передавать другим функциям, аннотируя параметры с помощью @ByVal
или @ByRef
, как в случае с sort()
функция в приведенном выше примере.
То же самое можно сделать и с виртуальными функциями, «чистыми» или нет. Рассмотрим следующий класс C++, определенный в файле с именем Foo.h
:
#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 обратный вызов(Foo *foo) { Фу->бар(); }
Функцию Foo::bar()
можно переопределить в Java, если мы объявим метод в одноранговом классе как native
или abstract
и добавим к нему аннотацию @Virtual
, например:
import org.bytedeco.javacpp.*;import org.bytedeco.javacpp.annotation.*;@Platform(include="Foo.h")public class VirtualFoo { static { Loader.load(); } Общественный статический класс Foo расширяет Pointer {static {Loader.load(); } Общественный Foo (int n) {выделить (n); } Частный родной void allocate (int n); @NoOffset public own int n(); общедоступный собственный Foo n(int n); @Virtual общедоступный собственный void bar(); } Public static родной обратный вызов void (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() + ")"); } }; фу.бар(); foo2.бар(); обратный вызов (фу); обратный вызов (foo2); } }
Что выводит то, что можно было бы предположить:
$ java -jar javacpp.jar VirtualFoo.java -exec Обратный вызов в C++ (n == 13) Обратный вызов в Java (n == 42) Обратный вызов в C++ (n == 13) Обратный вызов в Java (n == 42)
Самый простой в использовании — Avian, скомпилированный с библиотеками классов OpenJDK, поэтому начнем с этого. После создания и сборки программы, как описано выше, без каких-либо дальнейших изменений, мы можем напрямую выполнить ее с помощью этой команды:
$ /path/to/avian-dynamic -Xbootclasspath/a:javacpp.jar <MainClass>
Однако в случае с Android нам нужно проделать еще немного работы. Для системы сборки командной строки на основе Ant внутри каталога проекта:
Скопируйте файл javacpp.jar
в подкаталог libs/
и
Запустите эту команду, чтобы создать файлы библиотеки *.so
в 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>/
Чтобы сделать все автоматически, мы также можем вставить эту команду в файл build.xml
. В качестве альтернативы для интеграции с Android Studio мы можем использовать Gradle JavaCPP.
Аналогично для RoboVM, предполагая, что скомпилированные классы находятся в подкаталоге classes
:
Скопируйте файл javacpp.jar
в каталог проекта и
Выполните следующие команды, чтобы создать собственный двоичный файл:
$ java -jar javacpp.jar -cpclasses/ -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 at
gmail.com
Сайт разработчика: https://github.com/bytedeco/javacpp.
Группа обсуждения: http://groups.google.com/group/javacpp-project.