You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何升级Boost库中get_io_context()的使用以适配新版本

解决Boost Asio中get_io_context()兼容新版Boost的问题

问题描述

编译依赖旧版Boost库的项目时触发编译错误:

io.hpp:48:44: error: ‘boost::asio::serial_port’ {aka ‘class boost::asio::basic_serial_port<>’} has no member named ‘get_io_context’
   48 |     boost::asio::deadline_timer timer(port.get_io_context());

尝试用get_executor()替代但未成功,需要调整代码适配新版Boost Asio的API变化。

修改方案

1. 替换get_io_context()为get_executor().context()

新版Boost Asio中,所有I/O对象不再直接提供get_io_context()方法,而是通过get_executor()获取执行器后,调用.context()方法拿到对应的io_context实例。

2. 统一io_service的获取方式

代码中port.get_io_service()也需要替换为通过执行器获取的context,或者直接使用serial_controller类中已有的io_service成员(更高效,因为serial_port本身就绑定到该io_service)。

3. 修复构造函数中的错误

serial_controller构造函数中port.open()的参数传递有误,需要修正为正确的错误码处理方式。

修改后的完整代码

#pragma once

#include <boost/asio.hpp>
#include <boost/asio/serial_port.hpp> 
#include <boost/core/demangle.hpp>
#include <boost/endian/conversion.hpp>
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <optional>
#include <string>
#include <typeinfo>

#include <fmt/format.h>
#include <spdlog/spdlog.h>

#include <roboclaw/io/crc_calculator.hpp>
#include <roboclaw/logging.hpp>

namespace roboclaw::io
{

inline uint16_t read_crc(boost::asio::serial_port& port)
{
    uint16_t value;
    boost::asio::read(port, boost::asio::buffer(&value, 2));
    return boost::endian::big_to_native(value);
}

inline void write_crc(boost::asio::serial_port& port, uint16_t crc)
{
    boost::endian::native_to_big_inplace(crc);
    boost::asio::write(port, boost::asio::buffer(&crc, 2));
}

/** Read a raw value from the serial port. This does not manipulate the CRC calculator. */
template<typename T>
T read_value_raw(boost::asio::serial_port& port,
                 const boost::posix_time::time_duration& timeout =
                         boost::posix_time::milliseconds{100})
{
    static_assert(std::is_integral<T>(), "read_value() can only process integral types");
    T value;

    std::optional<boost::system::error_code> timer_result;
    // 替换为get_executor().context()获取io_context
    boost::asio::deadline_timer timer(port.get_executor().context());
    timer.expires_from_now(timeout);
    timer.async_wait([&timer_result](const auto& error) { timer_result = error; });

    std::optional<boost::system::error_code> read_result;
    boost::asio::async_read(
            port, boost::asio::buffer(&value, sizeof(T)),
            [&read_result](const auto& error, size_t) { read_result = error; });

    // 通过执行器获取io_context并操作
    auto& io_ctx = port.get_executor().context();
    io_ctx.reset();
    while (io_ctx.run_one())
    {
        if (read_result)
        {
            timer.cancel();
        }
        else if (timer_result)
        {
            port.cancel();
        }
    }
    if (*read_result)
    {
        if (!*timer_result)
        {
            throw std::runtime_error("read timed out");
        }
        else
        {
            throw std::runtime_error(fmt::format("read completed but with error: {}",
                                                 read_result->message()));
        }
    }

    return value;
}

/** Read a value from the serial port, adding this to the CRC calculator.
 * @param port The serial port to read from
 * @param crc Crc calculator the read value will be added to
 * @param log_str Log string the information about the read value will be added to
 * @param timeout Timeout for the read command
 * @returns The read value of the type selected as the template parameter
 *
 * @note This function only allows to read integral types.
 */
template<typename T>
T read_value(boost::asio::serial_port& port, crc_calculator_16& crc, std::string& log_str,
             const boost::posix_time::time_duration& timeout =
                     boost::posix_time::milliseconds{100})
{
    static_assert(std::is_integral<T>(), "read_value() can only process integral types");

    T value = read_value_raw<T>(port, timeout);
    crc << value;
    boost::endian::big_to_native_inplace(value);
    log_str += fmt::format(" {:d}", value);
    return value;
}

/** Write an integral value to the serial port and add this value to the CRC calculator
 * @param value Value to write
 * @param port Serial port to write to
 * @param crc CRC calculator the value will be added to
 * @param log_str Log string the information about the written value will be appended to
 *
 * @note This function does not yet support a timeout
 */
template<typename T>
void write_value(T value, boost::asio::serial_port& port, crc_calculator_16& crc,
                 std::string& log_str)
{
    static_assert(std::is_integral<T>(), "write_value() can only process integral types");
    log_str += fmt::format(" {:d}", value);
    boost::endian::native_to_big_inplace(value);
    boost::asio::write(port, boost::asio::buffer(&value, sizeof(T)));
    crc << value;
}

/** Roboclaw serial controller. */
class serial_controller
{
  public:
    /** Initialise the controller, giving the port name and its address.
     *
     * @param port_name Port name (path) of the controller, e.g. `/dev/roboclaw`
     * @param address Address of the controller.
     */
    serial_controller(const std::string& port_name, uint8_t address)
        : lg(get_roboclaw_logger()), address(address), port(io_service)
    {
        boost::system::error_code ec;
        // 修复open方法的错误码传递
        port.open(port_name, ec);
        if (ec || !port.is_open())
        {
            throw std::runtime_error(std::string("Could not open serial port: ") +
                                     port_name + ": " + ec.message());
        }
    }

    /** Return the address of the controller */
    uint8_t get_address() const { return address; }

    /** Return the serial port name of the controller */
    boost::asio::serial_port& get_port() { return port; }

    /** Read a value from the controller. The command is given by the template parameter
     * number.
     *
     * @throws std::runtime_error If the read times out.
     */
    template<typename command>
    typename command::return_type read()
    {
        std::string log_str;

        try
        {
            crc_calculator_16 calculated_crc;
            calculated_crc << get_address() << uint8_t(command::CMD);

            send_command<command>(log_str);

            log_str += "; recv:";
            typename command::return_type result =
                    command::read_response(get_port(), calculated_crc, log_str);
            uint16_t received_crc = read_crc(get_port());

            log_str += fmt::format(" {:#x}", received_crc);
            lg->debug(log_str);

            if (calculated_crc.get() != received_crc)
            {
                using boost::core::demangle;
                std::stringstream ss;
                ss << "received crc=" << std::hex << received_crc << " does not match "
                   << "calculated crc=" << std::hex << calculated_crc.get()
                   << " for command " << +uint8_t(command::CMD) << " ("
                   << demangle(typeid(command).name()) << ")";
                throw std::runtime_error(ss.str());
            }

            return result;
        }
        catch (std::runtime_error& e)
        {
            lg->error("failed read command '{}': {}", log_str, e.what());
            throw;
        }
    }

    /** Read the value from the controller but re-try if the read times out. The read
     * command is given as the template parameter.
     *
     * @param ntries Number of tries to re-try. If less than 1, try only once.
     * @returns The return value of the command. The type of the value is determined by
     * `command::return_type`.
     *
     * @throws std::runtime_error if the read command fails and the max number of tries is
     *         reached.
     */
    template<typename command>
    typename command::return_type read(int ntries)
    {
        if (ntries < 1) ntries = 1;

        for (int attempt = 1; attempt <= ntries; ++attempt)
        {
            try
            {
                return read<command>();
            }
            catch (...)
            {
                if (attempt >= ntries)
                {
                    lg->error("Reached {} attempts. Giving up.", ntries);
                    throw;
                }
                lg->info("Retrying the command");
            }
        }
    }

    template<typename command>
    bool write(const command& cmd)
    {
        std::string log_str;

        try
        {
            send_command<command>(log_str);

            crc_calculator_16 calculated_crc;
            calculated_crc << get_address() << uint8_t(command::CMD);

            cmd.write(get_port(), calculated_crc, log_str);
            write_crc(get_port(), calculated_crc.get());
            log_str += fmt::format(" {:#x}", calculated_crc.get());

            uint8_t response = read_value_raw<uint8_t>(port);
            log_str += fmt::format("; recv: {:#x}", response);

            lg->debug(log_str);
            return response == 0xff;
        }
        catch (std::runtime_error& e)
        {
            lg->error("Write for command {} failed: {}", log_str, e.what());
            return false;
        }
    }

    template<typename command>
    bool write(const command& cmd, int ntries)
    {
        if (ntries < 1) ntries = 1;

        for (int attempt = 1; attempt <= ntries; ++attempt)
        {
            bool success = write(cmd);
            if (success) return true;

            if (attempt >= ntries)
            {
                lg->error("Reached {} attempts. Giving up.", ntries);
                return false;
            }
            lg->info("Retrying the command");
        }
    }

    ~serial_controller() { port.close(); }

  private:
    std::shared_ptr<spdlog::logger> lg;

    uint8_t address;
    boost::asio::io_service io_service;
    boost::asio::serial_port port;

    template<typename command>
    void send_command(std::string& log_str)
    {
        uint8_t buffer[2] = {address, command::CMD};
        boost::asio::write(port, boost::asio::buffer(buffer, 2));

        log_str += fmt::format("serial send: {:x} {:d}", address, uint8_t(command::CMD));
    }
};

}  // namespace roboclaw::io

额外说明

  • 新版Boost Asio中io_service是io_context的别名,代码中可以直接替换为boost::asio::io_context以保持API一致性。
  • 如果使用的Boost版本已经移除deadline_timer,可以替换为steady_timer或system_timer,并调整时间相关的头文件和API调用。

内容的提问来源于stack exchange,提问作者Marcus Barnet

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 16:57:23