Introduce support for asynchronous D-Bus method calls on the client side (#42)

Fixes #32
This commit is contained in:
Stanislav Angelovič
2019-03-25 14:45:48 +01:00
committed by GitHub
parent 004f158817
commit 663df31398
18 changed files with 481 additions and 54 deletions
+17
View File
@@ -37,6 +37,7 @@ namespace sdbus {
class IObject;
class IObjectProxy;
class Variant;
class Error;
}
namespace sdbus {
@@ -165,6 +166,7 @@ namespace sdbus {
MethodInvoker& onInterface(const std::string& interfaceName);
template <typename... _Args> MethodInvoker& withArguments(_Args&&... args);
template <typename... _Args> void storeResultsTo(_Args&... args);
void dontExpectReply();
private:
@@ -175,6 +177,21 @@ namespace sdbus {
bool methodCalled_{};
};
class AsyncMethodInvoker
{
public:
AsyncMethodInvoker(IObjectProxy& objectProxy, const std::string& methodName);
AsyncMethodInvoker& onInterface(const std::string& interfaceName);
template <typename... _Args> AsyncMethodInvoker& withArguments(_Args&&... args);
//template <typename... _OutputArgs> void uponReplyInvoke(std::function<void(const Error*, _OutputArgs...)> callback);
template <typename _Function> void uponReplyInvoke(_Function&& callback);
private:
IObjectProxy& objectProxy_;
const std::string& methodName_;
AsyncMethodCall method_;
};
class SignalSubscriber
{
public:
+45 -2
View File
@@ -119,8 +119,7 @@ namespace sdbus {
// as a storage for the argument values deserialized from the message.
tuple_of_function_input_arg_types_t<_Function> inputArgs;
// Deserialize input arguments from the message into the tuple,
// plus store the result object as a last item of the tuple.
// Deserialize input arguments from the message into the tuple.
msg >> inputArgs;
// Invoke callback with input arguments from the tuple.
@@ -487,6 +486,50 @@ namespace sdbus {
}
inline AsyncMethodInvoker::AsyncMethodInvoker(IObjectProxy& objectProxy, const std::string& methodName)
: objectProxy_(objectProxy)
, methodName_(methodName)
{
}
inline AsyncMethodInvoker& AsyncMethodInvoker::onInterface(const std::string& interfaceName)
{
method_ = objectProxy_.createAsyncMethodCall(interfaceName, methodName_);
return *this;
}
template <typename... _Args>
inline AsyncMethodInvoker& AsyncMethodInvoker::withArguments(_Args&&... args)
{
SDBUS_THROW_ERROR_IF(!method_.isValid(), "DBus interface not specified when calling a DBus method", EINVAL);
detail::serialize_pack(method_, std::forward<_Args>(args)...);
return *this;
}
template <typename _Function>
void AsyncMethodInvoker::uponReplyInvoke(_Function&& callback)
{
SDBUS_THROW_ERROR_IF(!method_.isValid(), "DBus interface not specified when calling a DBus method", EINVAL);
objectProxy_.callMethod(method_, [callback = std::forward<_Function>(callback)](MethodReply& reply, const Error* error)
{
// Create a tuple of callback input arguments' types, which will be used
// as a storage for the argument values deserialized from the message.
tuple_of_function_input_arg_types_t<_Function> args;
// Deserialize input arguments from the message into the tuple (if no error occurred).
if (error == nullptr)
reply >> args;
// Invoke callback with input arguments from the tuple.
sdbus::apply(callback, error, args); // TODO: Use std::apply when switching to full C++17 support
});
}
inline SignalSubscriber::SignalSubscriber(IObjectProxy& objectProxy, const std::string& signalName)
: objectProxy_(objectProxy)
, signalName_(signalName)
+5
View File
@@ -57,6 +57,11 @@ namespace sdbus {
return message_;
}
bool isValid() const
{
return !getName().empty();
}
private:
std::string name_;
std::string message_;
+63 -2
View File
@@ -34,6 +34,7 @@
// Forward declarations
namespace sdbus {
class MethodCall;
class AsyncMethodCall;
class MethodReply;
class IConnection;
}
@@ -58,7 +59,7 @@ namespace sdbus {
*
* @param[in] interfaceName Name of an interface that the method is defined under
* @param[in] methodName Name of the method
* @return A method call message message
* @return A method call message
*
* Serialize method arguments into the returned message and invoke the method by passing
* the message with serialized arguments to the @c callMethod function.
@@ -68,6 +69,21 @@ namespace sdbus {
*/
virtual MethodCall createMethodCall(const std::string& interfaceName, const std::string& methodName) = 0;
/*!
* @brief Creates an asynchronous method call message
*
* @param[in] interfaceName Name of an interface that the method is defined under
* @param[in] methodName Name of the method
* @return A method call message
*
* Serialize method arguments into the returned message and invoke the method by passing
* the message with serialized arguments to the @c callMethod function.
* Alternatively, use higher-level API @c callMethodAsync(const std::string& methodName) defined below.
*
* @throws sdbus::Error in case of failure
*/
virtual AsyncMethodCall createAsyncMethodCall(const std::string& interfaceName, const std::string& methodName) = 0;
/*!
* @brief Calls method on the proxied D-Bus object
*
@@ -85,7 +101,23 @@ namespace sdbus {
*
* @throws sdbus::Error in case of failure
*/
virtual MethodReply callMethod(const sdbus::MethodCall& message) = 0;
virtual MethodReply callMethod(const MethodCall& message) = 0;
/*!
* @brief Calls method on the proxied D-Bus object asynchronously
*
* @param[in] message Message representing an async method call
* @param[in] asyncReplyHandler Handler for the async reply
*
* The call is non-blocking. It doesn't wait for the reply. Once the reply arrives,
* the provided async reply handler will get invoked from the context of the connection
* event loop processing thread.
*
* Note: To avoid messing with messages, use higher-level API defined below.
*
* @throws sdbus::Error in case of failure
*/
virtual void callMethod(const AsyncMethodCall& message, async_reply_handler asyncReplyCallback) = 0;
/*!
* @brief Registers a handler for the desired signal emitted by the proxied D-Bus object
@@ -131,6 +163,30 @@ namespace sdbus {
*/
MethodInvoker callMethod(const std::string& methodName);
/*!
* @brief Calls method on the proxied D-Bus object asynchronously
*
* @param[in] methodName Name of the method
* @return A helper object for convenient asynchronous invocation of the method
*
* This is a high-level, convenience way of calling D-Bus methods that abstracts
* from the D-Bus message concept. Method arguments/return value are automatically (de)serialized
* in a message and D-Bus signatures automatically deduced from the provided native arguments
* and return values.
*
* Example of use:
* @code
* int a = ..., b = ...;
* object_.callMethodAsync("multiply").onInterface(INTERFACE_NAME).withArguments(a, b).uponReplyInvoke([](int result)
* {
* std::cout << "Got result of multiplying " << a << " and " << b << ": " << result << std::endl;
* });
* @endcode
*
* @throws sdbus::Error in case of failure
*/
AsyncMethodInvoker callMethodAsync(const std::string& methodName);
/*!
* @brief Registers signal handler for a given signal of the proxied D-Bus object
*
@@ -197,6 +253,11 @@ namespace sdbus {
return MethodInvoker(*this, methodName);
}
inline AsyncMethodInvoker IObjectProxy::callMethodAsync(const std::string& methodName)
{
return AsyncMethodInvoker(*this, methodName);
}
inline SignalSubscriber IObjectProxy::uponSignal(const std::string& signalName)
{
return SignalSubscriber(*this, signalName);
+8
View File
@@ -163,6 +163,14 @@ namespace sdbus {
MethodReply sendWithNoReply() const;
};
class AsyncMethodCall : public Message
{
public:
using Message::Message;
AsyncMethodCall(MethodCall&& call) noexcept;
void send(void* callback, void* userData) const;
};
class MethodReply : public Message
{
public:
+28
View File
@@ -46,12 +46,14 @@ namespace sdbus {
class Signal;
class MethodResult;
template <typename... _Results> class Result;
class Error;
}
namespace sdbus {
using method_callback = std::function<void(MethodCall& msg, MethodReply& reply)>;
using async_method_callback = std::function<void(MethodCall& msg, MethodResult result)>;
using async_reply_handler = std::function<void(MethodReply& reply, const Error* error)>;
using signal_handler = std::function<void(Signal& signal)>;
using property_set_callback = std::function<void(Message& msg)>;
using property_get_callback = std::function<void(Message& reply)>;
@@ -368,6 +370,12 @@ namespace sdbus {
static constexpr bool is_async = false;
};
template <typename... _Args>
struct function_traits<void(const Error*, _Args...)>
: public function_traits_base<void, _Args...>
{
};
template <typename... _Args, typename... _Results>
struct function_traits<void(Result<_Results...>, _Args...)>
: public function_traits_base<std::tuple<_Results...>, _Args...>
@@ -498,6 +506,15 @@ namespace sdbus {
return std::forward<_Function>(f)(std::move(r), std::get<_I>(std::forward<_Tuple>(t))...);
}
template <class _Function, class _Tuple, std::size_t... _I>
constexpr decltype(auto) apply_impl( _Function&& f
, const Error* e
, _Tuple&& t
, std::index_sequence<_I...> )
{
return std::forward<_Function>(f)(e, std::get<_I>(std::forward<_Tuple>(t))...);
}
// Version of apply_impl for functions returning non-void values.
// In this case just forward function return value.
template <class _Function, class _Tuple, std::size_t... _I>
@@ -542,6 +559,17 @@ namespace sdbus {
, std::forward<_Tuple>(t)
, std::make_index_sequence<std::tuple_size<std::decay_t<_Tuple>>::value>{} );
}
// Convert tuple `t' of values into a list of arguments
// and invoke function `f' with those arguments.
template <class _Function, class _Tuple>
constexpr decltype(auto) apply(_Function&& f, const Error* e, _Tuple&& t)
{
return detail::apply_impl( std::forward<_Function>(f)
, e
, std::forward<_Tuple>(t)
, std::make_index_sequence<std::tuple_size<std::decay_t<_Tuple>>::value>{} );
}
}
#endif /* SDBUS_CXX_TYPETRAITS_H_ */