Recientemente, me ofrecieron trabajar en un proyecto interesante. Se requería desarrollar una aplicación móvil para una startup estadounidense en plataformas iOS y Android usando React Native. La característica técnica clave y el factor que decidieron inequívocamente mi participación en el proyecto fue la tarea de integrar una biblioteca escrita en C ++. Para mí, esta podría ser una nueva experiencia y una nueva prueba profesional.
¿Por qué fue necesario integrar la biblioteca de C ++?
Esta aplicación era necesaria para la autenticación de dos factores utilizando los protocolos FIDO UAF y U2F, utilizando datos biométricos como Face ID y Touch ID, y tecnologías similares para la plataforma Android. El cliente para la autenticación ya estaba listo. Era una biblioteca escrita en C ++ y utilizada por algunos otros clientes además de la aplicación móvil. Por lo tanto, tuve que integrarlo de manera similar en una aplicación móvil en React Native.
Como lo hice
Existe un enfoque para integrar C ++ en la aplicación React Native de Facebook. Sin embargo, el problema es que solo funciona para la plataforma iOS, y no está claro qué hacer con Android en este caso. Quería resolver el problema para dos plataformas a la vez.
Una bifurcación de la herramienta Djinni de Dropbox que le permite generar declaraciones de tipo multiplataforma. Esencialmente, es una aplicación móvil simple React Native con una conexión personalizada a Djinni. Lo tomé como base.
Por conveniencia, el código de la aplicación se divide en dos repositorios git.
El primero almacena el código fuente de la aplicación React Native, y
el segundo contiene Djinni y las dependencias necesarias.
Pasos adicionales
Primero debe declarar una interfaz para la interacción de C ++ y React Native code. En Djinni, esto se hace usando archivos .idl. Abra el archivo
react-native-cpp-support / idl / main.Djinni en el proyecto y familiarícese con su estructura.
Para nuestra conveniencia, algunos tipos de datos de JavaScript y enlaces para ellos ya se han declarado en el proyecto. Por lo tanto, podemos trabajar con los tipos String, Array, Map, Promise y otros sin ninguna descripción adicional.
En el ejemplo, este archivo se ve así:
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); }
Después de realizar cambios en el archivo de interfaces, debe regenerar las interfaces Java / Objective-C / C ++. Esto es fácil de hacer ejecutando el script
generate_wrappers.sh desde la carpeta
react-native-cpp-support / idl / . Este script recopilará todos los anuncios de nuestro archivo idl y creará las interfaces apropiadas para ellos, es muy conveniente.
En el ejemplo, hay dos archivos C ++ que nos interesan. El primero contiene una descripción, y la segunda implementación de métodos simples de C ++:
reaccionar-nativo-cpp / cpp / DemoModuleImpl.hpp
reaccionar-nativo-cpp / cpp / DemoModuleImpl.cppConsidere el código de uno de los métodos como ejemplo:
void DemoModuleImpl::testString(const std::string &value, const std::shared_ptr<::JavascriptPromise> &promise) { promise->resolveObject(JavascriptObject::fromString("Success!")); }
Tenga en cuenta que el resultado se devuelve no usando la
palabra clave return , sino usando el objeto
JavaScriptPromise pasado por el último parámetro, como se describe en el archivo
idl .
Ahora quedó claro cómo describir el código necesario en C ++. Pero, ¿cómo interactuar con esto en una aplicación React Native? Para comprenderlo, simplemente abra el archivo desde la carpeta
react-native-cpp / index.js , donde se llaman todas las funciones descritas en el ejemplo.
La función de nuestro ejemplo se llama en JavaScript de la siguiente manera:
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));
Ahora está claro cómo funcionan las funciones de prueba del lado de C ++ y JavaScript. Del mismo modo, puede agregar el código de cualquier otra función. Además, consideraré cómo funcionan los proyectos de Android e iOS junto con C ++.
React Native y C ++ para Android
Para la interacción de Android y C ++, debe instalar el NDK. Las instrucciones detalladas sobre cómo hacerlo están disponibles en
developer.android.com/ndk/guidesLuego, dentro del
archivo react-native-cpp / android / app / build.gradle, debe agregar la siguiente configuración:
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
Simplemente configuramos gradle para compilar la aplicación para las arquitecturas utilizadas y agregamos los indicadores de compilación necesarios para cmake, especificamos el archivo
CMAkeLists , que describiremos en el futuro, y también agregamos las clases java de Djinni que usaremos.
El siguiente paso para configurar un proyecto de Android es describir el archivo
CMakeLists.txt . En forma terminada, se puede ver a lo largo de la ruta
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} )
Aquí indicamos las rutas relativas a la biblioteca de soporte, agregamos directorios con el código C ++ y JNI necesario.
Otro paso importante es agregar
DjinniModulesPackage a nuestro proyecto. Para hacer esto, en el archivo
react-native-cpp / android / app / src / main / java / com / rncpp / jni / DjinniModulesPackage.java especificamos:
... 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() ); } ... }
El último detalle importante es la descripción de la clase
DjinniModulesPackage que acabamos de utilizar en la clase principal de nuestra aplicación. Se encuentra en la
ruta react-native-cpp / android / app / src / main / java / com / rncpp / jni / DjinniModulesPackage.java y contiene el siguiente código:
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; } }
De mayor interés en la clase anterior es la línea
System.loadLibrary ("native-lib"); Gracias a lo cual cargamos la biblioteca con nuestro código nativo y el código Djinni en la aplicación de Android.
Para entender cómo funciona esto, le aconsejo que se familiarice con el código jni de la
carpeta , que es un contenedor jni para trabajar con la funcionalidad de nuestro módulo, y su interfaz se describe en el archivo idl.
Como resultado, si el entorno de desarrollo de Android y React Native están configurados, puede compilar y ejecutar el proyecto React Native en Android. Para hacer esto, ejecute dos comandos en la terminal:
npm install
npm ejecutar android¡Hurra! Nuestro proyecto esta funcionando!
Y vemos la siguiente imagen en la pantalla del emulador de Android (se puede hacer clic):

Ahora veamos cómo funcionan iOS y React Native con C ++.
React Native y C ++ para iOS
Abra el proyecto react-native-cpp en Xcode.
Primero, agregue enlaces al código utilizado en el proyecto Objective-C y C ++ desde la biblioteca de soporte. Para hacer esto, transfiera el contenido de las carpetas
react-native-cpp-support / support-lib / objc / y
react-native-cpp-support / support-lib / cpp / al proyecto
Xcode . Como resultado, las carpetas con la biblioteca de soporte de código se mostrarán en el árbol de estructura del proyecto (se puede hacer clic en las imágenes):


Por lo tanto, agregamos descripciones de tipos de JavaScript de la biblioteca de soporte al proyecto.
El siguiente paso es agregar los contenedores del objetivo-c generados para nuestro módulo de prueba C ++. Necesitamos transferir el código de la carpeta
react-native-cpp / ios / rncpp / Generated / al proyecto.
Queda por agregar el código C ++ de nuestro módulo, para lo cual transferimos el código de las carpetas
react-native-cpp / cpp / y
react-native-cpp / cpp / gen / al proyecto.
Como resultado, el árbol de estructura del proyecto tendrá el siguiente aspecto (se puede hacer clic en la imagen):

Debe asegurarse de que los archivos agregados aparezcan en la lista de fuentes de compilación dentro de la pestaña Fases de compilación.

(se puede hacer clic en la imagen)
El último paso es cambiar el código del archivo AppDelegate.m para comenzar a inicializar el módulo Djinni cuando se inicia la aplicación. Y para esto necesita cambiar las siguientes líneas de código:
... #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]; ... }
Ahora inicie nuestra aplicación en iOS. (se puede hacer clic en la imagen)

¡La aplicación está funcionando!
Agregar una biblioteca C ++ a nuestro proyecto.
Por ejemplo, usamos la popular biblioteca OpenSSL.
Y comencemos con Android.
Clonamos el
repositorio con la biblioteca OpenSSL ya construida para Android.
Incluya la biblioteca OpenSSL en el archivo 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)
Luego agregamos a nuestro módulo C ++ un código de función simple que devuelve la versión de la biblioteca OpenSSL.
En el archivo
react-native-cpp / cpp / DemoModuleImpl.hpp agregue:
void getOpenSSLVersion(const std::shared_ptr<::JavascriptPromise> & promise) override;
En el archivo
react-native-cpp / cpp / DemoModuleImpl.cpp agregue:
#include <openssl/crypto.h> ... void DemoModuleImpl::getOpenSSLVersion(const std::shared_ptr<::JavascriptPromise> &promise) { promise->resolveString(SSLeay_version(1)); }
Queda por describir la interfaz de la nueva función en el archivo idl
`react-native-cpp-support / idl / main.djinni` :
getOpenSSLVersion(promise: JavascriptPromise);
Llamamos
al script
`generate_wrappers.sh` desde la
carpeta` react-native-cpp-support / idl /` .
Luego, en JavaScript, llamamos a la función recién creada:
async promiseTest() { ... this.appendLine("openSSL version: " + await DemoModule.getOpenSSLVersion()); }
Todo está listo para Android.
Pasemos a iOS.
Clonamos el
repositorio con la versión ensamblada de la biblioteca OpenSSL para iOS.
Abra el proyecto de iOS en Xcode y en la configuración de la pestaña Configuración de compilación agregue la ruta a la biblioteca openssl en el campo Otras banderas de C (a continuación se muestra un ejemplo de la ruta en mi computadora):
-I / Users / andreysaleba / projects / prebuilt-openssl / dist / openssl-1.0.2d-ios / includeEn el campo Otros indicadores de vinculador, agregue las siguientes líneas:
-L/Users/andreysaleba/projects/prebuilt-openssl/dist/openssl-1.0.2d-ios/lib -lcrypto -lssl
Todo esta listo. Biblioteca OpenSSL agregada para ambas plataformas.
Gracias por mirar!