Android z NDK obsługuje również kod C / C ++, a iOS z Objective-C ++ również obsługuje, więc jak mogę pisać aplikacje z natywnym kodem C / C ++ współdzielonym między systemami Android i iOS?
java
c++
java-native-interface
cross-platform
objective-c++
ademar111190
źródło
źródło
Odpowiedzi:
Aktualizacja.
Ta odpowiedź jest dość popularna nawet cztery lata po jej napisaniu, w ciągu tych czterech lat wiele się zmieniło, więc postanowiłem zaktualizować swoją odpowiedź, aby lepiej pasowała do naszej obecnej rzeczywistości. Pomysł odpowiedzi się nie zmienia; realizacja trochę się zmieniła. Mój angielski też się zmienił, bardzo się poprawił, więc odpowiedź jest teraz bardziej zrozumiała dla wszystkich.
Spójrz na repozytorium , abyś mógł pobrać i uruchomić kod, który pokażę poniżej.
Odpowiedź
Zanim pokażę kod, przeanalizuj poniższy diagram.
Każdy system operacyjny ma swój interfejs użytkownika i cechy szczególne, dlatego zamierzamy napisać określony kod do każdej platformy w tym zakresie. Z drugiej strony, cały kod logiczny, reguły biznesowe i rzeczy, które można udostępniać, zamierzamy napisać w C ++, abyśmy mogli skompilować ten sam kod na każdej platformie.
Na diagramie możesz zobaczyć warstwę C ++ na najniższym poziomie. Cały wspólny kod znajduje się w tym segmencie. Najwyższy poziom to zwykły kod Obj-C / Java / Kotlin, nie ma tu żadnych nowości, najtrudniejsza jest warstwa środkowa.
Środkowa warstwa po stronie iOS jest prosta; musisz tylko skonfigurować swój projekt do kompilacji przy użyciu wariantu Obj-c znanego jako Objective-C ++ i to wszystko, masz dostęp do kodu C ++.
Sytuacja stała się trudniejsza po stronie Androida, oba języki, Java i Kotlin, na Androida działają pod wirtualną maszyną Java. Tak więc jedynym sposobem uzyskania dostępu do kodu C ++ jest użycie JNI , poświęć trochę czasu na przeczytanie podstaw JNI. Na szczęście dzisiejsze IDE Android Studio ma ogromne ulepszenia po stronie JNI, a podczas edycji kodu wyświetlanych jest wiele problemów.
Kod według kroków
Nasza próbka to prosta aplikacja, w której wysyłasz tekst do CPP, a ona konwertuje ten tekst na coś innego i zwraca go. Pomysł jest taki, że iOS wyśle „Obj-C”, a Android wyśle „Java” z odpowiednich języków, a kod CPP utworzy następujący tekst: „cpp mówi cześć << otrzymano tekst >> ”.
Wspólny kod CPP
Przede wszystkim utworzymy udostępniony kod CPP, robiąc to mamy prosty plik nagłówkowy z deklaracją metody, która otrzymuje żądany tekst:
Oraz wdrożenie CPP:
Unix
Ciekawym bonusem jest to, że możemy użyć tego samego kodu dla Linuksa i Maca, a także innych systemów Unix. Ta możliwość jest szczególnie przydatna, ponieważ możemy szybciej przetestować udostępniony kod, więc utworzymy plik Main.cpp w następujący sposób, aby wykonać go z naszej maszyny i sprawdzić, czy współdzielony kod działa.
Aby zbudować kod, musisz wykonać:
iOS
Czas na wdrożenie po stronie mobilnej. O ile iOS ma prostą integrację, zaczynamy od niego. Nasza aplikacja na iOS jest typową aplikacją Obj-c z tylko jedną różnicą; pliki są
.mm
i nie.m
. tj. jest to aplikacja Obj-C ++, a nie aplikacja Obj-C.Dla lepszej organizacji tworzymy plik CoreWrapper.mm w następujący sposób:
Ta klasa jest odpowiedzialna za konwersję typów CPP i wywołań na typy i wywołania Obj-C. Nie jest to obowiązkowe, gdy możesz wywołać kod CPP w dowolnym pliku w Obj-C, ale pomaga to zachować organizację, a poza plikami opakowującymi utrzymujesz kompletny kod w stylu Obj-C, tylko plik opakowania otrzymuje styl CPP .
Po podłączeniu opakowania do kodu CPP możesz użyć go jako standardowego kodu Obj-C, np. ViewController ”
Zobacz, jak wygląda aplikacja:
Android
Teraz przyszedł czas na integrację z Androidem. Android używa Gradle jako systemu kompilacji, a do kodu C / C ++ używa CMake. Więc pierwszą rzeczą, którą musimy zrobić, to skonfigurować CMake w pliku gradle:
Drugim krokiem jest dodanie pliku CMakeLists.txt:
Plik CMake to miejsce, w którym musisz dodać pliki CPP i foldery nagłówkowe, których będziesz używać w projekcie, w naszym przykładzie dodajemy
CPP
folder i pliki Core.h / .cpp. Aby dowiedzieć się więcej o konfiguracji C / C ++, przeczytaj to.Teraz główny kod jest częścią naszej aplikacji, czas stworzyć mostek, aby uprościć i uporządkować rzeczy, tworzymy specjalną klasę o nazwie CoreWrapper, która będzie naszym opakowaniem między JVM i CPP:
Zwróć uwagę, że ta klasa ma
native
metodę i ładuje bibliotekę natywną o nazwienative-lib
. Ta biblioteka jest tą, którą tworzymy, w końcu kod CPP stanie się współdzielonym.so
plikiem obiektu osadzonym w naszym pliku APK iloadLibrary
załaduje go. Na koniec, po wywołaniu metody natywnej, JVM przekaże wywołanie do załadowanej biblioteki.Teraz najbardziej dziwną częścią integracji Androida jest JNI; Potrzebujemy następującego pliku cpp, w naszym przypadku „native-lib.cpp”:
Pierwszą rzeczą, którą zauważysz, jest to, że
extern "C"
ta część jest niezbędna do poprawnej pracy JNI z naszym kodem CPP i powiązaniami metod. Zobaczysz także symbole używane przez JNI do pracy z JVM jakoJNIEXPORT
iJNICALL
. Abyś zrozumiał znaczenie tych rzeczy, musisz poświęcić trochę czasu i przeczytać to , dla celów tego samouczka potraktuj je jako szablon.Jedną istotną rzeczą i zwykle źródłem wielu problemów jest nazwa metody; musi być zgodny ze wzorcem „Java_package_class_method”. Obecnie studio Android ma dla niego doskonałe wsparcie, więc może automatycznie wygenerować ten szablon i pokazać ci, kiedy jest poprawny lub nie został nazwany. W naszym przykładzie nasza metoda nosi nazwę „Java_ademar_androidioscppexample_CoreWrapper_concatenateMyStringWithCppString”, ponieważ „ademar.androidioscppexample” to nasz pakiet, więc zastępujemy „.” przez „_”, CoreWrapper jest klasą, w której łączymy metodę natywną, a „concatenateMyStringWithCppString” jest samą nazwą metody.
Ponieważ mamy poprawnie zadeklarowaną metodę, czas na analizę argumentów, pierwszy parametr jest wskaźnikiem
JNIEnv
, to sposób, w jaki mamy dostęp do rzeczy JNI, kluczowe jest, abyśmy dokonali naszych konwersji, jak wkrótce się przekonasz. Drugi tojobject
instancja obiektu, którego użyłeś do wywołania tej metody. Możesz pomyśleć, że to java " this ", na naszym przykładzie nie musimy go używać, ale nadal musimy to zadeklarować. Po tym zadaniu otrzymamy argumenty metody. Ponieważ nasza metoda ma tylko jeden argument - String „myString”, mamy tylko „jstring” o tej samej nazwie. Zauważ też, że nasz typ zwracany jest również jstringiem. Dzieje się tak, ponieważ nasza metoda Java zwraca ciąg znaków. Aby uzyskać więcej informacji na temat typów Java / JNI, przeczytaj ją.Ostatnim krokiem jest konwersja typów JNI do typów, których używamy po stronie CPP. W naszym przykładzie przekształcamy
jstring
go wconst char *
wysyłanie przekonwertowanego na CPP, uzyskujemy wynik i konwertujemy z powrotem najstring
. Jak wszystkie inne kroki na JNI, nie jest to trudne; jest to tylko kotłowe, cała praca jest wykonywana przezJNIEnv*
argument, który otrzymujemy, gdy wywołujemyGetStringUTFChars
iNewStringUTF
. Po tym nasz kod jest gotowy do uruchomienia na urządzeniach z Androidem, spójrzmy.źródło
Podejście opisane w doskonałej odpowiedzi powyżej może być całkowicie zautomatyzowane przez Scapix Language Bridge, który generuje kod opakowujący w locie bezpośrednio z nagłówków C ++. Oto przykład :
Zdefiniuj swoją klasę w C ++:
I zadzwoń od Swift:
A z Java:
źródło