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.cppPertimbangkan 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));
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/guidesKemudian, 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
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 AndroidHore! 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 / termasukDi 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!