BehaviorTree.CPP v3从动态库加载叶节点
文章目录
问题汇总
找不到生成的包含叶节点的动态库
terminate called after throwing an instance of 'BT::RuntimeError'
what(): could not load library: libWaitActionBtNode.so: cannot open shared object file: No such file or directory
问题原因:
传递到BT::BehaviorTreeFactory::registerFromPlugin()
函数中的路径并不是绝对路径。
解决办法:
- 传递动态库的绝对路径(推荐)
- 或使用相对于执行位置的相对路径。
无法定位其他第三方依赖库的符号
terminate called after throwing an instance of 'BT::RuntimeError'
what(): could not load library: libWaitActionBtNode.so: undefined symbol: _ZN13rclcpp_action10ClientBase33get_number_of_ready_subscriptionsEv
问题原因:
在我的工程中,实际依赖了ROS2包rclcpp_action,但却没有在CMakeLists.txt中标明,这虽然没有在生成libWaitActionBtNode.so的时候报错,但是却在动态加载时出错。
解决办法:
CMakeLists.txt增加以下内容
find_package(rclcpp REQUIRED)
find_package(rclcpp_action REQUIRED)
让cmake查找依赖包,然后在add_library(WaitActionBtNode Shared src/action/WaitAction.cpp)
后添加
ament_target_dependencies(WaitActionBtNode
rclcpp
rclcpp_action
)
找不到叶节点导出符号BT_RegisterNodesFromPlugin
ERROR loading library: can't find symbol [BT_RegisterNodesFromPlugin]
问题原因
BehaviorTree规定,定义叶节点so库,需要调用一个宏BT_REGISTER_NODES
,原型如下
#define BT_REGISTER_NODES(factory)\
static void BT_RegisterNodesFromPlugin(BT::BehaviorTreeFactory& factory)
在自定义的叶节点WaitAction.cpp文件中
BT_REGISTER_NODES(factory)
{
BT::NodeBuilder builder = [](const std::string & strName, const BT::NodeConfiguration & config)
{
return std::make_unique<WaitAction>(strName, config);
};
factory.registerBuilder<WaitAction>("wait", builder);
}
结果cmake构建完成之后竟然没有任何BT_RegisterNodesFromPlugin符号
$ objdump -T | grep BT_RegisterNodesFromPlugin
nothing...
$
观察BT_REGISTER_NODES的原型定义,发现,定义的BT_RegisterNodesFromPlugin竟然是一个static
的函数,而且没有人调用它,编译器自动将其识别为无用的代码从而进行优化,所以生成.so时没有任何BT_RegisterNodesFromPlugin
查看libWaitActionBtNode.so生成的符号,没有发现任何BT_相关的导出符号
objdump -T libWaitActionBtNode.so | grep BT_
0000000000000000 g DF .text 0000000000000000 Base BT_RegisterNodesFromPlugin
去掉static关键字
不直接使用BT_REGISTER_NODES宏,而直接定义BT_RegisterNodesFromPlugin函数,去掉static
关键字。
BT_RegisterNodesFromPlugin(BT::BehaviorTreeFactory& factory)
{
BT::NodeBuilder builder = [](const std::string & strName, const BT::NodeConfiguration & config)
{
return std::make_unique<WaitAction>(strName, config);
};
factory.registerBuilder<WaitAction>("wait", builder);
}
查看编译生成的动态库libWaitActionBtNode.so符号,发现了BT_相关的导出符号,但多了一些前缀和后缀
objdump -T libWaitActionBtNode.so | grep BT_
0000000000000000 g DF .text 0000000000000000 Base _Z26BT_RegisterNodesFromPluginRN2BT19BehaviorTreeFactoryE
增加extern "C"前置声明
extern "C" BT_RegisterNodesFromPlugin(BT::BehaviorTreeFactory& factory);
BT_RegisterNodesFromPlugin(BT::BehaviorTreeFactory& factory)
{
BT::NodeBuilder builder = [](const std::string & strName, const BT::NodeConfiguration & config)
{
return std::make_unique<WaitAction>(strName, config);
};
factory.registerBuilder<WaitAction>("wait", builder);
}
查看编译生成的动态库libWaitActionBtNode.so符号,发现了BT_相关的导出符号
objdump -T libWaitActionBtNode.so | grep BT_
0000000000000000 g DF .text 0000000000000000 Base BT_RegisterNodesFromPlugin
此时生成的动态库libWaitActionBtNode.so已经可以正常加载了。
正常解决办法
在bt_factory.h中有一个宏选项,会切换BT_REGISTER_NODES的定义
#if defined(__linux__) || defined __APPLE__
#define BT_REGISTER_NODES(factory)\
extern "C" void __attribute__((visibility("default")))BT_RegisterNodesFromPlugin(BT::BehaviorTreeFactory& factory)
所以在WaitAction.cpp源文件包含头文件“bt_factory.hpp”之前定义BT_PLUGIN_EXPORT
宏可以解决该问题。
#define BT_PLUGIN_EXPORT
#include <bt_factory.hpp>
g++/gcc编译器已经预定了__linux__宏
$ aarch64-linux-gnu-g++ -E -DM -</dev/null | grep linux
#define __linux 1
#define __linux__ 1
#define __gnu_linux__ 1
#define linux 1