我在之前的一篇文章【在VSCode下利用PlateFormIO开发Arduino的MicroROS遇到的一些问题】中的第10点中,提到一个问题:为啥在使用自定义消息类型时,有时候需要调用 micro_ros_utilities_create_message_memory 函数来对消息对象进行内存申请,而有时候不用呢?
其实答案就在这篇官方的指导/说明文章中:【Handling messages memory in micro-ROS】
是否需要对消息类型进行内存申请,取决于消息的成员类型。
假如你的消息的成员类型是基本类型(Basic type)及其数组类型(Array type),比如bool、byte、char、float32,bool[n]、byte[n]、char[n]、float32[n]等等,那就不用额外进行内存申请的操作,因为这些基本类型在实例化时就已经明确了空间大小,系统直接帮忙分配好了内存。
bool bool_test
byte byte_test
char char_test
float32 float32_test
float64 double_test
int8 int8_test
uint8 uint8_test
int16 int16_test
uint16 uint16_test
int32 int32_test
uint32 uint32_test
int64 int64_test
uint64 uint64_test
但是,假如消息的成员类型为:序列类型(Sequence type)、包含序列的复合类型(Compound type)、复合序列类型(Sequences of compound types),那就需要手动申请内存空间了。当然,假如复合类型中的成员全是基本类型,那也不用手动申请。
上面提到的这些类型,之所以需要人为去申请内存,原因很简单:系统不知道你要多大的空间。
就拿int32的序列int32[](注意,这个不是数组类型,中括号中间没有具体的数值)来说,在micro-ROS中,该类型经过解析后,得到的是这样一个结构体:
typedef struct rosidl_runtime_c__int32__Sequence
{
int32_t* data; /* The pointer to an array of int32 */
size_t size; /* The number of valid items in data */
size_t capacity; /* The number of allocated items in data */
} rosidl_runtime_c__int32__Sequence;
由于size、capacity都是未知的,那么系统如何知道要申请多大的内存空间并把指针值赋予data呢?
因此需要程序员自己手动申请并赋值一下。(这里解释一下上述结构体中的size、capacity的区别,类比电池的话,size表示剩余电量/可用电量,capacity表示电池总容量。这样设计,估计是为了在实例化一次这个对象后,能够对这个对象反复利用)
在这个【Handling messages memory in micro-ROS】文章中提到,在micro-ROS中处理消息的内存有两种方式:手动申请(Manual allocation)、辅助申请(micro-ROS utilities)。
这个就是要求对数据结构的各个成员进行数据填充、内存分配。比如对上面的rosidl_runtime_c__int32__Sequence类型,可以这样子初始化:
rosidl_runtime_c__int32__Sequence values;
values.capacity = 100;
values.data = (int32_t*) malloc(mymsg.values.capacity * sizeof(int32_t));
values.size = 0;
这样子操作比较繁琐,更好的是下面的方法。
在micro-ROS中,官方提供了一些函数及结构,可以让我们相对便捷地对消息类型进行内存管理。
这里看一下例子:
mypackage__msg__MyComplexType mymsg;
static micro_ros_utilities_memory_conf_t conf = {0};
micro_ros_utilities_memory_rule_t rules[] = {
{"multiheaders", 4},
{"multiheaders.frame_id", 60},
{"name", 10}
};
conf.rules = rules;
conf.n_rules = sizeof(rules) / sizeof(rules[0]);
// member named "values" of MyComplexType will have the default max_basic_type_sequence_capacity
bool success = micro_ros_utilities_create_message_memory(
ROSIDL_GET_MSG_TYPE_SUPPORT(mypackage, msg, MyComplexType),
&mymsg,
conf
);
其中,mypackage__msg__MyComplexType类型是这样子的:
typedef struct mypackage__msg__MyComplexType
{
std_msgs__msg__Header__Sequence multiheaders;
rosidl_runtime_c__int32__Sequence values;
double duration;
int8 coefficients[10];
rosidl_runtime_c__String name; // equal to rosidl_runtime_c__char__Sequence
} mypackage__msg__MyComplexType;
header的类型是这样的:
typedef struct std_msgs__msg__Header
{
builtin_interfaces__msg__Time stamp;
rosidl_runtime_c__String frame_id;
} std_msgs__msg__Header;
可以看到,针对mypackage__msg__MyComplexType的成员multiheaders,指定下面的内存申请规则:
...
micro_ros_utilities_memory_rule_t rules[] = {
{"multiheaders", 4}, // 对序列进行长度(capacity)的申请
{"multiheaders.frame_id", 60}, // frame_id是字符串,进行60字节的申请
...
};
...
rule的具体的写法应该是
{"对象成员名称", Sequence的capacity大小}
需要注意的是,序列成员的成员可以直接写,而不用序列号,比如上面的"multiheaders.frame_id"就不用写成"multiheaders.data[0].frame_id"之类的。
另外,对于未在rules中指定的序列类型成员,会按照micro_ros_utilities_memory_conf_t的max_string_capacity、max_ros2_type_sequence_capacity、max_basic_type_sequence_capacity来进行申请,假如需要覆盖默认值micro_ros_utilities_memory_conf_default,可以这样操作:
static micro_ros_utilities_memory_conf_t conf = {0};
conf.max_string_capacity = 50;
conf.max_ros2_type_sequence_capacity = 5;
conf.max_basic_type_sequence_capacity = 5;
定义好规则之后,当调用 micro_ros_utilities_create_message_memory 函数时,应该是对Sequence类型的capacity进行赋值,然后再根据此capacity进行实际内存的计算+申请。(可能会涉及递归过程?)
大概看一下源码,应该是的。具体实现过程有空再分析分析。
参考:
【Handling messages memory in micro-ROS】