Android с NDK поддерживает код C/С++ и iOS с Objective-C ++ поддерживает также, так как я могу писать приложения с собственным кодом на C/С++, разделяемым между Android и iOS?
Как использовать тот же код на С++ для Android и iOS?
Ответ 1
Обновить.
Этот ответ довольно популярен даже спустя четыре года после того, как я его написал, за эти четыре года многое изменилось, поэтому я решил обновить свой ответ, чтобы он лучше соответствовал нашей нынешней реальности. Идея ответа не меняется; реализация немного изменилась. Мой английский также изменился, он значительно улучшился, поэтому ответ теперь понятен всем.
Пожалуйста, посмотрите репозиторий, чтобы вы могли скачать и запустить код, который я покажу ниже.
Ответ
Прежде чем я покажу код, пожалуйста, обратите внимание на следующую диаграмму.
Каждая ОС имеет свой пользовательский интерфейс и особенности, поэтому мы намерены написать конкретный код для каждой платформы в этом отношении. В других руках весь логический код, бизнес-правила и вещи, которыми можно поделиться, мы намереваемся написать, используя 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
Прежде всего, мы собираемся создать общий код 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 имеет простую интеграцию, мы начинаем с нее. Наше приложение для 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. 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
Файл будет loadLibrary
в наш 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", в нашем примере нам не нужно его использовать, но мы все равно должны объявить его. После этого задания мы собираемся получить аргументы метода. Поскольку наш метод имеет только один аргумент - строку "myString", у нас есть только "jstring" с тем же именем. Также обратите внимание, что наш тип возврата также является jstring. Это потому, что наш метод Java возвращает строку, для получения дополнительной информации о типах Java/JNI, пожалуйста, прочитайте его.
Последний шаг - преобразование типов JNI в типы, которые мы используем на стороне CPP. В нашем примере мы преобразуем jstring
в const char *
отправляя его в CPP, получая результат и возвращая обратно в jstring
. Как и все другие шаги на JNI, это не сложно; это только шаблон, вся работа выполняется аргументом JNIEnv*
мы получаем, когда мы вызываем GetStringUTFChars
и NewStringUTF
. После этого наш код готов к запуску на устройствах Android, давайте посмотрим.
Ответ 2
Подход, описанный в превосходном ответе выше, может быть полностью автоматизирован 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);
};
И позвони из Свифта:
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});
}
}