Bagaimana cara menggunakan kode C ++ yang sama untuk Android dan iOS?

119

Android dengan NDK memiliki dukungan untuk kode C / C ++ dan iOS dengan Objective-C ++ juga memiliki dukungan, jadi bagaimana cara menulis aplikasi dengan kode C / C ++ asli yang dibagikan antara Android dan iOS?

ademar111190
sumber
1
coba cocos2d-x framework
glo
@glo sepertinya bagus, tapi saya mencari hal yang lebih umum, menggunakan c ++ tanpa kerangka kerja, "jelas mengecualikan JNI".
ademar111190

Jawaban:

273

Memperbarui.

Jawaban ini cukup populer bahkan empat tahun setelah saya menulisnya, dalam empat tahun ini banyak hal yang berubah, jadi saya memutuskan untuk memperbarui jawaban saya agar lebih sesuai dengan kenyataan kita saat ini. Ide jawaban tidak berubah; implementasinya sedikit berubah. Bahasa Inggris saya juga telah berubah, telah meningkat pesat, jadi jawabannya lebih dapat dimengerti oleh semua orang sekarang.

Silakan lihat di repo sehingga Anda dapat mengunduh dan menjalankan kode yang akan saya tunjukkan di bawah.

Jawabannya

Sebelum saya menunjukkan kodenya, harap banyak melihat diagram berikut.

Lengkungan

Setiap OS memiliki UI dan kekhasannya sendiri, jadi kami bermaksud untuk menulis kode khusus untuk setiap platform dalam hal ini. Di sisi lain, semua kode logika, aturan bisnis, dan hal-hal yang dapat dibagikan kami bermaksud untuk menulis menggunakan C ++, sehingga kami dapat mengkompilasi kode yang sama ke setiap platform.

Dalam diagram, Anda dapat melihat lapisan C ++ di tingkat paling bawah. Semua kode bersama ada di segmen ini. Level tertinggi adalah kode Obj-C / Java / Kotlin biasa, tidak ada berita di sini, bagian yang paling sulit adalah lapisan tengah.

Lapisan tengah ke sisi iOS sederhana; Anda hanya perlu mengonfigurasi proyek Anda untuk membangun menggunakan varian Obj-c yang dikenal sebagai Objective-C ++ dan itu saja, Anda memiliki akses ke kode C ++.

Hal menjadi lebih sulit di sisi Android, kedua bahasa, Java dan Kotlin, di Android, dijalankan di bawah Mesin Virtual Java. Jadi satu-satunya cara untuk mengakses kode C ++ menggunakan JNI , harap luangkan waktu untuk membaca dasar-dasar JNI. Untungnya, Android Studio IDE saat ini memiliki peningkatan besar di sisi JNI, dan banyak masalah ditampilkan kepada Anda saat Anda mengedit kode.

Kode demi langkah

Sampel kami adalah aplikasi sederhana yang Anda kirimi teks ke CPP, dan itu mengubah teks itu menjadi sesuatu yang lain dan mengembalikannya. Idenya adalah, iOS akan mengirim "Obj-C" dan Android akan mengirim "Java" dari bahasa masing-masing, dan kode CPP akan membuat teks sebagai berikut "cpp mengatakan halo ke << teks diterima >> ".

Kode CPP bersama

Pertama-tama, kita akan membuat kode CPP bersama, melakukannya kita memiliki file header sederhana dengan deklarasi metode yang menerima teks yang diinginkan:

#include <iostream>

const char *concatenateMyStringWithCppString(const char *myString);

Dan implementasi 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

Bonus yang menarik adalah, kita juga bisa menggunakan kode yang sama untuk Linux dan Mac serta sistem Unix lainnya. Kemungkinan ini sangat berguna karena kita dapat menguji kode bersama kita lebih cepat, jadi kita akan membuat Main.cpp sebagai berikut untuk mengeksekusinya dari mesin kita dan melihat apakah kode bersama berfungsi.

#include <iostream>
#include <string>
#include "../CPP/Core.h"

int main() {
  std::string textFromCppCore = concatenateMyStringWithCppString("Unix");
  std::cout << textFromCppCore << '\n';
  return 0;
}

Untuk membuat kode, Anda perlu menjalankan:

$ g++ Main.cpp Core.cpp -o main
$ ./main 
cpp says hello to Unix

iOS

Saatnya menerapkan di sisi seluler. Sejauh iOS memiliki integrasi sederhana, kami mulai dengannya. Aplikasi iOS kami adalah aplikasi Obj-c biasa dengan hanya satu perbedaan; file adalah .mmdan bukan .m. yaitu Ini adalah aplikasi Obj-C ++, bukan aplikasi Obj-C.

Untuk organisasi yang lebih baik, kami membuat CoreWrapper.mm sebagai berikut:

#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

Kelas ini memiliki tanggung jawab untuk mengubah jenis CPP dan panggilan ke jenis dan panggilan Obj-C. Ini tidak wajib setelah Anda dapat memanggil kode CPP pada file apa pun yang Anda inginkan di Obj-C, tetapi ini membantu untuk menjaga organisasi, dan di luar file pembungkus Anda, Anda mempertahankan kode gaya Obj-C lengkap, hanya file pembungkus menjadi gaya CPP .

Setelah pembungkus Anda terhubung ke kode CPP, Anda dapat menggunakannya sebagai kode Obj-C standar, misalnya 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

Lihat tampilan aplikasi:

Xcode iPhone

Android

Sekarang saatnya integrasi Android. Android menggunakan Gradle sebagai sistem build, dan untuk kode C / C ++ menggunakan CMake. Jadi, hal pertama yang perlu kita lakukan adalah mengkonfigurasi CMake pada file gradle:

android {
...
externalNativeBuild {
    cmake {
        path "CMakeLists.txt"
    }
}
...
defaultConfig {
    externalNativeBuild {
        cmake {
            cppFlags "-std=c++14"
        }
    }
...
}

Dan langkah kedua adalah menambahkan file 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}
)

File CMake adalah tempat Anda perlu menambahkan file CPP dan folder header yang akan Anda gunakan pada proyek, pada contoh kami, kami menambahkan CPPfolder dan file Core.h / .cpp. Untuk mengetahui lebih lanjut tentang konfigurasi C / C ++, silakan baca.

Sekarang kode inti adalah bagian dari aplikasi kita sekarang saatnya untuk membuat jembatan, untuk membuat semuanya lebih sederhana dan teratur kita membuat kelas khusus bernama CoreWrapper untuk menjadi pembungkus antara JVM dan CPP:

public class CoreWrapper {

    public native String concatenateMyStringWithCppString(String myString);

    static {
        System.loadLibrary("native-lib");
    }

}

Perhatikan bahwa kelas ini memiliki nativemetode dan memuat pustaka asli bernama native-lib. Library ini adalah yang kita buat, pada akhirnya, kode CPP akan menjadi objek bersama yang .sodisematkan File di APK kita, dan loadLibraryakan memuatnya. Terakhir, saat Anda memanggil metode native, JVM akan mendelegasikan panggilan tersebut ke library yang dimuat.

Sekarang bagian paling aneh dari integrasi Android adalah JNI; Kami membutuhkan file cpp sebagai berikut, dalam kasus kami "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;
}

}

Hal pertama yang akan Anda perhatikan adalah extern "C"bagian ini diperlukan agar JNI berfungsi dengan benar dengan kode CPP dan tautan metode kami. Anda juga akan melihat beberapa simbol yang digunakan JNI untuk bekerja dengan JVM sebagai JNIEXPORTdan JNICALL. Untuk Anda memahami arti dari hal-hal tersebut, perlu meluangkan waktu dan membacanya , untuk keperluan tutorial ini anggap saja hal-hal tersebut sebagai boilerplate.

Satu hal penting dan biasanya akar dari banyak masalah adalah nama metode; itu harus mengikuti pola "Java_package_class_method". Saat ini, Android studio memiliki dukungan yang sangat baik untuk itu sehingga dapat menghasilkan boilerplate ini secara otomatis dan menunjukkan kepada Anda kapan itu benar atau tidak dinamai. Pada contoh kami, metode kami bernama "Java_ademar_androidioscppexample_CoreWrapper_concatenateMyStringWithCppString" itu karena "ademar.androidioscppexample" adalah paket kami, jadi kami mengganti "." oleh "_", CoreWrapper adalah kelas tempat kita menghubungkan metode asli dan "concatenateMyStringWithCppString" adalah nama metode itu sendiri.

Karena kami memiliki metode yang dinyatakan dengan benar, inilah saatnya untuk menganalisis argumen, parameter pertama adalah penunjuknya JNIEnvadalah cara kami memiliki akses ke hal-hal JNI, sangat penting untuk kami membuat konversi seperti yang akan Anda lihat segera. Yang kedua adalah jobjectinstance dari objek yang Anda gunakan untuk memanggil metode ini. Anda dapat menganggapnya sebagai java " ini ", pada contoh kami, kami tidak perlu menggunakannya, tetapi kami masih perlu mendeklarasikannya. Setelah proyek ini, kita akan menerima argumen metode tersebut. Karena metode kami hanya memiliki satu argumen - String "myString", kami hanya memiliki "jstring" dengan nama yang sama. Juga perhatikan bahwa tipe kembalian kita juga jstring. Itu karena metode Java kami mengembalikan String, untuk informasi lebih lanjut tentang jenis Java / JNI silakan baca.

Langkah terakhir adalah mengonversi jenis JNI menjadi jenis yang kami gunakan di sisi CPP. Pada contoh kami, kami mengubah jstringmenjadi const char *pengiriman itu diubah menjadi CPP, mendapatkan hasilnya dan mengubahnya kembali ke jstring. Seperti semua langkah lainnya di JNI, ini tidak sulit; itu hanya boilerplated, semua pekerjaan dilakukan oleh JNIEnv*argumen yang kita terima ketika kita memanggil GetStringUTFCharsdan NewStringUTF. Setelah kode kita siap dijalankan di perangkat Android, mari kita lihat.

AndroidStudio Android

ademar111190
sumber
7
Penjelasan bagus
RED. Tengkorak
9
Saya tidak mengerti - tetapi +1 untuk salah satu jawaban dengan kualitas terbaik di SO
Michael Rodrigues
16
@ ademar111190 Sejauh ini posting yang paling berguna. Ini seharusnya tidak ditutup.
Jared Burrows
6
@JaredBurrows, saya setuju. Memilih untuk membuka kembali.
OmnipotentEntity
3
@KVISH Anda harus mengimplementasikan pembungkus di Objective-C terlebih dahulu, kemudian Anda akan mengakses pembungkus Objective-C dengan cepat dengan menambahkan header pembungkus ke file header penghubung Anda. Tidak ada cara untuk mengakses C ++ secara langsung di Swift seperti sekarang. Untuk informasi selengkapnya, lihat stackoverflow.com/a/24042893/1853977
Chris
3

Pendekatan yang dijelaskan dalam jawaban luar biasa di atas dapat sepenuhnya diotomatiskan oleh Scapix Language Bridge yang menghasilkan kode pembungkus dengan cepat langsung dari header C ++. Berikut ini contohnya :

Tentukan kelas Anda di 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);
};

Dan sebut saja dari Swift:

class ViewController: UIViewController {
    func send(friend: Contact) {
        let c = Contact()

        contact.sendMessage("Hello", friend)
        contact.addTags(["a","b","c"])
        contact.addFriends([friend])
    }
}

Dan dari Jawa:

class View {
    private contact = new Contact;

    public void send(Contact friend) {
        contact.sendMessage("Hello", friend);
        contact.addTags({"a","b","c"});
        contact.addFriends({friend});
    }
}
Boris Rasin
sumber