الدعم التجاري:
يوفر 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 Invoc، GlueGen، LWJGL Generator، JNIDirect، ctypes، JNA، JNIEasy، JniMarshall، JNative، J/Invoc، HawtJNI، JNR، BridJ، كففي، fficxx، كبشارب، cgo، وpybind11، وrust-bindgen، وPanama Native، وما إلى ذلك)، فهو يرسم بشكل طبيعي وفعال العديد من الميزات الشائعة التي توفرها لغة C++ وغالبًا ما تعتبر مشكلة، بما في ذلك عوامل التشغيل المثقلة، وقوالب الفئات والوظائف، وعمليات الاسترجاعات من خلال مؤشرات الوظائف، والكائنات الوظيفية ( ويعرف أيضًا باسم functors)، والوظائف الافتراضية ومؤشرات وظائف الأعضاء، وتعريفات البنية المتداخلة، والوسائط ذات الطول المتغير، ومساحات الأسماء المتداخلة، وهياكل البيانات الكبيرة التي تحتوي على دورات عشوائية، والوراثة الافتراضية والمتعددة، التمرير/الإرجاع حسب القيمة/المرجع/السلسلة/المتجه، والاتحادات المجهولة، وحقول البت، والاستثناءات، والمدمرات، والمؤشرات المشتركة أو الفريدة (إما عبر تجربة الموارد أو جمع البيانات المهملة)، وتعليقات التوثيق. من الواضح أن الدعم الدقيق لـ C++ بالكامل سيتطلب المزيد من العمل (على الرغم من أنه يمكن للمرء أن يجادل حول الدقة الجوهرية لـ C++)، ولكننا نصدره هنا كدليل على المفهوم.
كمثال على ذلك، استخدمناها بالفعل لإنتاج واجهات كاملة لـ OpenCV، FFmpeg، libdc1394، PGR FlyCapture، OpenKinect، videoInput، ARToolKitPlus، Leptonica، Tesseract، GSL، LLVM، HDF5، MKL، CUDA، Caffe، MXNet، TensorFlow وواجهات برمجة تطبيقات النظام وغيرها كجزء من إعدادات 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
)
LibraryDependeency += "org.bytedeco" % "javacpp" % "1.5.11"
هناك خيار آخر متاح لمستخدمي Gradle وهو Gradle JavaCPP، وبالمثل لمستخدمي Scala هناك SBT-JavaCPP.
لاستخدام JavaCPP، ستحتاج إلى تنزيل البرنامج التالي وتثبيته:
تطبيق Java SE 7 أو الأحدث:
OpenJDK http://openjdk.java.net/install/ أو
أوراكل JDK http://www.Oracle.com/technetwork/Java/javase/downloads/ أو
آي بي إم 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/
لتعديل الكود المصدري، يرجى ملاحظة أنه تم إنشاء ملفات المشروع من أجل:
مخضرم 3.x http://maven.apache.org/download.html
أخيرًا، نظرًا لأننا نتعامل مع تعليمات برمجية أصلية، فمن الممكن أن تتسبب الأخطاء في تعطيل الجهاز الظاهري بسهولة. لحسن الحظ، يوفر HotSpot VM بعض الأدوات لمساعدتنا في تصحيح الأخطاء في ظل هذه الظروف:
دليل استكشاف الأخطاء وإصلاحها لـ 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، يجب على المرء أولاً إلقاء نظرة على Mapping Recipes لمكتبات C/C++، ولكن تتوفر أيضًا نظرة عامة عالية المستوى على البنية الأساسية لفهم الصورة الأكبر. يوفر مستودع إعدادات JavaCPP المسبقة أيضًا أمثلة معقدة يمكننا استخدامها كقوالب، ولكنه يتضمن أيضًا صفحة wiki حول كيفية إنشاء إعدادات مسبقة جديدة تشرح هيكلها بالتفصيل بالإضافة إلى نموذج صغير ولكن كامل من المشروع يمكن للمرء أن يبدأ في تجربته مع.
لتنفيذ الأساليب native
، يقوم JavaCPP بإنشاء التعليمات البرمجية المناسبة لـ JNI، وتمريرها إلى مترجم C++ لإنشاء مكتبة أصلية. لا نحتاج في أي وقت إلى استخدام JNI أو ملفات makefiles أو الأدوات الأصلية الأخرى. الشيء المهم الذي يجب إدراكه هنا هو أنه بينما نقوم بجميع التخصيصات داخل لغة Java باستخدام التعليقات التوضيحية، فإن JavaCPP ينتج تعليمات برمجية لا تحتوي على أي حمل مقارنة بوظائف JNI المشفرة يدويًا (تحقق من ملفات .cpp التي تم إنشاؤها لإقناع نفسك). علاوة على ذلك، في وقت التشغيل، يقوم الأسلوب Loader.load()
تلقائيًا بتحميل المكتبات الأصلية من موارد Java، والتي تم وضعها في الدليل الصحيح من خلال عملية البناء. ويمكن أيضًا أرشفتها في ملف JAR، ولا يغير ذلك شيئًا. لا يحتاج المستخدمون ببساطة إلى معرفة كيفية جعل النظام يقوم بتحميل الملفات. هذه الخصائص تجعل JavaCPP مناسبًا لأي منهما
الوصول إلى واجهات برمجة التطبيقات الأصلية،
باستخدام أنواع C++ المعقدة،
تحسين أداء التعليمات البرمجية، أو
إنشاء وظائف رد الاتصال.
بالإضافة إلى الأمثلة القليلة الواردة أدناه، لمعرفة المزيد حول كيفية استخدام ميزات هذه الأداة، يرجى الرجوع إلى وصفات التعيين لمكتبات C/C++ بالإضافة إلى التعليمات البرمجية المصدر لإعدادات JavaCPP المسبقة للحصول على أمثلة. لمزيد من المعلومات حول واجهة برمجة التطبيقات نفسها، يمكن للمرء الرجوع إلى الوثائق التي تم إنشاؤها بواسطة Javadoc.
بطبيعة الحال، كل هذا يعمل مع لغة Scala أيضًا، ولكن لجعل العملية أكثر سلاسة، لا ينبغي أن يكون من الصعب جدًا إضافة دعم لـ "الخصائص الأصلية"، بحيث يمكن لإعلانات مثل @native var
إنشاء أداة getter الأصلية وأساليب ضبط...
تتضمن حالة الاستخدام الأكثر شيوعًا الوصول إلى بعض المكتبات الأصلية المكتوبة لـ 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::خاصية السلسلة؛ }; }
لإنجاز المهمة باستخدام JavaCPP، يمكننا بسهولة تعريف فئة Java مثل هذه - على الرغم من أنه يمكن للمرء استخدام Parser
لإنتاجها من ملف الرأس كما هو موضح في المشروع الفرعي JavaCPP Presets، باتباع المبادئ الموضحة في وصفات التعيين لمكتبات 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 { محمل. تحميل ()؛ } public NativeClass() { تخصيص(); } تخصيص الفراغ الأصلي الخاص () ؛ // لاستدعاء وظائف getter و setter public original @StdString String get_property(); public original void set_property(String property); // للوصول مباشرة إلى متغير العضو public original @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 قبل تنفيذه، أو يمكننا تركه يفعل كل شيء على النحو التالي:
$ 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" *> >") فئة ثابتة عامة PointerVectorVector Extends Pointer { static { Loader.load(); } public PointerVectorVector() { allocate(); } public PointerVectorVector(long n) { allocate(n); } public PointerVectorVector(Pointer p) { super(p); } // this = (vector<vector<void*> >*)p public original void allocate(); // this = new Vector<vector<void*> >() public original void allocate(long n); // this = new Vector<vector<void*> >(n) @Name("operator=") public original @ByRef PointerVectorVector put(@ByRef PointerVectorVector x); @Name("operator[]") public original @StdVector PointerPointer get(long n); الجمهور الأصلي @StdVector PointerPointer at(long n); حجم طويل أصلي عام () ؛ الجمهور الأصلي @Cast("bool") منطقي فارغ(); تغيير حجم الفراغ الأصلي العام (long n) ؛ الجمهور الأصلي @Index long size(long i); // return (*this)[i].size() public original @Index @Cast("bool") booleanempty(long i); // return (*this)[i].empty() public original @Index void resize(long i, long n); // (*this)[i].resize(n) public original @Index Pointer get(long i, long j); // return (*this)[i][j] public original void put(long i, long j, Pointer p); // (*هذا)[i][j] = p } public static void main(String[] args) { PointerVectorVector v = new PointerVectorVector(13); v.resize(0, 42); // v[0].resize(42) Pointer 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); } }
تنفيذ هذا البرنامج باستخدام هذا الأمر ينتج عنه المخرجات التالية:
$ java -jar javacpp.jar VectorTest.java -exec 13 42 org.bytedeco.javacpp.Pointer[address=0xdeadbeef,position=0,limit=0,capacity=0,deallocator=null] استثناء في مؤشر الترابط "الرئيسي" 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% من وقت تنفيذ البرنامج:
معالج الطبقة العامة { عملية الفراغ الثابتة العامة (java.nio.Buffer buffer, int size) { System.out.println("تتم المعالجة في Java..."); // ... } public static void main(String[] args) { معالجة (null, 0); } }
وبعد عدة أيام من العمل الشاق والعرق، اكتشف المهندسون بعض الحيل وتمكنوا من خفض هذه النسبة إلى 80%، لكن كما تعلمون، لم يكن المديرون راضين بعد. لذلك، يمكننا محاولة إعادة كتابتها بلغة C++ (أو حتى لغة التجميع لهذه المسألة عبر المجمع المضمن) ووضع الوظيفة الناتجة في ملف يسمى Processor.h
:
#include <iostream> عملية باطلة مضمنة ثابتة (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 buffer, int size); public static void main(String[] args) { العملية(null, 0); } }
سيتم بعد ذلك تجميع وتنفيذ مثل هذا:
$ java -jar javacpp.jar Processor.java -exec المعالجة في C++...
تتطلب بعض التطبيقات أيضًا طريقة لإعادة الاتصال بـ JVM من C/C++، لذلك يوفر JavaCPP طريقة بسيطة لتعريف عمليات الاسترجاعات المخصصة، إما كمؤشرات وظيفية أو كائنات وظيفية أو وظائف افتراضية. على الرغم من وجود أطر عمل، يمكن القول إنها أكثر صعوبة في الاستخدام، مثل Jace، أو JunC++ion، أو JCC، أو jni.hpp، أو Scapix التي يمكنها تعيين واجهات برمجة تطبيقات Java كاملة إلى C++، نظرًا لأن استدعاء طريقة Java من التعليمات البرمجية الأصلية يستغرق على الأقل وقتًا طويلاً. من حيث الحجم وقتًا أطول من العكس، فليس من المنطقي في رأيي التصدير كما هو الحال مع واجهة برمجة التطبيقات (API) التي تم تصميمها للاستخدام في Java. ومع ذلك، لنفترض أننا نريد تنفيذ بعض العمليات في Java، ونخطط لتغليف ذلك في وظيفة تسمى foo()
التي تستدعي بعض الطرق داخل الفئة Foo
، يمكننا كتابة التعليمات البرمجية التالية في ملف يسمى foo.cpp
، مع الحرص على تهيئة JVM إذا لزم الأمر باستخدام JavaCPP_init()
أو بأي وسيلة أخرى:
#include <iostream>#include "jniFoo.h"int main() { JavaCPP_init(0, NULL); حاول { فو(6, 7); } قبض (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 Extends FunctionPointer { // Loader.load() وallocate() مطلوبان فقط عند إنشاء مثيل بشكل صريح static { Loader.load(); } protected Callback() { تخصيص(); } تخصيص الفراغ الأصلي الخاص () ؛ public @Name("foo") استدعاء منطقي (int a, int b) يطرح استثناء { رمي استثناء جديد ("شريط" + a * b)؛ } } // يمكننا أيضًا تمرير (أو الحصول على) FunctionPointer كوسيطة إلى (أو إرجاع قيمة من) وظائف أخرى public static Native void Stable_sort(IntPointer first, IntPointer last, Callback Compare); // ولتمريره (أو الحصول عليه) ككائن دالة C++، قم بالتعليق باستخدام @ByVal أو @ByRef public static Native voidsort(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() { } شريط الفراغ الظاهري() { printf("رد الاتصال في C++ (n == %d)n"، n); } };رد الاتصال باطل(Foo *foo) { foo->bar(); }
يمكن تجاوز الدالة 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(); } public static class Foo Extends Pointer { static { Loader.load(); } public Foo(int n) { تخصيص(n); } تخصيص باطل أصلي خاص (int n)؛ @NoOffset public original int n(); الجمهور الأصلي Foo n(int n); @Virtual شريط الفراغ الأصلي العام () ؛ } رد اتصال باطل أصلي عام ثابت(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(); رد الاتصال(فو); رد الاتصال(foo2); } }
الذي ينتج ما يفترضه المرء بشكل طبيعي:
$ java -jar javacpp.jar VirtualFoo.java -exec رد الاتصال في C++ (ن == 13) رد الاتصال في جافا (ن == 42) رد الاتصال في C++ (ن == 13) رد الاتصال في جافا (ن == 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/ > -خصائص <android-arm|android-x86> -Dplatform.root=/path/to/android-ndk/ > -Dplatform .compiler=/path/to/<arm-linux-androideabi-g++|i686-linux-android-g++> -d ليبز/<armeabi|x86>/
لجعل كل شيء تلقائيًا، يمكننا أيضًا إدراج هذا الأمر في ملف build.xml
. وبدلاً من ذلك، للتكامل مع Android Studio، يمكننا استخدام Gradle JavaCPP.
وبالمثل بالنسبة لـ RoboVM، على افتراض أن الفئات المترجمة موجودة في الدليل الفرعي classes
:
انسخ ملف javacpp.jar
إلى دليل المشروع، و
قم بتشغيل الأوامر التالية لإنتاج الملف الثنائي الأصلي:
$ java -jar javacpp.jar -cp classs/ -properties <ios-arm|ios-x86> -o lib $ /path/to/robovm -arch <thumbv7|x86> -os ios -cp javacpp.jar:classes/ -libs classs/<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