📜 ⬆️ ⬇️

Integration of React Native and C ++ for iOS and Android

Recently I was offered to work on one interesting project. It was required to develop a mobile application for an American startup on iOS and Android platforms using React Native. The key technical feature and factor that clearly decided my participation in the project was the task of integrating the library written in C ++. For me, this could be a new experience and a new professional challenge.

Why was it necessary to integrate C ++ library


This application was necessary for two-factor authentication using FIDO UAF and U2F protocols using biometric data, such as Face ID and Touch ID, and similar technologies for the Android platform. The client for authentication was already ready. It was a library written in C ++ and used by some other clients besides the mobile application. So I was required to embed it in a similar way into a mobile application for React Native.

How i did it


There is an approach for integrating C ++ into the React Native application from Facebook. However, the problem is that it works only for the iOS platform, and it is not clear what to do with Android in this case. I also wanted to solve the problem for two platforms at once.

Fork of the Djinni tool from Dropbox , which allows you to generate cross-platform type declarations. In essence, it is a simple mobile application on React Native with a customized connection to Djinni. I took it as a basis.

For convenience, the application code is split into two git repositories. The first one stores the source code of the React Native application, and the second stores the Djinni and necessary dependencies.

Further steps


First you need to declare the C ++ interaction interface and React Native code. In Djinni, this is done using .idl files. Open the file react-native-cpp-support / idl / main.Djinni in the project and take a look at its structure.

In the project, for our convenience, we have already announced some JavaScript data types and binding for them. Thus, we can work with the types String, Array, Map, Promise and others without any additional description.

In the example, this file looks like this:

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

After making changes to the interfaces file, it is necessary to regenerate the Java / Objective-C / C ++ interfaces. This is easy to do by running the generate_wrappers.sh script from the react-native-cpp-support / idl / folder. This script will collect all the ads from our idl file and create the appropriate interfaces for them, it is very convenient.

In the example there are two C ++ files of interest to us. The first contains the description, and the second implementation of simple C ++ methods:

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

Consider the code for one of the methods as an example:

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

Note that the result is not returned using the keyword return , but using the JavaScriptPromise object passed in by the last parameter, as described in the idl file.

Now it became clear how to describe the necessary code in C ++. But how to interact with this in a React Native application? To understand, it is enough to open the file from the folder react-native-cpp / index.js , where all the functions described in the example are called.

The function from our example is called in JavaScript as follows:

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

Now I understand how the test functions on the C ++ and JavaScript side work. Similarly, you can add and code any other functions. Next, I will look at how Android and iOS projects work with C ++.

React Native and C ++ for Android


For the interaction of Android and C ++, you must install the NDK. Detailed instructions on how to do this are available at the developer.android.com/ndk/guides link.
Then inside the react-native-cpp / android / app / build.gradle file you need to add the following settings:

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

We just configured the program to build the application for the used architectures and added the necessary build flags for cmake, specified the CMAkeLists file, which we will describe in the future, and also add the java classes from Djinni that we will use.
The next step in setting up an Android project is the description of the CMakeLists.txt file. In finished form, it can be viewed on the path 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} ) 

Here we specified relative paths to the support library, added directories with the necessary C ++ code and JNI.

Another important step is to add DjinniModulesPackage to our project. To do this, in the file react-native-cpp / android / app / src / main / java / com / rncpp / jni / DjinniModulesPackage.java, we specify:

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

The last important detail is the description of the DjinniModulesPackage class, which we have just used in the main class of our application. It is located on the path react-native-cpp / android / app / src / main / java / com / rncpp / jni / DjinniModulesPackage.java and contains the following code:

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

The greatest interest in the above class is the string System.loadLibrary ("native-lib"); , thanks to which we load the library with our native code and Djinni code into the Android application.

To understand how this works, I advise you to familiarize yourself with the jni-code from the folder , which is a jni-wrapper for working with the functionality of our module, and its interface is described in the idl-file.

As a result, if the Android and React Native development environment is configured, you can build and run the React Native project on Android. To do this, execute two commands in the terminal:

npm install
npm run android

Hooray! Our project is working!

And we see the following image on the screen of the Android emulator (clickable):



Now let's look at how iOS and React Native work with C ++.

React Native and C ++ for iOS


Open the react-native-cpp project in XCode.

First, we add links to the code used in the Objective-C and C ++ projects from the support library. To do this, transfer the contents of the react-native-cpp-support / support-lib / objc / and react-native-cpp-support / support-lib / cpp / folders to the XCode project. As a result, folders with the code of the support library will be displayed in the project structure tree (the images are clickable):





Thus, we added descriptions of JavaScript types from the support library to the project.

The next step is to add the generated objective-c wrappers for our test C ++ module. We will need to transfer the code from the react-native-cpp / ios / rncpp / Generated / folder to the project.

It remains to add the C ++ code of our module, for which we will transfer the code from the react-native-cpp / cpp / and react-native-cpp / cpp / gen / folders to the project.

As a result, the project structure tree will look as follows (the image is clickable):



You need to make sure that the added files appear in the Compile Sources list inside the Build Phases tabs.



(the picture is clickable)

The last step is to change the code of the AppDelegate.m file to start the initialization of the Djinni module when the application starts. And for this you will need to change the following lines of code:

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

Now we will launch our application on iOS. (the picture is clickable)




The application works!

Adding C ++ library to our project.


For example, use the popular OpenSSL library.

And let's start with Android.

Clone the repository with the already compiled OpenSSL library for Android.

Include the OpenSSL library in the CMakeLists.txt file:

 .... 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) 

Then we add a simple function code to our C ++ module that returns the version of the OpenSSL library.

In the react-native-cpp / cpp / DemoModuleImpl.hpp file we add:

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

In the file react-native-cpp / cpp / DemoModuleImpl.cpp we add:

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

It remains to describe the interface of the new function in the idl file `react-native-cpp-support / idl / main.djinni` :

  getOpenSSLVersion(promise: JavascriptPromise); 

Call the `generate_wrappers.sh` script from the` react-native-cpp-support / idl / ` folder.

Then in JavaScript we call the function we just created:

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

For Android, everything is ready.
Let's go to iOS.

Clone the repository with the compiled version of the OpenSSL library for iOS.

Open the iOS project in Xcode and in the settings in the Build Settings tab add the path to the openssl library to the Other C Flags field (an example path on my computer is below):
-I / Users / andreysaleba / projects / prebuilt-openssl / dist / openssl-1.0.2d-ios / include

Add the following lines to the Other Linker Flags field:

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

All is ready. The OpenSSL library has been added for both platforms.

Thank you for watching!

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