Android с NDK поддерживает код C / C ++, а iOS с Objective-C ++ также поддерживает, так как я могу писать приложения с собственным кодом C / C ++, совместно используемым Android и iOS?
Android с NDK поддерживает код C / C ++, а iOS с Objective-C ++ также поддерживает, так как я могу писать приложения с собственным кодом C / C ++, совместно используемым Android и iOS?
Ответы:
Этот ответ довольно популярен даже через четыре года после того, как я его написал, за эти четыре года многое изменилось, поэтому я решил обновить свой ответ, чтобы он лучше соответствовал нашей нынешней реальности. Идея ответа не меняется; реализация немного изменилась. Мой английский тоже изменился, он сильно улучшился, поэтому теперь ответ стал более понятным для всех.
Взгляните на репозиторий, чтобы вы могли загрузить и запустить код, который я покажу ниже.
Прежде чем я покажу код, обратите внимание на следующую диаграмму.
Каждая ОС имеет свой пользовательский интерфейс и особенности, поэтому мы намерены написать специальный код для каждой платформы в этом отношении. С другой стороны, весь логический код, бизнес-правила и вещи, которыми можно поделиться, мы намерены написать с использованием C ++, чтобы мы могли скомпилировать один и тот же код для каждой платформы.
На схеме вы можете увидеть слой C ++ на самом нижнем уровне. Весь общий код находится в этом сегменте. Самый высокий уровень - это обычный код Obj-C / Java / Kotlin, здесь нет новостей, сложная часть - это средний уровень.
Средний уровень со стороны iOS прост; вам нужно только настроить свой проект для сборки с использованием варианта Obj-c, известного как Objective-C ++, и все, у вас есть доступ к коду C ++.
Со стороны Android дело стало сложнее: оба языка, Java и Kotlin, на Android работали под виртуальной машиной Java. Таким образом, единственный способ получить доступ к коду C ++ - использовать JNI , пожалуйста, найдите время, чтобы прочитать основы JNI. К счастью, сегодняшняя Android Studio IDE имеет значительные улучшения на стороне JNI, и при редактировании кода вам будет показано множество проблем.
Наш образец - это простое приложение, в котором вы отправляете текст в CPP, оно преобразует этот текст во что-то еще и возвращает его. Идея состоит в том, что iOS отправит «Obj-C», а Android отправит «Java» со своих соответствующих языков, а код CPP создаст текст в следующем виде: «cpp передает привет << полученному тексту >> ».
Прежде всего, мы собираемся создать общий код CPP, сделав это, у нас есть простой файл заголовка с объявлением метода, который получает желаемый текст:
#include <iostream>
const char *concatenateMyStringWithCppString(const char *myString);
И реализация CPP:
#include <string.h>
#include "Core.h"
const char *CPP_BASE_STRING = "cpp says hello to %s";
const char *concatenateMyStringWithCppString(const char *myString) {
char *concatenatedString = new char[strlen(CPP_BASE_STRING) + strlen(myString)];
sprintf(concatenatedString, CPP_BASE_STRING, myString);
return concatenatedString;
}
Интересным бонусом является то, что мы можем использовать один и тот же код для Linux и Mac, а также для других систем Unix. Эта возможность особенно полезна, потому что мы можем быстрее протестировать наш общий код, поэтому мы собираемся создать Main.cpp, как показано ниже, чтобы выполнить его с нашей машины и посмотреть, работает ли общий код.
#include <iostream>
#include <string>
#include "../CPP/Core.h"
int main() {
std::string textFromCppCore = concatenateMyStringWithCppString("Unix");
std::cout << textFromCppCore << '\n';
return 0;
}
Для сборки кода необходимо выполнить:
$ g++ Main.cpp Core.cpp -o main
$ ./main
cpp says hello to Unix
Пора реализовать на мобильной стороне. Поскольку iOS имеет простую интеграцию, мы начинаем с нее. Наше приложение для iOS представляет собой типичное приложение Obj-c с одним отличием; файлы есть .mm
и нет .m
. т.е. это приложение Obj-C ++, а не приложение Obj-C.
Для лучшей организации мы создаем CoreWrapper.mm следующим образом:
#import "CoreWrapper.h"
@implementation CoreWrapper
+ (NSString*) concatenateMyStringWithCppString:(NSString*)myString {
const char *utfString = [myString UTF8String];
const char *textFromCppCore = concatenateMyStringWithCppString(utfString);
NSString *objcString = [NSString stringWithUTF8String:textFromCppCore];
return objcString;
}
@end
Этот класс отвечает за преобразование типов и вызовов CPP в типы и вызовы Obj-C. Это не обязательно, если вы можете вызвать код CPP для любого файла, который хотите на Obj-C, но он помогает сохранить организацию, и за пределами ваших файлов-оберток вы поддерживаете полный код в стиле Obj-C, только файл оберток становится в стиле CPP ,
Как только ваша оболочка подключена к коду CPP, вы можете использовать ее как стандартный код Obj-C, например ViewController "
#import "ViewController.h"
#import "CoreWrapper.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UILabel *label;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSString* textFromCppCore = [CoreWrapper concatenateMyStringWithCppString:@"Obj-C++"];
[_label setText:textFromCppCore];
}
@end
Посмотрите, как выглядит приложение:
Пришло время интеграции с Android. Android использует Gradle в качестве системы сборки, а для кода C / C ++ использует CMake. Итак, первое, что нам нужно сделать, это настроить CMake в файле gradle:
android {
...
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
...
defaultConfig {
externalNativeBuild {
cmake {
cppFlags "-std=c++14"
}
}
...
}
И второй шаг - добавить файл CMakeLists.txt:
cmake_minimum_required(VERSION 3.4.1)
include_directories (
../../CPP/
)
add_library(
native-lib
SHARED
src/main/cpp/native-lib.cpp
../../CPP/Core.h
../../CPP/Core.cpp
)
find_library(
log-lib
log
)
target_link_libraries(
native-lib
${log-lib}
)
Файл CMake - это то место, где вам нужно добавить файлы CPP и папки заголовков, которые вы будете использовать в проекте, в нашем примере мы добавляем CPP
папку и файлы Core.h / .cpp. Чтобы узнать больше о конфигурации C / C ++, прочтите ее.
Теперь основной код является частью нашего приложения, пришло время создать мост, чтобы упростить и упорядочить вещи, мы создаем определенный класс с именем CoreWrapper, который будет нашей оболочкой между JVM и CPP:
public class CoreWrapper {
public native String concatenateMyStringWithCppString(String myString);
static {
System.loadLibrary("native-lib");
}
}
Обратите внимание, что этот класс имеет native
метод и загружает собственную библиотеку с именем native-lib
. Эта библиотека - та, которую мы создаем, в конце концов, код CPP станет .so
файлом общего объекта, встроенным в наш APK, и loadLibrary
он загрузит его. Наконец, когда вы вызываете собственный метод, JVM делегирует вызов загруженной библиотеке.
Теперь самая странная часть интеграции Android - это JNI; Нам нужен файл cpp, как показано ниже, в нашем случае "native-lib.cpp":
extern "C" {
JNIEXPORT jstring JNICALL Java_ademar_androidioscppexample_CoreWrapper_concatenateMyStringWithCppString(JNIEnv *env, jobject /* this */, jstring myString) {
const char *utfString = env->GetStringUTFChars(myString, 0);
const char *textFromCppCore = concatenateMyStringWithCppString(utfString);
jstring javaString = env->NewStringUTF(textFromCppCore);
return javaString;
}
}
Первое, что вы заметите, это то, что extern "C"
эта часть необходима для правильной работы JNI с нашим кодом CPP и связями методов. Вы также увидите некоторые символы, которые JNI использует для работы с JVM, например, JNIEXPORT
и JNICALL
. Чтобы вы поняли значение этих вещей, необходимо уделить время и прочитать их , для целей данного руководства просто рассматривайте эти вещи как шаблон.
Одна важная вещь, которая обычно является корнем многих проблем, - это название метода; он должен соответствовать шаблону «Java_package_class_method». В настоящее время студия Android имеет отличную поддержку для этого, поэтому она может автоматически сгенерировать этот шаблон и показать вам, правильно он или не назван. В нашем примере наш метод называется «Java_ademar_androidioscppexample_CoreWrapper_concatenateMyStringWithCppString», потому что «ademar.androidioscppexample» - это наш пакет, поэтому мы заменяем «.» по «_» CoreWrapper - это класс, в котором мы связываем собственный метод, а «concatenateMyStringWithCppString» - это само имя метода.
Поскольку у нас правильно объявлен метод, пришло время проанализировать аргументы, первый параметр - это указатель на JNIEnv
то, как мы имеем доступ к материалам JNI, очень важно, чтобы мы сделали наши преобразования, как вы скоро увидите. Второй - jobject
это экземпляр объекта, который вы использовали для вызова этого метода. Вы можете думать, что это java " this ", в нашем примере нам не нужно его использовать, но мы все равно должны его объявить. После этого задания мы получим аргументы метода. Поскольку наш метод имеет только один аргумент - String «myString», у нас есть только «jstring» с тем же именем. Также обратите внимание, что наш возвращаемый тип также является jstring. Это связано с тем, что наш метод Java возвращает String, для получения дополнительной информации о типах Java / JNI прочтите его.
Последний шаг - преобразовать типы JNI в типы, которые мы используем на стороне CPP. В нашем примере мы преобразуем jstring
его в const char *
отправку, конвертируем в CPP, получаем результат и конвертируем обратно в jstring
. Как и все другие шаги по JNI, это не сложно; это всего лишь шаблон, вся работа выполняется JNIEnv*
аргументом, который мы получаем, когда вызываем GetStringUTFChars
и NewStringUTF
. После этого наш код готов к запуску на устройствах Android, давайте посмотрим.
Подход, описанный в превосходном ответе выше, может быть полностью автоматизирован с помощью Scapix Language Bridge, который генерирует код оболочки на лету непосредственно из заголовков C ++. Вот пример :
Определите свой класс на C ++:
#include <scapix/bridge/object.h>
class contact : public scapix::bridge::object<contact>
{
public:
std::string name();
void send_message(const std::string& msg, std::shared_ptr<contact> from);
void add_tags(const std::vector<std::string>& tags);
void add_friends(std::vector<std::shared_ptr<contact>> friends);
};
И назовите его из Swift:
class ViewController: UIViewController {
func send(friend: Contact) {
let c = Contact()
contact.sendMessage("Hello", friend)
contact.addTags(["a","b","c"])
contact.addFriends([friend])
}
}
И с Java:
class View {
private contact = new Contact;
public void send(Contact friend) {
contact.sendMessage("Hello", friend);
contact.addTags({"a","b","c"});
contact.addFriends({friend});
}
}