상업적 지원:
JavaCPP는 일부 C/C++ 컴파일러가 어셈블리 언어와 상호 작용하는 방식과 다르지 않게 Java 내부의 기본 C++에 대한 효율적인 액세스를 제공합니다. SWIG, SIP, C++/CLI, Cython 또는 RPython과 같은 새로운 언어를 개발할 필요가 없습니다. 대신 cppyy가 Python에서 수행하려는 작업과 유사하게 Java와 C++ 간의 구문 및 의미 유사성을 활용합니다. 내부적으로는 JNI를 사용하므로 Android, Avian 및 RoboVM(지침) 외에도 Java SE의 모든 구현에서 작동합니다.
더 구체적으로 말하면 위 또는 다른 접근 방식(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++ 언어에서 제공되고 종종 문제가 있다고 간주되는 많은 공통 기능을 자연스럽고 효율적으로 매핑합니다. ), 가상 함수 및 멤버 함수 포인터, 중첩된 구조체 정의, 가변 길이 인수, 중첩된 네임스페이스, 임의 순환을 포함하는 대규모 데이터 구조, 가상 및 다중 상속, 전달/반환 값/참조/문자열/벡터, 익명 공용체, 비트 필드, 예외, 소멸자 및 공유 또는 고유 포인터(try-with-resources 또는 가비지 수집을 통해) 및 문서 주석. 분명히 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
파일 내부)
libraryDependency += "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/
Visual Studio https://visualstudio.microsoft.com/의 일부인 Microsoft C/C++ 컴파일러
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/
소스 코드를 수정하려면 프로젝트 파일이 다음을 위해 생성되었음을 참고하세요.
메이븐 3.x http://maven.apache.org/download.html
마지막으로, 우리는 네이티브 코드를 다루기 때문에 버그로 인해 가상 머신이 쉽게 충돌할 수 있습니다. 다행히 HotSpot VM은 이러한 상황에서 디버깅하는 데 도움이 되는 몇 가지 도구를 제공합니다.
HotSpot VM을 사용한 Java SE 문제 해결 가이드
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, makefile 또는 기타 기본 도구를 사용하여 손을 더럽힐 필요는 없습니다. 여기서 알아야 할 중요한 점은 주석을 사용하여 Java 언어 내에서 모든 사용자 정의를 수행하는 동안 JavaCPP는 수동으로 코딩된 JNI 함수에 비해 오버헤드가 없는 코드를 생성한다는 것입니다(생성된 .cpp 파일을 확인하여 확신을 가지십시오). 또한 런타임 시 Loader.load()
메서드는 빌드 프로세스에 의해 올바른 디렉터리에 배치된 Java 리소스에서 기본 라이브러리를 자동으로 로드합니다. JAR 파일로 보관할 수도 있지만 아무 것도 변경되지 않습니다. 사용자는 시스템에서 파일을 로드하는 방법을 알아낼 필요가 없습니다. 이러한 특성으로 인해 JavaCPP는 다음 중 하나에 적합합니다.
네이티브 API에 액세스
복잡한 C++ 유형을 사용하여
코드 성능 최적화 또는
콜백 함수 생성.
아래 제공된 몇 가지 예 외에도 이 도구의 기능을 사용하는 방법에 대해 자세히 알아보려면 C/C++ 라이브러리에 대한 매핑 레시피와 JavaCPP 사전 설정의 소스 코드 예를 참조하세요. API 자체에 대한 자세한 내용은 Javadoc에서 생성된 설명서를 참조할 수 있습니다.
물론 이 모든 것은 Scala 언어에서도 작동하지만 프로세스를 더욱 원활하게 만들기 위해 @native var
와 같은 선언이 기본 getter를 생성할 수 있도록 "기본 속성"에 대한 지원을 추가하는 것이 너무 어렵지 않아야 합니다. 그리고 세터 메소드...
가장 일반적인 사용 사례는 C++용으로 작성된 일부 네이티브 라이브러리에 액세스하는 것입니다. 예를 들어 다음 C++ 클래스가 포함된 NativeLibrary.h
라는 파일 내부에 있습니다.
#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 클래스를 쉽게 정의할 수 있습니다. 물론 매핑 레시피에 설명된 원칙에 따라 JavaCPP 사전 설정 하위 프로젝트에서 설명한 대로 Parser
사용하여 헤더 파일에서 클래스를 생성할 수도 있습니다. 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 { 로더.로드(); } 공공 NativeClass() { 할당(); } 개인 네이티브 void 할당(); // getter 및 setter 함수를 호출합니다. public Native @StdString String get_property(); 공개 네이티브 void set_property(String 속성); // 멤버 변수에 직접 액세스하려면 public Native @StdString String property(); 공개 네이티브 무효 속성(문자열 속성); } public static void main(String[] args) { // Java에 할당된 포인터 객체는 연결할 수 없게 되면 할당이 취소되지만 // C++ 소멸자는 여전히 Pointer.deallocate()를 사용하여 적시에 호출할 수 있습니다. NativeClass l = new NativeClass (); l.set_property("안녕하세요!"); System.out.println(l.property()); } }
일반적인 방법으로 Java 소스 코드를 컴파일한 후 실행하기 전에 JavaCPP를 사용하여 빌드해야 합니다. 그렇지 않으면 다음과 같이 모든 작업을 수행하도록 할 수 있습니다.
$ 자바 -jar javacpp.jar NativeLibrary.java -exec 안녕하세요 월드!
복잡한 데이터 유형에도 불구하고 상대적으로 사용하기 쉽다는 것을 보여주기 위해 vector<vector<void*> >
인수로 사용하는 C++ 함수가 있다고 상상해 보세요. 해당 유형을 지원하기 위해 다음과 같이 기본 클래스를 정의할 수 있습니다.
import org.bytedeco.javacpp.*;import org.bytedeco.javacpp.annotation.*;@Platform(include="<벡터>")public class VectorTest { @Name("std::벡터<std::Vector<void *> >") public static class PointerVectorVector는 Pointer { static { Loader.load(); } 공개 PointerVectorVector() { 할당(); } 공개 PointerVectorVector(long n) { 할당(n); } 공개 PointerVectorVector(포인터 p) { super(p); } // this = (벡터<벡터<void*> >*)p 프라이빗 네이티브 void 할당(); // this = new vector<Vector<void*> >() private Native void 할당(long n); // this = new vector<Vector<void*> >(n) @Name("operator=") public Native @ByRef PointerVectorVector put(@ByRef PointerVectorVector x); @Name("operator[]") 공개 네이티브 @StdVector PointerPointer get(long n); 공개 네이티브 @StdVector PointerPointer at(long n); 공개 기본 긴 크기(); 공개 네이티브 @Cast("bool") boolean 비어 있음(); 공개 네이티브 무효 크기 조정(long n); 공개 네이티브 @Index 긴 크기(긴 i); // return (*this)[i].size() public Native @Index @Cast("bool") boolean 비어 있음(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); // (*this)[i][j] = p } 공개 정적 void main(String[] args) { PointerVectorVector v = new PointerVectorVector(13); v.resize(0, 42); // v[0].resize(42) 포인터 p = new Pointer() { { 주소 = 0xDEADBEEFL; } }; v.put(0, 0, p); // v[0][0] = p PointerVectorVector v2 = new PointerVectorVector().put(v); 포인터 p2 = v2.get(0).get(); // p2 = *(&v[0][0]) System.out.println(v2.size() + " " + v2.size(0) + " " + p2); v2.at(42); } }
이 명령을 사용하여 해당 프로그램을 실행하면 다음과 같은 출력이 생성됩니다.
$ 자바 -jar javacpp.jar VectorTest.java -exec 13 42 org.bytedeco.javacpp.Pointer[주소=0xdeadbeef,위치=0,한도=0,용량=0,deallocator=null] "main" 스레드의 예외 java.lang.RuntimeException: vector::_M_range_check: __n(42) >= this->size()(13) VectorTest$PointerVectorVector.at(네이티브 메서드) VectorTest.main(VectorTest.java:44)에서
다른 경우에는 성능상의 이유로 C++(CUDA 포함)로 코딩하고 싶을 수도 있습니다. 프로파일러가 Processor.process()
라는 메서드가 프로그램 실행 시간의 90%를 차지한다는 것을 식별했다고 가정해 보겠습니다.
public class Processor { public static void process(java.nio.Buffer buffer, int size) { System.out.println("Java에서 처리 중..."); // ... } 공개 정적 void main(String[] args) { 프로세스(null, 0); } }
며칠 간의 노력과 땀 끝에 엔지니어들은 몇 가지 방법을 알아냈고 그 비율을 80%까지 낮추는데 성공했지만, 관리자들은 여전히 만족하지 못했습니다. 따라서 C++(또는 인라인 어셈블러를 통해 해당 문제에 대한 어셈블리 언어)로 다시 작성하고 결과 함수를 Processor.h
라는 파일에 배치할 수 있습니다.
#include <iostream>static inline void process(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(); } 공개 정적 네이티브 무효 프로세스(java.nio.Buffer 버퍼, int 크기); public static void main(String[] args) { 프로세스(null, 0); } }
그런 다음 다음과 같이 컴파일하고 실행합니다.
$ java -jar javacpp.jar Processor.java -exec C++로 처리 중...
일부 애플리케이션에는 C/C++에서 JVM으로 다시 호출하는 방법도 필요하므로 JavaCPP는 사용자 정의 콜백을 함수 포인터, 함수 객체 또는 가상 함수로 정의하는 간단한 방법을 제공합니다. 완전한 Java API를 C++에 매핑할 수 있는 Jace, JunC++ion, JCC, jni.hpp 또는 Scapix와 같이 사용하기 더 어려운 프레임워크가 있지만 네이티브 코드에서 Java 메소드를 호출하려면 최소한 그 반대의 경우보다 훨씬 더 많은 시간이 소요되기 때문에 Java에서 사용하도록 설계된 API를 내보내는 것은 제 생각에는 별 의미가 없습니다. 그럼에도 불구하고 Java에서 일부 작업을 수행하고 이를 Foo
클래스 내부의 일부 메서드를 호출하는 foo()
라는 함수로 래핑하려고 한다고 가정하면 foo.cpp
라는 파일에 다음 코드를 작성하여 초기화할 수 있습니다. 필요한 경우 JavaCPP_init()
또는 다른 방법을 사용하여 JVM을 수행합니다.
#include <iostream>#include "jniFoo.h"int main() { JavaCPP_init(0, NULL); 시도 { foo(6, 7); } 잡기 (std::예외 &e) { std::cout << e.what() << std::endl; } JavaCPP_uninit(); }
그런 다음 해당 함수를 다음과 같이 FunctionPointer
에 정의된 call()
또는 apply()
메서드에 선언할 수 있습니다.
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() 및 할당()은 인스턴스를 명시적으로 생성할 때만 필요합니다. static { Loader.load(); } 보호된 콜백() { 할당(); } 개인 네이티브 void 할당(); public @Name("foo") boolean call(int a, int b) throws Exception { throw new Exception("bar " + a * b); } } // 또한 FunctionPointer를 다른 함수의 인수로 전달(또는 가져오기)할 수 있습니다. public static Native void stable_sort(IntPointer first, IntPointer last, Callback Compare); // C++ 함수 객체로 전달(또는 가져오기)하려면 @ByVal 또는 @ByRef로 주석을 답니다. public static Native void sort(IntPointer first, IntPointer last, @ByVal Callback Compare); }
함수에도 포인터가 있으므로 FunctionPointer
인스턴스를 Haskell FFI의 FunPtr
유형과 유사한 방식으로 사용할 수 있지만, 던져진 java.lang.Throwable
객체는 std::exception
으로 변환됩니다. Linux x86_64에서 다음 명령을 사용하여 이 샘플 코드를 빌드하고 실행하면 예상되는 출력이 생성됩니다.
$ java -jar javacpp.jar Foo.java -헤더 $ 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: 바 42
이 예제에서 FunctionPointer
개체는 암시적으로 생성되지만 기본 함수 포인터를 호출하려면 대신 native call()/apply()
메서드를 포함하는 포인터를 정의하고 인스턴스를 명시적으로 만들 수 있습니다. 이러한 클래스는 콜백을 생성하기 위해 Java에서 확장될 수도 있으며 다른 일반 Pointer
객체와 마찬가지로 native void allocate()
메서드를 사용하여 할당해야 하므로 가비지 수집되므로 Java의 참조를 참조하는 것을 기억하세요 . . 보너스로 FunctionPointer.call()/apply()
실제로 sort()
와 마찬가지로 @ByVal
또는 @ByRef
로 매개변수에 주석을 달아 다른 함수에 전달할 수 있는 C++ 함수 객체의 오버로드된 operator()
에 매핑됩니다. 위 예의 함수입니다.
"순수" 여부에 관계없이 가상 함수를 사용하여 동일한 작업을 수행하는 것도 가능합니다. Foo.h
라는 파일에 정의된 다음 C++ 클래스를 살펴보세요.
#include <stdio.h>class Foo {public: int n; Foo(int n) : n(n) { } virtual ~Foo() { } virtual void bar() { printf("C++의 콜백 (n == %d)n", n); } };콜백 무효(Foo *foo) { foo->bar(); }
Foo::bar()
함수는 피어 클래스의 메서드를 native
또는 abstract
으로 선언하고 @Virtual
로 주석을 달면 Java에서 재정의될 수 있습니다. 예를 들면 다음과 같습니다.
import org.bytedeco.javacpp.*;import org.bytedeco.javacpp.annotation.*;@Platform(include="Foo.h")public class VirtualFoo { static { Loader.load(); } 공개 정적 클래스 Foo 확장 포인터 { 정적 { Loader.load(); } 공개 Foo(int n) { 할당(n); } 개인 네이티브 void 할당(int n); @NoOffset 공개 네이티브 int n(); 공개 네이티브 Foo n(int n); @Virtual public 네이티브 void bar(); } 공개 정적 네이티브 무효 콜백(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() + ")"); } }; foo.bar(); foo2.bar(); 콜백(foo); 콜백(foo2); } }
자연스럽게 가정하는 결과가 출력됩니다.
$ java -jar javacpp.jar VirtualFoo.java -exec C++의 콜백(n == 13) Java의 콜백(n == 42) C++의 콜백(n == 13) Java의 콜백(n == 42)
작업하기 가장 쉬운 방법은 OpenJDK 클래스 라이브러리로 컴파일된 Avian이므로 그것부터 시작해 보겠습니다. 위에서 설명한 대로 프로그램을 생성하고 빌드한 후 추가 수정 없이 다음 명령을 사용하여 직접 실행할 수 있습니다.
$ /path/to/avian-dynamic -Xbootclasspath/a:javacpp.jar <메인클래스>
하지만 안드로이드의 경우에는 조금 더 작업이 필요합니다. 프로젝트 디렉터리 내부에 있는 Ant 기반 명령줄 빌드 시스템의 경우:
javacpp.jar
파일을 libs/
하위 디렉터리에 복사하고
다음 명령을 실행하여 libs/armeabi/
에 *.so
라이브러리 파일을 생성합니다.
$ 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 -cp 클래스/ -properties <ios-arm|ios-x86> -o lib $ /path/to/robovm -arch <thumbv7|x86> -os ios -cp javacpp.jar:classes/ -libs 클래스/<ios-arm|ios-x86>/lib.o <MainClass>
그리고 이 경우 Loader.load()
대신 System.load("lib.o")
를 사용하여 라이브러리를 로드해야 하며 전혀 필요하지 않을 수도 있습니다.
프로젝트 리더: Samuel Audet gmail.com at
samuel.audet
개발자 사이트: https://github.com/bytedeco/javacpp
토론 그룹: http://groups.google.com/group/javacpp-project