Bereaksi Integrasi Asli dan C ++ untuk iOS dan Android

Baru-baru ini, saya ditawari untuk mengerjakan satu proyek yang menarik. Itu diperlukan untuk mengembangkan aplikasi mobile untuk startup Amerika di iOS dan Android menggunakan React Native. Fitur teknis utama dan faktor yang secara unik memutuskan partisipasi saya dalam proyek ini adalah tugas mengintegrasikan perpustakaan yang ditulis dalam C ++. Bagi saya, ini bisa menjadi pengalaman baru dan ujian profesional baru.

Mengapa perlu mengintegrasikan perpustakaan C ++


Aplikasi ini diperlukan untuk otentikasi dua faktor menggunakan protokol FIDO UAF dan U2F, menggunakan data biometrik, seperti ID Wajah dan ID Sentuh, dan teknologi serupa untuk platform Android. Klien untuk otentikasi sudah siap. Itu adalah perpustakaan yang ditulis dalam C ++ dan digunakan oleh beberapa klien lain sebagai tambahan untuk aplikasi seluler. Jadi saya diminta untuk mengintegrasikannya dengan cara yang mirip ke dalam aplikasi seluler di React Native.

Bagaimana saya melakukannya


Ada pendekatan untuk mengintegrasikan C ++ ke dalam aplikasi React Native Facebook. Namun, masalahnya adalah ini hanya berfungsi untuk platform iOS, dan tidak jelas apa yang harus dilakukan dengan Android dalam kasus ini. Saya ingin menyelesaikan masalah untuk dua platform sekaligus.

Garpu dari alat Dropbox Djinni yang memungkinkan Anda untuk membuat deklarasi tipe cross-platform Pada dasarnya, ini adalah aplikasi seluler React Native sederhana dengan koneksi khusus ke Djinni. Saya menganggapnya sebagai dasar.

Untuk kenyamanan, kode aplikasi dibagi menjadi dua repositori git. Yang pertama menyimpan kode sumber aplikasi Bereaksi Asli, dan yang kedua berisi Djinni dan dependensi yang diperlukan.

Langkah selanjutnya


Pertama, Anda perlu mendeklarasikan antarmuka untuk interaksi C ++ dan React Native code. Di Djinni, ini dilakukan menggunakan file .idl. Buka file react-native-cpp-support / idl / main.Djinni dalam proyek dan berkenalan dengan strukturnya.

Untuk kenyamanan kami, beberapa tipe data JavaScript dan binding untuk mereka telah dideklarasikan dalam proyek. Dengan demikian, kita dapat bekerja dengan tipe String, Array, Map, Promise dan lainnya tanpa deskripsi tambahan.

Dalam contoh ini, file ini terlihat seperti ini:

DemoModule = interface +r { const EVENT_NAME: string = "DEMO_MODULE_EVENT"; const STRING_CONSTANT: string = "STRING"; const INT_CONSTANT: i32 = 13; const DOUBLE_CONSTANT: f64 = 13.123; const BOOL_CONSTANT: bool = false; testPromise(promise: JavascriptPromise); testCallback(callback: JavascriptCallback); testMap(map: JavascriptMap, promise: JavascriptPromise); testArray(array: JavascriptArray, callback: JavascriptCallback); testBool(value: bool, promise: JavascriptPromise); testPrimitives(i: i32, d: f64, callback: JavascriptCallback); testString(value: string, promise: JavascriptPromise); testEventWithArray(value: JavascriptArray); testEventWithMap(value: JavascriptMap); } 

Setelah membuat perubahan pada file antarmuka, Anda harus membuat ulang antarmuka Java / Objective-C / C ++. Ini mudah dilakukan dengan menjalankan skrip generate_wrappers.sh dari folder react-native-cpp-support / idl / . Script ini akan mengumpulkan semua iklan dari file idl kami dan membuat antarmuka yang sesuai untuk mereka, sangat nyaman.

Dalam contoh ini, ada dua file C ++ yang menarik bagi kami. Yang pertama berisi deskripsi, dan implementasi kedua metode C ++ sederhana:

react-asli-cpp / cpp / DemoModuleImpl.hpp
react-asli-cpp / cpp / DemoModuleImpl.cpp

Pertimbangkan kode salah satu metode sebagai contoh:

 void DemoModuleImpl::testString(const std::string &value, const std::shared_ptr<::JavascriptPromise> &promise) { promise->resolveObject(JavascriptObject::fromString("Success!")); } 

Harap perhatikan bahwa hasilnya dikembalikan tidak menggunakan kata kunci kembali , tetapi menggunakan objek JavaScriptPromise yang dilewati oleh parameter terakhir, seperti yang dijelaskan dalam file idl .

Sekarang menjadi jelas bagaimana menggambarkan kode yang diperlukan dalam C ++. Tetapi bagaimana cara berinteraksi dengan ini dalam aplikasi React Native? Untuk memahami, cukup buka file dari folder react-native-cpp / index.js , di mana semua fungsi yang dijelaskan dalam contoh dipanggil.

Fungsi dari contoh kami disebut dalam JavaScript sebagai berikut:

 import { NativeAppEventEmitter, NativeModules... } from 'react-native'; const DemoModule = NativeModules.DemoModule; .... async promiseTest() { this.appendLine("testPromise: " + await DemoModule.testPromise()); this.appendLine("testMap: " + JSON.stringify(await DemoModule.testMap({a: DemoModule.INT_CONSTANT, b: 2}))); this.appendLine("testBool: " + await DemoModule.testBool(DemoModule.BOOL_CONSTANT)); // our sample function this.appendLine("testString: " + await DemoModule.testString(DemoModule.STRING_CONSTANT)); } 

Sekarang sudah jelas bagaimana fungsi pengujian bekerja di sisi C ++ dan JavaScript. Demikian pula, Anda dapat menambahkan kode fungsi lainnya. Lebih lanjut saya akan mempertimbangkan bagaimana proyek Android dan iOS bekerja sama dengan C ++.

Bereaksi Asli dan C ++ untuk Android


Untuk interaksi Android dan C ++, Anda harus menginstal NDK. Petunjuk terperinci tentang cara melakukan ini tersedia di developer.android.com/ndk/guides
Kemudian, di dalam file react-native-cpp / android / app / build.gradle, Anda perlu menambahkan pengaturan berikut:

 android { ... defaultConfig { ... ndk { abiFilters "armeabi-v7a", "x86" } externalNativeBuild { cmake { cppFlags "-std=c++14 -frtti -fexceptions" arguments "-DANDROID_TOOLCHAIN=clang", "-DANDROID_STL=c++_static" } } } externalNativeBuild { cmake { path "CMakeLists.txt" } } sourceSets { main { java.srcDirs 'src/main/java', '../../../react-native-cpp-support/support-lib/java' } } splits { abi { reset() enable enableSeparateBuildPerCPUArchitecture universalApk false // If true, also generate a universal APK include "armeabi-v7a", "x86" } } ... } 

Kami baru saja mengkonfigurasi gradle untuk membangun aplikasi untuk arsitektur yang digunakan dan menambahkan flag build yang diperlukan untuk cmake, menentukan file CMAkeLists , yang akan kami jelaskan di masa depan, dan juga menambahkan kelas java dari Djinni yang akan kami gunakan.
Langkah selanjutnya dalam menyiapkan proyek Android adalah deskripsi file CMakeLists.txt . Dalam bentuk yang sudah jadi, ini dapat dilihat di sepanjang jalur react-native-cpp / android / app / CMakeLists.txt .

 cmake_minimum_required(VERSION 3.4.1) set( PROJECT_ROOT "${CMAKE_SOURCE_DIR}/../.." ) set( SUPPORT_LIB_ROOT "${PROJECT_ROOT}/../react-native-cpp-support/support-lib" ) file( GLOB JNI_CODE "src/main/cpp/*.cpp" "src/main/cpp/gen/*.cpp" ) file( GLOB PROJECT_CODE "${PROJECT_ROOT}/cpp/*.cpp" "${PROJECT_ROOT}/cpp/gen/*.cpp" ) file( GLOB PROJECT_HEADERS "${PROJECT_ROOT}/cpp/*.hpp" "${PROJECT_ROOT}/cpp/gen/*.hpp" ) file( GLOB DJINNI_CODE "${SUPPORT_LIB_ROOT}/cpp/*.cpp" "${SUPPORT_LIB_ROOT}/jni/*.cpp" ) file( GLOB DJINNI_HEADERS "${SUPPORT_LIB_ROOT}/cpp/*.hpp" "${SUPPORT_LIB_ROOT}/jni/*.hpp" ) include_directories( "${SUPPORT_LIB_ROOT}/cpp" "${SUPPORT_LIB_ROOT}/jni" "${PROJECT_ROOT}/cpp" "${PROJECT_ROOT}/cpp/gen" ) add_library( # Sets the name of the library. native-lib # Sets the library as a shared library. SHARED ${JNI_CODE} ${DJINNI_CODE} ${DJINNI_HEADERS} ${PROJECT_CODE} ${PROJECT_HEADERS} ) 

Di sini kami menunjukkan jalur relatif ke perpustakaan dukungan, menambahkan direktori dengan kode C ++ dan JNI yang diperlukan.

Langkah penting lainnya adalah menambahkan DjinniModulesPackage ke proyek kami. Untuk melakukan ini, dalam file react-native-cpp / android / app / src / main / java / com / rncpp / jni / DjinniModulesPackage.java kita tentukan:

 ... import com.rncpp.jni.DjinniModulesPackage; ... public class MainApplication extends Application implements ReactApplication { ... @Override protected List<ReactPackage> getPackages() { return Arrays.<ReactPackage>asList( new MainReactPackage(), new DjinniModulesPackage() ); } ... } 

Detail penting terakhir adalah deskripsi kelas DjinniModulesPackage yang baru saja kita gunakan di kelas utama aplikasi kita. Itu terletak di jalan reaksi-asli-cpp / android / app / src / main / java / com / rncpp / jni / DjinniModulesPackage.java dan berisi kode berikut:

 package com.rncpp.jni; import com.facebook.react.ReactPackage; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.uimanager.ViewManager; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class DjinniModulesPackage implements ReactPackage { static { System.loadLibrary("native-lib"); } @Override public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { return Collections.emptyList(); } @Override public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) { List<NativeModule> modules = new ArrayList<>(); modules.add(new DemoModule(reactContext)); return modules; } } 

Yang paling menarik di kelas di atas adalah baris System.loadLibrary ("native-lib"); Berkat itu kami memuat pustaka dengan kode asli kami dan kode Djinni ke dalam aplikasi Android.

Untuk memahami bagaimana ini bekerja, saya menyarankan Anda untuk membiasakan diri dengan kode jni dari folder , yang merupakan pembungkus jni untuk bekerja dengan fungsionalitas modul kami, dan antarmuka dijelaskan dalam file idl.

Akibatnya, jika lingkungan pengembangan Android dan React Native dikonfigurasikan, Anda dapat membangun dan menjalankan proyek React Native di Android. Untuk melakukan ini, jalankan dua perintah di terminal:

npm instal
NPM menjalankan Android

Hore! Proyek kami berhasil!

Dan kita melihat gambar berikut di layar emulator Android (dapat diklik):



Sekarang mari kita lihat bagaimana iOS dan React Native bekerja dengan C ++.

Bereaksi Asli dan C ++ untuk iOS


Buka proyek reaksi-asli-cpp di Xcode.

Pertama, tambahkan tautan ke kode yang digunakan dalam proyek Objective-C dan C ++ dari perpustakaan dukungan. Untuk melakukan ini, transfer isi folder react-native-cpp-support / support-lib / objc / dan react-native-cpp-support / support-lib / cpp / ke proyek Xcode . Akibatnya, folder dengan pustaka dukungan kode akan ditampilkan di struktur pohon proyek (gambar dapat diklik):





Jadi, kami menambahkan deskripsi tipe JavaScript dari perpustakaan dukungan ke proyek.

Langkah selanjutnya adalah menambahkan pembungkus objektif-c yang dihasilkan untuk modul uji C ++ kami. Kita perlu mentransfer kode dari folder react-native-cpp / ios / rncpp / Generated / ke proyek.

Masih menambahkan kode C ++ dari modul kami, untuk itu kami mentransfer kode dari folder react-native-cpp / cpp / dan react-native-cpp / cpp / gen / ke proyek.

Akibatnya, struktur pohon proyek akan terlihat sebagai berikut (gambar dapat diklik):



Anda perlu memastikan bahwa file yang ditambahkan muncul di daftar Sumber Kompilasi di dalam tab Bangun Fase.



(gambar dapat diklik)

Langkah terakhir adalah mengubah kode file AppDelegate.m untuk mulai menginisialisasi modul Djinni ketika aplikasi dimulai. Dan untuk ini, Anda perlu mengubah baris kode berikut:

 ... #import "RCDjinniModulesInitializer.h" ... @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ... id<RCTBridgeDelegate> moduleInitialiser = [[RCDjinniModulesInitializer alloc] initWithURL:jsCodeLocation]; RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:moduleInitialiser launchOptions:nil]; RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"rncpp" initialProperties: nil]; ... } 

Sekarang luncurkan aplikasi kami di iOS. (gambar dapat diklik)




Aplikasi sedang bekerja!

Menambahkan pustaka C ++ ke proyek kami.


Sebagai contoh, kami menggunakan pustaka OpenSSL yang populer.

Dan mari kita mulai dengan Android.

Kami mengkloning repositori dengan pustaka OpenSSL yang sudah dibangun untuk Android.

Sertakan pustaka OpenSSL dalam file CMakeLists.txt:

 .... SET(OPENSSL_ROOT_DIR /Users/andreysaleba/projects/OpenSSL-for-Android-Prebuilt/openssl-1.0.2) SET(OPENSSL_LIBRARIES_DIR "${OPENSSL_ROOT_DIR}/${ANDROID_ABI}/lib") SET(OPENSSL_INCLUDE_DIR ${OPENSSL_ROOT_DIR}/include) SET(OPENSSL_LIBRARIES "ssl" "crypto") ... LINK_DIRECTORIES(${OPENSSL_LIBRARIES_DIR} ${ZLIB_LIBRARIES_DIR}) include_directories( "${SUPPORT_LIB_ROOT}/cpp" "${SUPPORT_LIB_ROOT}/jni" "${PROJECT_ROOT}/cpp" "${PROJECT_ROOT}/cpp/gen" "${OPENSSL_INCLUDE_DIR}" ) add_library(libssl STATIC IMPORTED) add_library(libcrypto STATIC IMPORTED) ... set_target_properties( libssl PROPERTIES IMPORTED_LOCATION ${OPENSSL_LIBRARIES_DIR}/libssl.a ) set_target_properties( libcrypto PROPERTIES IMPORTED_LOCATION ${OPENSSL_LIBRARIES_DIR}/libcrypto.a ) target_link_libraries(native-lib PRIVATE libssl libcrypto) 

Kemudian kami menambahkan ke modul C ++ kami kode fungsi sederhana yang mengembalikan versi pustaka OpenSSL.

Dalam file react-native-cpp / cpp / DemoModuleImpl.hpp tambahkan:

 void getOpenSSLVersion(const std::shared_ptr<::JavascriptPromise> & promise) override; 

Dalam file react-native-cpp / cpp / DemoModuleImpl.cpp tambahkan:

 #include <openssl/crypto.h> ... void DemoModuleImpl::getOpenSSLVersion(const std::shared_ptr<::JavascriptPromise> &promise) { promise->resolveString(SSLeay_version(1)); } 

Masih menjelaskan antarmuka fungsi baru di file idl `react-native-cpp-support / idl / main.djinni` :

  getOpenSSLVersion(promise: JavascriptPromise); 

Kami memanggil skrip `generate_wrappers.sh` dari folder` react-native-cpp-support / idl /` .

Kemudian, dalam JavaScript, kami memanggil fungsi yang baru dibuat:

 async promiseTest() { ... this.appendLine("openSSL version: " + await DemoModule.getOpenSSLVersion()); } 

Semuanya siap untuk Android.
Mari beralih ke iOS.

Kami mengkloning repositori dengan versi yang dirakit dari perpustakaan OpenSSL untuk iOS.

Buka proyek iOS dalam Xcode dan dalam pengaturan di tab Build Settings tambahkan path ke perpustakaan openssl di bidang Other C Flags (contoh jalur di komputer saya di bawah):
-I / Pengguna / andreysaleba / proyek / prebuilt-openssl / dist / openssl-1.0.2d-ios / termasuk

Di bidang Bendera Tautan Lainnya, tambahkan baris berikut:

 -L/Users/andreysaleba/projects/prebuilt-openssl/dist/openssl-1.0.2d-ios/lib -lcrypto -lssl 

Semuanya sudah siap. Pustaka OpenSSL ditambahkan untuk kedua platform.

Terima kasih telah menonton!

Source: https://habr.com/ru/post/id439676/


All Articles