Réagissez à l'intégration native et C ++ pour iOS et Android

Récemment, on m'a proposé de travailler sur un projet intéressant. Il était nécessaire de développer une application mobile pour le démarrage américain sur iOS et Android à l'aide de React Native. La caractéristique technique et le facteur clé qui ont décidé sans équivoque de ma participation au projet étaient la tâche d'intégrer une bibliothèque écrite en C ++. Pour moi, cela pourrait être une nouvelle expérience et un nouveau test professionnel.

Pourquoi était-il nécessaire d'intégrer la bibliothèque C ++


Cette application était nécessaire pour l'authentification à deux facteurs en utilisant les protocoles FIDO UAF et U2F, en utilisant des données biométriques, telles que Face ID et Touch ID, et des technologies similaires pour la plate-forme Android. Le client pour l'authentification était déjà prêt. Il s'agissait d'une bibliothèque écrite en C ++ et utilisée par d'autres clients en plus de l'application mobile. J'ai donc dû l'intégrer de manière similaire dans une application mobile sur React Native.

Comment je l'ai fait


Il existe une approche pour intégrer C ++ dans l'application React Native de Facebook. Cependant, le problème est qu'il ne fonctionne que pour la plate-forme iOS, et on ne sait pas quoi faire avec Android dans ce cas. Je voulais résoudre le problème pour deux plates-formes à la fois.

Un fork de l'outil Dropbox Djinni qui vous permet de générer des déclarations de type multiplateforme. Il s'agit essentiellement d'une application mobile React Native simple avec une connexion personnalisée à Djinni. Je l'ai pris comme base.

Pour plus de commodité, le code d'application est divisé en deux référentiels git. Le premier stocke le code source de l'application React Native, et le second contient Djinni et les dépendances nécessaires.

Etapes supplémentaires


Vous devez d'abord déclarer une interface pour l'interaction du code C ++ et React Native. En Djinni, cela se fait en utilisant des fichiers .idl. Ouvrez le fichier react-native-cpp-support / idl / main.Djinni dans le projet et familiarisez-vous avec sa structure.

Pour notre commodité, certains types de données JavaScript et leurs liaisons ont déjà été déclarés dans le projet. Ainsi, nous pouvons travailler avec les types String, Array, Map, Promise et autres sans description supplémentaire.

Dans l'exemple, ce fichier ressemble à ceci:

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

Après avoir apporté des modifications au fichier d'interfaces, vous devez régénérer les interfaces Java / Objective-C / C ++. C'est facile à faire en exécutant le script generate_wrappers.sh à partir du dossier react-native-cpp-support / idl / . Ce script va collecter toutes les publicités de notre fichier idl et créer les interfaces appropriées pour elles, c'est très pratique.

Dans l'exemple, il y a deux fichiers C ++ qui nous intéressent. La première contient une description et la seconde implémentation de méthodes C ++ simples:

react-native-cpp / cpp / DemoModuleImpl.hpp
react-native-cpp / cpp / DemoModuleImpl.cpp

Considérez le code de l'une des méthodes comme exemple:

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

Veuillez noter que le résultat est renvoyé non pas à l'aide du mot-clé return , mais à l'aide de l'objet JavaScriptPromise passé par le dernier paramètre, comme décrit dans le fichier idl .

Maintenant, il est devenu clair comment décrire le code nécessaire en C ++. Mais comment interagir avec cela dans une application React Native? Pour comprendre, ouvrez simplement le fichier à partir du dossier react-native-cpp / index.js , où toutes les fonctions décrites dans l'exemple sont appelées.

La fonction de notre exemple est appelée en JavaScript comme suit:

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

Maintenant, il est clair comment les fonctions de test fonctionnent du côté de C ++ et JavaScript. De même, vous pouvez ajouter le code de toute autre fonction. De plus, je considérerai comment les projets Android et iOS fonctionnent avec C ++.

React Native et C ++ pour Android


Pour l'interaction d'Android et C ++, vous devez installer le NDK. Des instructions détaillées sur la façon de procéder sont disponibles sur developer.android.com/ndk/guides
Ensuite, dans le fichier react-native-cpp / android / app / build.gradle, vous devez ajouter les paramètres suivants:

 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" } } ... } 

Nous venons de configurer gradle pour construire l'application pour les architectures utilisées et avons ajouté les drapeaux de construction nécessaires pour cmake, spécifié le fichier CMAkeLists , que nous décrirons à l'avenir, et avons également ajouté les classes java de Djinni que nous utiliserons.
L'étape suivante de la configuration d'un projet Android est la description du fichier CMakeLists.txt . Dans sa forme finale, il peut être consulté le long du chemin 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} ) 

Ici, nous avons indiqué les chemins relatifs à la bibliothèque de support, ajouté des répertoires avec le code C ++ et JNI nécessaire.

Une autre étape importante consiste à ajouter DjinniModulesPackage à notre projet. Pour ce faire, dans le fichier react-native-cpp / android / app / src / main / java / com / rncpp / jni / DjinniModulesPackage.java nous spécifions :

 ... 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() ); } ... } 

Le dernier détail important est la description de la classe DjinniModulesPackage que nous venons d'utiliser dans la classe principale de notre application. Il se trouve sur le chemin react-native-cpp / android / app / src / main / java / com / rncpp / jni / DjinniModulesPackage.java et contient le code suivant:

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

La ligne System.loadLibrary ("native-lib") présente le plus grand intérêt pour la classe ci-dessus ; Grâce à quoi nous chargeons la bibliothèque avec notre code natif et notre code Djinni dans l'application Android.

Pour comprendre comment cela fonctionne, je vous conseille de vous familiariser avec le code jni du dossier , qui est un wrapper jni pour travailler avec les fonctionnalités de notre module, et son interface est décrite dans le fichier idl.

Par conséquent, si l'environnement de développement Android et React Native sont configurés, vous pouvez créer et exécuter le projet React Native sur Android. Pour ce faire, exécutez deux commandes dans le terminal:

installation de npm
npm run android

Hourra! Notre projet fonctionne!

Et nous voyons l'image suivante sur l'écran de l'émulateur Android (cliquable):



Voyons maintenant comment iOS et React Native fonctionnent avec C ++.

React Native et C ++ pour iOS


Ouvrez le projet react-native-cpp dans Xcode.

Tout d'abord, ajoutez des liens vers le code utilisé dans le projet Objective-C et C ++ à partir de la bibliothèque de support. Pour ce faire, transférez le contenu des dossiers react-native-cpp-support / support-lib / objc / et react -native-cpp-support / support-lib / cpp / vers le projet Xcode . En conséquence, les dossiers avec la bibliothèque de support de code seront affichés dans l'arborescence de la structure du projet (les images sont cliquables):





Ainsi, nous avons ajouté des descriptions des types JavaScript de la bibliothèque de support au projet.

L'étape suivante consiste à ajouter les wrappers objective-c générés pour notre module de test C ++. Nous devons transférer le code du dossier react-native-cpp / ios / rncpp / Generated / vers le projet.

Il reste à ajouter le code C ++ de notre module, pour lequel nous transférons le code des dossiers react-native-cpp / cpp / et react-native-cpp / cpp / gen / au projet.

Par conséquent, l'arborescence de la structure du projet se présente comme suit (l'image est cliquable):



Vous devez vous assurer que les fichiers ajoutés apparaissent dans la liste Compiler les sources à l'intérieur de l'onglet Build Phases.



(l'image est cliquable)

La dernière étape consiste à modifier le code du fichier AppDelegate.m pour démarrer l'initialisation du module Djinni au démarrage de l'application. Et pour cela, vous devez modifier les lignes de code suivantes:

 ... #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]; ... } 

Lancez maintenant notre application sur iOS. (l'image est cliquable)




L'application fonctionne!

Ajout d'une bibliothèque C ++ à notre projet.


Par exemple, nous utilisons la bibliothèque OpenSSL populaire.

Et commençons par Android.

Nous clonons le référentiel avec la bibliothèque OpenSSL déjà construite pour Android.

Incluez la bibliothèque OpenSSL dans le fichier 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) 

Ensuite, nous ajoutons à notre module C ++ un code de fonction simple qui renvoie la version de la bibliothèque OpenSSL.

Dans le fichier react-native-cpp / cpp / DemoModuleImpl.hpp ajoutez:

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

Dans le fichier react-native-cpp / cpp / DemoModuleImpl.cpp ajoutez:

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

Il reste à décrire l'interface de la nouvelle fonction dans le fichier idl ` react -native-cpp-support / idl / main.djinni` :

  getOpenSSLVersion(promise: JavascriptPromise); 

Nous appelons le script `generate_wrappers.sh` à partir du dossier` react-native-cpp-support / idl /` .

Ensuite, en JavaScript, nous appelons la fonction nouvellement créée:

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

Tout est prêt pour Android.
Passons à iOS.

Nous clonons le référentiel avec la version assemblée de la bibliothèque OpenSSL pour iOS.

Ouvrez le projet iOS dans Xcode et dans les paramètres de l'onglet Paramètres de construction, ajoutez le chemin d'accès à la bibliothèque openssl dans le champ Autres indicateurs C (un exemple du chemin sur mon ordinateur est ci-dessous):
-I / Utilisateurs / andreysaleba / projets / prebuilt-openssl / dist / openssl-1.0.2d-ios / include

Dans le champ Autres indicateurs de l'éditeur de liens, ajoutez les lignes suivantes:

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

Tout est prêt. Bibliothèque OpenSSL ajoutée pour les deux plates-formes.

Merci d'avoir regardé!

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


All Articles