Android ve iOS için aynı C ++ kodu nasıl kullanılır?


119

NDK ile Android, C / C ++ kodunu desteklerken, Objective-C ++ ile iOS de desteğe sahiptir, bu nedenle Android ve iOS arasında paylaşılan yerel C / C ++ koduyla uygulamaları nasıl yazabilirim?


1
cocos2d-x çerçevesini deneyin
glo

@glo iyi görünüyor, ancak daha genel bir şey arıyorum, çerçevesiz c ++ kullanarak "açıkça JNI hariç".
ademar111190

Yanıtlar:


274

Güncelleme.

Bu cevap, yazdıktan dört yıl sonra bile oldukça popüler, bu dört yılda birçok şey değişti, bu yüzden cevabımı mevcut gerçekliğimize daha iyi uyacak şekilde güncellemeye karar verdim. Cevap fikri değişmez; uygulama biraz değişti. İngilizcem de değişti, çok gelişti, bu yüzden cevap artık herkes için daha anlaşılır.

Lütfen depoya bir göz atın , böylece aşağıda göstereceğim kodu indirip çalıştırabilirsiniz.

Cevap

Kodu göstermeden önce, lütfen aşağıdaki diyagramdan çok şey alın.

kemer

Her işletim sisteminin kendi kullanıcı arayüzü ve özellikleri vardır, bu nedenle bu bağlamda her platforma belirli bir kod yazmayı planlıyoruz. Diğer taraftan, tüm mantık kodu, iş kuralları ve paylaşılabilecek şeyler C ++ kullanarak yazmayı amaçlıyoruz, böylece her platform için aynı kodu derleyebiliriz.

Diyagramda, en düşük seviyede C ++ katmanını görebilirsiniz. Tüm paylaşılan kodlar bu segmentte. En yüksek seviye normal Obj-C / Java / Kotlin kodudur, burada haber yok, zor kısım orta katmandır.

İOS tarafının orta katmanı basittir; Projenizi yalnızca Objective-C ++ olarak bilinen bir Obj-c varyantını kullanarak inşa etmek için yapılandırmanız gerekir ve hepsi bu, C ++ koduna erişiminiz vardır.

Android tarafında her iki dilde, Java ve Kotlin, Android'de Java Sanal Makinesi altında çalışmak daha da zorlaştı. Dolayısıyla, C ++ koduna erişmenin tek yolu JNI kullanmaktır , lütfen JNI'nin temellerini okumak için zaman ayırın. Neyse ki, bugünün Android Studio IDE'sinin JNI tarafında büyük iyileştirmeleri var ve kodunuzu düzenlerken size birçok sorun gösteriliyor.

Adımlarla kod

Örneğimiz, CPP'ye bir metin gönderdiğiniz basit bir uygulamadır ve bu metni başka bir şeye dönüştürür ve döndürür. Fikir şu ki, iOS "Obj-C" gönderecek ve Android kendi dillerinden "Java" gönderecek ve CPP kodu aşağıdaki gibi bir metin oluşturacak "cpp, << metne merhaba >> " diyor .

Paylaşılan CPP kodu

Her şeyden önce, paylaşılan CPP kodunu oluşturacağız, bunu yaparak, istenen metni alan yöntem bildirimi ile basit bir başlık dosyamız var:

#include <iostream>

const char *concatenateMyStringWithCppString(const char *myString);

Ve CPP uygulaması:

#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

İlginç bir bonus, aynı kodu Linux ve Mac ve diğer Unix sistemleri için de kullanabiliriz. Bu olasılık özellikle yararlıdır çünkü paylaşılan kodumuzu daha hızlı test edebiliriz, bu nedenle makinemizden çalıştırmak için aşağıdaki gibi bir Main.cpp oluşturacağız ve paylaşılan kodun çalışıp çalışmadığını göreceğiz.

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

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

Kodu oluşturmak için şunları yapmanız gerekir:

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

iOS

Mobil tarafta uygulama zamanı. İOS'un basit bir entegrasyonu olduğu için, onunla başlıyoruz. İOS uygulamamız, yalnızca bir farkla tipik bir Obj-c uygulamasıdır; dosyalar .mmve değil .m. Yani bir Obj-C ++ uygulamasıdır, Obj-C uygulaması değildir.

Daha iyi bir organizasyon için CoreWrapper.mm'yi aşağıdaki gibi oluşturuyoruz:

#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

Bu sınıf, CPP türlerini ve çağrıları Obj-C türlerine ve çağrılarına dönüştürme sorumluluğuna sahiptir. Obj-C'de istediğiniz herhangi bir dosyada CPP kodunu çağırabildiğinizde zorunlu değildir, ancak organizasyonu korumaya yardımcı olur ve sarmalayıcı dosyalarınızın dışında tam bir Obj-C tarzı kod tutarsınız, yalnızca sarmalayıcılar dosyası CPP stili olur .

Sarıcınız CPP koduna bağlandıktan sonra, bunu standart bir Obj-C kodu olarak kullanabilirsiniz, örneğin 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

Uygulamanın nasıl göründüğüne bir göz atın:

Xcode iPhone

Android

Şimdi sıra Android entegrasyonuna geldi. Android, derleme sistemi olarak Gradle'ı kullanır ve C / C ++ kodu için CMake kullanır. Bu yüzden yapmamız gereken ilk şey CMake'i gradle dosyasında yapılandırmak:

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

İkinci adım ise CMakeLists.txt dosyasını eklemektir:

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 dosyası, projede kullanacağınız CPP dosyalarını ve başlık klasörlerini eklemeniz CPPgereken yerdir, örneğimizde klasörü ve Core.h / .cpp dosyalarını ekliyoruz. C / C ++ yapılandırması hakkında daha fazla bilgi edinmek için lütfen okuyun.

Artık temel kod uygulamamızın bir parçası, köprüyü oluşturma zamanı, işleri daha basit ve organize hale getirmek için JVM ve CPP arasında sarmalayıcımız olacak CoreWrapper adında belirli bir sınıf oluşturuyoruz:

public class CoreWrapper {

    public native String concatenateMyStringWithCppString(String myString);

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

}

Bu sınıfın bir nativeyöntemi olduğunu ve adlı yerel bir kitaplık yüklediğini unutmayın native-lib. Bu kütüphane oluşturduğumuz kütüphanedir, sonunda CPP kodu APK'mıza .soyerleştirilen paylaşılan bir nesne dosyası haline gelecek ve loadLibraryyükleyecektir. Son olarak, yerel yöntemi çağırdığınızda, JVM çağrıyı yüklenen kitaplığa devredecektir.

Şimdi Android entegrasyonunun en garip kısmı JNI; Aşağıdaki gibi bir cpp dosyasına ihtiyacımız var, bizim durumumuzda "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;
}

}

Fark edeceğiniz ilk şey extern "C", JNI'nin CPP kodumuz ve yöntem bağlantılarımızla doğru çalışması için bu bölümün gerekli olduğudur. JNI'nin JVM ile çalışmak için kullandığı bazı sembolleri JNIEXPORTve olarak da göreceksiniz JNICALL. Bu şeylerin anlamını anlamak için, biraz zaman ayırıp okumak gerekir , bu eğitim amaçları için bunları sadece şablon olarak düşünün.

Önemli bir şey ve genellikle pek çok sorunun kökü, yöntemin adıdır; "Java_package_class_method" modelini izlemesi gerekiyor. Şu anda, Android stüdyosu bunun için mükemmel bir desteğe sahiptir, bu nedenle bu ortak metini otomatik olarak oluşturabilir ve doğru olup olmadığını size gösterebilir. Örneğimizde yöntemimiz "Java_ademar_androidioscppexample_CoreWrapper_concatenateMyStringWithCppString" olarak adlandırılmıştır, çünkü "ademar.androidioscppexample" bizim paketimizdir, bu yüzden "." "_" ile CoreWrapper, yerel yöntemi bağladığımız sınıftır ve "concatenateMyStringWithCppString", yöntemin adının kendisidir.

Yöntemi doğru bir şekilde ilan ettiğimiz için, argümanları analiz etmenin zamanı geldi, ilk parametre bunun bir göstergesidir, JNIEnvJNI öğelerine erişim şeklimizdir, yakında göreceğiniz gibi dönüşümlerimizi yapmamız çok önemlidir. İkincisi, jobjectbu yöntemi çağırmak için kullandığınız nesnenin örneğidir. Bunu java " bu " olarak düşünebilirsiniz , örneğimizde onu kullanmamıza gerek yok ama yine de beyan etmemiz gerekiyor. Bu iş nesnesinden sonra yöntemin argümanlarını alacağız. Metodumuzun sadece bir argümanı olduğundan - "myString" String, aynı ada sahip sadece "jstring" e sahibiz. Ayrıca dönüş tipimizin de bir jstring olduğuna dikkat edin. Java yöntemimiz bir String döndürdüğü için, Java / JNI türleri hakkında daha fazla bilgi için lütfen okuyun.

Son adım, JNI türlerini CPP tarafında kullandığımız türlere dönüştürmektir. Örneğimizde, CPP'ye dönüştürülmüş jstringbir const char *göndermeye dönüştürüyoruz, sonucu alıyoruz ve geri dönüştürüyoruz jstring. JNI'deki diğer tüm adımlar gibi zor değil; sadece kaynatılır, tüm iş ve JNIEnv*dediğimizde aldığımız argüman tarafından yapılır . Kodumuz Android cihazlarda çalışmaya hazır olduktan sonra, bir göz atalım.GetStringUTFCharsNewStringUTF

AndroidStudio Android


7
Harika açıklama
RED.Skull

9
Anlayamıyorum - ancak SO'daki en kaliteli cevaplardan biri için +1
Michael Rodrigues

16
@ ademar111190 Şimdiye kadarki en yararlı gönderi. Bu kapatılmamalıydı.
Jared Burrows

6
@JaredBurrows, katılıyorum. Yeniden açılması için oy verildi.
OmnipotentEntity

3
@KVISH sarmalayıcıyı önce Objective-C'de uygulamanız gerekir, ardından sarmalayıcı başlığını köprü başlık dosyanıza ekleyerek hızla Objective-C sarmalayıcısına erişirsiniz. Şu an itibariyle Swift'de C ++ 'ya doğrudan erişmenin bir yolu yok. Daha fazla bilgi için stackoverflow.com/a/24042893/1853977
Chris

3

Yukarıdaki mükemmel yanıtta açıklanan yaklaşım, doğrudan C ++ başlıklarından anında sarmalayıcı kodu üreten Scapix Language Bridge tarafından tamamen otomatikleştirilebilir . İşte bir örnek :

Sınıfınızı C ++ 'da tanımlayın:

#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);
};

Ve Swift'den ara:

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

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

Ve Java'dan:

class View {
    private contact = new Contact;

    public void send(Contact friend) {
        contact.sendMessage("Hello", friend);
        contact.addTags({"a","b","c"});
        contact.addFriends({friend});
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.