Runtime Interface
相关链接
代码文件:
参考示例:
简介
如果说逻辑开发阶段的 C++ 接口主要是让用户开发具体的业务逻辑,那么本文档所介绍的运行时接口则是让用户决定如何部署、集成、运行这些业务逻辑。
AimRT 提供了两种部署集成方式:
App 模式:开发者在自己的 Main 函数中注册/创建各个模块,编译时直接将业务逻辑编译进主程序;
Pkg 模式:使用 AimRT 提供的aimrt_main可执行程序,在运行时根据配置文件加载动态库形式的
Pkg
,导入其中的Module
;
两者的优劣势和适用场景请参考AimRT 中的基本概念文档里的说明。
无论采用哪种方式都不影响业务逻辑,且两种方式可以共存,也可以比较简单的进行切换,实际采用哪种方式需要根据具体场景进行判断。
关于aimrt::CoreRef
句柄的相关信息,请参考CoreRef文档。
App 模式
开发者直接引用 CMake Target:aimrt::runtime::core,然后即可使用core/aimrt_core.h文件中的aimrt::runtime::core::AimRTCore
类,在 App 模式下需要使用的核心接口如下:
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
接口:
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文档。
以下是一个简单的例子,开发者需要编写的main.cc
文件如下:
#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
后即可停止进程。
详细示例代码请参考:
创建模块
在AimRTCore
实例调用Initialize
方法之后,通过CreateModule
可以创建一个模块,并返回一个aimrt::CoreRef
句柄,开发者可以直接基于此句柄调用一些框架的方法,比如 RPC 或者 Log 等。在此方式下没有一个比较清晰的Module
边界,不利于大型项目的组织,一般仅用于快速做一些小工具。
以下是一个简单的例子,实现了一个发布 channel 消息的功能,开发者需要编写的main.cc
文件如下:
#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
,直接启动编译后的可执行文件即可运行进程,该进程将在发布一个消息后,等待一段时间并退出。
更多示例请参考:
Pkg 模式
创建 Pkg
开发者可以引用 CMake Target:aimrt::interface::aimrt_pkg_c_interface,在其中的头文件aimrt_pkg_c_interface/pkg_main.h中,定义了几个要实现的接口:
#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文档。
通过这些接口,AimRT 框架运行时可以从 Pkg 动态库中获取想要的模块。开发者需要在一个C/CPP
文件中实现这些接口来创建一个 Pkg。
这些接口是纯 C 形式的,所以理论上只要开发者将 Pkg 的符号都隐藏起来,不同 Pkg 之间可以做到较好的兼容性。如果开发者使用 C++,也可以使用aimrt_pkg_c_interface/pkg_macro.h文件中的一个简单的宏来封装这些细节,用户只需要实现一个包含所有模块构造方法的静态数组即可。
以下是一个简单的示例,开发者需要编写如下的pkg_main.cc
文件:
#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 动态库。示例配置如下:
aimrt:
module:
pkgs:
- path: /path/to/your/pkg/libxxx_pkg.so
有了配置文件之后,通过以下示例命令启动 AimRT 进程,按下ctrl-c
后即可停止进程:
./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
命令查看这些参数功能。