高级Turbo模块主题
本主题提供了在创建复杂的Turbo模块时有用的其他信息。
Turbo模块CMAKE帮助程序
kepler_add_turbo_module_library函数是一个CMake帮助程序,它简化了定义Turbo模块库目标的过程,以便将其用于适用于Vega的React Native运行时。它处理Turbo模块库所需的常见配置和优化设置,通过自动应用必要的设置和优化来简化流程。
此帮助程序函数具有以下优点:
- 简化的配置: 通过单个函数调用精简了CMake样板代码
- 一致的最佳实践: 确保所有Turbo模块都遵循相同的配置模式,并为将来推出通用设置提供一个中心位置
- 提高了应用二进制接口 (ABI) 稳定性: 自动应用正确的符号可见性设置
- 优化的性能: 确保应用诸如链接时间和大小优化之类的性能优化
Turbo模块DSO的关键符号合约
Vega平台上的所有Turbo模块都必须遵守两个关键要求:
- 导出EntryPoint函数: Turbo模块库必须导出一个名为autoLinkVegaTurboModulesV1的单一符号。这个符号是Vega运行时用来自动链接和加载您的Turbo模块的入口点。
- 隐藏符号可见性: Turbo模块库中的所有其他符号都应隐藏。这样可以防止与其他库发生符号冲突,确保ABI稳定,缩短Turbo模块加载时间,并且在许多情况下可以减小库的大小。
kepler_add_turbo_module_library帮助程序会自动为您配置这些要求,从而无需手动进行符号可见性管理。
提供的功能
kepler_add_turbo_module_library函数提供了多个关键功能,可简化Turbo模块库的创建。这些功能分为三个主要类别。
创建共享库
帮助程序函数提供对共享库的支持,如下所示:
- 使用标准的CMake add_library命令创建具有指定名称的共享库。
- 将turbomoduleAPI静态库链接到该库,该静态库提供开发Turbo模块所需的C++方法,以及必要的平台级功能。
- 处理库目标的正确设置,类似于手动使用add_library和target_link_libraries所做的设置。
符号可见性控制
帮助程序函数自动配置符号可见性以确保ABI的稳定性:
- 将所有符号的默认可见性设置为“隐藏”
- 应用仅显式导出autoLinkVegaTurboModulesV1符号的版本脚本
- 使用-fno-semantic-interposition禁用符号插入,并使用-Bsymbolic应用全局引用的局部绑定以防止符号冲突
性能优化
帮助程序函数应用了多项优化来改善Turbo模块的性能和大小:
- 启用链路时间优化 (LTO) 以获得更好的性能和更小的二进制文件大小
- 通过对未使用的部分进行垃圾回收来应用大小优化
用法
要使用kepler_add_turbo_module_library函数,请将以下内容添加到您的CMakeLists.txt中。
## 使用“kepler_add_turbo_module_library”需要使用TMAPI程序包
find_package(turbomoduleAPI CONFIG REQUIRED)
## 定义源文件
set(SOURCES
   AutoLinkInit.cpp
   MyTurboModule.cpp
)
## 创建Turbo模块库
kepler_add_turbo_module_library(MyTurboModule ${SOURCES})
常见问题解答
问: 我应该迁移我现有的Turbo模块以使用这个帮助程序函数吗?
答: 是。如果您已有Turbo模块,强烈建议您迁移到此帮助程序函数。目前通过turbomoduleAPI库将优化标记作为INTERFACE标记传播的方法在未来将被删除。此帮助程序函数是更广泛策略的一部分,该策略旨在为Turbo模块提供更明确、更可维护的配置,同时避免隐式传播构建设置,从而导致意外行为。
问: 我应该如何迁移现有的Turbo模块以使用这个帮助程序函数?
答: 请按照以下步骤进行迁移:
- 查看您当前的CMakeLists.txt以了解您的Turbo模块的配置方式
- 确保CMakeLists.txt中有find_package(turbomoduleAPI CONFIG REQUIRED)行
- 将您的add_library调用和所有后续配置替换为单个kepler_add_turbo_module_library调用
- 删除之前用于管理autoLinkVegaTurboModulesV1导出的版本脚本(.map文件)
- 测试您的构建以确保一切正常
以下示例显示了迁移前后的CMake配置。
之前(手动配置)
## 查找turbomoduleAPI程序包
find_package(turbomoduleAPI CONFIG REQUIRED)
## 定义源文件
set(SOURCES
   AutoLinkInit.cpp
   MyTurboModule.cpp
)
## 创建共享库
add_library(MyTurboModule ${SOURCES})
## 链接到turbomoduleAPI库
target_link_libraries(MyTurboModule PRIVATE turbomoduleAPI::turbomoduleAPI)
## 应用版本脚本,以便仅允许导出稳定符号。
target_link_options(MyTurboModule PRIVATE -Wl,--version-script,${CMAKE_CURRENT_SOURCE_DIR}/MyTurboModule_symbols.map)
之后(使用帮助程序)
## 查找turbomoduleAPI程序包
find_package(turbomoduleAPI CONFIG REQUIRED)
## 定义源文件
set(SOURCES
   AutoLinkInit.cpp
   MyTurboModule.cpp
)
## 创建应用所有优化的Turbo模块库
kepler_add_turbo_module_library(MyTurboModule ${SOURCES})
问: 如何使用这个帮助程序函数处理版本脚本? 我可以导出autoLinkVegaTurboModulesV1之外的其他符号吗?
答: 帮助程序函数会自动应用仅导出autoLinkVegaTurboModulesV1符号的版本脚本。如果您已经在使用版本脚本,请从CMake配置中移除现有的版本脚本应用程序,以避免冲突。如果您需要导出autoLinkVegaTurboModulesV1之外的其他符号,则可以在调用帮助程序函数后应用其他版本脚本,但要确保每个脚本导出的符号之间没有重叠之处。应谨慎使用这种方法,因为导出其他符号可能会损害ABI的稳定性。
问: 如何调试kepler_add_turbo_module_library函数的问题?
答: 您可以检查生成的编译数据库(在构建目录中的compile_commands.json)以检查正在使用的编译器和链接器标记。默认情况下,帮助程序函数启用EXPORT_COMPILE_COMMANDS ON,这样将创建此数据库。您可以使用IDE打开数据库,查看应用的确切标记。
问: 如何添加其他目标行为?
答: 帮助程序函数已经处理了特定于平台的行为。如果您需要其他设置,可以在调用帮助程序函数后添加它们:
kepler_add_turbo_module_library(MyTurboModule ${SOURCES})
target_compile_definitions(MyTurboModule PRIVATE MY_CUSTOM_DEFINE=1)
符号隐藏
Vega Turbo模块API (TMAPI) 是一个稳定的API,可提供向后兼容性,从而保证您无需不断更新Turbo模块即可跟上API的变化。这种稳定性可确保通信在ABI方面是安全的,并且您的Turbo模块的编译共享库与任何版本的Vega OS的二进制文件兼容。
编译的原生二进制文件包括符号。符号是命名实体,例如变量、函数、类和命名空间。有时,Turbo模块库中的符号可能会与应用中其他库或操作系统sysroot生成的符号发生冲突,这种无意中泄露的符号可能会导致出现ABI问题。为确保Turbo模块的整体稳定性,亚马逊建议使用符号隐藏。这样可以确保库中的内部符号在默认情况下处于隐藏状态,并避免任何泄露或冲突。
在Turbo模块库中隐藏内部符号很重要,因如下的理由:
- 动态覆盖符号: 当内部符号公开时,来自不同二进制文件的符号可能会相互覆盖,具体取决于它们加载到进程中的顺序。这可能会导致意外行为、兼容性问题和潜在的安全漏洞。
- 标准C++库的不同版本: 当符号公开时,使用不同C++标准库(例如libstdc++或libcpp)编译的二进制文件可能会带来兼容性问题。
在TurboModule项目中隐藏符号
若要在使用Vega SDK v0.9及之前版本创建的Vega Turbo模块项目中隐藏符号,请在源代码中进行如下更改。从V0.10开始,Vega模板包括这些改进,所有新项目的符号都默认处于隐藏状态。
- 
    添加符号映射。 符号映射文件用于指明版本节点导出的符号已标记,还用于隐藏本地符号。Vega有选择地公开用于注册Turbo模块的自动链接功能中的符号,并隐藏所有其他符号。对于使用Vega模板创建的Turbo模块项目,使用的函数是 autoLinkVegaTurboModulesV1。# ./sampleturbomodule_symbols.map { global: autoLinkVegaTurboModulesV1; local: *; };注意: 有关符号版本控制和符号映射文件的更多信息,请参阅在GNU wiki的Symbol Versioning页面(仅提供英文版)。 
- 
    更新 CMakeLists.txt,以在cmake链接器选项中使用符号映射文件。# ./CMakeLists.txt ... add_library(SampleTurboModule SHARED ${HEADERS} ${SOURCES}) ... # 应用版本脚本,以便仅允许导出稳定符号。 target_link_options( SampleTurboModule PRIVATE -Wl, --version-script,${CMAKE_CURRENT_SOURCE_DIR}/sampleturbomodule_symbols.map )target_link_options命令将--version-script链接器选项应用于tmapp目标,该目标在链接过程中使用指定的符号映射文件。
验证符号是否已正确隐藏
要验证符号是否已有效隐藏,可以在应用符号隐藏更改之前和之后检查Turbo模块二进制文件公开的符号。使用Vega SDK中已经提供的llvm-nm工具来查看库中公开的符号。
从kepler sdk缓存文件夹中获取llvm-nm工具的路径
❯ find ~/.kepler -name llvm-nm
~/.kepler/conan/p/keple5543e70392195/p/bin/llvm-nm
列出没有更新的符号
在不隐藏符号的情况下,Turbo模块二进制文件会公开大量内部符号,包括与实现Turbo模块功能相关的符号。
❯ ~/.kepler/conan/p/keple5543e70392195/p/bin/llvm-nm -C -D --defined-only build/vega-tv2023-armv7-release/lib/libtmapp.so | grep "tmapp:"
000f55a4 T tmappTurboModule::tmapp::getConstants()
000f57b4 T tmappTurboModule::tmapp::getArrayBuffer(com::amazon::kepler::turbomodule::ArrayBuffer)
000f55f8 T tmappTurboModule::tmapp::getMajorVersion()
000f5600 T tmappTurboModule::tmapp::getMinorVersion()
000f5608 T tmappTurboModule::tmapp::getPatchVersion()
000f5904 T tmappTurboModule::tmapp::getValueWithPromise(bool)
000f58b0 T tmappTurboModule::tmapp::getValueWithCallback(com::amazon::kepler::turbomodule::Callback)
000f5664 T tmappTurboModule::tmapp::getBool(bool)
000f5760 T tmappTurboModule::tmapp::getArray(com::amazon::kepler::turbomodule::JSArray)
000f585c T tmappTurboModule::tmapp::getValue(double, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>, com::amazon::kepler::turbomodule::JSObject)
000f5610 T tmappTurboModule::tmapp::voidFunc()
000f56b8 T tmappTurboModule::tmapp::getNumber(double)
000f5808 T tmappTurboModule::tmapp::getObject(com::amazon::kepler::turbomodule::JSObject)
000f570c T tmappTurboModule::tmapp::getString(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>)
000f5568 T tmappTurboModule::tmapp::tmapp()
000f5568 T tmappTurboModule::tmapp::tmapp()
000f558c T tmappTurboModule::tmapp::~tmapp()
000f5588 T tmappTurboModule::tmapp::~tmapp()
000f5588 T tmappTurboModule::tmapp::~tmapp()
在符号隐藏更新后列出符号
实现符号隐藏后,Turbo模块二进制文件仅公开预期的稳定公共API符号。
❯ ~/.kepler/conan/p/keple5543e70392195/p/bin/llvm-nm -C -D --defined-only build/vega-tv2023-armv7-release/lib/libtmapp.so
000757e8 T autoLinkVegaTurboModulesV1
线程
所有Turbo模块方法都在JSThread上调用。为了保持良好的性能,必须避免阻塞JSThread,只要有益处就使用单独的非JSThread,例如用于Promise或Callback时。
根据具体要求,可以使用线程实用工具库,甚至直接使用std::thread为Turbo模块创建和管理单独的非JSThread。
事件处理
Turbo模块可以将事件从C++发送到JavaScript,但是@amzn/keplerscript-turbomodule-api程序包目前不支持处理这些事件。要侦听事件,可以使用react-native中的NativeEventEmitter。
Turbo模块可以使用内置的emit方法向JavaScript发送事件信号,该方法接受大多数Turbo模块C++类型作为有效负载。在JSThread上(即,没有创建新的线程来发出事件)时,应改为使用emitSync。
void SampleTurboModule::testEmit(std::string eventName) {
  std::thread([self = this, eventName]() {
    JSObject payload{};
    payload["StringValue"] = "JSObject的stringMessage";
    payload["intValue"] = 123;
    payload["doubleValue"] = 45.67;
    self->emit(eventName, payload);
  }).detach();
}
JavaScript端可以注册为使用NativeEventEmitter来接收事件。以下示例以实现JavaScript层中的说明为基础。
import { NativeEventEmitter } from 'react-native';
import PingerModule from './NativePinger';
class Pinger {
    __eventEmitter: NativeEventEmitter = null;
    constructor() {
        this.__eventEmitter = new NativeEventEmitter();
    }
    testEmit: (key: string) => {
        const eventName = 'SampleEvent';
        const subscription = this.__eventEmitter.addListener(
            eventName,
            (args) => {
                console.log('From JS: addListener ', args);
            }
        );
        SampleTurboModule.testEmit(eventName); // 这会发出“SampleEvent”,该事件将触发以上订阅
        subscription.remove(); // 确保对订阅进行清理
    }
    ...
}
export default new Pinger();
错误处理
运行Turbo模块时可能遇到的许多异常均抛出到JavaScript层中。如果有一个封装在原生模块上的JavaScript实现,则可以使用try/catch解决这些错误。
相关主题
Last updated: 2025年9月30日

