1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
|
//
// c_callback_wrapper.cpp
// ~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2023 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();
}
|