دعم مكتبة C++

يدعم NDK مكتبات وقت تشغيل C++ متعددة. يقدم هذا المستند معلومات حول هذه المكتبات والمفاضلات المتضمنة وكيفية استخدامها.

مكتبات وقت تشغيل لغة C++

الجدول 1. أوقات تشغيل NDK C++ وميزاتها.

الاسم الميزات
libc++ دعم C++ الحديث.
النظام new، delete (متوقف في الإصدار 18.)
بلا لا توجد رؤوس، محدود C++.

تتوفر libc++ كمكتبة ثابتة ومشتركة.

libc++

مكتبة libc++ الخاصة بـ LLVM هي مكتبة قياسية بـ C++ يستخدمها نظام التشغيل Android منذ إصدار Lollipop، واعتبارًا من NDK r18 هي مكتبة STL الوحيدة المتوفرة في NDK.

يتم ضبط C على أي إصدار من لغة C++ يتم ضبطه تلقائيًا على (C++14 حاليًا)، لذلك عليك ضبط CMAKE_CXX_STANDARD القياسي على القيمة المناسبة في ملف CMakeLists.txt لاستخدام ميزات C++17 أو الإصدارات الأحدث. يمكنك الاطّلاع على وثائق CMake الخاصة بـ CMAKE_CXX_STANDARD للحصول على مزيد من التفاصيل.

يترك ndk-build أيضًا القرار باستخدام عبارة clang بشكل تلقائي، لذلك على مستخدمي ndk-build استخدام APP_CPPFLAGS لإضافة -std=c++17 أو ما يريدونه بدلاً من ذلك.

المكتبة المشتركة لـ libc++ هي libc++_shared.so، والمكتبة الثابتة هي libc++_static.a. في الحالات النموذجية، سيتعامل نظام الإنشاء مع استخدام هذه المكتبات وتجميعها حسب الحاجة للمستخدم. في الحالات غير النموذجية أو عند تنفيذ نظام الإصدار الخاص بك، راجِع دليل صيانة نظام الإنشاء أو دليل استخدام أنظمة الإصدار الأخرى.

يخضع مشروع LLVM لترخيص Apache الإصدار 2.0 مع استثناءات LLVM. لمزيد من المعلومات، اطّلِع على ملف الترخيص.

النظامية

يشير وقت تشغيل النظام إلى اللغة /system/lib/libstdc++.so. يجب عدم الخلط بين هذه المكتبة وlibstdc++ الكاملة الميزات في GNU. في نظام Android، libstdc++ هي فقط new وdelete. استخدم libc++ للحصول على مكتبة قياسية كاملة الميزات بلغة C++.

يوفر وقت تشغيل C++ الدعم لواجهة ABI الأساسية لوقت تشغيل C++. وتوفّر هذه المكتبة بشكل أساسي اللغتين new وdelete. وعلى عكس الخيارات الأخرى المتاحة في NDK، لا يتوفر أي دعم للتعامل مع الاستثناء أو المراسلة النصية في الوقت الفعلي (RTTI).

ليس هناك دعم معياري للمكتبة باستثناء برامج تضمين C++ لعناوين مكتبة C مثل <cstdio>. إذا كنت تريد STL، يجب استخدام أحد الخيارات الأخرى المعروضة في هذه الصفحة.

ما من علاقة

ويمكنك أيضًا عدم استخدام STL. لا توجد متطلبات لربط أو ترخيص في هذه الحالة. لا تتوفر رؤوس قياسية لـ C++.

تحديد وقت تشغيل C++

CMaker

والقيمة التلقائية لأداة CMake هي c++_static.

يمكنك تحديد c++_shared أو c++_static أو none أو system باستخدام المتغيّر ANDROID_STL في ملف build.gradle على مستوى الوحدة. لمعرفة المزيد من المعلومات، اطّلِع على وثائق ANDROID_STL في CMake.

إصدار ndk

الإعداد التلقائي لإصدار ndk هو none.

يمكنك تحديد c++_shared أو c++_static أو none أو system باستخدام المتغيّر APP_STL في ملف Application.mk. مثلاً:

APP_STL := c++_shared

يتيح لك ndk-build إمكانية اختيار وقت تشغيل واحد فقط لتطبيقك، ولا يمكن تنفيذه إلا في Application.mk.

استخدام كلمة clang مباشرةً

إذا كنت تستخدم clang مباشرةً في نظام الإصدار الخاص بك، سيستخدم clang++ c++_shared بشكل تلقائي. لاستخدام الصيغة الثابتة، أضِف -static-libstdc++ إلى علامات الرابط. تجدر الإشارة إلى أنّه على الرغم من أنّ الخيار يستخدم الاسم "libstdc++" لأسباب سابقة، فإنّ هذا الأمر صحيح أيضًا لـ libc++.

اعتبارات مهمة

أوقات التشغيل الثابتة

إذا كانت كل الرموز البرمجية الأصلية لتطبيقك مضمنة في مكتبة مشتركة واحدة، فننصح باستخدام وقت التشغيل الثابت. ويسمح ذلك للرابط بتضمين وتقليص أكبر قدر ممكن من التعليمات البرمجية غير المستخدمة، مما يؤدي إلى تطبيق أصغر قدر ممكن من التحسين والأصغر. كما يتجنب أخطاء PackageManager والرابط الديناميكي في الإصدارات القديمة من Android ما يجعل التعامل مع مكتبات مشتركة متعددة أمرًا صعبًا وعرضًا للخطأ.

ومع ذلك، في C++، ليس من الآمن تحديد أكثر من نسخة واحدة لنفس الدالة أو الكائن في برنامج واحد. وهذا جانب من جوانب قاعدة التعريف الواحدة الموجودة في معيار C++.

عند استخدام وقت تشغيل ثابت (والمكتبات الثابتة بشكل عام)، من السهل انتهاك هذه القاعدة عن طريق الخطأ. على سبيل المثال، يوقف التطبيق التالي هذه القاعدة:

# Application.mk
APP_STL := c++_static
# Android.mk

include $(CLEAR_VARS)
LOCAL_MODULE := foo
LOCAL_SRC_FILES := foo.cpp
include $(BUILD_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := bar
LOCAL_SRC_FILES := bar.cpp
LOCAL_SHARED_LIBRARIES := foo
include $(BUILD_SHARED_LIBRARY)

في هذه الحالة، ستكون STL، بما في ذلك البيانات العامة والمنشئات الثابتة، موجودة في كلتا المكتبتين. إن سلوك وقت تشغيل هذا التطبيق غير محدد، وتعد الأعطال العملية شائعة للغاية من الناحية العملية. تتضمن المشكلات المحتملة الأخرى ما يلي:

  • يتم تخصيص الذاكرة في إحدى المكتبات وإخلاء بعض المساحة في مكتبة أخرى، ما يؤدي إلى تسرُّب الذاكرة أو تلف الذاكرة.
  • الاستثناءات التي تم جمعها في libfoo.so غير مرصودة في libbar.so، ما يؤدي إلى تعطُّل تطبيقك.
  • التخزين المؤقت لـ std::cout لا يعمل بشكل صحيح.

بالإضافة إلى المشاكل السلوكية المتضمنة، يؤدي ربط وقت التشغيل الثابت بمكتبات متعددة إلى تكرار الرمز البرمجي في كل مكتبة مشتركة، ما يزيد من حجم تطبيقك.

وبشكل عام، لا يمكنك استخدام متغير ثابت من وقت تشغيل C++ إلا إذا كان لديك مكتبة مشتركة واحدة فقط في تطبيقك.

أوقات التشغيل التي تتم مشاركتها

وإذا كان تطبيقك يتضمن مكتبات مشتركة متعددة، عليك استخدام libc++_shared.so.

على نظام Android، تختلف libc++ التي يستخدمها NDK عن تلك التي تعد جزءًا من نظام التشغيل. وهذا يمنح مستخدمي NDK إمكانية الوصول إلى أحدث ميزات libc++ وإصلاحات الأخطاء حتى عند استهداف إصدارات Android القديمة. أما إذا كنت تستخدم libc++_shared.so، فيجب تضمينه في تطبيقك. وإذا كنت ستنشئ تطبيقك باستخدام Gradle، سيتم إجراء ذلك تلقائيًا.

تضمّنت الإصدارات القديمة من نظام التشغيل Android أخطاءً في PackageManager وأداة الربط الديناميكي، ما أدّى إلى عدم موثوقية تثبيت المكتبات الأصلية وتحديثها وتحميلها. وعلى وجه الخصوص، إذا كان تطبيقك يستهدف إصدارًا من Android أقدم من نظام التشغيل Android 4.3 (المستوى 18 لواجهة برمجة تطبيقات Android)، وكنت تستخدم libc++_shared.so، عليك تحميل المكتبة المشتركة قبل أي مكتبة أخرى تعتمد عليها.

يوفر مشروع ReLinker حلولاً بديلة لجميع مشاكل تحميل المكتبة الأصلية المعروفة، وعادةً ما يكون خيارًا أفضل من كتابة الحلول البديلة التي تستخدمها.

STL واحد لكل تطبيق

سابقًا، كان NDK يدعم GNU libstdc++ وSTLport بالإضافة إلى libc++. إذا كان تطبيقك يعتمد على مكتبات تم إنشاؤها مسبقًا تم إنشاؤها باستخدام NDK عن تلك المستخدمة لإنشاء تطبيقك، فستحتاج إلى التأكد من ذلك بطريقة متوافقة.

يجب ألا يستخدم التطبيق أكثر من وقت تشغيل C++ واحد. تجدر الإشارة إلى أنّ STLs المختلفة غير متوافقة مع بعضها البعض. وكمثال على ذلك، لا يتطابق تنسيق std::string في libc++ مع تنسيق gnustl. لن تتمكن التعليمة البرمجية المكتوبة ضد STL من استخدام كائنات مكتوبة مقابل كائنات أخرى. هذا مجرد مثال واحد؛ حالات عدم التوافق كثيرة.

تمتد هذه القاعدة إلى ما هو أبعد من التعليمة البرمجية. يجب أن تستخدم جميع تبعياتك نفس STL التي حددتها. إذا كنت تعتمد على تبعية تابعة لجهة خارجية مغلقة المصدر تستخدم STL ولا توفر مكتبة لكل STL، لن يكون لديك خيار في STL. يجب استخدام نفس STL كتبعية.

من الممكن أن تعتمد على مكتبتين غير متوافقتين بشكل متبادل. في هذه الحالة، تكون الحلول الوحيدة هي إسقاط إحدى التبعيات أو مطالبة العامل بتقديم مكتبة تم إنشاؤها مقابل STL الأخرى.

استثناءات C++

يتم دعم استثناءات C++ من خلال libc++ ، ولكن يتم إيقافها افتراضيًا في ndk-build. وذلك لأن استثناءات C++ السابقة لم تكن متاحة في NDK. يتم تفعيل استثناءات C++ بشكل تلقائي لسلاسل الأدوات CMake أو المستقلّة.

لتفعيل الاستثناءات عبر تطبيقك بالكامل في ndk-build، أضِف السطر التالي إلى ملف Application.mk:

APP_CPPFLAGS := -fexceptions

لتفعيل الاستثناءات لوحدة إصدار ndk واحدة، أضِف السطر التالي إلى الوحدة المحددة في Android.mk الخاص بها:

LOCAL_CPP_FEATURES := exceptions

يمكنك بدلاً من ذلك استخدام ما يلي:

LOCAL_CPPFLAGS := -fexceptions

مراسلة نصية في الوقت الفعلي (RTTI)

يتم دعم ميزة RTTI في بعض الاستثناءات في libc++ ، ولكن يتم إيقافها تلقائيًا في ndk-build. يتم تفعيل ميزة "المراسلة النصية في الوقت الفعلي" (RTTI) تلقائيًا في سلاسل أدوات CMake وسلاسل الأدوات المستقلة.

لتفعيل ميزة RTTI في تطبيقك بالكامل في ndk-build، أضِف السطر التالي إلى ملف Application.mk:

APP_CPPFLAGS := -frtti

لتفعيل ميزة RTTI في وحدة إنشاء ndk واحدة، أضِف السطر التالي إلى الوحدة المطلوبة في ملف Android.mk الخاص بها:

LOCAL_CPP_FEATURES := rtti

يمكنك بدلاً من ذلك استخدام ما يلي:

LOCAL_CPPFLAGS := -frtti