Як використовувати той самий код C ++ для Android та iOS?


119

Android з NDK підтримує код C / C ++, а iOS з Objective-C ++ теж підтримують, тож як я можу писати додатки з рідним кодом C / C ++, який спільно використовується Android та iOS?


1
спробуйте frame cocos2d-x
гло

@glo, здається, добре, але я шукаю більш загальну річ, використовуючи c ++ без рамок, "очевидно, виключений JNI".
ademar111190

Відповіді:


273

Оновлення.

Ця відповідь є досить популярною навіть через чотири роки після її написання, за ці чотири роки багато чого змінилося, тому я вирішив оновити свою відповідь, щоб краще відповідати нашій теперішній реальності. Ідея відповіді не змінюється; реалізація трохи змінилася. Моя англійська також змінилася, вона значно покращилася, тому відповідь зараз зрозуміліша всім.

Погляньте, будь ласка, на репо щоб ви могли завантажити та запустити код, який я покажу нижче.

Відповідь

Перш ніж я покажу код, будь ласка, знайдіть багато на наступній схемі.

Арк

Кожна ОС має свій інтерфейс і особливості, тому ми маємо намір написати конкретний код для кожної платформи в цьому плані. Іншими руками, всі логічний код, ділові правила та речі, якими можна поділитися, ми маємо намір записати за допомогою 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

Перш за все, ми збираємося створити спільний код 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;
}

Unix

Цікавим бонусом є те, що ми також можемо використовувати той самий код для 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

Погляньте, як виглядає додаток:

Xcode iPhone

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файлом, вбудованим у наш 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, давайте подивимось.

AndroidStudio Android


7
Чудове пояснення
RED.Skull

9
Я не розумію - але +1 за одну з найвисокоякісніших відповідей на SO
Michael Rodrigues

16
@ ademar111190 На сьогоднішній день найбільш корисна публікація. Це не повинно було закриватися.
Джаред Берроуз

6
@JaredBurrows, я згоден. Проголосували за повторне відкриття.
Всемогутній Емітет

3
@KVISH вам потрібно спочатку реалізувати обгортку в Objective-C, потім ви отримаєте швидкий доступ до обгортки Objective-C, додавши заголовок обгортки до вашого мостового файлу заголовка. Зараз немає жодного способу отримати прямий доступ до C ++ у Swift. Для отримання додаткової інформації див. Stackoverflow.com/a/24042893/1853977
Кріс

3

Підхід, описаний у чудовій відповіді вище, може бути повністю автоматизований мовою 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});
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.