相关:
1 引言
本文是 pymavlink 源码剖析 文章的第二篇。上一篇 pymavlink 源码剖析(一)之XML文件的数据解析 主要是分析 pymavlink
对 MAVLINK XML 格式的定义文件是如何解析的,这一篇则分析 pymavlink
是如何在解析获得的结果的基础上生成目标代码的。本篇主要关注于 C
的目标代码生成的实现。即
mavgen_c.generate(opts.output, xml)
的内部实现,如果对 MAVLink 协议还不太熟悉请参考文章目录下方 “相关” 里面给出的链接。
为了方便阅读,这里把上篇的 opts
和 xml
的内容在这里重新列出来
opts
:
definitions: ['/mnt/d/github_/mavl...ommon.xml']
error_limit: 200
language: 'c'
output: '/mnt/d/github_/mavlink_cpp'
strict_units: False
validate: False
wire_protocol: '2.0'
xml
:
[<pymavlink.generator.mavparse.MAVXML object at 0x7f5a30ec1fd0>]
列表中的 MAVXML object
展开为:
allow_extensions: True
basename: 'common'
basename_upper: 'COMMON'
command_24bit: True
crc_extra: True
crc_struct: True
enum: [<pymavlink.generator...a30ed02b0>, <pymavlink.generator...a30ed06d8>, <pymavlink.generator...a30edf208>, <pymavlink.generator...a30edf278>, <pymavlink.generator...a30edf470>, <pymavlink.generator...a30edf898>, <pymavlink.generator...a30edfa90>, <pymavlink.generator...a30edfba8>, <pymavlink.generator...a30edfc18>, <pymavlink.generator...a30ee50b8>, <pymavlink.generator...a30ef0be0>, <pymavlink.generator...a30efb3c8>, <pymavlink.generator...a30efb630>, <pymavlink.generator...a30efba58>, ...]
filename: '/mnt/d/github_/mavlink/message_definitions/v1.0/common.xml'
include: []
largest_payload: 255
little_endian: True
message: [<pymavlink.generator...a30058dd8>, <pymavlink.generator...a3005e2e8>, <pymavlink.generator...a3005e9b0>, <pymavlink.generator...a3005eb00>, <pymavlink.generator...a3005edd8>, <pymavlink.generator...a30063208>, <pymavlink.generator...a300634a8>, <pymavlink.generator...a30063630>, <pymavlink.generator...a30063cf8>, <pymavlink.generator...a30063eb8>, <pymavlink.generator...a300671d0>, <pymavlink.generator...a30067358>, <pymavlink.generator...a300675f8>, <pymavlink.generator...a30067978>, ...]
message_crcs: {0: 50, 1: 124, 2: 137, 4: 237, 5: 217, 6: 104, 7: 119, 8: 117, 11: 89, 20: 214, 21: 159, 22: 220, 23: 168, 24: 24, ...}
message_flags: {0: 0, 1: 0, 2: 0, 4: 3, 5: 1, 6: 0, 7: 0, 8: 0, 11: 1, 20: 3, 21: 3, 22: 0, 23: 3, 24: 0, ...}
message_lengths: {0: 9, 1: 31, 2: 12, 4: 14, 5: 28, 6: 3, 7: 32, 8: 36, 11: 6, 20: 20, 21: 2, 22: 25, 23: 23, 24: 52, ...}
message_min_lengths: {0: 9, 1: 31, 2: 12, 4: 14, 5: 28, 6: 3, 7: 32, 8: 36, 11: 6, 20: 20, 21: 2, 22: 25, 23: 23, 24: 30, ...}
message_names: {0: 'HEARTBEAT', 1: 'SYS_STATUS', 2: 'SYSTEM_TIME', 4: 'PING', 5: 'CHANGE_OPERATOR_CONTROL', 6: 'CHANGE_OPERATOR_CONTROL_ACK', 7: 'AUTH_KEY', 8: 'LINK_NODE_STATUS', 11: 'SET_MODE', 20: 'PARAM_REQUEST_READ', 21: 'PARAM_REQUEST_LIST', 22: 'PARAM_VALUE', 23: 'PARAM_SET', 24: 'GPS_RAW_INT', ...}
message_target_component_ofs: {0: 0, 1: 0, 2: 0, 4: 13, 5: 0, 6: 0, 7: 0, 8: 0, 11: 0, 20: 3, 21: 1, 22: 0, 23: 5, 24: 0, ...}
message_target_system_ofs: {0: 0, 1: 0, 2: 0, 4: 12, 5: 0, 6: 0, 7: 0, 8: 0, 11: 4, 20: 2, 21: 0, 22: 0, 23: 4, 24: 0, ...}
parse_time: 'Fri Mar 27 2020'
protocol_marker: 253
sort_fields: True
version: 3
wire_protocol_version: '2.0'
__len__: 1
2 C 代码生成
pymavlink
生成 C 的原理是模板填充 — 给出代码模板然后把从 XML
中解析处的相关参数填充到其中。对应于C 代码生成的函数主要在 mavgen_c.py
这个文件里面。 在 mavgen_c.py
文件中定义了如下五个函数:
generate
: 代码生成入口函数generate_main_h
: 生成主头文件,所谓主头文件即以XML
文件名的去掉后缀来命名的文件;generate_mavlink_h
: 生成mavlink.h
文件;generate_message_h
: 生成各个message 的头文件;generate_one
: 由generate
调用,分别对每一个XML
生成头文件;generate_version_h
: 生成version.h
文件,其中包括了 MAVLink 的版本号,代码生成时间等;generate_testsuite_h
生成testsuite.h
文件,testsuite.h
为测试生成的头文件的代码。
这里首先从generate
函数看起
703 def generate(basename, xml_list):
704 '''generate complete MAVLink C implemenation'''
705
706 for idx in range(len(xml_list)):
707 xml = xml_list[idx]
708 xml.xml_idx = idx
709 generate_one(basename, xml)
710 copy_fixed_headers(basename, xml_list[0])
参数:
basename
:“common”xml_list
: [<pymavlink.generator.mavparse.MAVXML object at 0x7f5a30ec1fd0>]
从这段代码可以看出其只是对传入进来的 xml_list
列表调用 generate_one
进行解析,由于xml_list
只有一个元素所以循环只会执行一次。在generate_one
解析之后调用了 copy_fixed_headers
函数进行收尾。 对于generate_one
函数相对略复杂单独放在 第3节 说明,这里先分析copy_fixed_headers
函数。
519 def copy_fixed_headers(directory, xml):
520 '''copy the fixed protocol headers to the target directory'''
521 import shutil, filecmp
522 hlist = {
523 "0.9": [ 'protocol.h', 'mavlink_helpers.h', 'mavlink_types.h', 'checksum.h' ],
524 "1.0": [ 'protocol.h', 'mavlink_helpers.h', 'mavlink_types.h', 'checksum.h', 'mavlink_conversions.h' ],
525 "2.0": [ 'protocol.h', 'mavlink_helpers.h', 'mavlink_types.h', 'checksum.h', 'mavlink_conversions.h',
526 'mavlink_get_info.h', 'mavlink_sha256.h' ]
527 }
528 basepath = os.path.dirname(os.path.realpath(__file__))
529 srcpath = os.path.join(basepath, 'C/include_v%s' % xml.wire_protocol_version)
530 print("Copying fixed headers for protocol %s to %s" % (xml.wire_protocol_version, directory))
531 for h in hlist[xml.wire_protocol_version]:
532 src = os.path.realpath(os.path.join(srcpath, h))
533 dest = os.path.realpath(os.path.join(directory, h))
534 if src == dest or (os.path.exists(dest) and filecmp.cmp(src, dest)):
535 continue
536 shutil.copy(src, dest)
参数:
directory
: “common”xml
: <pymavlink.generator.mavparse.MAVXML object at 0x7f5a30ec1fd0>
可以看出,copy_fixed_headers
函数的主要功能就是依据当前的 MAVLink 版本把一些相对不要模板生成的头文件挨个复制到输出文件夹下。
3 generate_one 函数分析
generate_one 函数可以归纳为几部分:
- 对
MAVXML
中的数据进行预处理 - 调用头文件生成函数生成头文件
第 1 部分是 generate_one
的主要功能代码所覆盖的内容,第二部分则由下面几节涵盖。
属性名称明确化,方便后面进行处理
545 directory = os.path.join(basename, xml.basename)
546
547 print("Generating C implementation in directory %s" % directory)
548 mavparse.mkdir_p(directory)
549
550 if xml.little_endian:
551 xml.mavlink_endian = "MAVLINK_LITTLE_ENDIAN"
552 else:
553 xml.mavlink_endian = "MAVLINK_BIG_ENDIAN"
554
555 if xml.crc_extra:
556 xml.crc_extra_define = "1"
557 else:
558 xml.crc_extra_define = "0"
559
560 if xml.command_24bit:
561 xml.command_24bit_define = "1"
562 else:
563 xml.command_24bit_define = "0"
564
565 if xml.sort_fields:
566 xml.aligned_fields_define = "1"
567 else:
568 xml.aligned_fields_define = "0"
·
导入的其他 XML 文件的配置,例如对于 paparazzi.xml ,由于其中包含 <incude> common.xml</incldue>
, 此时xml.inlcude
的值为 common.xml
.
570 # work out the included headers
571 xml.include_list = []
572 for i in xml.include:
573 base = i[:-4]
574 xml.include_list.append(mav_include(base))
mav_include
的定义如下:
538 class mav_include(object):
539 def __init__(self, base):
540 self.base = base
则 xml.include_list
中包含一个 mav_include
实例,该实例的base
属性为common
.
生成 message_lengths_array
字符串,该字符串描述以每个消息的message_min_lengths
拼接而成,以逗号分隔,主要是针对的是 mavlink 0.9 版本。关于message_min_lengths
的含义见 上篇的代码段10
576 # form message lengths array
577 xml.message_lengths_array = ''
578 if not xml.command_24bit:
579 for msgid in range(256):
580 mlen = xml.message_min_lengths.get(msgid, 0)
581 xml.message_lengths_array += '%u, ' % mlen
582 xml.message_lengths_array = xml.message_lengths_array[:-2]
.
生成 crc 字节
‘{0, 50, 9, 9, 0, 0, 0}, ’
584 # and message CRCs array
585 xml.message_crcs_array = ''
586 if xml.command_24bit:
587 # we sort with primary key msgid
588 for msgid in sorted(xml.message_crcs.keys()):
589 xmy.message_crcs_array += '{%u, %u, %u, %u, %u, %u, %u}, ' % (msgid,
590 xml.message_crcs[msgid],
591 xml.message_min_lengths[msgid],
592 xml.message_lengths[msgid],
593 xml.message_flags[msgid],
594 xml.message_target_system_ofs[msgid],
595 xml.message_target_component_ofs[msgid])
596 else:
597 for msgid in range(256):
598 crc = xml.message_crcs.get(msgid, 0)
599 xml.message_crcs_array += '%u, ' % crc
.
生成消息名称
MAVLINK_MESSAGE_INFO_HEARTBEAT, MAVLINK_MESSAGE_INFO_SYS_STATUS
602 # form message info array
603 xml.message_info_array = ''
604 if xml.command_24bit:
605 # we sort with primary key msgid
606 for msgid in sorted(xml.message_names.keys()):
607 name = xml.message_names[msgid]
608 xml.message_info_array += 'MAVLINK_MESSAGE_INFO_%s, ' % name
609 else:
610 for msgid in range(256):
611 name = xml.message_names.get(msgid, None)
612 if name is not None:
613 xml.message_info_array += 'MAVLINK_MESSAGE_INFO_%s, ' % name
614 else:
615 # Several C compilers don't accept {NULL} for
616 # multi-dimensional arrays and structs
617 # feed the compiler a "filled" empty message
618 xml.message_info_array += '{"EMPTY",0,{{"","",MAVLINK_TYPE_CHAR,0,0,0}}}, '
619 xml.message_info_array = xml.message_info_array[:-2]
.
message_name_array
: '{ “ACTUATOR_CONTROL_TARGET”, 140 }, ’
621 # form message name array
622 xml.message_name_array = ''
623 # sort by names
624 for msgid, name in sorted(iteritems(xml.message_names), key=lambda k_v: (k_v[1], k_v[0])):
625 xml.message_name_array += '{ "%s", %u }, ' % (name, msgid)
626 xml.message_name_array = xml.message_name_array[:-2]
.
添加一些包括打印的格式格式字符串
628 # add some extra field attributes for convenience with arrays
629 for m in xml.message:
630 m.msg_name = m.name
631 if xml.crc_extra:
632 m.crc_extra_arg = ", %s" % m.crc_extra
633 else:
634 m.crc_extra_arg = ""
635 for f in m.fields:
636 if f.print_format is None:
637 f.c_print_format = 'NULL'
638 else:
639 f.c_print_format = '"%s"' % f.print_format
640 if f.array_length != 0:
641 f.array_suffix = '[%u]' % f.array_length
642 f.array_prefix = '*'
643 f.array_tag = '_array'
644 f.array_arg = ', %u' % f.array_length
645 f.array_return_arg = '%s, %u, ' % (f.name, f.array_length)
646 f.array_const = 'const '
647 f.decode_left = ''
648 f.decode_right = ', %s->%s' % (m.name_lower, f.name)
649 f.return_type = 'uint16_t'
650 f.get_arg = ', %s *%s' % (f.type, f.name)
651 if f.type == 'char':
652 f.c_test_value = '"%s"' % f.test_value
653 else:
654 test_strings = []
655 for v in f.test_value:
655 for v in f.test_value:
656 test_strings.append(str(v))
657 f.c_test_value = '{ %s }' % ', '.join(test_strings)
658 else:
659 f.array_suffix = ''
660 f.array_prefix = ''
661 f.array_tag = ''
662 f.array_arg = ''
663 f.array_return_arg = ''
664 f.array_const = ''
665 f.decode_left = "%s->%s = " % (m.name_lower, f.name)
666 f.decode_right = ''
667 f.get_arg = ''
668 f.return_type = f.type
669 if f.type == 'char':
670 f.c_test_value = "'%s'" % f.test_value
671 elif f.type == 'uint64_t':
672 f.c_test_value = "%sULL" % f.test_value
673 elif f.type == 'int64_t':
674 f.c_test_value = "%sLL" % f.test_value
675 else:
676 f.c_test_value = f.test_value
.
添加一些域
678 # cope with uint8_t_mavlink_version
679 for m in xml.message:
680 m.arg_fields = []
681 m.array_fields = []
682 m.scalar_fields = []
683 for f in m.ordered_fields:
684 if f.array_length != 0:
685 m.array_fields.append(f)
686 else:
687 m.scalar_fields.append(f)
688 for f in m.fields:
689 if not f.omit_arg:
690 m.arg_fields.append(f)
691 f.putname = f.name
692 else:
693 f.putname = f.const_value
至此所有的第 1 部分关于预处理的内容都分析完了,下面几节分析具体的头文件生成函数
4 MAVTemplate
由于MAVTemplate 的结构不够清晰,这里先阐述其原理,然后再对照代码具体描述其功能。
假设有 python 对象
class A:
crc = '{0,1,2}'
enum = [{'name':'good', 'length':4},{'name':'bad', 'length':3}]
messages = [{'name':'status', 'entry':['header', 'body']}]
a = A()
并假设有下面的模板
crc = ${crc};
${{enum:${name} = ${length};
}}
则对 a
应用模板则会生成
crc = {0, 1, 2};
good = 4;
bad = 3;
首先 ${crc}
会被 a.crc
值替换为 {0,1,2,3}
于是 crc = ${crc}
变成了 crc = {0, 1, 2}
。对于
${{enum:${name} = ${length};
}}
首先会在 a
中找到 enum
属性值,然后其值中的每个元素按照 ${name} = {length};\n
展开,注意到其中包括了换行符的 ascii 的 C 语言中的转义表示\n
。
下面开始剖析代码,MAVTemplate
中主要
find_end
: 找到字符的匹配位置find_var_end
: 通过调用find_end
找到字符串中}
的位置;find_rep_end
: 通过调用find_end
找到支付串中}}
的位置;substitute
: 按照模式${}
和${{}}
执行替换;write
: 调用substitute
进行替换。
5 头文件生成
这几个函数调用MAVTemplate
对象的方法生成具体的头文件。
generate_mavlink_h
: 生成 mavlink.hgenerate_version_h
: 生成 version.hgenerate_main_h
: 生成和XML
文件同名的头文件,如对于 common.xml 会生成common.h
generate_message_h
: 生成以 mavlink_msg_ 开头的各种消息类型的头文件generate_testsuite_h
: 生成 testsuite.h
下面大概概述一下各个头文件的内容:
mavlink.h: 包含了和版本相关的数据帧的配置同时 include
了 version.h 和 common.h
version.h: 包含了版本信息
common.h: 包含了针对 common.xml 中定义的消息类型的数据结构并include
了所有消息头文件
mavlink_msg_*.h: 所有消息实现的头文件
testsuite.h: 基于内存的消息测试