mirror of
https://github.com/boostorg/asio.git
synced 2025-05-12 14:11:39 +00:00
233 lines
8.5 KiB
C++
233 lines
8.5 KiB
C++
//
|
|
// c_callback_wrapper.cpp
|
|
// ~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
// Copyright (c) 2003-2025 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
|
//
|
|
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
|
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
|
//
|
|
|
|
#include <boost/asio.hpp>
|
|
#include <iostream>
|
|
#include <memory>
|
|
#include <new>
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
// This is a mock implementation of a C-based API that uses the function pointer
|
|
// plus void* context idiom for exposing a callback.
|
|
|
|
void read_input(const char* prompt, void (*cb)(void*, const char*), void* arg)
|
|
{
|
|
std::thread(
|
|
[prompt = std::string(prompt), cb, arg]
|
|
{
|
|
std::cout << prompt << ": ";
|
|
std::cout.flush();
|
|
std::string line;
|
|
std::getline(std::cin, line);
|
|
cb(arg, line.c_str());
|
|
}).detach();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
// This is an asynchronous operation that wraps the C-based API.
|
|
|
|
// To map our completion handler into a function pointer / void* callback, we
|
|
// need to allocate some state that will live for the duration of the
|
|
// operation. A pointer to this state will be passed to the C-based API.
|
|
template <boost::asio::completion_handler_for<void(std::string)> Handler>
|
|
class read_input_state
|
|
{
|
|
public:
|
|
read_input_state(Handler&& handler)
|
|
: handler_(std::move(handler)),
|
|
work_(boost::asio::make_work_guard(handler_))
|
|
{
|
|
}
|
|
|
|
// Create the state using the handler's associated allocator.
|
|
static read_input_state* create(Handler&& handler)
|
|
{
|
|
// A unique_ptr deleter that is used to destroy uninitialised objects.
|
|
struct deleter
|
|
{
|
|
// Get the handler's associated allocator type. If the handler does not
|
|
// specify an associated allocator, we will use a recycling allocator as
|
|
// the default. As the associated allocator is a proto-allocator, we must
|
|
// rebind it to the correct type before we can use it to allocate objects.
|
|
typename std::allocator_traits<
|
|
boost::asio::associated_allocator_t<Handler,
|
|
boost::asio::recycling_allocator<void>>>::template
|
|
rebind_alloc<read_input_state> alloc;
|
|
|
|
void operator()(read_input_state* ptr)
|
|
{
|
|
std::allocator_traits<decltype(alloc)>::deallocate(alloc, ptr, 1);
|
|
}
|
|
} d{boost::asio::get_associated_allocator(handler,
|
|
boost::asio::recycling_allocator<void>())};
|
|
|
|
// Allocate memory for the state.
|
|
std::unique_ptr<read_input_state, deleter> uninit_ptr(
|
|
std::allocator_traits<decltype(d.alloc)>::allocate(d.alloc, 1), d);
|
|
|
|
// Construct the state into the newly allocated memory. This might throw.
|
|
read_input_state* ptr =
|
|
new (uninit_ptr.get()) read_input_state(std::move(handler));
|
|
|
|
// Release ownership of the memory and return the newly allocated state.
|
|
uninit_ptr.release();
|
|
return ptr;
|
|
}
|
|
|
|
static void callback(void* arg, const char* result)
|
|
{
|
|
read_input_state* self = static_cast<read_input_state*>(arg);
|
|
|
|
// A unique_ptr deleter that is used to destroy initialised objects.
|
|
struct deleter
|
|
{
|
|
// Get the handler's associated allocator type. If the handler does not
|
|
// specify an associated allocator, we will use a recycling allocator as
|
|
// the default. As the associated allocator is a proto-allocator, we must
|
|
// rebind it to the correct type before we can use it to allocate objects.
|
|
typename std::allocator_traits<
|
|
boost::asio::associated_allocator_t<Handler,
|
|
boost::asio::recycling_allocator<void>>>::template
|
|
rebind_alloc<read_input_state> alloc;
|
|
|
|
void operator()(read_input_state* ptr)
|
|
{
|
|
std::allocator_traits<decltype(alloc)>::destroy(alloc, ptr);
|
|
std::allocator_traits<decltype(alloc)>::deallocate(alloc, ptr, 1);
|
|
}
|
|
} d{boost::asio::get_associated_allocator(self->handler_,
|
|
boost::asio::recycling_allocator<void>())};
|
|
|
|
// To conform to the rules regarding asynchronous operations and memory
|
|
// allocation, we must make a copy of the state and deallocate the memory
|
|
// before dispatching the completion handler.
|
|
std::unique_ptr<read_input_state, deleter> state_ptr(self, d);
|
|
read_input_state state(std::move(*self));
|
|
state_ptr.reset();
|
|
|
|
// Dispatch the completion handler through the handler's associated
|
|
// executor, using the handler's associated allocator.
|
|
boost::asio::dispatch(state.work_.get_executor(),
|
|
boost::asio::bind_allocator(d.alloc,
|
|
[
|
|
handler = std::move(state.handler_),
|
|
result = std::string(result)
|
|
]() mutable
|
|
{
|
|
std::move(handler)(result);
|
|
}));
|
|
}
|
|
|
|
private:
|
|
Handler handler_;
|
|
|
|
// According to the rules for asynchronous operations, we need to track
|
|
// outstanding work against the handler's associated executor until the
|
|
// asynchronous operation is complete.
|
|
boost::asio::executor_work_guard<
|
|
boost::asio::associated_executor_t<Handler>> work_;
|
|
};
|
|
|
|
// The initiating function for the asynchronous operation.
|
|
template <boost::asio::completion_token_for<void(std::string)> CompletionToken>
|
|
auto async_read_input(const std::string& prompt, CompletionToken&& token)
|
|
{
|
|
// Define a function object that contains the code to launch the asynchronous
|
|
// operation. This is passed the concrete completion handler, followed by any
|
|
// additional arguments that were passed through the call to async_initiate.
|
|
auto init = [](
|
|
boost::asio::completion_handler_for<void(std::string)> auto handler,
|
|
const std::string& prompt)
|
|
{
|
|
// The body of the initiation function object creates the long-lived state
|
|
// and passes it to the C-based API, along with the function pointer.
|
|
using state_type = read_input_state<decltype(handler)>;
|
|
read_input(prompt.c_str(), &state_type::callback,
|
|
state_type::create(std::move(handler)));
|
|
};
|
|
|
|
// The async_initiate function is used to transform the supplied completion
|
|
// token to the completion handler. When calling this function we explicitly
|
|
// specify the completion signature of the operation. We must also return the
|
|
// result of the call since the completion token may produce a return value,
|
|
// such as a future.
|
|
return boost::asio::async_initiate<CompletionToken, void(std::string)>(
|
|
init, // First, pass the function object that launches the operation,
|
|
token, // then the completion token that will be transformed to a handler,
|
|
prompt); // and, finally, any additional arguments to the function object.
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void test_callback()
|
|
{
|
|
boost::asio::io_context io_context;
|
|
|
|
// Test our asynchronous operation using a lambda as a callback. We will use
|
|
// an io_context to obtain an associated executor.
|
|
async_read_input("Enter your name",
|
|
boost::asio::bind_executor(io_context,
|
|
[](const std::string& result)
|
|
{
|
|
std::cout << "Hello " << result << "\n";
|
|
}));
|
|
|
|
io_context.run();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void test_deferred()
|
|
{
|
|
boost::asio::io_context io_context;
|
|
|
|
// Test our asynchronous operation using the deferred completion token. This
|
|
// token causes the operation's initiating function to package up the
|
|
// operation with its arguments to return a function object, which may then be
|
|
// used to launch the asynchronous operation.
|
|
auto op = async_read_input("Enter your name", boost::asio::deferred);
|
|
|
|
// Launch our asynchronous operation using a lambda as a callback. We will use
|
|
// an io_context to obtain an associated executor.
|
|
std::move(op)(
|
|
boost::asio::bind_executor(io_context,
|
|
[](const std::string& result)
|
|
{
|
|
std::cout << "Hello " << result << "\n";
|
|
}));
|
|
|
|
io_context.run();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void test_future()
|
|
{
|
|
// Test our asynchronous operation using the use_future completion token.
|
|
// This token causes the operation's initiating function to return a future,
|
|
// which may be used to synchronously wait for the result of the operation.
|
|
std::future<std::string> f =
|
|
async_read_input("Enter your name", boost::asio::use_future);
|
|
|
|
std::string result = f.get();
|
|
std::cout << "Hello " << result << "\n";
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
int main()
|
|
{
|
|
test_callback();
|
|
test_deferred();
|
|
test_future();
|
|
}
|