# Runtime Interface ## 相关链接 代码文件: - {{ '[aimrt_core.h]({}/src/runtime/core/aimrt_core.h)'.format(code_site_root_path_url) }} 参考示例: - {{ '[helloworld]({}/src/examples/cpp/helloworld)'.format(code_site_root_path_url) }} - {{ '[helloworld_app_registration_mode]({}/src/examples/cpp/helloworld/app/helloworld_app_registration_mode)'.format(code_site_root_path_url) }} - {{ '[helloworld_app]({}/src/examples/cpp/helloworld/app/helloworld_app)'.format(code_site_root_path_url) }} - {{ '[normal_publisher_app]({}/src/examples/cpp/pb_chn/app/normal_publisher_app)'.format(code_site_root_path_url) }} - {{ '[normal_subscriber_app]({}/src/examples/cpp/pb_chn/app/normal_subscriber_app)'.format(code_site_root_path_url) }} ## 简介 如果说逻辑开发阶段的 C++ 接口主要是让用户开发具体的业务逻辑,那么本文档所介绍的**运行时接口**则是让用户决定如何部署、集成、运行这些业务逻辑。 AimRT 提供了两种部署集成方式: - **App 模式**:开发者在自己的 Main 函数中注册/创建各个模块,编译时直接将业务逻辑编译进主程序; - **Pkg 模式**:使用 AimRT 提供的**aimrt_main**可执行程序,在运行时根据配置文件加载动态库形式的`Pkg`,导入其中的`Module`; 两者的优劣势和适用场景请参考[AimRT 中的基本概念](../concepts/concepts.md)文档里的说明。 无论采用哪种方式都不影响业务逻辑,且两种方式可以共存,也可以比较简单的进行切换,实际采用哪种方式需要根据具体场景进行判断。 关于`aimrt::CoreRef`句柄的相关信息,请参考[CoreRef](./core_ref.md)文档。 ## App 模式 开发者直接引用 CMake Target:**aimrt::runtime::core**,然后即可使用{{ '[core/aimrt_core.h]({}/src/runtime/core/aimrt_core.h)'.format(code_site_root_path_url) }}文件中的`aimrt::runtime::core::AimRTCore`类,在 App 模式下需要使用的核心接口如下: ```cpp namespace aimrt::runtime::core { class AimRTCore { public: struct Options { std::string cfg_file_path; }; public: void Initialize(const Options& options); void Start(); std::future AsyncStart(); void Shutdown(); // ... module::ModuleManager& GetModuleManager(); // ... }; } // namespace aimrt::runtime::core ``` 接口使用说明如下: - `void Initialize(const Options& options)`:用于初始化框架。 - 接收一个`AimRTCore::Options`作为初始化参数。其中最重要的项是`cfg_file_path`,用于设置配置文件路径。 - 如果初始化失败,会抛出一个异常。 - `void Start()`:启动框架。 - 如果启动失败,会抛出一个异常。 - 必须在 Initialize 方法之后调用,否则行为未定义。 - 如果启动成功,会阻塞当前线程,并将当前线程作为本`AimRTCore`实例的主线程。 - `std::future AsyncStart()`:异步启动框架。 - 如果启动失败,会抛出一个异常。 - 必须在 Initialize 方法之后调用,否则行为未定义。 - 如果启动成功,会返回一个`std::future`句柄,在外部可以调用该句柄的`wait`方法阻塞等待结束。 - 该方法会在内部新启动一个线程作为本`AimRTCore`实例的主线程。 - `void Shutdown()`:停止框架。 - 可以在任意线程、任意阶段调用此方法,也可以调用任意次数。 - 调用此方法后,`Start`方法将在执行完主线程中的所有任务后,退出阻塞。 - 需要注意,有时候业务会阻塞住主线程中的任务,导致`Start`方法无法退出阻塞、优雅结束整个框架,此时需要在外部强制 kill。 开发者可以在自己的 Main 函数中创建一个`AimRTCore`实例,依次调用其`Initialize`、`Start`/`AsyncStart`方法,并可以自己捕获`Ctrl-C`信号来调用`Shutdown`方法,以实现`AimRTCore`实例的优雅退出。 `AimRTCore`类型的`GetModuleManager`方法可以返回一个`ModuleManager`句柄,可以用来注册或创建模块,App 模式下需要使用其提供的`RegisterModule`接口或`CreateModule`接口: ```cpp namespace aimrt::runtime::core::module { class ModuleManager { public: void RegisterModule(const aimrt_module_base_t* module); const aimrt_core_base_t* CreateModule(std::string_view module_name); }; } // namespace aimrt::runtime::core::module ``` `RegisterModule`和`CreateModule`代表了 App 模式下编写逻辑的两种方式:**注册模块**方式与**创建模块**方式,前者仍然需要编写一个继承于`ModuleBase`类的业务模块类,后者则更加自由。 ### 注册模块 通过`RegisterModule`可以直接注册一个标准模块。开发者需要先实现一个继承于`ModuleBase`基类的`Module`,然后在`AimRTCore`实例调用`Initialize`方法之前注册该`Module`实例,在此方式下仍然有一个比较清晰的`Module`边界。 关于`ModuleBase`基类的相关信息,请参考[ModuleBase](./module_base.md)文档。 以下是一个简单的例子,开发者需要编写的`main.cc`文件如下: ```cpp #include #include "core/aimrt_core.h" #include "aimrt_module_cpp_interface/module_base.h" AimRTCore* global_core_ptr_ = nullptr; void SignalHandler(int sig) { if (global_core_ptr_ && (sig == SIGINT || sig == SIGTERM)) { global_core_ptr_->Shutdown(); return; } raise(sig); }; class HelloWorldModule : public aimrt::ModuleBase { public: HelloWorldModule() = default; ~HelloWorldModule() override = default; ModuleInfo Info() const override { return ModuleInfo{.name = "HelloWorldModule"}; } bool Initialize(aimrt::CoreRef core) override { return true; } bool Start() override { return true; } void Shutdown() override {} }; int32_t main(int32_t argc, char** argv) { signal(SIGINT, SignalHandler); signal(SIGTERM, SignalHandler); // Create AimRTCore ins AimRTCore core; global_core_ptr_ = &core; // Register module HelloWorldModule helloworld_module; core.GetModuleManager().RegisterModule(helloworld_module.NativeHandle()); // Initialize AimRTCore ins AimRTCore::Options options; options.cfg_file_path = "path/to/cfg/xxx_cfg.yaml"; core.Initialize(options); // Start AimRTCore ins, will block here core.Start(); // Shutdown AimRTCore ins core.Shutdown(); return 0; } ``` 编译上面示例的`main.cc`,直接启动编译后的可执行文件即可运行进程,按下`ctrl-c`后即可停止进程。 详细示例代码请参考: - {{ '[helloworld_app_registration_mode]({}/src/examples/cpp/helloworld/app/helloworld_app_registration_mode)'.format(code_site_root_path_url) }} ### 创建模块 在`AimRTCore`实例调用`Initialize`方法之后,通过`CreateModule`可以创建一个模块,并返回一个`aimrt::CoreRef`句柄,开发者可以直接基于此句柄调用一些框架的方法,比如 RPC 或者 Log 等。在此方式下没有一个比较清晰的`Module`边界,不利于大型项目的组织,一般仅用于快速做一些小工具。 以下是一个简单的例子,实现了一个发布 channel 消息的功能,开发者需要编写的`main.cc`文件如下: ```cpp #include "core/aimrt_core.h" #include "aimrt_module_cpp_interface/core.h" #include "aimrt_module_protobuf_interface/channel/protobuf_channel.h" #include "event.pb.h" int32_t main(int32_t argc, char** argv) { // Create AimRTCore ins AimRTCore core; // Initialize AimRTCore ins AimRTCore::Options options; options.cfg_file_path = "path/to/cfg/xxx_cfg.yaml"; core.Initialize(options); // Create module aimrt::CoreRef core_handle(core.GetModuleManager().CreateModule("HelloWorldModule")); // Register a msg type for publish auto publisher = core_handle.GetChannelHandle().GetPublisher("test_topic"); aimrt::channel::RegisterPublishType(publisher); // Start AimRTCore ins auto fu = core.AsyncStart(); // Publish a message ExampleEventMsg msg; msg.set_msg("example msg"); aimrt::channel::Publish(publisher, msg); // Wait for seconds std::this_thread::sleep_for(std::chrono::seconds(5)); // Shutdown AimRTCore ins core.Shutdown(); // Wait for complete shutdown fu.wait(); return 0; } ``` 编译上面示例的`main.cc`,直接启动编译后的可执行文件即可运行进程,该进程将在发布一个消息后,等待一段时间并退出。 更多示例请参考: - {{ '[helloworld_app]({}/src/examples/cpp/helloworld/app/helloworld_app)'.format(code_site_root_path_url) }} - {{ '[normal_publisher_app]({}/src/examples/cpp/pb_chn/app/normal_publisher_app)'.format(code_site_root_path_url) }} - {{ '[normal_subscriber_app]({}/src/examples/cpp/pb_chn/app/normal_subscriber_app)'.format(code_site_root_path_url) }} ## Pkg 模式 ### 创建 Pkg 开发者可以引用 CMake Target:**aimrt::interface::aimrt_pkg_c_interface**,在其中的头文件{{ '[aimrt_pkg_c_interface/pkg_main.h]({}/src/interface/aimrt_pkg_c_interface/pkg_main.h)'.format(code_site_root_path_url) }}中,定义了几个要实现的接口: ```cpp #ifdef __cplusplus extern "C" { #endif // Get the num of modules in the pkg size_t AimRTDynlibGetModuleNum(); // Get the list of module names in the pkg const aimrt_string_view_t* AimRTDynlibGetModuleNameList(); // Create a module with the name const aimrt_module_base_t* AimRTDynlibCreateModule(aimrt_string_view_t module_name); // Destroy module void AimRTDynlibDestroyModule(const aimrt_module_base_t* module_ptr); #ifdef __cplusplus } #endif ``` 其中,`aimrt_module_base_t`可以由继承了`ModuleBase`基类的派生类获得。关于`ModuleBase`基类的相关信息,请参考[ModuleBase](./module_base.md)文档。 通过这些接口,AimRT 框架运行时可以从 Pkg 动态库中获取想要的模块。开发者需要在一个`C/CPP`文件中实现这些接口来创建一个 Pkg。 这些接口是纯 C 形式的,所以理论上只要开发者将 Pkg 的符号都隐藏起来,不同 Pkg 之间可以做到较好的兼容性。如果开发者使用 C++,也可以使用{{ '[aimrt_pkg_c_interface/pkg_macro.h]({}/src/interface/aimrt_pkg_c_interface/pkg_macro.h)'.format(code_site_root_path_url) }}文件中的一个简单的宏来封装这些细节,用户只需要实现一个包含所有模块构造方法的静态数组即可。 以下是一个简单的示例,开发者需要编写如下的`pkg_main.cc`文件: ```cpp #include "aimrt_pkg_c_interface/pkg_macro.h" #include "bar_module.h" #include "foo_module.h" static std::tuple> aimrt_module_register_array[]{ {"FooModule", []() -> aimrt::ModuleBase* { return new FooModule(); }}, {"BarModule", []() -> aimrt::ModuleBase* { return new BarModule(); }}}; AIMRT_PKG_MAIN(aimrt_module_register_array) ``` ### 启动 Pkg 将上面的示例`pkg_main.cc`编译为动态库后,即可使用 AimRT 提供的**aimrt_main**可执行程序启动进程,通过配置中指定的路径来加载 Pkg 动态库。示例配置如下: ```yaml aimrt: module: pkgs: - path: /path/to/your/pkg/libxxx_pkg.so ``` 有了配置文件之后,通过以下示例命令启动 AimRT 进程,按下`ctrl-c`后即可停止进程: ```shell ./aimrt_main --cfg_file_path=/path/to/your/cfg/xxx_cfg.yaml ``` AimRT官方提供**aimrt_main**可执行程序接收一些参数作为 AimRT 运行时的初始化参数,这些参数功能如下: | 参数项 | 类型 | 默认值 |作用 | 示例 | | ---- | ---- | ---- | ---- | ---- | | cfg_file_path | string | "" | 配置文件路径。 | --cfg_file_path=/path/to/your/xxx_cfg.yaml | | dump_cfg_file | bool | false | 是否 Dump 配置文件。 | --dump_cfg_file=true | | dump_cfg_file_path | string | "./dump_cfg.yaml" | Dump 配置文件的路径。 | --dump_cfg_file_path=/path/to/your/xxx_dump_cfg.yaml | | dump_init_report | bool | false | 是否 Dump 初始化报告。
请注意,仅当初始化成功后才有初始化报告。 | --dump_init_report=true | | dump_init_report_path | string | "./init_report.txt" | Dump 初始化报告的路径。 | --dump_init_report_path=/path/to/your/xxx_init_report.txt | | register_signal | bool | true | 是否注册 sigint 和 sigterm 信号,用于触发 Shutdown。 | --register_signal=true | | running_duration | int32 | 0 | 本次运行时间,单位:s。如果为 0 则表示一直运行。 | --running_duration=10 | 开发者也可以使用`./aimrt_main --help`命令查看这些参数功能。