如何升级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
相关产品推荐
相关产品推荐

