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 know як 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 " це ", на нашому прикладі нам це не потрібно використовувати, але нам все-таки потрібно це заявити. Після цього завдання ми отримаємо аргументи методу. Оскільки в нашому методі є лише один аргумент - рядок "myString", у нас є лише "jstring" з тим же ім'ям. Також зауважте, що наш тип повернення - це також jstring. Це тому, що наш метод Java повертає String, для отримання додаткової інформації про типи Java / JNI прочитайте її.
Останнім кроком є перетворення типів JNI у типи, які ми використовуємо на стороні CPP. На нашому прикладі ми перетворили jstring
до const char *
відправлення конвертації в СРР, отримання результату і перетворення назад в jstring
. Як і всі інші кроки щодо JNI, це не важко; це лише котлованна, вся робота виконується JNIEnv*
аргументом, який ми отримуємо, коли ми викликаємо GetStringUTFChars
і NewStringUTF
. Після цього наш код готовий працювати на пристроях Android, давайте подивимось.
Підхід, описаний у чудовій відповіді вище, може бути повністю автоматизований мовою Scapix Language, який генерує код обгортки під час руху безпосередньо із заголовків 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});
}
}