NDK obsługuje wiele bibliotek środowisk wykonawczych C++. Ten dokument zawiera informacje o tych bibliotekach, związanych z nimi kompromisach i sposobach korzystania z nich.
Biblioteki środowiska wykonawczego C++
Tabela 1. Środowiska i funkcje NDK C++.
Nazwa | Funkcje |
---|---|
libc++, | Nowoczesna obsługa C++. |
system | new i delete . (Wycofane w r18). |
brak | Bez nagłówków, ograniczony kod C++. |
Biblioteka libc++ jest dostępna zarówno jako biblioteka statyczna, jak i biblioteka współdzielona.
libc++
Biblioteka libc++ LLVM to standardowa biblioteka C++, która jest używana w systemie operacyjnym Android od wersji Lollipop. Od NDK r18 jest jedynym STL dostępnym w NDK.
CMake przyjmuje domyślnie dowolną domyślną wersję clang C++ (obecnie C++14), więc aby używać funkcji C++17 lub nowszych, należy ustawić standardową CMAKE_CXX_STANDARD
na odpowiednią wartość w pliku CMakeLists.txt
. Więcej informacji znajdziesz w dokumentacji CMake for CMAKE_CXX_STANDARD
.
Opcja ndk-build pozostawia też domyślnie decyzję o clangu, więc użytkownicy ndk-build powinni zamiast tego użyć elementu APP_CPPFLAGS
, aby dodać -std=c++17
lub inny element.
Biblioteka współdzielona libc++ to libc++_shared.so
, a biblioteka statyczna to libc++_static.a
. W typowych przypadkach system kompilacji obsługuje i pakuje te biblioteki odpowiednio do potrzeb użytkownika. W nietypowych przypadkach lub w przypadku wdrażania własnego systemu kompilacji zapoznaj się z przewodnikiem dla właścicieli systemu kompilacji lub z przewodnikiem dotyczącym używania innych systemów kompilacji.
Projekt LLVM jest objęty licencją Apache w wersji 2.0 z wyjątkami LLVM. Więcej informacji znajdziesz w pliku licencji.
system
Systemowe środowisko wykonawcze odnosi się do /system/lib/libstdc++.so
. Tej biblioteki
nie należy mylić z w pełni funkcjonalną biblioteką libstdc++ na Androidzie. Na Androidzie libstdc++ to tylko
new
i delete
. Użyj libc++, aby utworzyć w pełni funkcjonalną bibliotekę standardową w C++.
Systemowe środowisko wykonawcze C++ obsługuje podstawowe środowisko wykonawcze C++ ABI.
Zasadniczo ta biblioteka udostępnia new
i delete
. W przeciwieństwie do innych opcji w pakiecie NDK nie ma obsługi wyjątków ani RTTI.
Biblioteka nie jest dostępna w standardowej postaci poza kodami C++ dla nagłówków biblioteki C, np. <cstdio>
. Jeśli potrzebujesz pomocy STL, użyj jednej z pozostałych opcji przedstawionych na tej stronie.
brak
Można też wybrać opcję braku STL. W takich przypadkach nie ma żadnych wymagań związanych z łączeniem czy licencjonowaniem. Brak dostępnych standardowych nagłówków C++.
Wybieranie środowiska wykonawczego C++
CMake
Wartość domyślna CMake to c++_static
.
Możesz określić c++_shared
, c++_static
, none
lub system
za pomocą zmiennej ANDROID_STL
w pliku build.gradle
na poziomie modułu. Aby dowiedzieć się więcej, zapoznaj się z dokumentacją dotyczącą ANDROID_STL w CMake.
NK Build
Wartością domyślną dla ndk-build jest none
.
Możesz określić c++_shared
, c++_static
, none
lub system
za pomocą zmiennej APP_STL
w pliku Application.mk. Na przykład:
APP_STL := c++_shared
Narzędzie ndk-build pozwala wybrać tylko jedno środowisko wykonawcze dla aplikacji i można to zrobić tylko w pliku Application.mk.
Używaj clang bezpośrednio
Jeśli używasz clangu bezpośrednio w swoim systemie kompilacji, clang++ będzie domyślnie korzystać z c++_shared
. Aby użyć wariantu statycznego, dodaj -static-libstdc++
do flag tagu łączącego. Chociaż ze względów historycznych ta opcja używa nazwy „libstdc++”, jest to prawidłowe również w przypadku libc++.
Ważne informacje
Statyczne środowiska wykonawcze
Jeśli cały kod natywny Twojej aplikacji jest umieszczony w jednej bibliotece współdzielonej, zalecamy korzystanie ze statycznego środowiska wykonawczego. Dzięki temu tag łączący może umieścić w tekście i usunąć jak najwięcej nieużywanego kodu, co prowadzi do najbardziej zoptymalizowanego i mniejszego wykorzystania. Pozwala to też uniknąć błędów dotyczących PackageManagera i dynamicznych linków łączących w starszych wersjach Androida, które utrudniają obsługę wielu bibliotek współdzielonych i są podatne na błędy.
Jednak w C++ nie można bezpiecznie zdefiniować więcej niż jednej kopii tej samej funkcji lub obiektu w jednym programie. Jest to jeden z aspektów pojedynczej reguły definicji stosowanej w standardzie C++.
Gdy używasz statycznego środowiska wykonawczego (i ogólnie bibliotek statycznych), łatwo jest przypadkowo naruszyć tę regułę. Na przykład taka aplikacja narusza tę regułę:
# 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)
W takiej sytuacji plik STL, w tym dane globalne i konstruktory statyczne, będzie znajdować się w obu bibliotekach. Działanie tej aplikacji w środowisku wykonawczym jest nieokreślone i w praktyce często zdarzają się awarie. Inne możliwe problemy to między innymi:
- Pamięć przydzielona w jednej bibliotece i wolna w innej, powodując wyciek pamięci lub uszkodzenie sterty.
- Wyjątki zgłoszone w aplikacji
libfoo.so
nie zostaną przechwycone w aplikacjilibbar.so
, co spowoduje awarię aplikacji. - Buforowanie
std::cout
nie działa prawidłowo.
Połączenie statycznego środowiska wykonawczego z wieloma bibliotekami spowoduje zduplikowanie kodu w każdej udostępnianej bibliotece, a tym samym zwiększenie rozmiaru aplikacji.
Ogólnie możesz użyć statycznego wariantu środowiska wykonawczego C++ tylko wtedy, gdy w swojej aplikacji masz jedną i tylko jedną udostępnianą bibliotekę.
Współdzielone środowiska wykonawcze
Jeśli aplikacja zawiera wiele bibliotek udostępnionych, użyj libc++_shared.so
.
Na Androidzie licencja libc++ z pakietu NDK różni się od tej w systemie operacyjnym. Dzięki temu użytkownicy pakietu NDK mają dostęp do najnowszych funkcji i poprawek błędów dostępnych w bibliotece libc++, nawet jeśli aplikacja jest kierowana na starsze wersje Androida. Natomiast jeśli używasz narzędzia libc++_shared.so
, musisz dodać je do swojej aplikacji. Jeśli tworzysz aplikację za pomocą Gradle, odbywa się to automatycznie.
W starszych wersjach Androida występowały błędy w narzędziu PackageManager i dynamicznym tagu łączącym, które sprawiały, że instalowanie, aktualizowanie i wczytywanie bibliotek natywnych nie działało prawidłowo. Jeśli Twoja aplikacja jest kierowana na Androida w wersji starszej niż 4.3 (Android API na poziomie 18) i używasz interfejsu libc++_shared.so
, musisz wczytać bibliotekę współdzieloną przed każdą inną biblioteką, która jest od niej zależna.
Projekt ReLinker oferuje sposoby obejścia wszystkich znanych problemów z wczytywaniem bibliotek natywnych. Zwykle jest to lepszym rozwiązaniem niż tworzenie własnych obejść.
1 STL na aplikację
Dawniej oprócz libc++ pakiet NDK obsługiwał GNU libstdc++ i STLport. Jeśli Twoja aplikacja wymaga gotowych bibliotek, które zostały utworzone na podstawie pakietu NDK innego niż ten, który został użyty do skompilowania aplikacji, sprawdź, czy robi to w zgodny sposób.
Aplikacja nie powinna używać więcej niż 1 środowiska wykonawczego C++. Różne modele STL nie są ze sobą zgodne. Na przykład układ std::string
w libc++ nie jest taki sam jak w gnustl. Kody napisane w ramach jednego kodu STL
nie mogą używać obiektów zapisanych względem innego. To tylko jeden przykład
z wieloma niezgodnościami.
Ta reguła wykracza poza Twój kod. Wszystkie zależności muszą używać tej samej metody wyznaczania stawek (STL). Jeśli korzystasz z zewnętrznych narzędzi STL, które są uzależnione od zamkniętego źródła, i nie udostępniają biblioteki dla każdego z nich, nie masz wyboru w przypadku STL. Musisz użyć tego samego STL co zależność.
Możliwe, że będziesz polegać na 2 niekompatybilnych bibliotekach. W tej sytuacji jedynym rozwiązaniem jest rezygnacja z jednej z zależności lub poproszenie administratora o udostępnienie biblioteki utworzonej na podstawie innej metody STL.
Wyjątki C++
Wyjątki C++ są obsługiwane przez libc++, ale są domyślnie wyłączone w ndk-build. Dzieje się tak, ponieważ wcześniej w pakiecie NDK nie były dostępne wyjątki C++. CMake i samodzielne łańcuchy narzędzi mają domyślnie włączone wyjątki C++.
Aby włączyć wyjątki w całej aplikacji w ndk-build, dodaj ten wiersz do pliku Application.mk:
APP_CPPFLAGS := -fexceptions
Aby włączyć wyjątki dla pojedynczego modułu kompilacji ndk, dodaj ten wiersz do danego modułu w pliku Android.mk:
LOCAL_CPP_FEATURES := exceptions
Możesz też użyć polecenia:
LOCAL_CPPFLAGS := -fexceptions
RTTI
Podobnie jak w przypadku wyjątków, protokół RTTI jest obsługiwany przez libc++, ale jest domyślnie wyłączony w ndk-build. CMake i samodzielne łańcuchy narzędzi mają domyślnie włączony protokół RTTI.
Aby włączyć RTTI w całej aplikacji w ndk-build, dodaj ten wiersz do pliku Application.mk:
APP_CPPFLAGS := -frtti
Aby włączyć RTTI w pojedynczym module kompilacji ndk, dodaj ten wiersz do danego modułu w pliku Android.mk:
LOCAL_CPP_FEATURES := rtti
Możesz też użyć polecenia:
LOCAL_CPPFLAGS := -frtti