最近,有人提议我从事一个有趣的项目。 需要使用React Native在iOS和Android上为美国创业公司开发移动应用程序。 明确决定我参与该项目的关键技术特征和因素是集成用C ++编写的库的任务。 对我来说,这可能是一种新的体验和一种新的专业测试。
为什么需要集成C ++库
对于使用FIDO UAF和U2F协议,使用生物识别数据(例如Face ID和Touch ID)以及适用于Android平台的类似技术的两因素身份验证,此应用程序是必需的。 身份验证客户端已准备就绪。 它是一个用C ++编写的库,除移动应用程序外还被其他一些客户端使用。 因此,我被要求以类似的方式将其集成到React Native上的移动应用程序中。
我是怎么做到的
有一种方法可以将C ++集成到Facebook的React Native应用中。 但是,问题在于它仅适用于iOS平台,尚不清楚在这种情况下如何处理Android。 我想一次解决两个平台的问题。
Dropbox Djinni工具的一个分支,它允许您生成跨平台的类型声明。 本质上,它是一个简单的React Native移动应用程序,具有与Djinni的自定义连接。 我以此为基础。
为了方便起见,将应用程序代码分为两个git存储库。
第一个存储React Native应用程序的源代码,
第二个包含Djinni和必要的依赖项。
进一步的步骤
首先,您需要声明一个接口,用于C ++和React Native代码的交互。 在Djinni中,这是使用.idl文件完成的。 在项目中打开文件
react-native-cpp-support / idl / main.Djinni并熟悉其结构。
为方便起见,项目中已经声明了一些JavaScript数据类型及其绑定。 因此,我们可以使用String,Array,Map,Promise等类型,而无需任何其他描述。
在示例中,该文件如下所示:
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); }
在对interfaces文件进行更改之后,您需要重新生成Java / Objective-C / C ++接口。 通过从
react-native-cpp-support / idl /文件夹运行
generate_wrappers.sh脚本,可以轻松做到这一点。 该脚本将从idl文件中收集所有广告,并为它们创建适当的界面,这非常方便。
在示例中,有两个我们感兴趣的C ++文件。 第一个包含说明,第二个包含简单的C ++方法的实现:
react-native-cpp / cpp / DemoModuleImpl.hpp
react-native-cpp / cpp / DemoModuleImpl.cpp以其中一种方法的代码为例:
void DemoModuleImpl::testString(const std::string &value, const std::shared_ptr<::JavascriptPromise> &promise) { promise->resolveObject(JavascriptObject::fromString("Success!")); }
请注意,返回结果不是使用
关键字return ,而是使用最后一个参数传递的
JavaScriptPromise对象,如
idl文件中所述。
现在,很清楚如何用C ++描述必要的代码。 但是如何在React Native应用程序中与此交互? 要理解,只需从
react-native-cpp / index.js文件夹中打开文件,该示例中描述的所有功能都将在其中被调用。
我们的示例中的函数在JavaScript中的调用如下:
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));
现在很清楚,测试功能如何在C ++和JavaScript方面发挥作用。 同样,您可以添加任何其他功能的代码。 此外,我将考虑Android和iOS项目如何与C ++一起工作。
用于Android的React Native和C ++
为了使Android和C ++交互,您必须安装NDK。 有关如何执行此操作的详细说明,请访问
developer.android.com/ndk/guides然后,在
react-native-cpp / android / app / build.gradle文件中,您需要添加以下设置:
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
我们只是配置gradle来为所使用的体系结构构建应用程序,并为cmake添加了必要的构建标志,指定了我们将在以后描述的
CMAkeLists文件,还添加了我们将使用的Djinni中的java类。
设置Android项目的下一步是描述
CMakeLists.txt文件。 以完成的形式,可以沿着路径
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} )
在这里,我们指出了支持库的相对路径,并添加了带有必要的C ++和JNI代码的目录。
另一个重要步骤是将
DjinniModulesPackage添加到我们的项目中。 为此,请在
react-native-cpp / android / app / src / main / java / com / rncpp / jni / DjinniModulesPackage.java文件中指定:
... 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() ); } ... }
最后一个重要的细节是我们刚刚在应用程序的主类中使用的
DjinniModulesPackage类的描述。 它位于
路径react-native-cpp / android / app / src / main / java / com / rncpp / jni / DjinniModulesPackage.java中,并包含以下代码:
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; } }
在上述类中,最感兴趣的是
System.loadLibrary(“ native-lib”)行; 借助于此,我们将带有本机代码和Djinni代码的库加载到Android应用程序中。
为了理解它的工作原理,我建议您熟悉
文件夹中的jni代码,该
文件夹是用于使用模块功能的jni包装器,其接口在idl文件中进行了描述。
因此,如果配置了Android开发环境和React Native,则可以在Android上构建和运行React Native项目。 为此,请在终端中执行两个命令:
npm安装
npm运行android万岁! 我们的项目正在运作!
而且,我们在Android模拟器的屏幕(可点击)上看到以下图片:

现在,让我们看看iOS和React Native如何与C ++一起工作。
用于iOS的React Native和C ++
在Xcode中打开react-native-cpp项目。
首先,从支持库添加指向Objective-C和C ++项目中使用的代码的链接。 为此,请将
react-native-cpp-support / support-lib / objc /和
react-native-cpp-support / support-lib / cpp /文件夹的内容传输到
Xcode项目。 结果,带有代码支持库的文件夹将显示在项目结构树中(图片可单击):


因此,我们从支持库向项目添加了JavaScript类型的描述。
下一步是为我们的测试C ++模块添加生成的Objective-C包装器。 我们需要将代码从
react-native-cpp / ios / rncpp / Generated /文件夹传输到项目。
仍然需要添加模块的C ++代码,为此,我们将代码从
react-native-cpp / cpp /和
react-native-cpp / cpp / gen /文件夹传输到项目中。
结果,项目结构树将如下所示(图片是可单击的):

您需要确保添加的文件出现在“构建阶段”选项卡内的“编译源”列表中。

(图片可点击)
最后一步是更改AppDelegate.m文件的代码,以在应用程序启动时开始初始化Djinni模块。 为此,您需要更改以下代码行:
... #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]; ... }
现在在iOS上启动我们的应用程序。 (图片可点击)

该应用程序正在运行!
在我们的项目中添加一个C ++库。
例如,我们使用流行的OpenSSL库。
让我们从Android开始。
我们使用适用于Android的OpenSSL库克隆
存储库。
在CMakeLists.txt文件中包含OpenSSL库:
.... 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)
然后,将简单的函数代码添加到C ++模块,该函数代码返回OpenSSL库的版本。
在文件
react-native-cpp / cpp / DemoModuleImpl.hpp中添加:
void getOpenSSLVersion(const std::shared_ptr<::JavascriptPromise> & promise) override;
在文件
react-native-cpp / cpp / DemoModuleImpl.cpp中添加:
#include <openssl/crypto.h> ... void DemoModuleImpl::getOpenSSLVersion(const std::shared_ptr<::JavascriptPromise> &promise) { promise->resolveString(SSLeay_version(1)); }
仍然需要在idl文件
“ react-native-cpp-support / idl / main.djinni”中描述新功能的接口:
getOpenSSLVersion(promise: JavascriptPromise);
我们从文件
夹react-native-cpp-support / idl /`中调用脚本
`generate_wrappers.sh` 。
然后,在JavaScript中,我们调用新创建的函数:
async promiseTest() { ... this.appendLine("openSSL version: " + await DemoModule.getOpenSSLVersion()); }
一切就绪,可用于Android。
让我们继续iOS。
我们使用适用于iOS的OpenSSL库的组合版本克隆
存储库。
在Xcode中打开iOS项目,然后在Build Settings选项卡的设置中,在Other C Flags字段中添加openssl库的路径(下面是我计算机上路径的示例):
-I /用户/ andreysaleba /项目/prebuilt-openssl/dist/openssl-1.0.2d-ios/include在“其他链接器标志”字段中,添加以下行:
-L/Users/andreysaleba/projects/prebuilt-openssl/dist/openssl-1.0.2d-ios/lib -lcrypto -lssl
一切准备就绪。 为两个平台添加了OpenSSL库。
感谢收看!