qt qml 中 可以把 c++对象注入 qml engine
reac native 自从收编 nitro 之后,也获得了这种能力
我已经用qt qml 在windows/linux/mac / embed linux中写gui, 但对于android和ios 一直没有实践
1是确实没有什么项目, 2是lgpl 和商城规则或多或少存在争议
所以在知道react native也可以这么说后,我打算开始玩一玩, 尝试把我自己写的c++ 静态反射库应用于其中
于是有此笔记,将和往常一样不定时整理更新
1 配置sdk版本,及兼容的最低版本
1.1 IOS
1.1.1 SDK版本选择
sdk向下兼容是有限制的,所以想开发兼容很旧的ios系统的程序,只能下载旧的xcode版本开发,且开发后上不了架,并且ios系统不一定能运行旧版本的xcode, 想开发不上架的ios app,有可能需要装虚拟机,安装旧的系统,再安装旧的xcode,再开发
xcode 版本支持的sdk版本支持参考这里
因为apple store要求sdk需要使用较新版本,所以想上架的话, 最好在apple store安装最新的xcode,更新最新的sdk
apple store 安装完的xcode 第一次打开时可能有弹出一个选择安装sdk的版本,如果有的话把ios sdk 打钩安装
下载的xcode.xip解压的好像没有这个弹窗 ,有的话也是把ios sdk打钩安装(这试了几次都没有)
选择了xcode就等于选择了sdk 版本了,想改只能切换xcode版本
附上一些在命令行中切换 xcode和sdk版本的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
#选择xcode
xcode-select -p
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
xcodebuild -showsdks
#查看可用sdk
xcodebuild -showsdks
ls `xcode-select -p`/Platforms/MacOSX.platform/Developer/SDKs/
#查看当前sdk
xcrun --show-sdk-path
xcrun --sdk macosx --show-sdk-version
#临时配置sdk
export SDKROOT=`xcode-select -p`/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.3.sdk
#编译时配置sdk
cd build && xcodebuild -sdk macosx13.3
#or
#cmake
cmake .. -DCMAKE_OSX_SYSROOT=`xcode-select -p`/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.3.sdk
#GN
gn gen out/Default --args='mac_sdk_path="`xcode-select -p`/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.3.sdk"'
|
1.1.2 向下兼容至何种版本
向下兼容至何种版本,则需要开发者指定, 且只能在xcode 支持的版本范围中选 择参考这里
有几个位置需要修改 ,以xcode 16为例,它支持 ios 16.0 - 18.2的开发, 我想兼容到16.0
…/react_active_src/ios/Podfile中的
1
|
platform :ios, 16.0 #安装哪种sdk的依赖库
|
如果已经安装过pods了,需要删除一下,重新安装 删除Podfile.lock ,还有pods目录,重新安装依赖
1
2
3
|
rm -rf .../react_active_src/ios/pods
rm .../react_active_src/ios/Podfile.lock
cd .../react_active_src/ios/ && pod install
|
xcode打开工程设置

如果是真机,直接选择,不用安装simulator, 如果是simulator, 提交安装想兼容的最低版本 , 最新版本的simualtor也需要安装,否则选择不了其他低版本的simulator , 图上图我选中iPhone 14 pro,但安装了simualtor 16.0和 18.2才可以选择16.0
general 页设置minimum deployments

build setting

显示为灰色的ios 18.2是目前的xcode最新的sdk, 不管选中的minimum deployments是什么版本, 都是用最新的sdk编译的
1.2 Andorid
react native 版本影响 JDK版本, 和 Gradle 包管理器的版本 , 它们只是参与了工程构建,不影响app 上架, 根据具体使用的react native版本按需要选择
1.2.1 SDK版本选择
指定版本需要修改
…/react_active_src/android/build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
buildscript {
//额外属性 extra
ext {
buildToolsVersion = "35.0.0"
minSdkVersion = 24
compileSdkVersion = 35
targetSdkVersion = 35
ndkVersion = "27.1.12297006"
kotlinVersion = "2.0.21"
}
//源
repositories {
google()
mavenCentral()
}
//依赖
dependencies {
classpath("com.android.tools.build:gradle")
classpath("com.facebook.react:react-native-gradle-plugin")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin")
}
}
apply plugin: "com.facebook.react.rootproject"
|
不同的目录层级可能会有多个build.gradle, 但版本相关的设置都会引用根目录的build.gradle中ext中指定的版本
1
2
3
4
5
6
7
8
9
|
AwesomeProject/android on main [!?] via 🅶 v8.13 via ☕ v17.0.15 took 17m22s
❯ grep -irn "rootproject" .
./app/build.gradle:76: ndkVersion rootProject.ext.ndkVersion
./app/build.gradle:77: buildToolsVersion rootProject.ext.buildToolsVersion
./app/build.gradle:78: compileSdk rootProject.ext.compileSdkVersion
./app/build.gradle:83: minSdkVersion rootProject.ext.minSdkVersion
./app/build.gradle:84: targetSdkVersion rootProject.ext.targetSdkVersion
./build.gradle:21:apply plugin: "com.facebook.react.rootproject"
./settings.gradle:4:rootProject.name = 'AwesomeProject'
|
和ios一样, 如果想上架的话,google play也sdk版本有要求
1.2.1 向下兼容至何种版本
andorid 版本, sdk版本, ndk版本, 搭配大致可以参照下表
compileSdkVersion |
Android API |
推荐 NDK 版本 |
支持的 NDK 功能 |
支持的架构 |
34 |
Android 14 |
r26 ~ r27b |
64位架构,arm64-v8a,逐渐放弃旧架构 |
arm64-v8a, armeabi-v7a, x86_64 |
33 |
Android 13 |
r23b ~ r26b |
优化的性能、稳定性支持,支持 Android 12 及以上 |
arm64-v8a, armeabi-v7a, x86_64 |
32 |
Android 12L |
r22b ~ r25c |
支持 Android 12,API 30,逐渐加入新的功能 |
arm64-v8a, armeabi-v7a, x86_64 |
31 |
Android 12 |
r21e ~ r23b |
启用 64 位架构支持,NDK 特性基本稳定 |
arm64-v8a, armeabi-v7a |
30 |
Android 11 |
r20b ~ r22b |
完全支持 arm64-v8a , armeabi-v7a ,向下兼容 |
arm64-v8a, armeabi-v7a |
29 |
Android 10 |
r19c ~ r21b |
默认支持 armeabi-v7a , arm64-v8a , 32 位/64 位 |
armeabi-v7a, arm64-v8a |
28 |
Android 9 |
r18b ~ r20b |
启动 64 位架构支持,目标为 armeabi-v7a |
armeabi-v7a |
27 以下 |
Android 8.x |
r17b ~ r19b |
不支持新的架构,只支持 armeabi-v7a |
armeabi-v7a |
截止2025年5月3日 , react native 0.79.2 的模板版本配置为
1
2
3
4
5
6
|
buildToolsVersion = "35.0.0"
minSdkVersion = 24
compileSdkVersion = 35
targetSdkVersion = 35
ndkVersion = "27.1.12297006"
kotlinVersion = "2.0.21"
|
如果不考虑ndk的话,这个版本已经覆盖市场上95%的手机了, 建议开发新的app就用这个配置
而使用24的ndk版本,则说明 使用android 12以下的系统api有不兼容的风险,尽量避免不稳定的api 的使用即可
由于我主要使用c++ 和 react native进行开发,所以kotlin版本我比较不在意,暂时不探索
2. 在app中添加多个Turbo C++ 模块
个安装环境参考 环境设置
官方文档推荐的作法是使用一个framework 如 expo 进行开发
但听很多吐槽这个框架性能问题的声音,我还是更倾向于不使用framework
于是我找到这个文档getting-started-without-a-framework
在这个基础上, 参考native开发文档探索加入c++ 进行编译
2.1 用Ts生成跨平台胶水代码
进入工程根目录,创建目录 specs
用于存放ts接口 ,然后创建两个接口
- NativeViewModelA.ts
- NativeViewModelB.ts
名称必需以Native
开头,否则不能正常生成c++胶水代码
两个文件内容如下, 最后一行的注册名称建议和文件名保持一致, Spec
按需改,后面会成为一些胶水类的名称后辍
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
//NativeViewModelA.ts
import {TurboModule, TurboModuleRegistry} from 'react-native';
export interface Spec extends TurboModule {
readonly getStr: (input: string) => string;
}
export default TurboModuleRegistry.getEnforcing<Spec>('NativeViewModelA');
//NativeViewModelB.ts
import {TurboModule, TurboModuleRegistry} from 'react-native';
export interface Spec extends TurboModule {
readonly getStr: (input: string) => string;
}
export default TurboModuleRegistry.getEnforcing<Spec>('NativeViewModelB');
|
然后修改package.json,添加codegen 配置
1
2
3
4
5
6
7
8
9
10
|
// ....
"codegenConfig": {
"name": "ViewmodelsSpecs",
"type": "modules",
"jsSrcsDir": "specs",
"android": {
"javaPackageName": "com.viewmodels.specs"
}
},
//....
|
2.1.1 Android
接着执行下面的命令 生胶水代码
如果是android 生成的代码在 …/root/android/app/build
1
2
3
4
5
|
generated/
| source/
| | codegen/
| | | java/
| | | jni/
|
即生成了java接口,也生成了jni 中的c++接口
查了一下,好像目前没办法把Java生成的步骤省略掉,不过生成了不去用它也没有问题
不只有接口定义,还有一些胶水代码, c++ 还有一个cmakelists.txt,后面需要配置到构建系统参与构建
2.1.2 IOS
如果是ios 生成的代码在 ios/build
2.2 理解生成的c++胶水
2.1 中生成的.cpp和.h文件,不是所有都和我们最开始定义的typescript 的接口有直接的关系,
我们先只了解下面这三个类就足够
它们都在ViewmodelsSpecsJsI.h头文件里 (加粗部分为package.json,codegenconfig节点的的name)
后继在写c++实现的的时候,只需要include 这个头文件,继承_CxxSpec类,实现接口即可
_CxxSpecJSI 和 _CxxSpec 为后辍的类,都继参自 TurboModule
Delegate继承了_CxxSpecJSI,也间接继承自TurboModule类
2.2.1 _CxxSpecJSI类
_CxxSpecJSI 只是定义 Ts中的定义的module的方法(没有实现)
以及一个构造函数用于传递TurboModule类构造函数参数jsInvoker ,以便以在c++中执行js
2.2.2 _CxxSpec类
_CxxSpec 这个类重载了 TurboModule的create和getPropertiyNames两个方法
应该在是jsx用直接访问属性用的,暂时没看到具体实现,应该是用报turboModule的默认实现了
以及一个根据配置的名称生成的静态的字符字段
2.2.3 Delegate 类
CxxSpec 的私有友元类Delegate,继承了_CxxSpecJSI , 实现了Ts中定义的那些方法的包装, 主要做参数校验, 以及返回Js绑定给jsx
2.3 实现编码, 添加构建任务
创建shared目录, 创建
- NativeViewModelA.h
- NativeViewModelA.cpp
- NativeViewModelB.h
- NativeViewModelB.cpp
头文件 include ViewmodelsSpecsJsI.h 后
NativeViewModelA 继承 NativeViewModelACxxSpec类
NativeViewModelB 继承 NativeViewModelBCxxSpec类
2.2中已经说过了, CxxSpec类中对应 Ts中定义中的方法 会在其私有友元类Delegate中被包装,进行简单的参数个数校验,以及返回到 Jsx 中
所以在实现时不需要考虑这些东西
2.3.1 实现编码
这个步骤 ios 和android都需要,这些代码都创建在shared目录
//log.h 用于在ios和andorid上用一致的宏记录日志
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
#ifndef HBBLOG
#define HBBLOG
#if __ANDROID__
#include <android/log.h>
#define HBBLOG_TAG "TurboModule"
#define LOG_INFO(...) __android_log_print(ANDROID_LOG_INFO, HBBLOG_TAG, __VA_ARGS__)
#define LOG_ERROR(...) __android_log_print(ANDROID_LOG_ERROR, HBBLOG_TAG, __VA_ARGS__)
#elif __APPLE__
#include <TargetConditionals.h>
#if TARGET_OS_IPHONE
#ifdef __OBJC__
// 只有在 Objective-C/Objective-C++ 编译器中才允许用 NSLog
#import <Foundation/Foundation.h>
#define LOG_INFO(...) NSLog(__VA_ARGS__)
#define LOG_ERROR(...) NSLog(__VA_ARGS__)
#else
// 纯 C++ 模式时不允许 NSString/@
#define LOG_INFO(...) printf(__VA_ARGS__)
#define LOG_ERROR(...) fprintf(stderr, __VA_ARGS__)
#endif
#else
#define LOG_INFO(...) printf(__VA_ARGS__)
#define LOG_ERROR(...) fprintf(stderr, __VA_ARGS__)
#endif
#else
#define LOG_INFO(...) printf(__VA_ARGS__)
#define LOG_ERROR(...) fprintf(stderr, __VA_ARGS__)
#endif
#endif // HBBLOG
// example
//LOG_INFO("Init success: %d", 42);
//LOG_ERROR("Error code: %s", errMessage.c_str());
//LOG_INFO("Initializing YourModule");
|
//NativeViewModelA.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
#include "ViewmodelsSpecsJSI.h"
#include <memory>
#include <string>
namespace facebook::react {
class JSI_EXPORT NativeViewModelA :public NativeViewModelACxxSpec<NativeViewModelA>
{
public :
NativeViewModelA(std::shared_ptr<CallInvoker> jsInvoker);
jsi::String getStr(jsi::Runtime &rt, jsi::String input);
};
}
// namespace facebook::react
|
//NativeViewModelA.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
#include "NativeViewModelA.h"
#include "log.h"
namespace facebook::react {
NativeViewModelA::NativeViewModelA(std::shared_ptr<CallInvoker> jsInvoker)
: NativeViewModelACxxSpec(std::move(jsInvoker)) {}
jsi::String NativeViewModelA::getStr(jsi::Runtime &rt, jsi::String input)
{
LOG_ERROR("get string with native view model A");
return input;
}
} // namespace facebook::react
|
//NativeViewModelB.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
#include "ViewmodelsSpecsJSI.h"
#include <memory>
#include <string>
namespace facebook::react {
class JSI_EXPORT NativeViewModelB :public NativeViewModelBCxxSpec<NativeViewModelB>
{
public :
NativeViewModelB(std::shared_ptr<CallInvoker> jsInvoker);
jsi::String getStr(jsi::Runtime &rt, jsi::String input);
};
}
// namespace facebook::react
|
//NativeViewModelB.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
#include "NativeViewModelB.h"
#include "log.h"
namespace facebook::react {
NativeViewModelB::NativeViewModelB(std::shared_ptr<CallInvoker> jsInvoker)
: NativeViewModelBCxxSpec(std::move(jsInvoker)) {}
jsi::String NativeViewModelB::getStr(jsi::Runtime &rt, jsi::String input)
{
LOG_ERROR("get string with native view model B");
return input;
}
} // namespace facebook::react
|
2.3.2 Android
2.3.2.1 添加到构建系统
android使用gradle 添加额外的cmake 步骤来编译c++代码
在app 根目执行下面命令,创建一个android jni 目录
1
|
android/app/src/main/jni
|
创建app_root/android/app/src/main/jni/CMakeLists.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
cmake_minimum_required(VERSION 3.13)
# Define the library name here.
project(appmodules)
# This file includes all the necessary to let you build your React Native application
include(${REACT_ANDROID_DIR}/cmake-utils/ReactNative-application.cmake)
# Define where the additional source code lives. We need to crawl back the jni, main, src, app, android folders
target_sources(${CMAKE_PROJECT_NAME} PRIVATE
../../../../../shared/NativeViewModelA.cpp
../../../../../shared/NativeViewModelB.cpp
)
# Define where CMake can find the additional header files. We need to crawl back the jni, main, src, app, android folders
target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC ../../../../../shared)
target_link_libraries(${CMAKE_PROJECT_NAME} -llog)
|
添加到 app_root/android/app/build.gradle
的android结点下
1
2
3
4
5
6
7
8
9
|
...
android{
...
externalNativeBuild {
cmake {
path "src/main/jni/CMakeLists.txt"
}
}
}
|
2.3.2.1 创建provider
下载onLoad.cpp ,以它为模板修改Jsx请求c++对象时的创建实例逻辑,可以在这里管理c++实例的生命周期
1
|
cd android/app/src/main/jni && curl -O https://raw.githubusercontent.com/facebook/react-native/v0.76.0/packages/react-native/ReactAndroid/cmake-utils/default-app-setup/OnLoad.cpp
|
修改onLoad.cpp
- include NativeViewModelA.h 和 NativeViewModelB.h
- 修改cxxModuleProvider方法,管理jsx请求的c++实例的创建逻辑
#onLoad.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
...
#include <NativeViewModelA.h>
#include <NativeViewModelB.h>
...
std::shared_ptr<TurboModule> cxxModuleProvider(
const std::string& name,
const std::shared_ptr<CallInvoker>& jsInvoker) {
if (name == NativeViewModelA::kModuleName) {
return std::make_shared<NativeViewModelA>(jsInvoker);
}
else if (name == NativeViewModelB::kModuleName) {
return std::make_shared<NativeViewModelB>(jsInvoker);
}
// And we fallback to the CXX module providers autolinked
return autolinking_cxxModuleProvider(name, jsInvoker);
}
...
|
编译运行
使用andorid sutido 运行,可以在logcat里看到调用时打印的日志
2.3.3 ISO
2.3.3.1 添加到构建系统
ios使用 xcodeproj 编译c++代码



添加Provider类,用来加载模块

2.3.3.3 创建provider
每个npm库应该有一个provider类,当jsx请求module时,为jsx创建实例
在package.json中的codegen结点再加一个ios子节点
1
2
3
4
5
6
|
"ios": {
"modulesProvider": {
"NativeViewModelA": "ViewmodelsProvider",
"NativeViewModelB": "ViewmodelsProvider"
}
}
|
可以自己控件实例的生命周期
选中项目,按 command + N

名称和package.json 中的 codegenconfig节点的的name 字段一样,再加provider后辍就行, 稍后还需要在codegen节点中添加ios provider
//package.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
"codegenConfig": {
"name": "ViewmodelsSpecs",
"type": "modules",
"jsSrcsDir": "specs",
"android": {
"javaPackageName": "com.viewmodels.specs"
},
"ios": {
"modulesProvider": {
"NativeViewModelA": "ViewmodelsProvider",
"NativeViewModelB": "ViewmodelsProvider"
}
}
},
|
2.4 测试
修改app.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
import React from 'react';
import {
Button,
SafeAreaView,
StyleSheet,
Text,
TextInput,
View,
} from 'react-native';
import ViewModelA from './specs/NativeViewModelA';
import ViewModelB from './specs/NativeViewModelB';
function App(): React.JSX.Element {
//const [value, setValue] = React.useState('');
//const [reversedValue, setReversedValue] = React.useState('');
//const onPress = () => {
// const revString = SampleTurboModule.reverseString(value);
// setReversedValue(revString);
//};
const [value1, setValue1] = React.useState('');
const [str1Value, setStr1Value] = React.useState('');
const onPress1 = () => {
const str1 = ViewModelA.getStr(value1);
setStr1Value(str1);
};
const [value2, setValue2] = React.useState('');
const [str2Value, setStr2Value] = React.useState('');
const onPress2 = () => {
const str2 = ViewModelB.getStr(value2);
setStr2Value(str2);
};
return (
<SafeAreaView style={styles.container}>
<View>
<Text style={styles.title}>
Welcome to C++ Turbo Native Module Example
</Text>
<Text>Write down here he text you want to revert</Text>
<TextInput
style={styles.textInput}
placeholder="Write your text here"
onChangeText={setValue1}
value={value1}
/>
<Button title="setstr1" onPress={onPress1} />
<Text>Reversed text: {str1Value}</Text>
<TextInput
style={styles.textInput}
placeholder="Write your text here"
onChangeText={setValue2}
value={value2}
/>
<Button title="setstr1" onPress={onPress2} />
<Text>Reversed text: {str2Value}</Text>
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
title: {
fontSize: 18,
marginBottom: 20,
},
textInput: {
borderColor: 'black',
borderWidth: 1,
borderRadius: 5,
padding: 10,
marginTop: 10,
},
});
export default App;
|
用android stuido 或xcode 调试时,可以看到执行c++代码时的打印
>> Home
Comments