تتفاعل الأصلية والتكامل C ++ لنظام التشغيل iOS و Android

في الآونة الأخيرة ، عرض علي العمل في مشروع واحد مثير للاهتمام. كان مطلوبًا تطوير تطبيق جوال لبدء التشغيل الأمريكي على iOS و Android باستخدام React Native. كانت الميزة التقنية الرئيسية والعامل الذي قرر بشكل لا لبس فيه مشاركتي في المشروع مهمة دمج مكتبة مكتوبة في C ++. بالنسبة لي ، قد تكون هذه تجربة جديدة واختبارًا احترافيًا جديدًا.

لماذا كان من الضروري دمج مكتبة C ++


كان هذا التطبيق ضروريًا للمصادقة ثنائية العوامل باستخدام بروتوكولي FIDO UAF و U2F ، باستخدام البيانات البيومترية ، مثل Face ID و Touch ID ، والتقنيات المماثلة لمنصة Android. عميل المصادقة جاهز بالفعل. كانت مكتبة مكتوبة بلغة C ++ وتستخدمها بعض العملاء الآخرين بالإضافة إلى تطبيق الهاتف المحمول. لذلك كان مطلوبًا دمجها بطريقة مماثلة في تطبيق الهاتف المحمول على React Native.

كيف فعلت ذلك


هناك طريقة لدمج C ++ في تطبيق React Native على Facebook. ومع ذلك ، فإن المشكلة تكمن في أنه يعمل فقط مع نظام التشغيل iOS ، وليس من الواضح ما يجب القيام به مع Android في هذه الحالة. كنت أرغب في حل المشكلة لمنصة اثنين في وقت واحد.

تفرع من أداة Dropbox Djinni التي تتيح لك إنشاء إعلانات نوع النظام الأساسي. في الأساس ، إنه تطبيق React Native للجوال بسيط مع اتصال مخصص بـ Djinni. أخذته كأساس.

للراحة ، يتم تقسيم رمز التطبيق إلى مستودعين بوابة. الأول يخزن الكود المصدري للتطبيق React Native ، والثاني يحتوي على جيني والتبعيات اللازمة.

خطوات إضافية


تحتاج أولاً إلى إعلان واجهة للتفاعل بين C ++ و React Native code. في جيني ، يتم ذلك باستخدام ملفات .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); } 

بعد إجراء تغييرات على ملف الواجهات ، تحتاج إلى إعادة إنشاء واجهات Java / Objective-C / C ++. يعد ذلك سهلاً من خلال تشغيل البرنامج النصي create_wrappers.sh من مجلد react-native-cpp-support / idl / folder. سيقوم هذا البرنامج النصي بجمع كل الإعلانات من ملف 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!")); } 

يرجى ملاحظة أن النتيجة يتم إرجاعها دون استخدام إرجاع الكلمات الرئيسية ، ولكن باستخدام كائن 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)); // our sample function this.appendLine("testString: " + await DemoModule.testString(DemoModule.STRING_CONSTANT)); } 

أصبح من الواضح الآن كيف تعمل وظائف الاختبار بجانب C ++ و JavaScript. وبالمثل ، يمكنك إضافة رمز أي وظائف أخرى. علاوة على ذلك ، سوف أفكر في كيفية عمل مشاريع Android و iOS مع C ++.

الرد الأصلي و 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 // If true, also generate a universal APK include "armeabi-v7a", "x86" } } ... } 

لقد قمنا للتو بتهيئة gradle لإنشاء تطبيق للبنية المستخدمة وأضفنا علامات البناء اللازمة لـ cmake ، وحددنا ملف CMAkeLists ، الذي سنصفه في المستقبل ، وأضفنا أيضًا فئات java من Djinni التي سنستخدمها.
الخطوة التالية في إعداد مشروع Android هي وصف ملف CMakeLists.txt . في الشكل النهائي ، يمكن مشاهدته على طول مسار رد الفعل 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 ، فيمكنك إنشاء وتشغيل مشروع React Native على Android. للقيام بذلك ، قم بتنفيذ أمرين في الجهاز الطرفي:

تثبيت npm
npm تشغيل الروبوت

الصيحة! مشروعنا يعمل!

ونحن نرى الصورة التالية على شاشة محاكي Android (قابل للنقر):



الآن دعونا نرى كيف تعمل iOS و React Native مع C ++.

تتفاعل الأصلية و C ++ لنظام التشغيل iOS


افتح مشروع react-native-cpp في Xcode.

أولاً ، أضف روابط إلى الكود المستخدم في مشروع Objective-C و C ++ من مكتبة الدعم. للقيام بذلك ، قم بنقل محتويات مجلد react-native-cpp-support / support-lib / objc / و react-native-cpp-support / support-lib / cpp / المجلدات إلى مشروع Xcode . نتيجة لذلك ، سيتم عرض المجلدات التي بها مكتبة دعم الشفرة في شجرة هيكل المشروع (الصور قابلة للنقر):





وبالتالي ، أضفنا أوصافًا لأنواع JavaScript من مكتبة الدعم إلى المشروع.

والخطوة التالية هي إضافة الأغلفة الموضوعية المولدة لوحدة اختبار 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]; ... } 

الآن إطلاق التطبيق لدينا على دائرة الرقابة الداخلية. (الصورة قابلة للنقر)




التطبيق يعمل!

إضافة مكتبة C ++ إلى مشروعنا.


على سبيل المثال ، نستخدم مكتبة OpenSSL الشائعة.

ودعنا نبدأ مع Android.

نقوم باستنساخ المستودع من خلال مكتبة OpenSSL المضمنة بالفعل لنظام Android.

قم بتضمين مكتبة OpenSSL في ملف 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) 

ثم نضيف إلى الوحدة النمطية 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); 

نسمي البرنامج النصي " gener_wrappers.sh` " من المجلد `react-native-cpp-support / idl /` .

بعد ذلك ، في JavaScript ، نسمي الوظيفة المنشأة حديثًا:

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

كل شيء جاهز لنظام Android.
دعنا ننتقل إلى دائرة الرقابة الداخلية.

نقوم باستنساخ المستودع بالإصدار المجمع لمكتبة OpenSSL لنظام التشغيل iOS.

افتح مشروع iOS في Xcode وفي الإعدادات الموجودة في علامة التبويب Build Settings ، أضف المسار إلى مكتبة opensl في حقل Other C Flags (مثال على المسار الموجود على جهاز الكمبيوتر الخاص بي أدناه):
-I / المستخدمين / andreysaleba / المشاريع / prebuilt-openssl / dist / openssl-1.0.2d-ios / include

في حقل علامات Linker الأخرى ، أضف الأسطر التالية:

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

كل شيء جاهز. تمت إضافة مكتبة OpenSSL لكلا المنصتين.

شكرا للمشاهدة!

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


All Articles