mirror of
https://github.com/Kistler-Group/sdbus-cpp.git
synced 2025-08-03 12:04:27 +02:00
Simplify and unify callback design for both sync and async methods
This commit is contained in:
@@ -57,7 +57,6 @@ set(SDBUSCPP_PUBLIC_HDRS
|
|||||||
${SDBUSCPP_INCLUDE_DIR}/IObjectProxy.h
|
${SDBUSCPP_INCLUDE_DIR}/IObjectProxy.h
|
||||||
${SDBUSCPP_INCLUDE_DIR}/Message.h
|
${SDBUSCPP_INCLUDE_DIR}/Message.h
|
||||||
${SDBUSCPP_INCLUDE_DIR}/MethodResult.h
|
${SDBUSCPP_INCLUDE_DIR}/MethodResult.h
|
||||||
${SDBUSCPP_INCLUDE_DIR}/MethodResult.inl
|
|
||||||
${SDBUSCPP_INCLUDE_DIR}/Types.h
|
${SDBUSCPP_INCLUDE_DIR}/Types.h
|
||||||
${SDBUSCPP_INCLUDE_DIR}/TypeTraits.h
|
${SDBUSCPP_INCLUDE_DIR}/TypeTraits.h
|
||||||
${SDBUSCPP_INCLUDE_DIR}/Flags.h
|
${SDBUSCPP_INCLUDE_DIR}/Flags.h
|
||||||
|
@@ -150,7 +150,7 @@ This is how a simple Concatenator service implemented upon the basic sdbus-c++ A
|
|||||||
// to emit signals.
|
// to emit signals.
|
||||||
sdbus::IObject* g_concatenator{};
|
sdbus::IObject* g_concatenator{};
|
||||||
|
|
||||||
void concatenate(sdbus::MethodCall& call, sdbus::MethodReply& reply)
|
void concatenate(sdbus::MethodCall call)
|
||||||
{
|
{
|
||||||
// Deserialize the collection of numbers from the message
|
// Deserialize the collection of numbers from the message
|
||||||
std::vector<int> numbers;
|
std::vector<int> numbers;
|
||||||
@@ -170,8 +170,10 @@ void concatenate(sdbus::MethodCall& call, sdbus::MethodReply& reply)
|
|||||||
result += (result.empty() ? std::string() : separator) + std::to_string(number);
|
result += (result.empty() ? std::string() : separator) + std::to_string(number);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serialize resulting string to the reply
|
// Serialize resulting string to the reply and send the reply to the caller
|
||||||
|
auto reply = call.createReply();
|
||||||
reply << result;
|
reply << result;
|
||||||
|
reply.send();
|
||||||
|
|
||||||
// Emit 'concatenated' signal
|
// Emit 'concatenated' signal
|
||||||
const char* interfaceName = "org.sdbuscpp.Concatenator";
|
const char* interfaceName = "org.sdbuscpp.Concatenator";
|
||||||
@@ -205,7 +207,7 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
We establish a D-Bus sytem connection and request `org.sdbuscpp.concatenator` D-Bus name on it. This name will be used by D-Bus clients to find the service. We then create an object with path `/org/sdbuscpp/concatenator` on this connection. We register interfaces, its methods, signals that the object provides, and, through `finishRegistration()`, export the object (i.e., make it visible to clients) on the bus. Then we need to make sure to run the event loop upon the connection, which handles all incoming, outgoing and other requests.
|
We establish a D-Bus sytem connection and request `org.sdbuscpp.concatenator` D-Bus name on it. This name will be used by D-Bus clients to find the service. We then create an object with path `/org/sdbuscpp/concatenator` on this connection. We register interfaces, its methods, signals that the object provides, and, through `finishRegistration()`, export the object (i.e., make it visible to clients) on the bus. Then we need to make sure to run the event loop upon the connection, which handles all incoming, outgoing and other requests.
|
||||||
|
|
||||||
The callback for any D-Bus object method on this level is any callable of signature `void(sdbus::MethodCall& call, sdbus::MethodReply& reply)`. The first parameter `call` is the incoming method call message. We need to deserialize input arguments from it. When we can invoke the logic and get the results. Then we serialize the results to the second parameter, the pre-constructed `reply` message. The reply is then sent automatically by sdbus-c++. We also fire a signal with the results. To do this, we need to create a signal message via object's `createSignal()`, serialize the results into it, and then send it out to subscribers by invoking object's `emitSignal()`.
|
The callback for any D-Bus object method on this level is any callable of signature `void(sdbus::MethodCall call)`. The `call` parameter is the incoming method call message. We need to deserialize our method input arguments from it. Then we can invoke the logic of the method and get the results. Then for the given `call`, we create a `reply` message, pack results into it and send it back to the caller through `send()`. (If we had a void-returning method, we'd just send an empty `reply` back.) We also fire a signal with the results. To do this, we need to create a signal message via object's `createSignal()`, serialize the results into it, and then send it out to subscribers by invoking object's `emitSignal()`.
|
||||||
|
|
||||||
Please note that we can create and destroy D-Bus objects on a connection dynamically, at any time during runtime, even while there is an active event loop upon the connection. So managing D-Bus objects' lifecycle (creating, exporting and destroying D-Bus objects) is completely thread-safe.
|
Please note that we can create and destroy D-Bus objects on a connection dynamically, at any time during runtime, even while there is an active event loop upon the connection. So managing D-Bus objects' lifecycle (creating, exporting and destroying D-Bus objects) is completely thread-safe.
|
||||||
|
|
||||||
@@ -709,12 +711,12 @@ Asynchronous server-side methods
|
|||||||
|
|
||||||
So far in our tutorial, we have only considered simple server methods that are executed in a synchronous way. Sometimes the method call may take longer, however, and we don't want to block (potentially starve) other clients (whose requests may take relative short time). The solution is to execute the D-Bus methods asynchronously, and return the control quickly back to the D-Bus dispatching thread. sdbus-c++ provides API supporting async methods, and gives users the freedom to come up with their own concrete implementation mechanics (one worker thread? thread pool? ...).
|
So far in our tutorial, we have only considered simple server methods that are executed in a synchronous way. Sometimes the method call may take longer, however, and we don't want to block (potentially starve) other clients (whose requests may take relative short time). The solution is to execute the D-Bus methods asynchronously, and return the control quickly back to the D-Bus dispatching thread. sdbus-c++ provides API supporting async methods, and gives users the freedom to come up with their own concrete implementation mechanics (one worker thread? thread pool? ...).
|
||||||
|
|
||||||
### Lower-level API
|
### Using basic sdbus-c++ API
|
||||||
|
|
||||||
Considering the Concatenator example based on lower-level API, if we wanted to write `concatenate` method in an asynchronous way, you only have to adapt method signature and its body (registering the method and all the other stuff stays the same):
|
This is how the concatenate method would look like if wrote it as an asynchronous D-Bus method using the basic, lower-level API of sdbus-c++:
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
void concatenate(sdbus::MethodCall call, sdbus::MethodResult&& result)
|
void concatenate(sdbus::MethodCall call)
|
||||||
{
|
{
|
||||||
// Deserialize the collection of numbers from the message
|
// Deserialize the collection of numbers from the message
|
||||||
std::vector<int> numbers;
|
std::vector<int> numbers;
|
||||||
@@ -725,24 +727,27 @@ void concatenate(sdbus::MethodCall call, sdbus::MethodResult&& result)
|
|||||||
call >> separator;
|
call >> separator;
|
||||||
|
|
||||||
// Launch a thread for async execution...
|
// Launch a thread for async execution...
|
||||||
std::thread([numbers = std::move(numbers), separator = std::move(separator), result = std::move(result)]()
|
std::thread([numbers = std::move(numbers), separator = std::move(separator), call = std::move(call)]()
|
||||||
{
|
{
|
||||||
// Return error if there are no numbers in the collection
|
// Return error if there are no numbers in the collection
|
||||||
if (numbers.empty())
|
if (numbers.empty())
|
||||||
{
|
{
|
||||||
// This will send the error reply message back to the client
|
// Let's send the error reply message back to the client
|
||||||
result.returnError("org.sdbuscpp.Concatenator.Error", "No numbers provided");
|
auto reply = call.createErrorReply({"org.sdbuscpp.Concatenator.Error", "No numbers provided"});
|
||||||
|
reply.send();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string concatenatedStr;
|
std::string result;
|
||||||
for (auto number : numbers)
|
for (auto number : numbers)
|
||||||
{
|
{
|
||||||
concatenatedStr += (result.empty() ? std::string() : separator) + std::to_string(number);
|
result += (result.empty() ? std::string() : separator) + std::to_string(number);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This will send the reply message back to the client
|
// Let's send the reply message back to the client
|
||||||
result.returnResults(concatenatedStr);
|
auto reply = call.createReply();
|
||||||
|
reply << result;
|
||||||
|
reply.send();
|
||||||
|
|
||||||
// Emit 'concatenated' signal (creating and emitting signals is thread-safe)
|
// Emit 'concatenated' signal (creating and emitting signals is thread-safe)
|
||||||
const char* interfaceName = "org.sdbuscpp.Concatenator";
|
const char* interfaceName = "org.sdbuscpp.Concatenator";
|
||||||
@@ -753,34 +758,53 @@ void concatenate(sdbus::MethodCall call, sdbus::MethodResult&& result)
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Notice these differences as compared to the synchronous version:
|
There are a few slight differences compared to the synchronous version. Notice that we `std::move` the `call` message to the worker thread (btw we might also do input arguments deserialization in the worker thread, we don't have to do it in the current thread and then move input arguments to the worker thread...). We need the `call` message there to create the reply message once we have the (normal or error) result. Creating and sending replies, as well as creating and emitting signals is thread-safe by design. Also notice that, unlike in sync methods, sending back errors cannot be done by throwing `Error`, since we are now in the context of the worker thread, not that of the D-Bus dispatcher thread. Instead, we pass the `Error` object to the `createErrorReply()` method of the call message (this way of sending back errors, in addition to throwing, we can actually use also in classic synchronous D-Bus methods).
|
||||||
|
|
||||||
* `MethodCall` message is not given by reference, but it's `std::move`d in. Usually, we either deserialize D-Bus method input arguments synchronously (as in the example above) and then `std::move` them to the worker thread, or we `std::move` (or copy, but moving is faster and idiomatic) the `MethodCall` message to that thread directly and do both the deserialization of input arguments and the logic in that thread.
|
Method callback signature is the same in sync and async version. That means sdbus-c++ doesn't care how we execute our D-Bus method. We might very well in run-time decide whether we execute it synchronously, or whether (perhaps in case of longer, more complex calculations) we move the execution to a worker thread.
|
||||||
|
|
||||||
* Instead of `MethodReply` message given by reference, there is `MethodResult&&` as a second parameter of the callback. `MethodResult` class is move-only.
|
|
||||||
|
|
||||||
* We shall `std::move` the `MethodResult` to the worker thread, and eventually write D-Bus method results (or method error, respectively) to it via its `returnResults()` method (or `returnError()` method, respectively).
|
|
||||||
|
|
||||||
* Unlike in sync methods, reporting errors cannot be done by throwing `Error`, since the execution takes place out of the context of the D-Bus dispatcher thread. Instead, just pass the error name and message to the `returnError` method of the result holder.
|
|
||||||
|
|
||||||
That's all.
|
|
||||||
|
|
||||||
### Convenience API
|
### Convenience API
|
||||||
|
|
||||||
Callbacks of async methods in convenience sdbus-c++ API also need to take the result object as a parameter. The requirements are:
|
Callbacks of async methods based on convenience sdbus-c++ API have slightly different signature. They take a result object parameter in addition to other input parameters. The requirements are:
|
||||||
|
|
||||||
* The result holder is of type `sdbus::Result<Types...>`, where `Types...` is a list of method output argument types.
|
* The result holder is of type `Result<Types...>&&`, where `Types...` is a list of method output argument types.
|
||||||
* The result object must be a first physical parameter of the callback taken by value.
|
* The result object must be the first physical parameter of the callback taken by r-value ref. `Result` class template is move-only.
|
||||||
* The callback itself is physically a void-returning function.
|
* The callback itself is physically a void-returning function.
|
||||||
* Method input arguments are taken by value rathern than by const ref, because we usually want to `std::move` them to the worker thread. Moving is usually a lot cheaper than copying, and it's idiomatic. For non-movable types, copying is a fallback.
|
* Method input arguments are taken by value rathern than by const ref, because we usually want to `std::move` them to the worker thread. Moving is usually a lot cheaper than copying, and it's idiomatic. For non-movable types, this falls back to copying.
|
||||||
|
|
||||||
For example, we would have to change the concatenate callback signature from `std::string concatenate(const std::vector<int32_t>& numbers, const std::string& separator)` to `void concatenate(sdbus::Result<std::string>&& result, std::vector<int32_t> numbers, std::string separator)`.
|
So the concatenate callback signature would change from `std::string concatenate(const std::vector<int32_t>& numbers, const std::string& separator)` to `void concatenate(sdbus::Result<std::string>&& result, std::vector<int32_t> numbers, std::string separator)`:
|
||||||
|
|
||||||
The `Result` class template has effectively the same API as the `MethodResult` class mentioned in the above example (it inherits from `MethodResult`), so you use it in the very same way to send the results or an error back to the client.
|
```c++
|
||||||
|
void concatenate(sdbus::Result<std::string>&& result, std::vector<int32_t> numbers, std::string separator) override
|
||||||
|
{
|
||||||
|
// Launch a thread for async execution...
|
||||||
|
std::thread([this, methodResult = std::move(result), numbers = std::move(numbers), separator = std::move(separator)]()
|
||||||
|
{
|
||||||
|
// Return error if there are no numbers in the collection
|
||||||
|
if (numbers.empty())
|
||||||
|
{
|
||||||
|
// Let's send the error reply message back to the client
|
||||||
|
methodResult.returnError({"org.sdbuscpp.Concatenator.Error", "No numbers provided"});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Nothing else has to be changed. The registration of the method callback (`implementedAs`) and all the mechanics around remains completely the same.
|
std::string result;
|
||||||
|
for (auto number : numbers)
|
||||||
|
{
|
||||||
|
result += (result.empty() ? std::string() : separator) + std::to_string(number);
|
||||||
|
}
|
||||||
|
|
||||||
Note: Async D-Bus method doesn't necessarily mean we always have to delegate the work to a different thread and immediately return. We can very well execute the work and return the results (via `returnResults()`) or an error (via `return Error()`) synchronously -- i.e. directly in this thread. sdbus-c++ doesn't care, it supports both approaches. This has the benefit that we can decide at run-time, per each method call, whether we execute it synchronously or (in case of complex operation, for example) execute it asynchronously by moving the work to a worker thread.
|
// Let's send the reply message back to the client
|
||||||
|
methodResult.returnReply(result);
|
||||||
|
|
||||||
|
// Emit the 'concatenated' signal with the resulting string
|
||||||
|
this->concatenated(result);
|
||||||
|
}).detach();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `Result` is a convenience class that represents a future method result, and it is where we write the results (`returnReply()`) or an error (`returnError()`) which we want to send back to the client.
|
||||||
|
|
||||||
|
Registraion (`implementedAs()`) doesn't change. Nothing else needs to change.
|
||||||
|
|
||||||
### Marking server-side async methods in the IDL
|
### Marking server-side async methods in the IDL
|
||||||
|
|
||||||
|
@@ -65,8 +65,7 @@ namespace sdbus {
|
|||||||
std::string interfaceName_;
|
std::string interfaceName_;
|
||||||
std::string inputSignature_;
|
std::string inputSignature_;
|
||||||
std::string outputSignature_;
|
std::string outputSignature_;
|
||||||
method_callback syncCallback_;
|
method_callback methodCallback_;
|
||||||
async_method_callback asyncCallback_;
|
|
||||||
Flags flags_;
|
Flags flags_;
|
||||||
int exceptions_{}; // Number of active exceptions when SignalRegistrator is constructed
|
int exceptions_{}; // Number of active exceptions when SignalRegistrator is constructed
|
||||||
};
|
};
|
||||||
|
@@ -55,6 +55,7 @@ namespace sdbus {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when registering a DBus method", EINVAL);
|
SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when registering a DBus method", EINVAL);
|
||||||
|
SDBUS_THROW_ERROR_IF(!methodCallback_, "Method handler not specified when registering a DBus method", EINVAL);
|
||||||
|
|
||||||
// registerMethod() can throw. But as the MethodRegistrator shall always be used as an unnamed,
|
// registerMethod() can throw. But as the MethodRegistrator shall always be used as an unnamed,
|
||||||
// temporary object, i.e. not as a stack-allocated object, the double-exception situation
|
// temporary object, i.e. not as a stack-allocated object, the double-exception situation
|
||||||
@@ -65,12 +66,7 @@ namespace sdbus {
|
|||||||
// Therefore, we can allow registerMethod() to throw even if we are in the destructor.
|
// Therefore, we can allow registerMethod() to throw even if we are in the destructor.
|
||||||
// Bottomline is, to be on the safe side, the caller must take care of catching and reacting
|
// Bottomline is, to be on the safe side, the caller must take care of catching and reacting
|
||||||
// to the exception thrown from here if the caller is a destructor itself.
|
// to the exception thrown from here if the caller is a destructor itself.
|
||||||
if (syncCallback_)
|
object_.registerMethod(interfaceName_, methodName_, inputSignature_, outputSignature_, std::move(methodCallback_), flags_);
|
||||||
object_.registerMethod(interfaceName_, methodName_, inputSignature_, outputSignature_, std::move(syncCallback_));
|
|
||||||
else if(asyncCallback_)
|
|
||||||
object_.registerMethod(interfaceName_, methodName_, inputSignature_, outputSignature_, std::move(asyncCallback_));
|
|
||||||
else
|
|
||||||
SDBUS_THROW_ERROR("Method handler not specified when registering a DBus method", EINVAL);
|
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -86,14 +82,14 @@ namespace sdbus {
|
|||||||
{
|
{
|
||||||
inputSignature_ = signature_of_function_input_arguments<_Function>::str();
|
inputSignature_ = signature_of_function_input_arguments<_Function>::str();
|
||||||
outputSignature_ = signature_of_function_output_arguments<_Function>::str();
|
outputSignature_ = signature_of_function_output_arguments<_Function>::str();
|
||||||
syncCallback_ = [callback = std::forward<_Function>(callback)](MethodCall& msg, MethodReply& reply)
|
methodCallback_ = [callback = std::forward<_Function>(callback)](MethodCall call)
|
||||||
{
|
{
|
||||||
// Create a tuple of callback input arguments' types, which will be used
|
// Create a tuple of callback input arguments' types, which will be used
|
||||||
// as a storage for the argument values deserialized from the message.
|
// as a storage for the argument values deserialized from the message.
|
||||||
tuple_of_function_input_arg_types_t<_Function> inputArgs;
|
tuple_of_function_input_arg_types_t<_Function> inputArgs;
|
||||||
|
|
||||||
// Deserialize input arguments from the message into the tuple
|
// Deserialize input arguments from the message into the tuple
|
||||||
msg >> inputArgs;
|
call >> inputArgs;
|
||||||
|
|
||||||
// Invoke callback with input arguments from the tuple.
|
// Invoke callback with input arguments from the tuple.
|
||||||
// For callbacks returning a non-void value, `apply' also returns that value.
|
// For callbacks returning a non-void value, `apply' also returns that value.
|
||||||
@@ -102,7 +98,9 @@ namespace sdbus {
|
|||||||
|
|
||||||
// The return value is stored to the reply message.
|
// The return value is stored to the reply message.
|
||||||
// In case of void functions, ret is an empty tuple and thus nothing is stored.
|
// In case of void functions, ret is an empty tuple and thus nothing is stored.
|
||||||
|
auto reply = call.createReply();
|
||||||
reply << ret;
|
reply << ret;
|
||||||
|
reply.send();
|
||||||
};
|
};
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
@@ -113,17 +111,17 @@ namespace sdbus {
|
|||||||
{
|
{
|
||||||
inputSignature_ = signature_of_function_input_arguments<_Function>::str();
|
inputSignature_ = signature_of_function_input_arguments<_Function>::str();
|
||||||
outputSignature_ = signature_of_function_output_arguments<_Function>::str();
|
outputSignature_ = signature_of_function_output_arguments<_Function>::str();
|
||||||
asyncCallback_ = [callback = std::forward<_Function>(callback)](MethodCall msg, MethodResult&& result)
|
methodCallback_ = [callback = std::forward<_Function>(callback)](MethodCall call)
|
||||||
{
|
{
|
||||||
// Create a tuple of callback input arguments' types, which will be used
|
// Create a tuple of callback input arguments' types, which will be used
|
||||||
// as a storage for the argument values deserialized from the message.
|
// as a storage for the argument values deserialized from the message.
|
||||||
tuple_of_function_input_arg_types_t<_Function> inputArgs;
|
tuple_of_function_input_arg_types_t<_Function> inputArgs;
|
||||||
|
|
||||||
// Deserialize input arguments from the message into the tuple.
|
// Deserialize input arguments from the message into the tuple.
|
||||||
msg >> inputArgs;
|
call >> inputArgs;
|
||||||
|
|
||||||
// Invoke callback with input arguments from the tuple.
|
// Invoke callback with input arguments from the tuple.
|
||||||
sdbus::apply(callback, std::move(result), std::move(inputArgs)); // TODO: Use std::apply when switching to full C++17 support
|
sdbus::apply(callback, typename function_traits<_Function>::async_result_t{std::move(call)}, std::move(inputArgs));
|
||||||
};
|
};
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
|
@@ -76,30 +76,6 @@ namespace sdbus {
|
|||||||
, method_callback methodCallback
|
, method_callback methodCallback
|
||||||
, Flags flags = {} ) = 0;
|
, Flags flags = {} ) = 0;
|
||||||
|
|
||||||
/*!
|
|
||||||
* @brief Registers method that the object will provide on D-Bus
|
|
||||||
*
|
|
||||||
* @param[in] interfaceName Name of an interface that the method will belong to
|
|
||||||
* @param[in] methodName Name of the method
|
|
||||||
* @param[in] inputSignature D-Bus signature of method input parameters
|
|
||||||
* @param[in] outputSignature D-Bus signature of method output parameters
|
|
||||||
* @param[in] asyncMethodCallback Callback that implements the body of the method
|
|
||||||
* @param[in] flags D-Bus method flags (privileged, deprecated, or no reply)
|
|
||||||
*
|
|
||||||
* This overload register a method callback that will have a freedom to execute
|
|
||||||
* its body in asynchronous contexts, and send the results from those contexts.
|
|
||||||
* This can help in e.g. long operations, which then don't block the D-Bus processing
|
|
||||||
* loop thread.
|
|
||||||
*
|
|
||||||
* @throws sdbus::Error in case of failure
|
|
||||||
*/
|
|
||||||
virtual void registerMethod( const std::string& interfaceName
|
|
||||||
, const std::string& methodName
|
|
||||||
, const std::string& inputSignature
|
|
||||||
, const std::string& outputSignature
|
|
||||||
, async_method_callback asyncMethodCallback
|
|
||||||
, Flags flags = {} ) = 0;
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @brief Registers signal that the object will emit on D-Bus
|
* @brief Registers signal that the object will emit on D-Bus
|
||||||
*
|
*
|
||||||
|
@@ -31,43 +31,11 @@
|
|||||||
|
|
||||||
// Forward declaration
|
// Forward declaration
|
||||||
namespace sdbus {
|
namespace sdbus {
|
||||||
namespace internal {
|
|
||||||
class Object;
|
|
||||||
}
|
|
||||||
class Error;
|
class Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace sdbus {
|
namespace sdbus {
|
||||||
|
|
||||||
/********************************************//**
|
|
||||||
* @class MethodResult
|
|
||||||
*
|
|
||||||
* Represents result of an asynchronous server-side method.
|
|
||||||
* An instance is provided to the method and shall be set
|
|
||||||
* by the method to either method return value or an error.
|
|
||||||
*
|
|
||||||
***********************************************/
|
|
||||||
class MethodResult
|
|
||||||
{
|
|
||||||
protected:
|
|
||||||
friend sdbus::internal::Object;
|
|
||||||
|
|
||||||
MethodResult() = default;
|
|
||||||
MethodResult(MethodCall msg);
|
|
||||||
|
|
||||||
MethodResult(const MethodResult&) = delete;
|
|
||||||
MethodResult& operator=(const MethodResult&) = delete;
|
|
||||||
|
|
||||||
MethodResult(MethodResult&& other) = default;
|
|
||||||
MethodResult& operator=(MethodResult&& other) = default;
|
|
||||||
|
|
||||||
template <typename... _Results> void returnResults(const _Results&... results) const;
|
|
||||||
void returnError(const Error& error) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
MethodCall call_;
|
|
||||||
};
|
|
||||||
|
|
||||||
/********************************************//**
|
/********************************************//**
|
||||||
* @class Result
|
* @class Result
|
||||||
*
|
*
|
||||||
@@ -77,18 +45,52 @@ namespace sdbus {
|
|||||||
*
|
*
|
||||||
***********************************************/
|
***********************************************/
|
||||||
template <typename... _Results>
|
template <typename... _Results>
|
||||||
class Result : protected MethodResult
|
class Result
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Result() = default;
|
Result() = default;
|
||||||
Result(MethodResult&& result);
|
Result(MethodCall call);
|
||||||
|
|
||||||
|
Result(const Result&) = delete;
|
||||||
|
Result& operator=(const Result&) = delete;
|
||||||
|
|
||||||
|
Result(Result&& other) = default;
|
||||||
|
Result& operator=(Result&& other) = default;
|
||||||
|
|
||||||
void returnResults(const _Results&... results) const;
|
void returnResults(const _Results&... results) const;
|
||||||
void returnError(const Error& error) const;
|
void returnError(const Error& error) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
MethodCall call_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename... _Results>
|
||||||
|
inline Result<_Results...>::Result(MethodCall call)
|
||||||
|
: call_(std::move(call))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... _Results>
|
||||||
|
inline void Result<_Results...>::returnResults(const _Results&... results) const
|
||||||
|
{
|
||||||
|
assert(call_.isValid());
|
||||||
|
auto reply = call_.createReply();
|
||||||
|
#ifdef __cpp_fold_expressions
|
||||||
|
(reply << ... << results);
|
||||||
|
#else
|
||||||
|
using _ = std::initializer_list<int>;
|
||||||
|
(void)_{(void(reply << results), 0)...};
|
||||||
|
#endif
|
||||||
|
reply.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... _Results>
|
||||||
|
inline void Result<_Results...>::returnError(const Error& error) const
|
||||||
|
{
|
||||||
|
auto reply = call_.createErrorReply(error);
|
||||||
|
reply.send();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#include <sdbus-c++/MethodResult.inl>
|
|
||||||
|
|
||||||
#endif /* SDBUS_CXX_METHODRESULT_H_ */
|
#endif /* SDBUS_CXX_METHODRESULT_H_ */
|
||||||
|
@@ -1,79 +0,0 @@
|
|||||||
/**
|
|
||||||
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
|
|
||||||
*
|
|
||||||
* @file MethodResult.inl
|
|
||||||
*
|
|
||||||
* Created on: Mar 21, 2019
|
|
||||||
* Project: sdbus-c++
|
|
||||||
* Description: High-level D-Bus IPC C++ library based on sd-bus
|
|
||||||
*
|
|
||||||
* This file is part of sdbus-c++.
|
|
||||||
*
|
|
||||||
* sdbus-c++ is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Lesser General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 2.1 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* sdbus-c++ is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public License
|
|
||||||
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef SDBUS_CXX_METHODRESULT_INL_
|
|
||||||
#define SDBUS_CXX_METHODRESULT_INL_
|
|
||||||
|
|
||||||
#include <sdbus-c++/MethodResult.h>
|
|
||||||
#include <cassert>
|
|
||||||
|
|
||||||
namespace sdbus {
|
|
||||||
|
|
||||||
inline MethodResult::MethodResult(MethodCall msg)
|
|
||||||
: call_(std::move(msg))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename... _Results>
|
|
||||||
inline void MethodResult::returnResults(const _Results&... results) const
|
|
||||||
{
|
|
||||||
assert(call_.isValid());
|
|
||||||
auto reply = call_.createReply();
|
|
||||||
#ifdef __cpp_fold_expressions
|
|
||||||
(reply << ... << results);
|
|
||||||
#else
|
|
||||||
using _ = std::initializer_list<int>;
|
|
||||||
(void)_{(void(reply << results), 0)...};
|
|
||||||
#endif
|
|
||||||
reply.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void MethodResult::returnError(const Error& error) const
|
|
||||||
{
|
|
||||||
auto reply = call_.createErrorReply(error);
|
|
||||||
reply.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename... _Results>
|
|
||||||
inline Result<_Results...>::Result(MethodResult&& result)
|
|
||||||
: MethodResult(std::move(result))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename... _Results>
|
|
||||||
inline void Result<_Results...>::returnResults(const _Results&... results) const
|
|
||||||
{
|
|
||||||
MethodResult::returnResults(results...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename... _Results>
|
|
||||||
inline void Result<_Results...>::returnError(const Error& error) const
|
|
||||||
{
|
|
||||||
MethodResult::returnError(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* SDBUS_CXX_METHODRESULT_INL_ */
|
|
@@ -44,15 +44,13 @@ namespace sdbus {
|
|||||||
class MethodCall;
|
class MethodCall;
|
||||||
class MethodReply;
|
class MethodReply;
|
||||||
class Signal;
|
class Signal;
|
||||||
class MethodResult;
|
|
||||||
template <typename... _Results> class Result;
|
template <typename... _Results> class Result;
|
||||||
class Error;
|
class Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace sdbus {
|
namespace sdbus {
|
||||||
|
|
||||||
using method_callback = std::function<void(MethodCall& msg, MethodReply& reply)>;
|
using method_callback = std::function<void(MethodCall msg)>;
|
||||||
using async_method_callback = std::function<void(MethodCall msg, MethodResult&& result)>;
|
|
||||||
using async_reply_handler = std::function<void(MethodReply& reply, const Error* error)>;
|
using async_reply_handler = std::function<void(MethodReply& reply, const Error* error)>;
|
||||||
using signal_handler = std::function<void(Signal& signal)>;
|
using signal_handler = std::function<void(Signal& signal)>;
|
||||||
using property_set_callback = std::function<void(Message& msg)>;
|
using property_set_callback = std::function<void(Message& msg)>;
|
||||||
@@ -381,6 +379,7 @@ namespace sdbus {
|
|||||||
: public function_traits_base<std::tuple<_Results...>, _Args...>
|
: public function_traits_base<std::tuple<_Results...>, _Args...>
|
||||||
{
|
{
|
||||||
static constexpr bool is_async = true;
|
static constexpr bool is_async = true;
|
||||||
|
using async_result_t = Result<_Results...>;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename... _Args, typename... _Results>
|
template <typename... _Args, typename... _Results>
|
||||||
@@ -388,6 +387,7 @@ namespace sdbus {
|
|||||||
: public function_traits_base<std::tuple<_Results...>, _Args...>
|
: public function_traits_base<std::tuple<_Results...>, _Args...>
|
||||||
{
|
{
|
||||||
static constexpr bool is_async = true;
|
static constexpr bool is_async = true;
|
||||||
|
using async_result_t = Result<_Results...>;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename _ReturnType, typename... _Args>
|
template <typename _ReturnType, typename... _Args>
|
||||||
@@ -504,9 +504,9 @@ namespace sdbus {
|
|||||||
|
|
||||||
namespace detail
|
namespace detail
|
||||||
{
|
{
|
||||||
template <class _Function, class _Tuple, std::size_t... _I>
|
template <class _Function, class _Tuple, typename... _Args, std::size_t... _I>
|
||||||
constexpr decltype(auto) apply_impl( _Function&& f
|
constexpr decltype(auto) apply_impl( _Function&& f
|
||||||
, MethodResult&& r
|
, Result<_Args...>&& r
|
||||||
, _Tuple&& t
|
, _Tuple&& t
|
||||||
, std::index_sequence<_I...> )
|
, std::index_sequence<_I...> )
|
||||||
{
|
{
|
||||||
@@ -558,8 +558,8 @@ namespace sdbus {
|
|||||||
|
|
||||||
// Convert tuple `t' of values into a list of arguments
|
// Convert tuple `t' of values into a list of arguments
|
||||||
// and invoke function `f' with those arguments.
|
// and invoke function `f' with those arguments.
|
||||||
template <class _Function, class _Tuple>
|
template <class _Function, class _Tuple, typename... _Args>
|
||||||
constexpr decltype(auto) apply(_Function&& f, MethodResult&& r, _Tuple&& t)
|
constexpr decltype(auto) apply(_Function&& f, Result<_Args...>&& r, _Tuple&& t)
|
||||||
{
|
{
|
||||||
return detail::apply_impl( std::forward<_Function>(f)
|
return detail::apply_impl( std::forward<_Function>(f)
|
||||||
, std::move(r)
|
, std::move(r)
|
||||||
|
@@ -45,6 +45,7 @@ MethodRegistrator::~MethodRegistrator() noexcept(false) // since C++11, destruct
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when registering a DBus method", EINVAL);
|
SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when registering a DBus method", EINVAL);
|
||||||
|
SDBUS_THROW_ERROR_IF(!methodCallback_, "Method handler not specified when registering a DBus method", EINVAL);
|
||||||
|
|
||||||
// registerMethod() can throw. But as the MethodRegistrator shall always be used as an unnamed,
|
// registerMethod() can throw. But as the MethodRegistrator shall always be used as an unnamed,
|
||||||
// temporary object, i.e. not as a stack-allocated object, the double-exception situation
|
// temporary object, i.e. not as a stack-allocated object, the double-exception situation
|
||||||
@@ -55,12 +56,7 @@ MethodRegistrator::~MethodRegistrator() noexcept(false) // since C++11, destruct
|
|||||||
// Therefore, we can allow registerMethod() to throw even if we are in the destructor.
|
// Therefore, we can allow registerMethod() to throw even if we are in the destructor.
|
||||||
// Bottomline is, to be on the safe side, the caller must take care of catching and reacting
|
// Bottomline is, to be on the safe side, the caller must take care of catching and reacting
|
||||||
// to the exception thrown from here if the caller is a destructor itself.
|
// to the exception thrown from here if the caller is a destructor itself.
|
||||||
if (syncCallback_)
|
object_.registerMethod(interfaceName_, methodName_, inputSignature_, outputSignature_, std::move(methodCallback_), flags_);
|
||||||
object_.registerMethod(interfaceName_, methodName_, inputSignature_, outputSignature_, std::move(syncCallback_), flags_);
|
|
||||||
else if(asyncCallback_)
|
|
||||||
object_.registerMethod(interfaceName_, methodName_, inputSignature_, outputSignature_, std::move(asyncCallback_), flags_);
|
|
||||||
else
|
|
||||||
SDBUS_THROW_ERROR("Method handler not specified when registering a DBus method", EINVAL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SignalRegistrator::SignalRegistrator(IObject& object, const std::string& signalName)
|
SignalRegistrator::SignalRegistrator(IObject& object, const std::string& signalName)
|
||||||
|
@@ -51,37 +51,8 @@ void Object::registerMethod( const std::string& interfaceName
|
|||||||
{
|
{
|
||||||
SDBUS_THROW_ERROR_IF(!methodCallback, "Invalid method callback provided", EINVAL);
|
SDBUS_THROW_ERROR_IF(!methodCallback, "Invalid method callback provided", EINVAL);
|
||||||
|
|
||||||
auto syncCallback = [callback = std::move(methodCallback)](MethodCall& msg)
|
|
||||||
{
|
|
||||||
auto reply = msg.createReply();
|
|
||||||
callback(msg, reply);
|
|
||||||
reply.send();
|
|
||||||
};
|
|
||||||
|
|
||||||
auto& interface = interfaces_[interfaceName];
|
auto& interface = interfaces_[interfaceName];
|
||||||
InterfaceData::MethodData methodData{inputSignature, outputSignature, std::move(syncCallback), flags};
|
InterfaceData::MethodData methodData{inputSignature, outputSignature, std::move(methodCallback), flags};
|
||||||
auto inserted = interface.methods_.emplace(methodName, std::move(methodData)).second;
|
|
||||||
|
|
||||||
SDBUS_THROW_ERROR_IF(!inserted, "Failed to register method: method already exists", EINVAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Object::registerMethod( const std::string& interfaceName
|
|
||||||
, const std::string& methodName
|
|
||||||
, const std::string& inputSignature
|
|
||||||
, const std::string& outputSignature
|
|
||||||
, async_method_callback asyncMethodCallback
|
|
||||||
, Flags flags )
|
|
||||||
{
|
|
||||||
SDBUS_THROW_ERROR_IF(!asyncMethodCallback, "Invalid method callback provided", EINVAL);
|
|
||||||
|
|
||||||
auto asyncCallback = [callback = std::move(asyncMethodCallback)](MethodCall& msg)
|
|
||||||
{
|
|
||||||
MethodResult result{msg};
|
|
||||||
callback(std::move(msg), std::move(result));
|
|
||||||
};
|
|
||||||
|
|
||||||
auto& interface = interfaces_[interfaceName];
|
|
||||||
InterfaceData::MethodData methodData{inputSignature, outputSignature, std::move(asyncCallback), flags};
|
|
||||||
auto inserted = interface.methods_.emplace(methodName, std::move(methodData)).second;
|
auto inserted = interface.methods_.emplace(methodName, std::move(methodData)).second;
|
||||||
|
|
||||||
SDBUS_THROW_ERROR_IF(!inserted, "Failed to register method: method already exists", EINVAL);
|
SDBUS_THROW_ERROR_IF(!inserted, "Failed to register method: method already exists", EINVAL);
|
||||||
@@ -245,7 +216,7 @@ int Object::sdbus_method_callback(sd_bus_message *sdbusMessage, void *userData,
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
callback(message);
|
callback(std::move(message));
|
||||||
}
|
}
|
||||||
catch (const sdbus::Error& e)
|
catch (const sdbus::Error& e)
|
||||||
{
|
{
|
||||||
|
@@ -52,13 +52,6 @@ namespace internal {
|
|||||||
, method_callback methodCallback
|
, method_callback methodCallback
|
||||||
, Flags flags ) override;
|
, Flags flags ) override;
|
||||||
|
|
||||||
void registerMethod( const std::string& interfaceName
|
|
||||||
, const std::string& methodName
|
|
||||||
, const std::string& inputSignature
|
|
||||||
, const std::string& outputSignature
|
|
||||||
, async_method_callback asyncMethodCallback
|
|
||||||
, Flags flags ) override;
|
|
||||||
|
|
||||||
void registerSignal( const std::string& interfaceName
|
void registerSignal( const std::string& interfaceName
|
||||||
, const std::string& signalName
|
, const std::string& signalName
|
||||||
, const std::string& signature
|
, const std::string& signature
|
||||||
@@ -93,7 +86,7 @@ namespace internal {
|
|||||||
{
|
{
|
||||||
std::string inputArgs_;
|
std::string inputArgs_;
|
||||||
std::string outputArgs_;
|
std::string outputArgs_;
|
||||||
std::function<void(MethodCall&)> callback_;
|
method_callback callback_;
|
||||||
Flags flags_;
|
Flags flags_;
|
||||||
};
|
};
|
||||||
std::map<MethodName, MethodData> methods_;
|
std::map<MethodName, MethodData> methods_;
|
||||||
|
Reference in New Issue
Block a user