Recentemente, fui oferecido para trabalhar em um projeto interessante. Foi necessário desenvolver um aplicativo móvel para a inicialização americana no iOS e Android usando o React Native. O principal recurso e fator técnico que decidiu inequivocamente minha participação no projeto foi a tarefa de integrar uma biblioteca escrita em C ++. Para mim, isso poderia ser uma nova experiência e um novo teste profissional.
Por que foi necessário integrar a biblioteca C ++
Esse aplicativo era necessário para autenticação de dois fatores usando os protocolos FIDO UAF e U2F, usando dados biométricos, como Face ID e Touch ID, e tecnologias semelhantes para a plataforma Android. O cliente para autenticação já estava pronto. Era uma biblioteca escrita em C ++ e usada por alguns outros clientes, além do aplicativo móvel. Portanto, fui obrigado a integrá-lo de maneira semelhante a um aplicativo móvel no React Native.
Como eu fiz isso
Existe uma abordagem para integrar o C ++ ao aplicativo React Native do Facebook. No entanto, o problema é que ele funciona apenas para a plataforma iOS e não está claro o que fazer com o Android nesse caso. Eu queria resolver o problema para duas plataformas ao mesmo tempo.
Uma bifurcação da ferramenta Dropbox Djinni que permite gerar declarações de tipo de plataforma cruzada. Essencialmente, é um aplicativo móvel simples do React Native com uma conexão personalizada ao Djinni. Eu tomei isso como base.
Por conveniência, o código do aplicativo é dividido em dois repositórios git.
O primeiro armazena o código-fonte do aplicativo React Native e
o segundo contém Djinni e as dependências necessárias.
Passos adicionais
Primeiro, você precisa declarar uma interface para a interação do C ++ e do código React Native. No Djinni, isso é feito usando arquivos .idl. Abra o arquivo
react-native-cpp-support / idl / main.Djinni no projeto e familiarize-se com sua estrutura.
Para nossa conveniência, alguns tipos de dados JavaScript e ligações para eles já foram declarados no projeto. Assim, podemos trabalhar com os tipos String, Array, Map, Promise e outros sem nenhuma descrição adicional.
No exemplo, esse arquivo se parece com o seguinte:
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); }
Depois de fazer alterações no arquivo de interfaces, é necessário regenerar as interfaces Java / Objective-C / C ++. Isso é fácil, executando o script
generate_wrappers.sh na pasta
react-native-cpp-support / idl / . Este script irá coletar todos os anúncios do nosso arquivo idl e criar as interfaces apropriadas para eles, é muito conveniente.
No exemplo, existem dois arquivos C ++ que nos interessam. O primeiro contém uma descrição e a segunda implementação de métodos C ++ simples:
react-native-cpp / cpp / DemoModuleImpl.hpp
react-native-cpp / cpp / DemoModuleImpl.cppConsidere o código de um dos métodos como um exemplo:
void DemoModuleImpl::testString(const std::string &value, const std::shared_ptr<::JavascriptPromise> &promise) { promise->resolveObject(JavascriptObject::fromString("Success!")); }
Observe que o resultado é retornado não usando o
retorno de palavra-chave , mas usando o objeto
JavaScriptPromise passado pelo último parâmetro, conforme descrito no arquivo
idl .
Agora ficou claro como descrever o código necessário em C ++. Mas como interagir com isso em um aplicativo React Native? Para entender, basta abrir o arquivo na
pasta react-native-cpp / index.js , na qual todas as funções descritas no exemplo são chamadas.
A função do nosso exemplo é chamada em JavaScript da seguinte maneira:
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));
Agora está claro como as funções de teste funcionam ao lado de C ++ e JavaScript. Da mesma forma, você pode adicionar o código de qualquer outra função. Além disso, considerarei como os projetos Android e iOS funcionam em conjunto com o C ++.
Reagir nativo e C ++ para Android
Para a interação do Android e C ++, você deve instalar o NDK. Instruções detalhadas sobre como fazer isso estão disponíveis em
developer.android.com/ndk/guidesEm seguida, dentro do
arquivo react-native-cpp / android / app / build.gradle, você precisará adicionar as seguintes configurações:
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
Acabamos de configurar o gradle para construir o aplicativo para as arquiteturas usadas e adicionamos os sinalizadores de construção necessários para o cmake, especificamos o arquivo
CMAkeLists , que descreveremos no futuro, e também adicionamos as classes java do Djinni que usaremos.
A próxima etapa na configuração de um projeto Android é a descrição do arquivo
CMakeLists.txt . No formato final, ele pode ser visualizado no caminho
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} )
Aqui, indicamos os caminhos relativos à biblioteca de suporte, adicionamos diretórios com o código C ++ e JNI necessário.
Outro passo importante é adicionar o
DjinniModulesPackage ao nosso projeto. Para fazer isso, no arquivo
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() ); } ... }
O último detalhe importante é a descrição da classe
DjinniModulesPackage que acabamos de usar na classe principal do nosso aplicativo. Ele está localizado no
caminho react-native-cpp / android / app / src / main / java / com / rncpp / jni / DjinniModulesPackage.java e contém o seguinte 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 maior interesse na classe acima é a linha
System.loadLibrary ("native-lib"); Graças ao qual carregamos a biblioteca com nosso código nativo e código Djinni no aplicativo Android.
Para entender como isso funciona, aconselho que você se familiarize com o código jni da
pasta , que é um wrapper jni para trabalhar com a funcionalidade do nosso módulo, e sua interface é descrita no arquivo idl.
Como resultado, se o ambiente de desenvolvimento Android e o React Native estiverem configurados, você poderá criar e executar o projeto React Native no Android. Para fazer isso, execute dois comandos no terminal:
instalação npm
npm run androidViva! Nosso projeto está funcionando!
E vemos a seguinte imagem na tela do emulador do Android (clicável):

Agora vamos ver como o iOS e o React Native funcionam com C ++.
Reagir nativo e C ++ para iOS
Abra o projeto react-native-cpp no Xcode.
Primeiro, adicione links ao código usado no projeto Objective-C e C ++ da biblioteca de suporte. Para fazer isso, transfira o conteúdo das pastas
react -native-cpp-support / support-lib / objc / e
react-native-cpp-support / support-lib / cpp / para o projeto
Xcode . Como resultado, as pastas com a biblioteca de suporte ao código serão exibidas na árvore da estrutura do projeto (as imagens são clicáveis):


Assim, adicionamos descrições de tipos JavaScript da biblioteca de suporte ao projeto.
A próxima etapa é adicionar os wrappers objetivo-c gerados para o nosso módulo C ++ de teste. Precisamos transferir o código da pasta
react-native-cpp / ios / rncpp / Generated / para o projeto.
Resta adicionar o código C ++ do nosso módulo, para o qual transferimos o código das pastas
react-native-cpp / cpp / e
react-native-cpp / cpp / gen / para o projeto.
Como resultado, a árvore da estrutura do projeto terá a seguinte aparência (a imagem é clicável):

Você precisa garantir que os arquivos adicionados apareçam na lista Fontes de compilação, na guia Fases de construção.

(a imagem é clicável)
A última etapa é alterar o código do arquivo AppDelegate.m para iniciar a inicialização do módulo Djinni quando o aplicativo for iniciado. E para isso, você precisa alterar as seguintes linhas 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]; ... }
Agora inicie nosso aplicativo no iOS. (a imagem é clicável)

A aplicação está funcionando!
Adicionando uma biblioteca C ++ ao nosso projeto.
Por exemplo, usamos a popular biblioteca OpenSSL.
E vamos começar com o Android.
Clonamos o
repositório com a biblioteca OpenSSL já construída para Android.
Inclua a biblioteca OpenSSL no arquivo 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)
Em seguida, adicionamos ao nosso módulo C ++ um código de função simples que retorna a versão da biblioteca OpenSSL.
No arquivo
react-native-cpp / cpp / DemoModuleImpl.hpp, adicione:
void getOpenSSLVersion(const std::shared_ptr<::JavascriptPromise> & promise) override;
No arquivo
react-native-cpp / cpp / DemoModuleImpl.cpp, adicione:
#include <openssl/crypto.h> ... void DemoModuleImpl::getOpenSSLVersion(const std::shared_ptr<::JavascriptPromise> &promise) { promise->resolveString(SSLeay_version(1)); }
Resta descrever a interface da nova função no arquivo idl
` react
-native-cpp-support / idl / main.djinni` :
getOpenSSLVersion(promise: JavascriptPromise);
Chamamos o script
`generate_wrappers.sh` da
pasta` react-native-cpp-support / idl /` .
Em JavaScript, chamamos a função recém-criada:
async promiseTest() { ... this.appendLine("openSSL version: " + await DemoModule.getOpenSSLVersion()); }
Tudo está pronto para o Android.
Vamos para o iOS.
Clonamos o
repositório com a versão montada da biblioteca OpenSSL para iOS.
Abra o projeto iOS no Xcode e, nas configurações da guia Configurações de compilação, adicione o caminho à biblioteca openssl no campo Outros sinalizadores C (um exemplo do caminho no meu computador está abaixo):
-I / Usuários / andreysaleba / projects / pré-construído-openssl / dist / openssl-1.0.2d-ios / includeNo campo Outros sinalizadores de vinculador, adicione as seguintes linhas:
-L/Users/andreysaleba/projects/prebuilt-openssl/dist/openssl-1.0.2d-ios/lib -lcrypto -lssl
Está tudo pronto. Biblioteca OpenSSL adicionada para ambas as plataformas.
Obrigado por assistir!