- C++ Library Support
- On this page
- Helper Runtimes
- How to set your runtime
- Runtime Characteristics
- libstdc++ (default system runtime)
- GAbi++ runtime
- STLport runtime
- GNU STL runtime
- libC++ runtime:
- atomic support
- Compatibility
- Important Considerations
- C++ Exceptions
- Static runtimes
- Shared runtimes
- Licensing
- Введение в Android NDK
- Что такое Android NDK?
- Для чего используют NDK?
- Что такое JNI?
- Преимущества JNI
- Как устроен JNI
- Локальные и глобальные ссылки
- Обработка ошибок
- Примитивные типы JNI
- Ссылочные типы JNI
- Модифицированный UTF-8
- Функции JNI
- Пример использования функций JNI
- Потоки
- Первые шаги
- Android.mk
- Application.mk
- NDK-BUILDS
- Как собрать проект?
- Вызов нативных методов из Java кода
C++ Library Support
On this page
The Android platform provides a very minimal C++ runtime support library ( libstdc++ ). This minimal support does not include, for example:
- Standard C++ Library support (except a few trivial headers).
- C++ exceptions support
- RTTI support
The NDK provides headers for use with this default library. In addition, the NDK provides a number of helper runtimes that provide additional features. This page provides information about these helper runtimes, their characteristics, and how to use them.
Helper Runtimes
Table 1 provides names, brief explanations, and features of runtimes available inthe NDK.
Table 1. NDK Runtimes and Features.
Name | Explanation> | Features |
---|---|---|
libstdc++ (default) | The default minimal system C++ runtime library. | N/A |
gabi++_static | The GAbi++ runtime (static). | C++ Exceptions and RTTI |
gabi++_shared | The GAbi++ runtime (shared). | C++ Exceptions and RTTI |
stlport_static | The STLport runtime (static). | C++ Exceptions and RTTI; Standard Library |
stlport_shared | The STLport runtime (shared). | C++ Exceptions and RTTI; Standard Library |
gnustl_static | The GNU STL (static). | C++ Exceptions and RTTI; Standard Library |
gnustl_shared | The GNU STL (shared). | C++ Exceptions and RTTI; Standard Library |
c++_static | The LLVM libc++ runtime (static). | C++ Exceptions and RTTI; Standard Library |
c++_shared | The LLVM libc++ runtime (shared). | C++ Exceptions and RTTI; Standard Library |
How to set your runtime
Use the APP_STL variable in your Application.mk file to specify the runtime you wish to use. Use the values in the «Name» column in Table 1 as your setting. For example:
You may only select one runtime for your app, and can only do in Application.mk .
Even if you do not use the NDK build system, you can still use STLport, libc++ or GNU STL. For more information on how to use these runtimes with your own toolchain, see Standalone Toolchain.
Runtime Characteristics
libstdc++ (default system runtime)
This runtime only provides the following headers, with no support beyond them:
- cassert
- cctype
- cerrno
- cfloat
- climits
- cmath
- csetjmp
- csignal
- cstddef
- cstdint
- cstdio
- cstdlib
- cstring
- ctime
- cwchar
- new
- stl_pair.h
- typeinfo
- utility
GAbi++ runtime
This runtime provides the same headers as the default runtime, but adds support for RTTI (RunTime Type Information) and exception handling.
STLport runtime
This runtime is an Android port of STLport (http://www.stlport.org). It provides a complete set of C++ standard library headers. It also, by embedding its own instance of GAbi++, provides support for RTTI and exception handling.
While shared and static versions of this runtime are avilable, we recommend using the shared version. For more information, see Static runtimes.
The shared library file is named libstlport_shared.so instead of libstdc++.so as is common on other platforms.
In addition to the static- and shared-library options, you can also force the NDK to build the library from sources by adding the following line to your Application.mk file, or setting it in your environment prior to building:
GNU STL runtime
This runtime is the GNU Standard C++ Library, ( libstdc++-v3 ). Its shared library file is named libgnustl_shared.so .
libC++ runtime:
This runtime is an Android port of LLVM libc++. Its shared library file is named libc++_shared.so .
By default, this runtime compiles with -std=c++11 . As with GNU libstdc++ , you need to explicitly turns on exceptions or RTTI support. For information on how to do this, see C++ Exceptions and RTTI.
The NDK provides prebuilt static and shared libraries for libc++ compiled by Clang 3.4, but you can force the NDK to rebuild libc++ from sources by adding the following line to your Application.mk file, or setting it in your environment prior to building:
atomic support
If you are using your own toolchain, use:
Note: -latomic is only available for GCC 4.8. Because Clang 3.5 and Clang 3.6 use GCC 4.8’s headers and libraries, as well as its as and ld options, those versions of Clang also get -latomic .
Compatibility
Around 99% of tests pass when compiling libc++ with Clang 3.4 for all supported ABIs. The failures are mostly in the areas of wchar_t and locales that Android bionic doesn’t support. Switching locale from the default produces the following warning in logcat :
We do not recommend using libc++ with GCC 4.6 because of GCC 4.6’s limited c++11 support.
For information on libc++ tests that fail to compile, black_list* in $NDK/tests/device/test-libc++-shared-full/jni/Android.mk . For information about tests that fail to run correctly, see $NDK/tests/device/test-libc++-shared-full/BROKEN_RUN . $NDK , here, is the your NDK installation’s root directory.
Important Considerations
C++ Exceptions
In all versions of the NDK later than NDKr5, the NDK toolchain allows you to use C++ runtimes that support exception handling. However, to ensure compatibility with earlier releases, it compiles all C++ sources with -fno-exceptions support by default. You can enable C++ exceptions either for your entire app, or for individual modules.
To enable exception-handling support for your entire app, add the following line to your Application.mk file. To enable exception-handling support for individual modules’, add the following line to their respective Android.mk files.
In all versions of the NDK later than NDKr5, the NDK toolchain allows you to use C++ runtimes that support RTTI. However, to ensure compatibility with earlier releases, it compiles all C++ sources with -fno-rtti by default.
To enable RTTI support for your entire app for your entire application, add the following line to your Application.mk file: To enable RTTI support for individual modules, add the following line to their respective Android.mk files: Alternatively, you can use:
Static runtimes
Linking the static library variant of a C++ runtime to more than one binary may result in unexpected behavior. For example, you may experience:
- Memory allocated in one library, and freed in the other, causing memory leakage or heap corruption.
- Exceptions raised in libfoo.so going uncaught in libbar.so , causing your app to crash.
- Buffering of std::cout not working properly
In addition, if you link two shared libraries–or a shared library and an executable– against the same static runtime, the final binary image of each shared library includes a copy of the runtime’s code. Having multiple instances of runtime code is problematic because of duplication of certain global variables that the runtime uses or provides internally.
This problem does not apply to a project comprising a single shared library. For example, you can link against stlport_static , and expect your app to behave correctly. If your project requires several shared library modules, we recommend that you use the shared library variant of your C++ runtime.
Shared runtimes
If your app targets a version of Android earlier than Android 4.3 (Android API level 18), and you use the shared library variant of a given C++ runtime, you must load the shared library before any other library that depends on it.
For example, an app may have the following modules:
- libfoo.so
- libbar.so which is used by libfoo.so
- libstlport_shared.so, used by both libfoo and libbar
You must load the libraries in reverse dependency order:
Note: Do not use the lib prefix when calling System.loadLibrary() .
Licensing
STLport is licensed under a BSD-style open-source license. See $NDK/sources/cxx-stl/stlport/README for more details about STLport.
GNU libstdc++ is covered by the GPLv3 license, and not the LGPLv2 or LGPLv3. For more information, see License on the GCC website.
LLVM libc++ is dual-licensed under both the University of Illinois «BSD-Like» license and the MIT license.
Источник
Введение в Android NDK
Для разработки приложений под ОС Android, Google предоставляет два пакета разработки: SDK и NDK. Про SDK существует много статей, книжек, а так же хорошие guidelines от Google. Но про NDK даже сам Google мало что пишет. А из стоящих книг я бы выделил только одну, Cinar O. — Pro Android C++ with the NDK – 2012.
Эта статья ориентирована на тех, кто ещё не знаком (или мало знаком) с Android NDK и хотел бы укрепить свои знания. Внимание я уделю JNI, так как мне кажется начинать нужно именно с этого интерфейса. Так же, в конце рассмотрим небольшой пример с двумя функциями записи и чтения файла. Кто не любит много текста, тот может посмотреть видео версию.
Что такое Android NDK?
Android NDK (native development kit) – это набор инструментов, которые позволяют реализовать часть вашего приложения используя такие языки как С/С++.
Для чего используют NDK?
Google рекомендует прибегать к использованию NDK только в редчайших случаях. Зачастую это такие случаи:
- Нужно увеличить производительность (например, сортировка большого объема данных);
- Использовать стороннюю библиотеку. Например, много уже чего написано на С/С++ языках и нужно просто заиспользовать существующий материал. Пример таких библиотек, как, Ffmpeg, OpenCV;
- Программирование на низком уровне (например, всё что выходит за рамки Dalvik);
Что такое JNI?
Java Native Interface – стандартный механизм для запуска кода, под управлением виртуальной машины Java, который написан на языках С/С++ или Assembler, и скомпонован в виде динамических библиотек, позволяет не использовать статическое связывание. Это даёт возможность вызова функции С/С++ из программы на Java, и наоборот.
Преимущества JNI
Основное преимущество перед аналогами (Netscape Java Runtime Interface или Microsoft’s Raw Native Interface and COM/Java Interface) является то что JNI изначально разрабатывался для обеспечения двоичной совместимости, для совместимости приложений, написанных на JNI, для любых виртуальных машин Java на конкретной платформе (когда я говорю о JNI, то я не привязываюсь к Dalvik машине, потому как JNI был написан Oracle для JVM который подходит для всех Java виртуальных машин). Поэтому скомпилированный код на С/С++ будет выполнятся в не зависимости от платформы. Более ранние версии не позволяли реализовывать двоичную совместимость.
Двоичная совместимость или же бинарная совместимость – вид совместимости программ, позволяющий программе работать в различных средах без изменения её исполняемых файлов.
Как устроен JNI
JNI таблица, организована как таблица виртуальных функций в С++. VM может работать с несколькими такими таблицами. Например, одна будет для отладки, вторая для использования. Указатель на JNI интерфейс действителен только в текущем потоке. Это значит, что указатель не может гулять с одного потока в другой. Но нативные методы могут быть вызваны из разных потоков. Пример:
- *env – указатель на интерфейс;
- оbj – ссылка на объект в котором описан нативный метод;
- i and s – передаваемые аргументы;
Примитивные типы копируются между VM и нативным кодом, а объекты передаются по ссылке. VM обязана отслеживать все ссылки которые передаются в нативный код. Все переданные ссылки в нативный код не могут быть освобождены GC. Но нативный код в свою очередь должен информировать VM о том что ему больше не нужны ссылки на переданные объекты.
Локальные и глобальные ссылки
JNI делит ссылки на три типа: локальные, глобальные и слабые глобальные ссылки. Локальные действительны пока не завершиться метод. Все Java объекты которые возвращает функции JNI являются локальными. Программист должен надеется на то что VM сама подчистит все локальные ссылки. Локальные ссылки доступны лишь в том потоке в котором были созданы. Однако если есть необходимость то их можно освобождать сразу методом JNI интерфейса DeleteLocalRef:
Глобальные ссылки остаются пока они явно не будут освобождены. Что бы зарегистрировать глобальную ссылку следует вызвать метод NewGlobalRef. Если же глобальная ссылка уже не нужна, то её можно удалить методом DeleteGlobalRef:
Обработка ошибок
JNI не проверяет ошибки такие как NullPointerException, IllegalArgumentException. Причины:
- снижение производительности;
- в большинстве функций C библиотек очень и очень трудно защитится от ошибок.
JNI позволяет использовать Java Exception. Большинство JNI функций возвращают код ошибок а не сам Exception, и поэтому приходится обрабатывать сам код, а в Java уже выбрасывать Exception. В JNI следует проверять код ошибки вызываемых функций и после них следует вызвать ExceptionOccurred(), которая в свою очередь возвращает объект ошибки:
Например, некоторые функции JNI доступа к массивам не возвращают ошибки, но могут вызвать исключения ArrayIndexOutOfBoundsException или ArrayStoreException.
Примитивные типы JNI
В JNI существуют свои примитивные и ссылочные типы данных.
Java Type | Native Type | Description |
---|---|---|
boolean | jboolean | unsigned 8 bits |
byte | jbyte | signed 8 bits |
char | jchar | unsigned 16 bits |
short | jshort | signed 16 bits |
int | jint | signed 32 bits |
long | jlong | signed 64 bits |
float | jfloat | 32 bits |
double | jdouble | 64 bits |
void | void | N/A |
Ссылочные типы JNI
Модифицированный UTF-8
JNI использует модифицированную кодировку UTF-8 для представления строк. Java в свою очередь использует UTF-16. UTF-8 в основном используется в С, потому что он кодирует \u0000 в 0xc0, вместо привычной 0x00. Изменённые строки кодируются так, что последовательность символов, которые содержат только ненулевой ASCII символы могут быть представлены с использованием только одного байта.
Функции JNI
Интерфейс JNI содержит в себе не только собственный набор данных, но и свои собственные функции. На их рассмотрение уйдёт много времени, так как их не один десяток. Ознакомится с ними вы сможете в официальной документации.
Пример использования функций JNI
Небольшой пример, что бы вы усвоили пройденный материал:
Разберём построчно:
- JavaVM – предоставляет интерфейс для вызова функций, которые позволяют создавать и уничтожать JavaVM;
- JNIEnv – обеспечивает большинство функций JNI;
- JavaVMInitArgs – аргументы для JavaVM;
- JavaVMOption – опции для JavaVM;
Метод JNI_CreateJavaVM() инициализирует JavaVM и возвращает на неё указатель. Метод JNI_DestroyJavaVM() выгружает созданную JavaVM.
Потоки
Всеми потоками в Linux управляет ядро, но они могут быть прикреплены к JavaVM функциями AttachCurrentThread и AttachCurrentThreadAsDaemon. Пока поток не присоединён он не имеет доступа к JNIEnv. Важно, Android не приостанавливает потоки которые были созданы JNI, даже если срабатывает GC. Но перед тем как поток завершиться он должен вызвать метод DetachCurrentThread что бы отсоединиться от JavaVM.
Первые шаги
Структура проекта у вас должна выглядеть следующим образом:
Как мы видим из рисунка 3, весь нативный код находится в папке jni. После сборки проекта, в папке libs создастся четыре папки под каждую архитектуру процессора, в которой будет лежать ваша нативная библиотека (количество папок зависит от количество выбранных архитектур).
Для того, чтобы создать нативный проект, нужно создать обычный Android проект и проделать следующие шаги:
- В корне проекта нужно создать папку jni, в которую поместить исходники нативного кода;
- Создать файл Android.mk, который будет собирать проект;
- Создать файл Application.mk, в котором описываются детали сборки. Он не является обязательным условием, но позволяет гибко настроить сборку;
- Создать файл ndk-build, который будет запускать процесс сборки (тоже не является обязательным).
Android.mk
Как упоминалось уже выше, это make файл для сборки нативного проекта. Android.mk позволяет группировать ваш код в модули. Модули могут быть как статические библиотеки (static library, только они будут скопированные в ваш проект, в папку libs), разделяемые библиотеки (shared library), автономный исполняемый файл (standalone executable).
Пример минимальной конфигурации:
Рассмотрим детально:
- LOCAL_PATH := $(call my-dir) – функция call my-dir возвращает путь папки в которой вызывается файл;
- include $(CLEAR_VARS) – очищает переменные которые использовались до этого кроме LOCAL_PATH. Это необходимо так как все переменные являются глобальными, потому что сборка происходит в контексте одного GNU Make;
- LOCAL_MODULE – имя выходного модуля. В нашем примере имя выходной библиотеки установлено как NDKBegining, но после сборки в папке libs создадутся библиотеки с именами libNDKBegining. Android добавляет к названию префикс lib, но в java коде при подключении вы должны указывать название библиотеки без префикса (то есть названия должны совпадать с установленными в make файлах);
- LOCAL_SRC_FILES – перечисление исходных файлов из которых следует создать сборку;
- include $(BUILD_SHARED_LIBRARY) – указывает тип выходного модуля.
В Android.mk можно определить свои переменные, но они не должны иметь такой синтаксис: LOCAL_, PRIVATE_, NDK_, APP_, my-dir. Google, рекомендует называть свои переменные, как MY_. Например:
Application.mk
NDK-BUILDS
Ndk-build из себя представляет обёртку GNU Make. После 4-й версии ввели флаги для ndk-build:
- clean – очищает все сгенеренные бинарные файлы;
- NDK_DEBUG=1 – генерирует отладочный код;
- NDK_LOG=1 – показывает лог сообщений (используется для отладки);
- NDK_HOST_32BIT=1 – Android имеет средства для поддержки 64-х битных версий утилит (например NDK_PATH\toolchains\mipsel-linux-android-4.8\prebuilt\windows-x86_64 и т.д.);
- NDK_APPLICATION_MK — указывается путь к Application.mk.
В 5-й версии NDK был введён такой флаг как NDK_DEBUG. Если он установлен в 1 то создаётся отладочная версия. Если флаг не установлен то ndk-build по умолчанию проверяет стоит ли атрибут android:debuggable=«true» в AndroidManifest.xml. Если вы используете ndk выше 8-й версии, то Google не рекомендует использовать атрибут android:debuggable в AndroidManifest.xml (потому что если вы используете «ant debug» или строите отладочную версию с помощью ADT плагина то они автоматически добавляют флаг NDK_DEBUG=1).
По умолчанию устанавливается поддержка 64-х разрядной версии утилит, но вы можете принудительно собрать только для 32-х установив флаг NDK_HOST_32BIT=1. Google, рекомендует всё же использовать 64-х разрядность утилит для повышения производительности больших программ.
Как собрать проект?
Раньше это было мучением. Нужно было установить CDT плагин, скачать компилятор cygwin или mingw. Скачать Android NDK. Подключить это всё в настройках Eclipse. И как на зло это всё оказывалось не рабочим. Я первый раз когда столкнулся с Android NDK, то настраивал это всё 3 дня (а проблема оказалось в том что в cygwin нужно было дать разрешение 777 на папку проекта).
Сейчас с этим всё намного проще. Идёте по этой ссылке. Качаете Eclipse ADT Bundle в котором уже есть всё то что необходимо для сборки.
Вызов нативных методов из Java кода
Для того что бы использовать нативный код из Java вам сперва следует определить нативные методы в Java классе. Например:
Перед методом следует поставить зарезервированное слово «native». Таким образом компилятор знает, что это точка входа в JNI. Эти методы нам нужно реализовать в С/С++ файле. Так же Google рекомендует начинать именовать методы со слова nativeХ, где Х – реальное название метода. Но перед тем как реализовывать эти методы вручную, следует сгенерировать header файл. Это можно сделать вручную, но можно использовать утилиту javah, которая находится в jdk. Но пойдём дальше и не будет использовать её через консоль, а будем это делать при помощи стандартных средств Eclipse.
Теперь можете запускать. В директории bin/classes будут лежать ваши header файлы.
Далее копируем эти файлы в jni директорию нашего нативного проекта. Вызываем контекстное меню проекта и выбираем пункт Android Tools – Add Native Library. Это позволит нам использовать jni.h функции. Дальше вы уже можете создавать cpp файл (иногда Eclipse его создаёт по умолчанию) и писать тела методов, которые уже описаны в header файле.
Пример кода я не стал добавлять в статью, чтобы не растягивать её. Пример вы можете посмотреть/скачать с github.
Источник