zhangyi1357 7d5fe68319 update
2024-09-30 17:22:32 +08:00

318 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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<void> 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<void> AsyncStart()`:异步启动框架。
- 如果启动失败,会抛出一个异常。
- 必须在 Initialize 方法之后调用,否则行为未定义。
- 如果启动成功,会返回一个`std::future<void>`句柄,在外部可以调用该句柄的`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 <csignal>
#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<ExampleEventMsg>(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<std::string_view, std::function<aimrt::ModuleBase*()>> 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 初始化报告。<br>请注意,仅当初始化成功后才有初始化报告。 | --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`命令查看这些参数功能。