Obsługa biblioteki C++

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 aplikacji libbar.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