Основы Android NDK: работа с C/C++ кодом

Android NDK path

Использование нативного кода, написанного на C или С++ — это тема, которую многие разработчики не затрагивают вовсе. Но порой использование C++ в приложениях намного упрощает/ускоряет разработку. В этой статье будут рассмотрены основные принципы работы с native кодом.

Предварительная настройка

Если у вас ещё не настроен Eclipse, то читаем как настроить Eclipse для работы с Android. Только помимо того, что в статье сказано, при установке ещё необходимо выбрать и установить NDK плагин.

Так же вам необходимо установить CDT для Eclipse. Под Виндой вам вроде как ещё понадобиться установить Cygwin.

Теперь необходимо создать проект и прописать пути.

В проекте будет создана папка jni, где вы должны размещать файлы с C++ кодом. В ранних версиях был баг, когда Eclipse не мог верно настроить пути до некоторых хэдеров из NDK. В последней версии всё нормально. Просто очистите проект (Clean project), а затем перестройте его (Build project).

Зачем нужен NDK?

Думаю, необходимо предварительно объяснить, когда вообще стоит (и стоит ли?) использовать ndk. Многие советуют использовать C++, когда требуются какие-то большие/сложные вычисления. Что значит сложно? =/ В общем, лучше назову конкретные случаи, когда использование NDK оправдано:

Возможности NDK огромны. Вы можете из Java вызывать C++ методы. В то же время, вам ничто не машет вызывать Java методы из C++. Даже есть возможность создавать приложение практически без использования Java, используя NativeActivity (API 9 и выше).

Java. Путешествие в Native (или туда и обратно).

Да простит меня профессор за упоминание его работы (: И так, рассмотреть всё в рамках одной статьи невозможно. Поэтому, для начала реализуем лишь вызов native методов из Java.

Перечислю кратко основные моменты при работе с native:

  • Создание файлов с C++ кодом.
  • Определение C++ методов для экспорта.
  • Создание .mk файлов.
  • Генерация библиотеки.
  • Подключение библиотеки в Java и вызов C++ методов.

Создание файлов с C++ кодом

В native определим всего 3 метода: передача строки, изменение строки, получение строки.

Создадим для начала файл def.h, подключим пару нужных файлов и определим методы для вывода в консоль.

#include <android/log.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#ifdef __ANDROID__
#define LOG_TAG "MyNative"
#define STRINGIFY(x) #x
#define LOG_TAG    __FILE__ ":" STRINGIFY(__MyNative__)
#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)


#endif

Создадим файл MyNative.h и определим в нём спецификации методов для экспорта, чтоб вызывать их из Java кода потом.

#include <def.h>
#include <jni.h>

char  MyStr[80];

extern "C" {
    JNIEXPORT void Java_ru_suvitruf_androidndk_AndroidNDK_SetString(JNIEnv * env, jobject obj, jstring str);
    JNIEXPORT void Java_ru_suvitruf_androidndk_AndroidNDK_ChangeString(JNIEnv * env, jobject obj);
    JNIEXPORT jstring Java_ru_suvitruf_androidndk_AndroidNDK_GetString(JNIEnv * env, jobject obj);
}

Теперь все три метода можно вызвать из Java кода. Я этот код ручками писал. Но можно заюзать javah, которая будет сама генерить эти заголовки. extern "C" нужен, чтобы компилятор C++ не менял имена объявленных функций.

Стоит немного сказать про наименование методов. Java_ – обязательный префикс. ru_suvitruf_androidndk, так как у нас пакет ru.suvitruf.androidndk, ну а дальше наименование класса и метода на стороне Java. В каждой функции в качестве аргумента имеется JNIEnv* — интерфейс для работы с Java, при помощи него можно вызывать Java-методы, создавать Java-объекты. Второй обязательный параметр — jobject или jclass — в зависимости от того, является ли метод статическим. Если метод статический, то аргумент будет типа jclass (ссылка на класс объекта, в котором объявлен метод), если не статический — jobject — ссылка на объект, у которого был вызван метод.

Ну и создадим MyNative.cpp с реализацией методов.

#include <MyNative.h>

JNIEXPORT void Java_ru_suvitruf_androidndk_AndroidNDK_SetString(JNIEnv * env, jobject obj, jstring str){
	jboolean isCopy;
	const char * Str;
	Str = env->GetStringUTFChars(str, &isCopy);
	strcpy(MyStr,Str);
	LOGI("string = \"%s\"",MyStr);
}

void ChangeStr(){
	strcat(MyStr," and bb.");
}

JNIEXPORT void Java_ru_suvitruf_androidndk_AndroidNDK_ChangeString(JNIEnv * env, jobject obj){
	ChangeStr();
	LOGI("string after change = \"%s\"",MyStr);
}

JNIEXPORT jstring Java_ru_suvitruf_androidndk_AndroidNDK_GetString(JNIEnv * env, jobject obj){
	LOGI("returned string = \"%s\"",MyStr);
	return env->NewStringUTF(MyStr);
}

Работа с Application.mk

В этом файле описаны глобальные настройки для сборки либы.

# Без этой строчки ничего не будет работать (:
APP_STL:=stlport_static
# Список модулей/либ, которые нужна забилдить. Они будут такие же как в LOCAL_MODULE в Android.mk файле
APP_MODULES      := AndroidNDK
# Указываем под какой arm собирать. Не обязательный параметр.
APP_ABI := armeabi armeabi-v7a
# Платформа, под которую билдим. Не обязательный параметр.
APP_PLATFORM := android-10

Работа с Android.mk

Здесь указываем параметры/настройки по линковке и прочее, чтобы собрать либу.

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

# имя нашего модуля, который будет вызываться в Java при помощи System.loadLibrary()
LOCAL_MODULE    := AndroidNDK

LOCAL_DEFAULT_CPP_EXTENSION := cpp

#список файлов, который нужно собрать
LOCAL_SRC_FILES := MyNative.cpp

#список библиотек из ndk, которые надо включить в сборку
LOCAL_LDLIBS :=  -llog -landroid

include $(BUILD_SHARED_LIBRARY)

В Android.mk вообще есть не мало всяких флагов и прочего. Можно добавлять в сборку уже готовые библиотеки и т.д. В следующих статьях напишу, как это делается.

После того, как вы создали C++ файлы и .mk сделали, можно забилдить проект, тогда в папке obj появится библиотека libAndroidNDK.so.

Подключение библиотеки в Java и вызов C++ методов.

Native simple project

Теперь остаётся только написать Java код. Сделаем простенькое приложение. Разместим поле для ввода текста, три кнопки (передача текста в native, изменение текста в native и возврат изменённой строки из native) и поле для нового текста, который получили из native кода.

Для того, чтобы использовать native методы создадим класс AndroidNDK.

public class AndroidNDK {
	
	// Загрузка модуля «AndroidNDK» — нативной библиотеки, в которой реализованы методы. 
	// Название этого модуля задается в файле Android.mk.
	static {
		System.loadLibrary("AndroidNDK");
	}
	
	public static native void SetString(String str);
	public static native void ChangeString();
	public static native String GetString(); 
}

Ключевое тут:

static {
System.loadLibrary("AndroidNDK");
}

Этот код выполнит загрузку нашей библиотеки, в которой реализованы методы. Обратите ещё раз внимание на этот класс и методы и вспомните наименование методов при экспорте в native коде: Java_ru_suvitruf_androidndk_AndroidNDK_ChangeString.

Java код по обработке нажатий на кнопки писать не буду, ибо это тривиальная задача. В любом случае, если понадобиться, можете посмотреть в исходниках к статье. В логе будет вот что:

native log

Для знакомства с native достаточно написал. Можете скачать исходники AndroidNDK.rar.

  Категории: Android, C/C++, java, Коддинг
  • Hero

    А будут ли дальше уроки по LibGDX? Очень хочу узнать как делать Runner на LibGDX.

    • http://suvitruf.ru Suvitruf

      Будут, но пока времени нет =/

      • true програмер

        А может просто “в лом”)))))

        • http://suvitruf.ru Suvitruf

          Если бы было лень, то этого блога совсем б не было)

  • Alti

    Скажите пожалуйста, а когда будет продолжение?

    • http://suvitruf.ru Suvitruf

      По NDK?

      • Alti

        Да. Хочется побольше узнать о добавлении готовых библиотек в сборку.

        • http://suvitruf.ru Suvitruf

          На этих выходных напишу вторую статью по NDK тогда )

  • Alti

    О замечательно. Спасибо!)

  • Alti

    Еще вопрос, вы развиваете эту тему как возможность использования с++ и сами эти пользуетесь в разработке приложений, или используете только яву? еще что очень интересно возможность интеграции с игровыми движками, например как мин3д, ну или libgdx, да впрочем с любыми…? Вопрос может не очень корректен, т.к. только начинаю изучать яву, начал с разбора движка мин3д…

    • http://suvitruf.ru Suvitruf

      Я сейчас портирую игрушки с iPhone, поэтому и стал статьи про ndk писать, ибо актуально. По поводу интеграции…вы хотите что-то свою дописать в библиотеку LibGDX? Или я вас не так понял? )

  • Pingback: Основы Android NDK: вызов Java-методов из C/C++ кода при помощи JNI | Suvitruf's Blog()

  • Alti

    Дописать да, но не в либгдх, работаю с 3д приложениями…

    • http://suvitruf.ru Suvitruf

      Если вы либу собираете сами, то можно что угодно дописать.

      Если вы используете уже собранную С++ библиотеку, то сложно сказать. Можно попробовать создать свою библиотеку, в которую вложить чью-то готовую и вызывать в своей либе методы той готовой библиотеки.

      В принципе, я когда в native работаю с zip или OpenGL, то юзаю уже готовые библиотеки. Другое дело, что вам заголовочные файлы всё равно нужны.

  • Pingback: Основы Android NDK: доступ к ресурсам директории assets из C++ | Suvitruf's Blog()

  • Pingback: Основы Android NDK: работа с OpenAL и форматами WAV, OGG | Suvitruf's Blog()

  • Валерий

    Извиняюсь, за глупый вопрос. не подскажете а вот библиотеки которые мы подгружаем в коде на С++, они как подгружаются? Мне просто необходимо openssl включить и возник данный вопрос. Буду благодарен за ответ, если конечно вы еще просматриваете эту тему.

    • http://suvitruf.ru Suvitruf

      Примерно как тут. Собираете openssl как static библиотеку, а потом подключаете в Android.mk

    • Саша

      openssl (немного урезанный) есть на каждом андроидном девайсе, так что если его вам достаточно, можно пользоваться системной библиотекой. В Андроиде работает обычный линуксовый dlopen(), но нельзя установить LD_LIBRARY_PATH.

  • 4ntoine

    Можно писать сразу на android- устройстве. Скопипастю новость (сори, что на англ) “I’d recommend CppDroid – new C/C++ IDE on Android. It has a lot of included C/C++ examples and tutorials. Blog: http://cppdroid.blogspot.com, Google Play: https://play.google.com/store/apps/details?id=name.antonsmirnov.android.cppdroid

  • Александр

    Eclipse -> Window -> Preferences -> Android -> NDK
    У меня нет такой вкладки. У меня ADT Bundle, качал с офсайта неделю назад. Может с тех пор что-то изменилось? В PATH путь к NDK прописал.

    • http://suvitruf.ru Suvitruf

      У меня на работе такая же фигня была. Я удалил все компоненты ADT, CDT и все плагины для C++. Установил заново и всё заработало.