Using C++ 11 (mavgen)
The MAVLink C++ 11 library generated by mavgen is a header-only implementation that is highly optimized for resource-constrained systems with limited RAM and flash memory.
The C++ 11 library is a thin wrapper around the C library: parsing/framing (mavlink_parse_char(), channels, MAVLink 1 interop) is unchanged, but messages are represented as C++ structs with serialize()/deserialize() methods instead of being packed/decoded via free functions, and everything is scoped under the mavlink namespace.
This topic explains how to get and use the library.
Getting the C++ 11 MAVLink Library
Unlike the C library, there is no pre-built C++ 11 library repository to download. You will always need to install mavgen and generate the headers yourself (using --lang=C++11), for whichever dialect(s) you need.
The libraries can be placed/generated anywhere in your project tree.
Adding Library to C++ Project
This example below assumes the MAVLink headers to be located in: generated/include/mavlink/.
To use MAVLink in your C++ project, include the common.hpp header file for your dialect:
#include <mavlink/common/common.hpp>This will automatically add the header files for all messages in your dialect, and for any dialect files that it includes. Everything the library provides — message structs, enums, and the framing functions inherited from the C layer (e.g. mavlink_parse_char()) — is scoped under the mavlink namespace, with each dialect getting its own nested namespace (e.g. mavlink::common).
WARNING
Only include the header file for a single dialect. If you need to support messages from a number of dialects then create a new "parent" dialect XML file that includes them (and use its generated header as shown above).
TIP
Do not include the individual message files. If you generate your own headers, you will have to add their output location to your C++ compiler's search path.
When compiling the project, make sure to add the include directory and enable C++11 (or later):
g++ -std=c++11 ... -I generated/include ...Adding Library to Cmake Project
To include the headers in cmake, install them locally, e.g. into the directory install:
cmake -Bbuild -H. -DCMAKE_INSTALL_PREFIX=install -DMAVLINK_DIALECT=common -DMAVLINK_VERSION=2.0
cmake --build build --target installThis single install step generates and installs both the C (*.h) and C++ 11 (*.hpp) headers (controlled by the MAVLINK_ENABLE_CXX cmake option, which defaults to ON) — there's no separate install command for the C++ headers.
Then use find_package to get the dependency in CMakeLists.txt:
find_package(MAVLink REQUIRED)
add_executable(my_program my_program.cpp)
target_compile_features(my_program PRIVATE cxx_std_11)
target_link_libraries(my_program PRIVATE MAVLink::mavlink)And pass the local install directory to cmake (adapt to your directory structure):
cd ../my_program
cmake -Bbuild -H. -DCMAKE_PREFIX_PATH=../mavlink/installBuild Warnings
-Waddress-of-packed-member
Building the headers may result in warnings like:
mavlink/common/../mavlink_helpers.h:86:24: warning: taking address of packed member of ‘__mavlink_message’ may result in an unaligned pointer value [-Waddress-of-packed-member]
86 | crc_accumulate_buffer(&msg->checksum, _MAV_PAYLOAD(msg), msg->len);The warning indicates the potential for hard faults caused by unaligned access to packed data. This does not happen on most of the common architectures on which MAVLink is run, and generally the warning can be suppressed.
You can suppress the warnings using -Wno-address-of-packed-member.
INFO
The issue causes hard faults on Cortex-M0 and other platforms listed here. Please raise issues in ArduPilot/pymavlink if you find other hardware that is affected.
MAVLink 1
The C++ 11 library only ever generates MAVLink 2 headers — there is no C++ 11 equivalent of the old MAVLink-1-only c_library_v1 to upgrade from.
This doesn't stop you interoperating with MAVLink-1-only systems: the generated MAVLink 2 headers can send/receive MAVLink 1 framed packets on a channel-by-channel basis, in exactly the same way as the C library. See Working with MAVLink 1 below.
Multiple Streams ("channels")
The MAVLink library utilizes a "channel" metaphor to allow for simultaneous processing of multiple, independent MAVLink streams in the same program. All receiving and transmitting functions provided by this library require a channel, and it is important to use the correct channel for each operation.
By default up to 16 channels may be defined on Windows, Linux and macOS, and up to 4 channels may be define on other systems. Systems can specify a different maximum number of channels/comms buffers using MAVLINK_COMM_NUM_BUFFERS (for example, this might be reduced to 1 if running MAVLink on very memory constrained hardware).
If only one MAVLink stream exists, channel 0 should be used by specifying the mavlink::MAVLINK_COMM_0 constant.
Receiving
MAVLink reception/decoding is done in a number of phases:
- Parse the incoming stream into objects representing each packet (
mavlink::mavlink_message_t). - Deserialize the MAVLink message contained in the packet payload into a generated C++ struct (that has fields mapping the original XML definition).
These steps are demonstrated below.
Parsing Packets
The mavlink::mavlink_parse_char(...) convenience function (inherited from mavlink_helpers.h) is used to parse incoming MAVLink data. The function parses the data one byte at a time, returning 0 (MAVLINK_FRAMING_INCOMPLETE) as it progresses, and 1 (MAVLINK_FRAMING_OK) on successful decoding of a packet. The r_mavlink_status parameter is updated with the channel status/errors as decoding progresses (you can check mavlink_status_t.msg_received to get the current byte's decode status/error and mavlink_status_t.parse_state for the current parse state). On successful decoding of a packet, the r_message argument is populated with an object representing the decoded packet.
The function prototype and parameters are shown below:
MAVLINK_HELPER uint8_t mavlink_parse_char(uint8_t chan, uint8_t c, mavlink_message_t* r_message, mavlink_status_t* r_mavlink_status)Parameters:
uint8_t chan: ID of the current channel.uint8_t c: The char to parse.mavlink_message_t* r_message: On success, the decoded message. NULL if the message couldn't be decoded.mavlink_status_t* r_mavlink_status: The channel statistics, including information about the current parse state.
Returns: 0 if the packet decoding is incomplete. 1 if the packet successfully decoded.
The code fragment below shows the typical use of this function when reading data from a socket/serial port (socket_fd):
#include <mavlink/common/common.hpp>
mavlink::mavlink_status_t status;
mavlink::mavlink_message_t message;
int chan = mavlink::MAVLINK_COMM_0;
for (int i = 0; i < num_bytes_read; ++i) {
if (mavlink::mavlink_parse_char(chan, buffer[i], &message, &status) == 1) {
printf("Received message with ID %d, sequence: %d from component %d of system %d\n",
message.msgid, message.seq, message.compid, message.sysid);
// ... DECODE THE MESSAGE PAYLOAD HERE ...
}
}WARNING
Unlike the C library, the C++ 11 generator does not emit a mavlink::mavlink_get_msg_entry() lookup function. mavlink_parse_char() needs this internally to find the length and CRC extra of an incoming message id, so you must provide it yourself before parsing will work.
The simplest approach is to build it from the dialect's MESSAGE_ENTRIES table (which is sorted by message id), as shown in the UDP example:
namespace mavlink {
const mavlink_msg_entry_t* mavlink_get_msg_entry(uint32_t msgid)
{
for (const auto& entry : common::MESSAGE_ENTRIES) {
if (entry.msgid == msgid) {
return &entry;
}
}
return nullptr;
}
} // namespace mavlinkOmitting this results in a linker error, since mavlink_parse_char() declares (but doesn't define) mavlink_get_msg_entry().
Decoding the Payload
The message/packet object retrieved in the previous section (mavlink::mavlink_message_t) contains fields in the MAVLink packet/serialization format - including the message id (msgid) and the payload (payload64).
To get the fields of the specific message in the packet you need to further decode the payload. This is typically done by providing a switch statement that maps the ids of the messages you wish to decode to a generated message struct, then calling .deserialize() on it via a mavlink::MsgMap:
if (mavlink::mavlink_parse_char(chan, byte, &message, &status) == 1) {
switch (message.msgid) {
case mavlink::common::msg::GLOBAL_POSITION_INT::MSG_ID:
{
// Deserialize the whole payload into the generated struct.
mavlink::common::msg::GLOBAL_POSITION_INT global_position;
mavlink::MsgMap map(&message);
global_position.deserialize(map);
}
break;
default:
break;
}
}Each generated message struct is a plain C++ class with fields mapping to the original XML message definition, plus a handful of static members used by the framing layer:
namespace mavlink {
namespace common {
namespace msg {
struct GLOBAL_POSITION_INT : mavlink::Message {
static constexpr msgid_t MSG_ID = 33;
static constexpr size_t LENGTH = 28;
static constexpr size_t MIN_LENGTH = 28;
static constexpr uint8_t CRC_EXTRA = 104;
uint32_t time_boot_ms; /*< [ms] Timestamp (time since system boot). */
int32_t lat; /*< [degE7] Latitude, expressed */
int32_t lon; /*< [degE7] Longitude, expressed */
int32_t alt; /*< [mm] Altitude (MSL). */
int32_t relative_alt; /*< [mm] Altitude above ground */
int16_t vx; /*< [cm/s] Ground X Speed (Latitude, positive north) */
int16_t vy; /*< [cm/s] Ground Y Speed (Longitude, positive east) */
int16_t vz; /*< [cm/s] Ground Z Speed (Altitude, positive down) */
uint16_t hdg; /*< [cdeg] Vehicle heading (yaw angle) */
void serialize(MsgMap& map) const override { ... }
void deserialize(MsgMap& map) override { ... }
};
} // namespace msg
} // namespace common
} // namespace mavlinkThere are no separate "get a single field" decoder functions as there are in C — deserialize the whole struct and read the field you need.
Command Decoding
A MAVLink Command encodes a command defined in a MAV_CMD enum value into a COMMAND_INT or COMMAND_LONG message.
Command packets are parsed and decoded in the same way as any other payload - i.e. you switch on mavlink::common::msg::COMMAND_INT::MSG_ID/mavlink::common::msg::COMMAND_LONG::MSG_ID and deserialize into the corresponding generated struct.
INFO
The message types differ in that COMMAND_INT has int32 types for parameter fields 6 and 7 (instead of float) and also includes a field for the geometric frame of reference of any positional information in the command.
To decode the specific command you then switch on the value of the deserialized struct's command field, cast to mavlink::common::MAV_CMD.
Further interpretation and handling of the message then has to be done manually (there are no convenience functions).
Additional Checks
The library have a number of #define values that you can set to enable various features:
MAVLINK_CHECK_MESSAGE_LENGTH: Enable this option to check the length of each message. This allows invalid messages to be caught much sooner. Use if the transmission medium is prone to missing (or extra) characters (e.g. a radio that fades in and out). Only use if the channel will only contain messages types listed in the headers.
Transmitting
Transmitting messages means filling in a generated message struct, serializing it, then finalizing and sending the resulting packet:
// Fill in the generated HEARTBEAT struct.
mavlink::minimal::msg::HEARTBEAT heartbeat;
heartbeat.type = static_cast<uint8_t>(mavlink::minimal::MAV_TYPE::GENERIC);
heartbeat.autopilot = static_cast<uint8_t>(mavlink::minimal::MAV_AUTOPILOT::GENERIC);
heartbeat.base_mode = 0;
heartbeat.custom_mode = 0;
heartbeat.system_status = static_cast<uint8_t>(mavlink::minimal::MAV_STATE::STANDBY);
// Serialize it into a mavlink_message_t ...
mavlink::mavlink_message_t message;
mavlink::MsgMap map(message);
heartbeat.serialize(map);
// ... and finalize it by adding the header and CRC. This is where the
// system and component id as well as the sequence number get filled in.
mavlink::mavlink_finalize_message_chan(
&message,
system_id,
static_cast<uint8_t>(mavlink::minimal::MAV_COMPONENT::COMP_ID_PERIPHERAL),
mavlink::MAVLINK_COMM_0,
heartbeat.MIN_LENGTH,
heartbeat.LENGTH,
heartbeat.CRC_EXTRA);
// Now convert the message into a byte buffer to send it out.
uint8_t buffer[MAVLINK_MAX_PACKET_LEN];
const int len = mavlink::mavlink_msg_to_send_buffer(buffer, &message);
// ... write buffer[0..len) to your transport (UART, UDP socket, etc.)Note that the message struct's own MIN_LENGTH, LENGTH and CRC_EXTRA static members are passed to mavlink_finalize_message_chan() — there's no separate table to look these up from, unlike raw use of the C API.
Enum fields (e.g. MAV_TYPE, MAV_AUTOPILOT) are C++ 11 scoped enums (enum class), so they need an explicit static_cast<uint8_t> when writing them into a struct field, and a static_cast back to the enum type when reading a field for comparison (see Decoding the Payload).
Message Signing
Message Signing (authentication) allows a MAVLink 2 system to verify that messages originate from a trusted source.
The signing mechanism is implemented in the shared C layer that the C++ 11 library builds on, so it works the same way as for the C library. The topic C Message Signing explains the remaining code that a system must implement to enable signing (the API used is namespaced under mavlink:: as elsewhere in this topic).
Connection/Heartbeat
The sections above explain how you can send and receive messages. What messages are sent/received depends on the systems that you're working with. The set of messages that most systems can send are documented in common.xml and there are various microservices microservices that you may want to use.
Minimally MAVLink components should implement the HEARTBEAT/Connection protocol as this is used by other systems as proof-of-life for the component, and also for routing.
Working with MAVLink 1
This section explains how to use the MAVLink 2 C++ 11 library to work with MAVLink 1 systems.
Version Handshaking/Negotiation
MAVLink Versions explains the handshaking used to determine the supported MAVLink version of either end of the channel, and how to negotiate the version to use.
Sending and Receiving MAVLink 1 Packets
The library will send packets in MAVLink 2 framing by default. To force sending MAVLink 1 packets on a particular channel you change the flags field of the status object.
For example, the following code causes subsequent packets on the given channel to be sent as MAVLink 1:
mavlink::mavlink_status_t* chan_state = mavlink::mavlink_get_channel_status(mavlink::MAVLINK_COMM_0);
chan_state->flags |= mavlink::MAVLINK_STATUS_FLAG_OUT_MAVLINK1;Incoming MAVLink 1 packets will be automatically handled as MAVLink 1. If you need to determine if a particular message was received as MAVLink 1 or MAVLink 2 then you can use the magic field of the message:
if (message.magic == mavlink::MAVLINK_STX_MAVLINK1) {
printf("This is a MAVLink 1 message\n");
}In most cases this should not be necessary as the XML message definition files for MAVLink 1 and MAVLink 2 are the same, so you can treat incoming MAVLink 1 messages the same as MAVLink 2 messages.
INFO
MAVLink 1 is restricted to message IDs less than 256, so any messages with a higher message ID won't be received as MAVLink 1.
It is advisable to switch to MAVLink 2 when the communication partner sends MAVLink 2 (see Version Handshaking). The minimal solution is to watch incoming packets using code similar to this:
if (mavlink::mavlink_parse_char(mavlink::MAVLINK_COMM_0, buf[i], &message, &status)) {
// check if we received version 2 and request a switch.
if (!(mavlink::mavlink_get_channel_status(mavlink::MAVLINK_COMM_0)->flags & mavlink::MAVLINK_STATUS_FLAG_IN_MAVLINK1)) {
// this will only switch to proto version 2
chan_state->flags &= ~(mavlink::MAVLINK_STATUS_FLAG_OUT_MAVLINK1);
}
}Examples
The following examples show the use of the API.
- UDP Example: Simple C++ example of a MAVLink UDP interface for Unix-like systems (Linux, MacOS, BSD, etc.).

