forked from Kistler-Group/sdbus-cpp
Compare commits
18 Commits
Author | SHA1 | Date | |
---|---|---|---|
5673a9bcf2 | |||
b46fb170ea | |||
878ce6fa5c | |||
461ac241c8 | |||
a5692c08ea | |||
4fd2479b06 | |||
581e849534 | |||
63637b639f | |||
fc60700e1b | |||
a04ab9f445 | |||
1c4abab3e4 | |||
d489eee9c0 | |||
cbf2218301 | |||
6f79c5bf14 | |||
7c968e78cb | |||
fd7be39dd4 | |||
26c6ea8730 | |||
663df31398 |
34
CMakeLists.txt
Executable file → Normal file
34
CMakeLists.txt
Executable file → Normal file
@ -4,7 +4,7 @@
|
||||
|
||||
cmake_minimum_required(VERSION 3.8)
|
||||
|
||||
project(sdbus-c++ VERSION 0.4.3 LANGUAGES C CXX)
|
||||
project(sdbus-c++ VERSION 0.5.1 LANGUAGES C CXX)
|
||||
|
||||
include(GNUInstallDirs) # Installation directories for `install` command and pkgconfig file
|
||||
|
||||
@ -28,7 +28,6 @@ set(SDBUSCPP_CPP_SRCS
|
||||
${SDBUSCPP_SOURCE_DIR}/ConvenienceClasses.cpp
|
||||
${SDBUSCPP_SOURCE_DIR}/Error.cpp
|
||||
${SDBUSCPP_SOURCE_DIR}/Message.cpp
|
||||
${SDBUSCPP_SOURCE_DIR}/MethodResult.cpp
|
||||
${SDBUSCPP_SOURCE_DIR}/Object.cpp
|
||||
${SDBUSCPP_SOURCE_DIR}/ObjectProxy.cpp
|
||||
${SDBUSCPP_SOURCE_DIR}/Types.cpp
|
||||
@ -58,10 +57,11 @@ set(SDBUSCPP_PUBLIC_HDRS
|
||||
${SDBUSCPP_INCLUDE_DIR}/IObjectProxy.h
|
||||
${SDBUSCPP_INCLUDE_DIR}/Message.h
|
||||
${SDBUSCPP_INCLUDE_DIR}/MethodResult.h
|
||||
${SDBUSCPP_INCLUDE_DIR}/sdbus-c++.h
|
||||
${SDBUSCPP_INCLUDE_DIR}/MethodResult.inl
|
||||
${SDBUSCPP_INCLUDE_DIR}/Types.h
|
||||
${SDBUSCPP_INCLUDE_DIR}/TypeTraits.h
|
||||
${SDBUSCPP_INCLUDE_DIR}/Flags.h)
|
||||
${SDBUSCPP_INCLUDE_DIR}/Flags.h
|
||||
${SDBUSCPP_INCLUDE_DIR}/sdbus-c++.h)
|
||||
|
||||
set(SDBUSCPP_SRCS ${SDBUSCPP_CPP_SRCS} ${SDBUSCPP_HDR_SRCS} ${SDBUSCPP_PUBLIC_HDRS})
|
||||
|
||||
@ -80,14 +80,16 @@ include_directories("${CMAKE_SOURCE_DIR}/src")
|
||||
set(SDBUSCPP_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}")
|
||||
set(SDBUSCPP_VERSION "${PROJECT_VERSION}")
|
||||
|
||||
# We are building in two steps: first objects, then link them into a library,
|
||||
# and that's because we need object files since unit tests link against them.
|
||||
add_library(sdbuscppobjects OBJECT ${SDBUSCPP_SRCS})
|
||||
target_include_directories(sdbuscppobjects PUBLIC ${SYSTEMD_INCLUDE_DIRS})
|
||||
target_compile_definitions(sdbuscppobjects PRIVATE BUILDLIB=1)
|
||||
set_target_properties(sdbuscppobjects PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
||||
option(BUILD_SHARED_LIBS "Build shared libraries (.so) instead of static ones (.a)" ON)
|
||||
|
||||
add_library(sdbus-c++ SHARED $<TARGET_OBJECTS:sdbuscppobjects>)
|
||||
add_library(sdbus-cpp OBJECT ${SDBUSCPP_SRCS})
|
||||
target_include_directories(sdbus-cpp PUBLIC ${SYSTEMD_INCLUDE_DIRS})
|
||||
target_compile_definitions(sdbus-cpp PRIVATE BUILDLIB=1)
|
||||
if(BUILD_SHARED_LIBS)
|
||||
set_target_properties(sdbus-cpp PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
||||
endif()
|
||||
|
||||
add_library(sdbus-c++ $<TARGET_OBJECTS:sdbus-cpp>)
|
||||
set_target_properties(sdbus-c++
|
||||
PROPERTIES
|
||||
PUBLIC_HEADER "${SDBUSCPP_PUBLIC_HDRS}"
|
||||
@ -110,9 +112,9 @@ install(TARGETS sdbus-c++
|
||||
# TESTS
|
||||
#----------------------------------
|
||||
|
||||
option(ENABLE_TESTS "Build and install tests (default ON)" ON)
|
||||
option(BUILD_TESTS "Build and install tests (default OFF)" OFF)
|
||||
|
||||
if(ENABLE_TESTS)
|
||||
if(BUILD_TESTS)
|
||||
enable_testing()
|
||||
add_subdirectory("${CMAKE_SOURCE_DIR}/test")
|
||||
endif()
|
||||
@ -131,7 +133,11 @@ endif()
|
||||
# DOCUMENTATION
|
||||
#----------------------------------
|
||||
|
||||
# TODO Build doxygen
|
||||
option(BUILD_DOC "Build doxygen documentation for sdbus-c++ API" ON)
|
||||
|
||||
if(BUILD_DOC)
|
||||
add_subdirectory("${CMAKE_SOURCE_DIR}/doc")
|
||||
endif()
|
||||
|
||||
#----------------------------------
|
||||
# CMAKE CONFIG & PACKAGE CONFIG
|
||||
|
14
ChangeLog
14
ChangeLog
@ -51,3 +51,17 @@ v0.4.3
|
||||
- Add another abstraction layer for sd-bus to improve isolation for unit testing
|
||||
- Minor code improvements for consistency and clarity
|
||||
- Not-existing interface name and object path fixes in integration tests
|
||||
|
||||
v0.5.0
|
||||
- Redesign of the Connection model for scalability, thread-safety, simplicity and clarity
|
||||
- [[Breaking ABI change]] Introduce support asynchronous invocation of D-Bus methods on client-side
|
||||
- Revise and extend sdbus-c++ tutorial
|
||||
- Introduce stress tests that involve a D-Bus server and client interacting in dense communication in both sync and async ways
|
||||
- Compilation error fixes for older gcc versions
|
||||
|
||||
v0.5.1
|
||||
- Cleanups of CMakeLists.txt
|
||||
- Rename sdbus-c++ test executables for consistency
|
||||
- Rename sdbus-c++ stub code generator executable
|
||||
- Introduce generation of doxygen documentation
|
||||
- Improve documentation
|
||||
|
26
README.md
26
README.md
@ -16,9 +16,31 @@ $ make
|
||||
$ sudo make install
|
||||
```
|
||||
|
||||
By default, the library builds its unit and integration tests. That incorporates downloading and building static libraries of Google Test. Use `-DENABLE_TESTS=OFF` configure flag if you want to disable building the tests.
|
||||
### CMake configuration flags
|
||||
|
||||
By default, the library doesn't build the code generator for adaptor and proxy interfaces. Use `-DBUILD_CODE_GEN=ON` flag to also build the code generator.
|
||||
* `BUILD_CODE_GEN` [boolean]
|
||||
|
||||
Option for building the stub code generator `sdbus-c++-xml2cpp` for generating the adaptor and proxy interfaces out of the D-Bus IDL XML description. Default value: `OFF`. Use `-DBUILD_CODE_GEN=ON` flag to turn on building the code gen.
|
||||
|
||||
* `BUILD_DOC` [boolean]
|
||||
|
||||
Option for building Doxygen documentation of sdbus-c++ API. If `BUILD_DOC` is enabled, the documentation must still be built explicitly through `make doc`. Default value: `ON`. Use `-DBUILD_DOC=OFF` to disable searching for Doxygen and building Doxygen documentation of sdbus-c++ API.
|
||||
|
||||
* `BUILD_TESTS` [boolean]
|
||||
|
||||
Option for building sdbus-c++ unit and integration tests, invokable by `make test`. That incorporates downloading and building static libraries of Google Test. Default value: `OFF`. Use `-DBUILD_TESTS=ON` to enable building the tests. With this option turned on, you may also enable/disable the following options:
|
||||
|
||||
* `BUILD_PERF_TESTS` [boolean]
|
||||
|
||||
Option for building sdbus-c++ performance tests. Default value: `OFF`.
|
||||
|
||||
* `BUILD_STRESS_TESTS` [boolean]
|
||||
|
||||
Option for building sdbus-c++ stress tests. Default value: `OFF`.
|
||||
|
||||
* `TESTS_INSTALL_PATH` [string]
|
||||
|
||||
Path where the test binaries shall get installed. Default value: `/opt/test/bin`.
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
17
doc/CMakeLists.txt
Normal file
17
doc/CMakeLists.txt
Normal file
@ -0,0 +1,17 @@
|
||||
# Building doxygen documentation
|
||||
|
||||
find_package(Doxygen)
|
||||
|
||||
if(DOXYGEN_FOUND)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile @ONLY)
|
||||
|
||||
add_custom_target(doc
|
||||
COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMENT "Generating API documentation with Doxygen"
|
||||
VERBATIM)
|
||||
|
||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html DESTINATION ${CMAKE_INSTALL_DOCDIR})
|
||||
else()
|
||||
message(WARNING "Documentation enabled, but Doxygen cannot be found")
|
||||
endif()
|
2494
doc/Doxyfile.in
Normal file
2494
doc/Doxyfile.in
Normal file
File diff suppressed because it is too large
Load Diff
0
doc/sdbus-c++-class-diagram.png
Executable file → Normal file
0
doc/sdbus-c++-class-diagram.png
Executable file → Normal file
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
@ -7,24 +7,23 @@ Using sdbus-c++ library
|
||||
2. [Integrating sdbus-c++ into your project](#integrating-sdbus-c-into-your-project)
|
||||
3. [Header files and namespaces](#header-files-and-namespaces)
|
||||
4. [Error signalling and propagation](#error-signalling-and-propagation)
|
||||
5. [Multiple layers of sdbus-c++ API](#multiple-layers-of-sdbus-c-api)
|
||||
6. [An example: Number concatenator](#an-example-number-concatenator)
|
||||
7. [Implementing the Concatenator example using basic sdbus-c++ API layer](#implementing-the-concatenator-example-using-basic-sdbus-c-api-layer)
|
||||
8. [Implementing the Concatenator example using convenience sdbus-c++ API layer](#implementing-the-concatenator-example-using-convenience-sdbus-c-api-layer)
|
||||
9. [Implementing the Concatenator example using sdbus-c++-generated stubs](#implementing-the-concatenator-example-using-sdbus-c-generated-stubs)
|
||||
10. [Asynchronous server-side methods](#asynchronous-server-side-methods)
|
||||
11. [Using D-Bus properties](#using-d-bus-properties)
|
||||
12. [Conclusion](#conclusion)
|
||||
5. [Design of sdbus-c++](#design-of-sdbus-c)
|
||||
6. [Multiple layers of sdbus-c++ API](#multiple-layers-of-sdbus-c-api)
|
||||
7. [An example: Number concatenator](#an-example-number-concatenator)
|
||||
8. [Implementing the Concatenator example using basic sdbus-c++ API layer](#implementing-the-concatenator-example-using-basic-sdbus-c-api-layer)
|
||||
9. [Implementing the Concatenator example using convenience sdbus-c++ API layer](#implementing-the-concatenator-example-using-convenience-sdbus-c-api-layer)
|
||||
10. [Implementing the Concatenator example using sdbus-c++-generated stubs](#implementing-the-concatenator-example-using-sdbus-c-generated-stubs)
|
||||
11. [Asynchronous server-side methods](#asynchronous-server-side-methods)
|
||||
12. [Asynchronous client-side methods](#asynchronous-client-side-methods)
|
||||
13. [Using D-Bus properties](#using-d-bus-properties)
|
||||
14. [Conclusion](#conclusion)
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
sdbus-c++ is a C++ wrapper library built on top of [sd-bus](http://0pointer.net/blog/the-new-sd-bus-api-of-systemd.html), a lightweight D-Bus client
|
||||
library implemented within systemd project. It provides D-Bus functionality on a higher level of abstraction, trying to employ C++ type system
|
||||
to shift as much work as possible from the developer to the compiler.
|
||||
sdbus-c++ is a C++ wrapper library built on top of [sd-bus](http://0pointer.net/blog/the-new-sd-bus-api-of-systemd.html), a lightweight D-Bus client library implemented within systemd project. It provides D-Bus functionality on a higher level of abstraction, trying to employ C++ type system to shift as much work as possible from the developer to the compiler.
|
||||
|
||||
sdbus-c++ does not cover the entire sd-bus API, but provides tools for implementing the most common functionality - RPC
|
||||
method calls, signals and properties. There is room for additions and improvements, as needed and when needed.
|
||||
sdbus-c++ does not cover the entire sd-bus API, but provides tools for implementing the most common functionality - RPC method calls, signals and properties. There is room for additions and improvements, as needed and when needed.
|
||||
|
||||
Integrating sdbus-c++ into your project
|
||||
---------------------------------------
|
||||
@ -49,8 +48,7 @@ Note: sdbus-c++ library depends on C++17, since it uses C++17 `std::uncaught_exc
|
||||
Header files and namespaces
|
||||
---------------------------
|
||||
|
||||
All sdbus-c++ header files reside in the `sdbus-c++` subdirectory within the standard include directory. Users can either include
|
||||
individual header files, like so:
|
||||
All sdbus-c++ header files reside in the `sdbus-c++` subdirectory within the standard include directory. Users can either include individual header files, like so:
|
||||
|
||||
```cpp
|
||||
#include <sdbus-c++/IConnection.h>
|
||||
@ -69,37 +67,51 @@ Error signalling and propagation
|
||||
--------------------------------
|
||||
|
||||
`sdbus::Error` exception is used to signal errors in sdbus-c++. There are two types of errors:
|
||||
* D-Bus related errors, like call timeouts, failed socket allocation, etc.
|
||||
* user errors, i.e. errors signalled and propagated from remote methods back to the caller.
|
||||
|
||||
The exception object carries the error name and error message with it.
|
||||
* D-Bus related errors, like call timeouts, failed socket allocation, etc. These are raised by the D-Bus library or D-Bus daemon itself.
|
||||
* user-defined errors, i.e. errors signalled and propagated from remote methods back to the caller. So these are issued by sdbus-c++ users.
|
||||
|
||||
sdbus-c++ design
|
||||
----------------
|
||||
`sdbus::Error` is a carrier for both types of errors, carrying the error name and error message with it.
|
||||
|
||||
Design of sdbus-c++
|
||||
-------------------
|
||||
|
||||
The following diagram illustrates the major entities in sdbus-c++.
|
||||
|
||||

|
||||
|
||||
`IConnection` represents the concept of a D-Bus connection. You can connect to either the system bus or a session bus. Services can assign unique service names to those connections. A processing loop can be run on the connection.
|
||||
`IConnection` represents the concept of a D-Bus connection. You can connect to either the system bus or a session bus. Services can assign unique service names to those connections. A processing loop should be run on the connection.
|
||||
|
||||
`IObject` represents the concept of an object that exposes its methods, signals and properties. Its responsibilities are:
|
||||
* registering (possibly multiple) interfaces and methods, signals, properties on those interfaces,
|
||||
* emitting signals.
|
||||
|
||||
|
||||
* registering (possibly multiple) interfaces and methods, signals, properties on those interfaces,
|
||||
* emitting signals.
|
||||
|
||||
`IObjectProxy` represents the concept of the proxy, which is a view of the `Object` from the client side. Its responsibilities are:
|
||||
* invoking remote methods of the corresponding object,
|
||||
* registering handlers for signals.
|
||||
|
||||
* invoking remote methods of the corresponding object, in both synchronous and asynchronous way,
|
||||
* registering handlers for signals,
|
||||
|
||||
`Message` class represents a message, which is the fundamental DBus concept. There are three distinctive types of message that derive from the `Message` class:
|
||||
* `MethodCall` (with serialized parameters),
|
||||
* `MethodReply` (with serialized return values),
|
||||
* or a `Signal` (with serialized parameters).
|
||||
|
||||
* `MethodCall` (with serialized parameters),
|
||||
* `MethodReply` (with serialized return values),
|
||||
* or a `Signal` (with serialized parameters).
|
||||
|
||||
### Thread safety in sdbus-c++
|
||||
|
||||
sdbus-c++ is thread-aware by design. But, in general, it's not thread-safe. At least not in all places. There are situations where sdbus-c++ provides and guarantees API-level thread safety by design. It is safe to do these operations from multiple threads at the same time:
|
||||
|
||||
* Making and destroying `Object`s and `ObjectProxy`s, even on a shared connection that is already running an event loop. Under *making* here is meant a complete atomic sequence of construction, registration of method/signal/property callbacks and export of the `Object`/`ObjectProxy` so it is ready to issue/receive messages. This sequence must be done in one thread.
|
||||
* Creating and sending asynchronous method replies on an `Object` instance.
|
||||
* Creating and emitting signals on an `Object` instance.
|
||||
* Creating and sending method calls (both synchronously and asynchronously) on an `ObjectProxy` instance. (But it's generally better that our threads use their own exclusive instances of object proxy, to minimize shared state and contention.)
|
||||
|
||||
Multiple layers of sdbus-c++ API
|
||||
-------------------------------
|
||||
|
||||
sdbus-c++ API comes in two layers:
|
||||
|
||||
* [the basic layer](#implementing-the-concatenator-example-using-basic-sdbus-c-api-layer), which is a simple wrapper layer on top of sd-bus, using mechanisms that are native to C++ (e.g. serialization/deserialization of data from messages),
|
||||
* [the convenience layer](#implementing-the-concatenator-example-using-convenience-sdbus-c-api-layer), building on top of the basic layer, which aims at alleviating users from unnecessary details and enables them to write shorter, safer, and more expressive code.
|
||||
|
||||
@ -109,21 +121,22 @@ An example: Number concatenator
|
||||
-------------------------------
|
||||
|
||||
Let's have an object `/org/sdbuscpp/concatenator` that implements the `org.sdbuscpp.concatenator` interface. The interface exposes the following:
|
||||
* a `concatenate` method that takes a collection of integers and a separator string and returns a string that is the concatenation of all
|
||||
integers from the collection using given separator,
|
||||
* a `concatenated` signal that is emitted at the end of each successful concatenation.
|
||||
|
||||
* a `concatenate` method that takes a collection of integers and a separator string and returns a string that is the concatenation of all integers from the collection using given separator,
|
||||
* a `concatenated` signal that is emitted at the end of each successful concatenation.
|
||||
|
||||
In the following sections, we will elaborate on the ways of implementing such an object on both the server and the client side.
|
||||
|
||||
Implementing the Concatenator example using basic sdbus-c++ API layer
|
||||
---------------------------------------------------------------------
|
||||
|
||||
In the basic API layer, we already have abstractions for D-Bus connections, objects and object proxies, with which we can interact via
|
||||
interfaces. However, we still work with the concept of messages. To issue a method call for example, we have to go through several steps:
|
||||
we have to create a method call message first, serialize method arguments into the message, and send the message at last. We get the reply
|
||||
message (if applicable) in return, so we have to deserialize the return values from it manually.
|
||||
In the basic API layer, we already have abstractions for D-Bus connections, objects and object proxies, with which we can interact via their interface classes (`IConnection`, `IObject`, `IObjectProxy`), but, analogously to the underlying sd-bus C library, we still work on the level of D-Bus messages. We need to
|
||||
|
||||
Overloaded versions of C++ insertion/extraction operators are used for serialization/deserialization. That makes the client code much simpler.
|
||||
* create them,
|
||||
* serialize/deserialize arguments to/from them (thanks to many overloads of C++ insertion/extraction operators, this is very simple),
|
||||
* send them over to the other side.
|
||||
|
||||
This is how a simple Concatenator service implemented upon the basic sdbus-c++ API could look like:
|
||||
|
||||
### Server side
|
||||
|
||||
@ -190,6 +203,12 @@ 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.
|
||||
|
||||
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()`.
|
||||
|
||||
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.
|
||||
|
||||
### Client side
|
||||
|
||||
```c++
|
||||
@ -256,33 +275,57 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
```
|
||||
|
||||
### Proxy and D-Bus connection
|
||||
In simple cases, we don't need to create D-Bus connection explicitly for our proxies. Unless a connection is provided to an object proxy explicitly via factory parameter, the proxy will create a connection of his own, and it will be a system bus connection. This is the case in the example above. (This approach is not scalable and resource-saving if we have plenty of proxies; see section [Working with D-Bus connections](#working-with-d-bus-connections-in-sdbus-c) for elaboration.) So, in the example, we create a proxy for object `/org/sdbuscpp/concatenator` publicly available at bus `org.sdbuscpp.concatenator`. We register signal handlers, if any, and finish the registration, making the object proxy ready for use.
|
||||
|
||||
There are three ways of creating the object proxy -- three overloads of `sdbus::createObjectProxy`. They differ from each other as to how the proxy towards the connection will behave upon creation:
|
||||
The callback for a D-Bus signal handler on this level is any callable of signature `void(sdbus::Signal& signal)`. The one and only parameter `signal` is the incoming signal message. We need to deserialize arguments from it, and then we can do our business logic with it.
|
||||
|
||||
* One that takes no connection as a parameter. This one is for convenience -- if you have a simple application and don't want to bother with connections, call this one. Internally, it will create a connection object, and it will be a *system* bus connection. The proxy will immediately create an internal thread and start a processing loop upon the clone of this connection in this thread as long as there is at least one signal registered, so the signals are correctly received and the callbacks are handled from within this internal thread. If there is no signal, i.e. the proxy just provides methods and/or properties, no connection clone is made, no thread is created and no processing loop is started -- you don't pay for what you don't use.
|
||||
Subsequently, we invoke two RPC calls to object's `concatenate()` method. We create a method call message by invoking proxy's `createMethodCall()`. We serialize method input arguments into it, and make a synchronous call via proxy's `callMethod()`. As a return value we get the reply message as soon as it arrives. We deserialize return values from that message, and further use it in our program. The second `concatenate()` RPC call is done with invalid arguments, so we get a D-Bus error reply from the service, which as we can see is manifested via `sdbus::Error` exception being thrown.
|
||||
|
||||
* One that takes the connection as an **rvalue unique_ptr**. This one behaves the same as the above one, just that you must create the connection by yourself, and then `std::move` the ownership of it to the proxy. This comes with a flexibility that you can choose connection type (system, session).
|
||||
Please note that we can create and destroy D-Bus object proxies dynamically, at any time during runtime, even when they share a common D-Bus connection and there is an active event loop upon the connection. So managing D-Bus object proxies' lifecycle (creating and destroying D-Bus object proxies) is completely thread-safe.
|
||||
|
||||
* One that takes the connection as an **lvalue reference**. This one behaves differently. You as a client are the owner of the connection, you take full control of it. The proxy just references the connection. This means the proxy does no async processing on it even when there are signals. It relies on you to manage the processing loop yourself (if you need it for signals).
|
||||
### Working with D-Bus connections in sdbus-c++
|
||||
|
||||
The design of D-Bus connections in sdbus-c++ allows for certain flexibility and enables users to choose simplicity over scalability or scalability (at a finer granularity of user's choice) at the cost of slightly decreased simplicity.
|
||||
|
||||
How shall we use connections in relation to D-Bus objects and object proxies?
|
||||
|
||||
A D-Bus connection is represented by a `Connection` instance. Each connection needs an event loop being run upon it. So it needs a thread handling the event loop. This thread serves all incoming and outgoing messages and all communication towards D-Bus daemon.
|
||||
|
||||
One process can have multiple D-Bus connections, with assigned unique bus names or without, as long as those with assigned bus names do not share a common bus name.
|
||||
|
||||
A D-Bus connection can be created for and used exclusively by one D-Bus object (represented by one `Object` instance) or D-Bus object proxy (represented by one `ObjectProxy` instance), but can very well be used by and shared across multiple objects, multiple proxies or even both multiple objects and proxies at the same time. When shared, one must bear in mind that the access to the connection is mutually exclusive and is serialized. This means, for example, that if an object's callback is going to be invoked for an incoming remote method call and in another thread we use a proxy to call remote method in another process, the threads are contending and only one can go on while the other must wait and can only proceed after the first one has finished, because both are using a shared resource -- the connection.
|
||||
|
||||
The former case (1:1) is one extreme; it's usually simple, has zero resource contention, but hurts scalability (for example, 50 proxies in our program need 50 D-Bus connections and 50 event loop threads upon them). The latter case (1:N) is the other extreme -- all D-Bus objects and proxies share one single connection. This is the most scalable solution (since, for example, 5 or 200 objects/proxies use always one single connection), but may increase contention and hurt concurrency (since only one of all those objects/proxies can work with the connection at a time). And then there are limitless options between the two (for example, we can use one connection for all objects, and another connection for all proxies in our service...). sdbus-c++ gives its users freedom to choose whatever approach is more suitable to them in their application at fine granularity.
|
||||
|
||||
How can we use connections from the server and the client perspective?
|
||||
|
||||
* On the *server* side, we generally need to create D-Bus objects and publish their APIs. For that we first need a connection with a unique bus name. We need to create the D-Bus connection manually ourselves, request bus name on it, and manually launch its event loop (in a blocking way, through `enterProcessingLoop()`, or non-blocking async way, through `enterProcessingLoopAsync()`). At any time before or after running the event loop on the connection, we can create and "hook", as well as remove, objects and proxies upon that connection.
|
||||
|
||||
* On the *client* side, for our D-Bus object proxies, we have more options (corresponding to three overloads of the `createObjectProxy()` factory):
|
||||
|
||||
* We don't bother about any connection when creating a proxy. For each proxy instance sdbus-c++ also creates an internal connection instance to be used just by this proxy, and it will be a *system bus* connection. Additionally, an event loop thread for that connection is created and run internally.
|
||||
|
||||
This hurts scalability (see discussion above), but our code is simpler, and since each proxy has its own connection, there is zero contention.
|
||||
|
||||
* We create a connection explicitly by ourselves and `std::move` it to the object proxy factory. The proxy becomes an owner of this connection, and will run the event loop on that connection. This is the same as in the above bullet point, but with a flexibility that we can choose the bus type (system, session bus).
|
||||
|
||||
* We are always full owners of the connection. We create the connection, and a proxy only takes and keeps a reference to it. We take care of the event loop upon that connection (and we must ensure the connection exists as long as all its users exist).
|
||||
|
||||
Implementing the Concatenator example using convenience sdbus-c++ API layer
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
One of the major sdbus-c++ design goals is to make the sdbus-c++ API easy to use correctly, and hard to use incorrectly.
|
||||
|
||||
The convenience API layer abstracts the concept of underlying D-Bus messages away completely. It abstracts away D-Bus signatures. And it tries
|
||||
to provide an interface that uses small, focused functions, with one or zero parameters, to form a chained function statement that reads like
|
||||
a sentence to a human reading the code. To achieve that, sdbus-c++ utilizes the power of the C++ type system, so many issues are resolved at
|
||||
compile time, and the run-time performance cost compared to the basic layer is close to zero.
|
||||
The convenience API layer abstracts the concept of underlying D-Bus messages away completely. It abstracts away D-Bus signatures. The interface uses small, focused functions, with a few parameters only, to form a chained function statement that reads like a human language sentence. To achieve that, sdbus-c++ utilizes the power of the C++ type system, which deduces and resolves a lot of things at compile time, and the run-time performance cost compared to the basic layer is close to zero.
|
||||
|
||||
Thus, in the end of the day, the code written using the convenience API is:
|
||||
- more expressive,
|
||||
- closer to the abstraction level of the problem being solved,
|
||||
- shorter,
|
||||
- almost as fast (if not equally fast) as one written using the basic API layer.
|
||||
|
||||
Rather than *how*, the code written using this layer expresses *what* it does. Let's look at code samples to see if you agree :)
|
||||
- more expressive,
|
||||
- closer to the abstraction level of the problem being solved,
|
||||
- shorter,
|
||||
- almost as fast as one written using the basic API layer.
|
||||
|
||||
The code written using this layer expresses *what* it does, rather then *how*. Let's look at code samples.
|
||||
|
||||
### Server side
|
||||
|
||||
@ -394,24 +437,22 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
```
|
||||
|
||||
Several lines of code have shrunk into one-liners when registering/calling methods or signals. D-Bus signatures and the serialization/deserialization
|
||||
of arguments from the messages is generated at compile time, by introspecting signatures of provided callbacks or deducing types of provided arguments.
|
||||
When registering methods, calling methods or emitting signals, multiple lines of code have shrunk into simple one-liners. Signatures of provided callbacks are introspected and types of provided arguments are deduced at compile time, so the D-Bus signatures as well as serialization and deserialization of arguments to and from D-Bus messages are generated for us completely by the compiler.
|
||||
|
||||
sdbus-c++ users shall prefer the convenience API to the lower level, basic API. When feasible, using generated adaptor and proxy stubs is even better. These stubs provide yet another, higher API level built on top of the convenience API. They are described in the following section.
|
||||
|
||||
Implementing the Concatenator example using sdbus-c++-generated stubs
|
||||
---------------------------------------------------------------------
|
||||
|
||||
sdbus-c++ ships with the native stub generator tool called sdbuscpp-xml2cpp. The tool is very similar to dbusxx-xml2cpp tool that comes from
|
||||
dbus-c++ project.
|
||||
sdbus-c++ ships with the native stub generator tool called `sdbus-c++-xml2cpp`. The tool is very similar to `dbusxx-xml2cpp` tool that comes from dbus-c++ project.
|
||||
|
||||
The generator tool takes D-Bus XML IDL description of D-Bus interfaces on its input, and can be instructed to generate one or both of these:
|
||||
an adaptor header file for use at server side, and a proxy header file for use at client side. Like this:
|
||||
The generator tool takes D-Bus XML IDL description of D-Bus interfaces on its input, and can be instructed to generate one or both of these: an adaptor header file for use at server side, and a proxy header file for use at client side. Like this:
|
||||
|
||||
```bash
|
||||
sdbuscpp-xml2cpp database-bindings.xml --adaptor=database-server-glue.h --proxy=database-client-glue.h
|
||||
sdbus-c++-xml2cpp database-bindings.xml --adaptor=database-server-glue.h --proxy=database-client-glue.h
|
||||
```
|
||||
|
||||
The adaptor header file contains classes that can be used to implement described interfaces. The proxy header file contains classes that can be used
|
||||
to make calls to remote objects.
|
||||
The adaptor header file contains classes that can be used to implement described interfaces. The proxy header file contains classes that can be used to make calls to remote objects.
|
||||
|
||||
### XML description of the Concatenator interface
|
||||
|
||||
@ -438,13 +479,11 @@ After running this through the stubs generator, we get the stub code that is des
|
||||
|
||||
### concatenator-server-glue.h
|
||||
|
||||
There is specific class for each interface in the XML IDL file. The class is de facto an interface which shall be implemented by inheriting from it.
|
||||
The class' constructor takes care of registering all methods, signals and properties. For each D-Bus method there is a pure virtual member function.
|
||||
These pure virtual functions must be implemented in the child class. For each signal, there is a public function member that emits this signal.
|
||||
There is specific class for each interface in the XML IDL file. The class is de facto an interface which shall be implemented by inheriting from it. The class' constructor takes care of registering all methods, signals and properties. For each D-Bus method there is a pure virtual member function. These pure virtual functions must be implemented in the child class. For each signal, there is a public function member that emits this signal.
|
||||
|
||||
```cpp
|
||||
/*
|
||||
* This file was automatically generated by sdbuscpp-xml2cpp; DO NOT EDIT!
|
||||
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
|
||||
*/
|
||||
|
||||
#ifndef __sdbuscpp__concatenator_server_glue_h__adaptor__H__
|
||||
@ -490,13 +529,11 @@ private:
|
||||
|
||||
### concatenator-client-glue.h
|
||||
|
||||
Analogously to the adaptor classes described above, there is specific class for each interface in the XML IDL file. The class is de facto a proxy
|
||||
to the concrete interface of a remote object. For each D-Bus signal there is a pure virtual member function whose body must be provided in a child
|
||||
class. For each method, there is a public function member that calls the method remotely.
|
||||
Analogously to the adaptor classes described above, there is specific class for each interface in the XML IDL file. The class is de facto a proxy to the concrete interface of a remote object. For each D-Bus signal there is a pure virtual member function whose body must be provided in a child class. For each method, there is a public function member that calls the method remotely.
|
||||
|
||||
```cpp
|
||||
/*
|
||||
* This file was automatically generated by sdbuscpp-xml2cpp; DO NOT EDIT!
|
||||
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
|
||||
*/
|
||||
|
||||
#ifndef __sdbuscpp__concatenator_client_glue_h__proxy__H__
|
||||
@ -542,11 +579,7 @@ private:
|
||||
|
||||
### Providing server implementation based on generated adaptors
|
||||
|
||||
To implement a D-Bus object that implements all its D-Bus interfaces, we shall create a class representing the object that inherits from all
|
||||
corresponding `*_adaptor` classes and implements all pure virtual member functions. Specifically, the object class shall inherit from the
|
||||
`Interfaces` template class, the template arguments of which are individual adaptor classes. The `Interfaces` is just a convenience class that
|
||||
hides a few boiler-plate details. For example, in its constructor, it creates an `Object` instance, it takes care of proper initialization of
|
||||
all adaptor superclasses, and exports the object finally.
|
||||
To implement a D-Bus object that implements all its D-Bus interfaces, we shall create a class representing the object that inherits from all corresponding `*_adaptor` classes and implements all pure virtual member functions. Specifically, the object class shall inherit from the `Interfaces` template class, the template arguments of which are individual adaptor classes. The `Interfaces` is just a convenience class that hides a few boiler-plate details. For example, in its constructor, it creates an `Object` instance, it takes care of proper initialization of all adaptor superclasses, and exports the object finally.
|
||||
|
||||
```cpp
|
||||
#include <sdbus-c++/sdbus-c++.h>
|
||||
@ -583,8 +616,7 @@ protected:
|
||||
};
|
||||
```
|
||||
|
||||
That's it. We now have an implementation of a D-Bus object implementing `org.sdbuscpp.Concatenator` interface. Let's now create a service
|
||||
publishing the object.
|
||||
That's it. We now have an implementation of a D-Bus object implementing `org.sdbuscpp.Concatenator` interface. Let's now create a service publishing the object.
|
||||
|
||||
```cpp
|
||||
#include "Concatenator.h"
|
||||
@ -604,15 +636,11 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
```
|
||||
|
||||
It's that simple!
|
||||
It's that simple :)
|
||||
|
||||
### Providing client implementation based on generated proxies
|
||||
|
||||
To implement a proxy for a remote D-Bus object, we shall create a class representing the object proxy that inherits from all corresponding
|
||||
`*_proxy` classes and -- if applicable -- implements all pure virtual member functions. Specifically, the object proxy class shall inherit
|
||||
from the `ProxyInterfaces` template class. As its template arguments we shall provide all proxy classes. The `ProxyInterfaces` is just a
|
||||
convenience class that hides a few boiler-plate details. For example, in its constructor, it creates an `ObjectProxy` instance, and it takes
|
||||
care of proper initialization of all proxy superclasses.
|
||||
To implement a proxy for a remote D-Bus object, we shall create a class representing the object proxy that inherits from all corresponding `*_proxy` classes and -- if applicable -- implements all pure virtual member functions. Specifically, the object proxy class shall inherit from the `ProxyInterfaces` template class. As its template arguments we shall provide all proxy classes. The `ProxyInterfaces` is just a convenience class that hides a few boiler-plate details. For example, in its constructor, it creates an `ObjectProxy` instance, and it takes care of proper initialization of all proxy superclasses.
|
||||
|
||||
```cpp
|
||||
#include <sdbus-c++/sdbus-c++.h>
|
||||
@ -679,14 +707,14 @@ int main(int argc, char *argv[])
|
||||
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. How physically is that done is up to the server design (e.g. thread pool), but sdbus-c++ provides API supporting async 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? ...).
|
||||
|
||||
### Lower-level 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):
|
||||
|
||||
```c++
|
||||
void concatenate(sdbus::MethodCall& call, sdbus::MethodResult result)
|
||||
void concatenate(sdbus::MethodCall call, sdbus::MethodResult&& result)
|
||||
{
|
||||
// Deserialize the collection of numbers from the message
|
||||
std::vector<int> numbers;
|
||||
@ -697,7 +725,7 @@ void concatenate(sdbus::MethodCall& call, sdbus::MethodResult result)
|
||||
call >> separator;
|
||||
|
||||
// Launch a thread for async execution...
|
||||
std::thread([numbers, separator, result = std::move(result)]()
|
||||
std::thread([numbers = std::move(numbers), separator = std::move(separator), result = std::move(result)]()
|
||||
{
|
||||
// Return error if there are no numbers in the collection
|
||||
if (numbers.empty())
|
||||
@ -716,45 +744,47 @@ void concatenate(sdbus::MethodCall& call, sdbus::MethodResult result)
|
||||
// This will send the reply message back to the client
|
||||
result.returnResults(concatenatedStr);
|
||||
|
||||
// Note: emitting signals from other than D-Bus dispatcher thread is not supported yet...
|
||||
/*
|
||||
// Emit 'concatenated' signal
|
||||
// Emit 'concatenated' signal (creating and emitting signals is thread-safe)
|
||||
const char* interfaceName = "org.sdbuscpp.Concatenator";
|
||||
auto signal = g_concatenator->createSignal(interfaceName, "concatenated");
|
||||
signal << result;
|
||||
g_concatenator->emitSignal(signal);
|
||||
*/
|
||||
}).detach();
|
||||
}
|
||||
```
|
||||
|
||||
Notice these differences as compared to the synchronous version:
|
||||
|
||||
* Instead of `MethodReply` message given by reference, there is `MethodResult` as a second parameter of the callback, which will hold method results and can be written to from any thread.
|
||||
* You shall pass the result holder (`MethodResult` instance) by moving it to the thread of execution, and eventually write method results (or method error) to it via its `returnResults()` or `returnError()` method, respectively.
|
||||
* Unlike in sync methods, reporting errors cannot be done by throwing sdbus::Error, since the execution takes place out of context of the D-Bus dispatcher thread. Instead, just pass the error name and message to the `returnError` method of the result holder.
|
||||
* `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.
|
||||
|
||||
* 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.
|
||||
|
||||
Note: We can use the concept of asynchronous D-Bus methods in both the synchronous and asynchronous way. Whether we return the results directly in the callback in the synchronous way, or we pass the arguments and the result holder to a different thread, and compute and set the results in there, is irrelevant to sdbus-c++. This has the benefit that we can decide at run-time, per each method call, whether we execute it synchronously or (perhaps in case of complex operation) execute it asynchronously to e.g. a thread pool.
|
||||
|
||||
### Convenience API
|
||||
|
||||
Method callbacks in convenience sdbus-c++ API also need to take the result object as a parameter. The requirements are:
|
||||
Callbacks of async methods in convenience sdbus-c++ API also need to take the result object as a parameter. The requirements are:
|
||||
|
||||
* The result holder is of type `sdbus::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 callback itself is physically a void-returning function.
|
||||
* The result holder is of type `sdbus::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 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.
|
||||
|
||||
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, const std::vector<int32_t>& numbers, const std::string& separator)`.
|
||||
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)`.
|
||||
|
||||
`sdbus::Result` class template has effectively the same API as `sdbus::MethodResult` class from 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.
|
||||
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.
|
||||
|
||||
Nothing else has to be changed. The registration of the method callback (`implementedAs`) and all the mechanics around remains completely the same.
|
||||
|
||||
### Marking async methods in the IDL
|
||||
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.
|
||||
|
||||
sdbus-c++ stub generator can generate stub code for server-side async methods. We just need to annotate the method with the `annotate` element having the "org.freedesktop.DBus.Method.Async" name, like so:
|
||||
### Marking server-side async methods in the IDL
|
||||
|
||||
sdbus-c++ stub generator can generate stub code for server-side async methods. We just need to annotate the method with the `annotate` element having the "org.freedesktop.DBus.Method.Async" name. The element value must be either "server" (async method on server-side only) or "clientserver" (async method on both client- and server-side):
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
@ -774,6 +804,125 @@ sdbus-c++ stub generator can generate stub code for server-side async methods. W
|
||||
</node>
|
||||
```
|
||||
|
||||
For a real example of a server-side asynchronous D-Bus method, please look at sdbus-c++ [stress tests](/test/stresstests).
|
||||
|
||||
Asynchronous client-side methods
|
||||
--------------------------------
|
||||
|
||||
sdbus-c++ also supports asynchronous approach at the client (the proxy) side. With this approach, we can issue a D-Bus method call without blocking current thread's execution while waiting for the reply. We go on doing other things, and when the reply comes, a given callback is invoked within the context of the D-Bus dispatcher thread.
|
||||
|
||||
### Lower-level API
|
||||
|
||||
Considering the Concatenator example based on lower-level API, if we wanted to call `concatenate` in an async way, we'd have to pass a callback to the proxy when issuing the call, and that callback gets invoked when the reply arrives:
|
||||
|
||||
```c++
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
/* ... */
|
||||
|
||||
auto callback = [](MethodReply& reply, const sdbus::Error* error)
|
||||
{
|
||||
if (error == nullptr) // No error
|
||||
{
|
||||
std::string result;
|
||||
reply >> result;
|
||||
std::cout << "Got concatenate result: " << result << std::endl;
|
||||
}
|
||||
else // We got a D-Bus error...
|
||||
{
|
||||
std::cerr << "Got concatenate error " << error->getName() << " with message " << error->getMessage() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// Invoke concatenate on given interface of the object
|
||||
{
|
||||
auto method = concatenatorProxy->createMethodCall(interfaceName, "concatenate");
|
||||
method << numbers << separator;
|
||||
concatenatorProxy->callMethod(method, callback);
|
||||
// When the reply comes, we shall get "Got concatenate result 1:2:3" on the standard output
|
||||
}
|
||||
|
||||
// Invoke concatenate again, this time with no numbers and we shall get an error
|
||||
{
|
||||
auto method = concatenatorProxy->createMethodCall(interfaceName, "concatenate");
|
||||
method << std::vector<int>() << separator;
|
||||
concatenatorProxy->callMethod(method, callback);
|
||||
// When the reply comes, we shall get concatenation error message on the standard error output
|
||||
}
|
||||
|
||||
/* ... */
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
The callback is a void-returning function taking two arguments: a reference to the reply message, and a pointer to the prospective `sdbus::Error` instance. Zero `Error` pointer means that no D-Bus error occurred while making the call, and the reply message contains valid reply. Non-zero `Error` pointer, however, points to the valid `Error` instance, meaning that an error occurred. Error name and message can then be read out by the client from that instance.
|
||||
|
||||
### Convenience API
|
||||
|
||||
On the convenience API level, the call statement starts with `callMethodAsync()`, and ends with `uponReplyInvoke()` that takes a callback handler. The callback is a void-returning function that takes at least one argument: pointer to the `sdbus::Error` instance. All subsequent arguments shall exactly reflect the D-Bus method output arguments. A concatenator example:
|
||||
|
||||
```c++
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
/* ... */
|
||||
|
||||
auto callback = [](const sdbus::Error* error, const std::string& concatenatedString)
|
||||
{
|
||||
if (error == nullptr) // No error
|
||||
std::cout << "Got concatenate result: " << concatenatedString << std::endl;
|
||||
else // We got a D-Bus error...
|
||||
std::cerr << "Got concatenate error " << error->getName() << " with message " << error->getMessage() << std::endl;
|
||||
}
|
||||
|
||||
// Invoke concatenate on given interface of the object
|
||||
{
|
||||
concatenatorProxy->callMethodAsync("concatenate").onInterface(interfaceName).withArguments(numbers, separator).uponReplyInvoke(callback);
|
||||
// When the reply comes, we shall get "Got concatenate result 1:2:3" on the standard output
|
||||
}
|
||||
|
||||
// Invoke concatenate again, this time with no numbers and we shall get an error
|
||||
{
|
||||
concatenatorProxy->callMethodAsync("concatenate").onInterface(interfaceName).withArguments(std::vector<int>{}, separator).uponReplyInvoke(callback);
|
||||
// When the reply comes, we shall get concatenation error message on the standard error output
|
||||
}
|
||||
|
||||
/* ... */
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
When the `Error` pointer is zero, it means that no D-Bus error occurred while making the call, and subsequent arguments are valid D-Bus method return values. Non-zero `Error` pointer, however, points to the valid `Error` instance, meaning that an error occurred during the call (and subsequent arguments are simply default-constructed). Error name and message can then be read out by the client from `Error` instance.
|
||||
|
||||
### Marking client-side async methods in the IDL
|
||||
|
||||
sdbus-c++ stub generator can generate stub code for client-side async methods. We just need to annotate the method with the `annotate` element having the "org.freedesktop.DBus.Method.Async" name. The element value must be either "client" (async on the client-side only) or "clientserver" (async method on both client- and server-side):
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<node name="/org/sdbuscpp/concatenator">
|
||||
<interface name="org.sdbuscpp.Concatenator">
|
||||
<method name="concatenate">
|
||||
<annotation name="org.freedesktop.DBus.Method.Async" value="client" />
|
||||
<arg type="ai" name="numbers" direction="in" />
|
||||
<arg type="s" name="separator" direction="in" />
|
||||
<arg type="s" name="concatenatedString" direction="out" />
|
||||
</method>
|
||||
<signal name="concatenated">
|
||||
<arg type="s" name="concatenatedString" />
|
||||
</signal>
|
||||
</interface>
|
||||
</node>
|
||||
```
|
||||
|
||||
For each client-side async method, a corresponding `on<MethodName>Reply` pure virtual function, where <MethodName> is capitalized D-Bus method name, is generated in the generated proxy class. This function is the callback invoked when the D-Bus method reply arrives, and must be provided a body by overriding it in the implementation class.
|
||||
|
||||
So in the specific example above, the stub generator will generate a `Concatenator_proxy` class similar to one shown in a [dedicated section above](#concatenator-client-glueh), with the difference that it will also generate an additional `virtual void onConcatenateReply(const sdbus::Error* error, const std::string& concatenatedString);` method, which we shall override in derived `ConcatenatorProxy`.
|
||||
|
||||
For a real example of a client-side asynchronous D-Bus method, please look at sdbus-c++ [stress tests](/test/stresstests).
|
||||
|
||||
Using D-Bus properties
|
||||
----------------------
|
||||
|
||||
|
@ -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,20 @@ 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 _Function> void uponReplyInvoke(_Function&& callback);
|
||||
|
||||
private:
|
||||
IObjectProxy& objectProxy_;
|
||||
const std::string& methodName_;
|
||||
AsyncMethodCall method_;
|
||||
};
|
||||
|
||||
class SignalSubscriber
|
||||
{
|
||||
public:
|
||||
|
51
include/sdbus-c++/ConvenienceClasses.inl
Executable file → Normal file
51
include/sdbus-c++/ConvenienceClasses.inl
Executable file → Normal file
@ -113,18 +113,17 @@ namespace sdbus {
|
||||
{
|
||||
inputSignature_ = signature_of_function_input_arguments<_Function>::str();
|
||||
outputSignature_ = signature_of_function_output_arguments<_Function>::str();
|
||||
asyncCallback_ = [callback = std::forward<_Function>(callback)](MethodCall& msg, MethodResult result)
|
||||
asyncCallback_ = [callback = std::forward<_Function>(callback)](MethodCall msg, MethodResult&& result)
|
||||
{
|
||||
// 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> 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.
|
||||
sdbus::apply(callback, std::move(result), inputArgs); // TODO: Use std::apply when switching to full C++17 support
|
||||
sdbus::apply(callback, std::move(result), std::move(inputArgs)); // TODO: Use std::apply when switching to full C++17 support
|
||||
};
|
||||
|
||||
return *this;
|
||||
@ -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)
|
||||
|
13
include/sdbus-c++/Error.h
Executable file → Normal file
13
include/sdbus-c++/Error.h
Executable file → Normal file
@ -57,6 +57,11 @@ namespace sdbus {
|
||||
return message_;
|
||||
}
|
||||
|
||||
bool isValid() const
|
||||
{
|
||||
return !getName().empty();
|
||||
}
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
std::string message_;
|
||||
@ -65,12 +70,12 @@ namespace sdbus {
|
||||
sdbus::Error createError(int errNo, const std::string& customMsg);
|
||||
}
|
||||
|
||||
#define SDBUS_THROW_ERROR(_MSG, _ERRNO) \
|
||||
throw sdbus::createError((_ERRNO), (_MSG)) \
|
||||
#define SDBUS_THROW_ERROR(_MSG, _ERRNO) \
|
||||
throw sdbus::createError((_ERRNO), (_MSG)) \
|
||||
/**/
|
||||
|
||||
#define SDBUS_THROW_ERROR_IF(_COND, _MSG, _ERRNO) \
|
||||
if (_COND) SDBUS_THROW_ERROR((_MSG), (_ERRNO)) \
|
||||
#define SDBUS_THROW_ERROR_IF(_COND, _MSG, _ERRNO) \
|
||||
if (!(_COND)) ; else SDBUS_THROW_ERROR((_MSG), (_ERRNO)) \
|
||||
/**/
|
||||
|
||||
#endif /* SDBUS_CXX_ERROR_H_ */
|
||||
|
@ -38,8 +38,8 @@ namespace sdbus {
|
||||
* An interface to D-Bus bus connection. Incorporates implementation
|
||||
* of both synchronous and asynchronous processing loop.
|
||||
*
|
||||
* All methods throw @sdbus::Error in case of failure. The class is
|
||||
* thread-aware, but not thread-safe.
|
||||
* All methods throw sdbus::Error in case of failure. All methods in
|
||||
* this class are thread-aware, but not thread-safe.
|
||||
*
|
||||
***********************************************/
|
||||
class IConnection
|
||||
@ -67,7 +67,7 @@ namespace sdbus {
|
||||
* @brief Enters the D-Bus processing loop
|
||||
*
|
||||
* The incoming D-Bus messages are processed in the loop. The method
|
||||
* blocks indefinitely, until unblocked via @leaveProcessingLoop.
|
||||
* blocks indefinitely, until unblocked via leaveProcessingLoop.
|
||||
*
|
||||
* @throws sdbus::Error in case of failure
|
||||
*/
|
||||
@ -76,7 +76,7 @@ namespace sdbus {
|
||||
/*!
|
||||
* @brief Enters the D-Bus processing loop in a separate thread
|
||||
*
|
||||
* The same as @enterProcessingLoop, except that it doesn't block
|
||||
* The same as enterProcessingLoop, except that it doesn't block
|
||||
* because it runs the loop in a separate thread managed internally.
|
||||
*/
|
||||
virtual void enterProcessingLoopAsync() = 0;
|
||||
|
17
include/sdbus-c++/IObject.h
Executable file → Normal file
17
include/sdbus-c++/IObject.h
Executable file → Normal file
@ -47,8 +47,11 @@ namespace sdbus {
|
||||
* An interface to D-Bus object. Provides API for registration
|
||||
* of methods, signals, properties, and for emitting signals.
|
||||
*
|
||||
* All methods throw @c sdbus::Error in case of failure. The class is
|
||||
* thread-aware, but not thread-safe.
|
||||
* All methods throw @c sdbus::Error in case of failure.
|
||||
* In general, the class is thread-aware, but not thread-safe.
|
||||
* However, the operation of creating and sending asynchronous
|
||||
* method replies, as well as creating and emitting
|
||||
* signals, is thread-safe.
|
||||
*
|
||||
***********************************************/
|
||||
class IObject
|
||||
@ -62,7 +65,7 @@ namespace sdbus {
|
||||
* @param[in] inputSignature D-Bus signature of method input parameters
|
||||
* @param[in] outputSignature D-Bus signature of method output parameters
|
||||
* @param[in] methodCallback Callback that implements the body of the method
|
||||
* @param[in] noReply If true, the method isn't expected to send reply
|
||||
* @param[in] flags D-Bus method flags (privileged, deprecated, or no reply)
|
||||
*
|
||||
* @throws sdbus::Error in case of failure
|
||||
*/
|
||||
@ -81,7 +84,7 @@ namespace sdbus {
|
||||
* @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] noReply If true, the method isn't expected to send reply
|
||||
* @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.
|
||||
@ -103,6 +106,7 @@ namespace sdbus {
|
||||
* @param[in] interfaceName Name of an interface that the signal will fall under
|
||||
* @param[in] signalName Name of the signal
|
||||
* @param[in] signature D-Bus signature of signal parameters
|
||||
* @param[in] flags D-Bus signal flags (deprecated)
|
||||
*
|
||||
* @throws sdbus::Error in case of failure
|
||||
*/
|
||||
@ -118,6 +122,7 @@ namespace sdbus {
|
||||
* @param[in] propertyName Name of the property
|
||||
* @param[in] signature D-Bus signature of property parameters
|
||||
* @param[in] getCallback Callback that implements the body of the property getter
|
||||
* @param[in] flags D-Bus property flags (deprecated, property update behavior)
|
||||
*
|
||||
* @throws sdbus::Error in case of failure
|
||||
*/
|
||||
@ -135,6 +140,7 @@ namespace sdbus {
|
||||
* @param[in] signature D-Bus signature of property parameters
|
||||
* @param[in] getCallback Callback that implements the body of the property getter
|
||||
* @param[in] setCallback Callback that implements the body of the property setter
|
||||
* @param[in] flags D-Bus property flags (deprecated, property update behavior)
|
||||
*
|
||||
* @throws sdbus::Error in case of failure
|
||||
*/
|
||||
@ -327,6 +333,9 @@ namespace sdbus {
|
||||
* The provided connection will be used by the object to export methods,
|
||||
* issue signals and provide properties.
|
||||
*
|
||||
* Creating a D-Bus object instance is (thread-)safe even upon the connection
|
||||
* which is already running its processing loop.
|
||||
*
|
||||
* Code example:
|
||||
* @code
|
||||
* auto proxy = sdbus::createObject(connection, "/com/kistler/foo");
|
||||
|
99
include/sdbus-c++/IObjectProxy.h
Executable file → Normal file
99
include/sdbus-c++/IObjectProxy.h
Executable file → Normal file
@ -34,6 +34,7 @@
|
||||
// Forward declarations
|
||||
namespace sdbus {
|
||||
class MethodCall;
|
||||
class AsyncMethodCall;
|
||||
class MethodReply;
|
||||
class IConnection;
|
||||
}
|
||||
@ -46,8 +47,10 @@ namespace sdbus {
|
||||
* An interface to D-Bus object proxy. Provides API for calling
|
||||
* methods, getting/setting properties, and for registering to signals.
|
||||
*
|
||||
* All methods throw @c sdbus::Error in case of failure. The class is
|
||||
* thread-aware, but not thread-safe.
|
||||
* All methods throw @c sdbus::Error in case of failure.
|
||||
* In general, the class is thread-aware, but not thread-safe.
|
||||
* However, the operation of creating and sending method calls
|
||||
* (both synchronously and asynchronously) is thread-safe.
|
||||
*
|
||||
***********************************************/
|
||||
class IObjectProxy
|
||||
@ -58,7 +61,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 +71,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 +103,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] asyncReplyCallback 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 +165,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 +255,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);
|
||||
@ -223,11 +286,10 @@ namespace sdbus {
|
||||
* @return Pointer to the object proxy instance
|
||||
*
|
||||
* The provided connection will be used by the proxy to issue calls against the object,
|
||||
* and signals, if any, will be subscribed to on this connection. Since the caller still
|
||||
* remains the owner of the connection (the proxy just keeps reference to it) after the call,
|
||||
* the proxy will not start its own background processing loop for incoming signals (if any),
|
||||
* as it will rely on the client as an owner of the connection to handle processing of
|
||||
* incoming messages on that connection by themselves.
|
||||
* and signals, if any, will be subscribed to on this connection. The caller still
|
||||
* remains the owner of the connection (the proxy just keeps a reference to it), and
|
||||
* should make sure that a processing loop is running on that connection, so the proxy
|
||||
* may receive incoming signals and asynchronous method replies.
|
||||
*
|
||||
* Code example:
|
||||
* @code
|
||||
@ -247,11 +309,10 @@ namespace sdbus {
|
||||
* @return Pointer to the object proxy instance
|
||||
*
|
||||
* The provided connection will be used by the proxy to issue calls against the object,
|
||||
* and signals, if any, will be subscribed to on this connection. Object proxy becomes
|
||||
* an exclusive owner of this connection. The effect of this is that when there is at
|
||||
* least one signal in proxy's interface, then the proxy will immediately start its own
|
||||
* processing loop for this connection in a separate internal thread, causing incoming
|
||||
* signals to be correctly received and processed in the context of that internal thread.
|
||||
* and signals, if any, will be subscribed to on this connection. The Object proxy becomes
|
||||
* an exclusive owner of this connection, and will automatically start a procesing loop
|
||||
* upon that connection in a separate internal thread. Handlers for incoming signals and
|
||||
* asynchronous method replies will be executed in the context of that thread.
|
||||
*
|
||||
* Code example:
|
||||
* @code
|
||||
@ -269,14 +330,14 @@ namespace sdbus {
|
||||
* @param[in] objectPath Path of the D-Bus object
|
||||
* @return Pointer to the object proxy instance
|
||||
*
|
||||
* This factory overload creates a proxy that manages its own D-Bus connection(s).
|
||||
* When there is at least one signal in proxy's interface, then the proxy will immediately
|
||||
* start its own processing loop for this connection in its own separate thread, causing
|
||||
* incoming signals to be correctly received and processed in the context of that thread.
|
||||
* No D-Bus connection is provided here, so the object proxy will create and manage
|
||||
* his own connection, and will automatically start a procesing loop upon that connection
|
||||
* in a separate internal thread. Handlers for incoming signals and asynchronous
|
||||
* method replies will be executed in the context of that thread.
|
||||
*
|
||||
* Code example:
|
||||
* @code
|
||||
* auto proxy = sdbus::createObjectProxy(connection, "com.kistler.foo", "/com/kistler/foo");
|
||||
* auto proxy = sdbus::createObjectProxy("com.kistler.foo", "/com/kistler/foo");
|
||||
* @endcode
|
||||
*/
|
||||
std::unique_ptr<sdbus::IObjectProxy> createObjectProxy( std::string destination
|
||||
|
@ -44,16 +44,22 @@ namespace sdbus {
|
||||
class ObjectPath;
|
||||
class Signature;
|
||||
template <typename... _ValueTypes> class Struct;
|
||||
|
||||
class Message;
|
||||
class MethodCall;
|
||||
class MethodReply;
|
||||
class Signal;
|
||||
template <typename... _Results> class Result;
|
||||
namespace internal {
|
||||
class ISdBus;
|
||||
}
|
||||
}
|
||||
|
||||
namespace sdbus {
|
||||
|
||||
// Assume the caller has already obtained message ownership
|
||||
struct adopt_message_t { explicit adopt_message_t() = default; };
|
||||
#ifdef __cpp_inline_variables
|
||||
inline constexpr adopt_message_t adopt_message{};
|
||||
#else
|
||||
constexpr adopt_message_t adopt_message{};
|
||||
#endif
|
||||
|
||||
/********************************************//**
|
||||
* @class Message
|
||||
*
|
||||
@ -72,7 +78,9 @@ namespace sdbus {
|
||||
{
|
||||
public:
|
||||
Message() = default;
|
||||
Message(void *msg) noexcept;
|
||||
Message(internal::ISdBus* sdbus) noexcept;
|
||||
Message(void *msg, internal::ISdBus* sdbus) noexcept;
|
||||
Message(void *msg, internal::ISdBus* sdbus, adopt_message_t) noexcept;
|
||||
Message(const Message&) noexcept;
|
||||
Message& operator=(const Message&) noexcept;
|
||||
Message(Message&& other) noexcept;
|
||||
@ -141,10 +149,8 @@ namespace sdbus {
|
||||
void rewind(bool complete);
|
||||
|
||||
protected:
|
||||
void* getMsg() const;
|
||||
|
||||
private:
|
||||
void* msg_{};
|
||||
internal::ISdBus* sdbus_{};
|
||||
mutable bool ok_{true};
|
||||
};
|
||||
|
||||
@ -163,6 +169,15 @@ namespace sdbus {
|
||||
MethodReply sendWithNoReply() const;
|
||||
};
|
||||
|
||||
class AsyncMethodCall : public Message
|
||||
{
|
||||
public:
|
||||
using Message::Message;
|
||||
AsyncMethodCall() = default; // Fixes gcc 6.3 error (default c-tor is not imported in above using declaration)
|
||||
AsyncMethodCall(MethodCall&& call) noexcept;
|
||||
void send(void* callback, void* userData) const;
|
||||
};
|
||||
|
||||
class MethodReply : public Message
|
||||
{
|
||||
public:
|
||||
|
58
include/sdbus-c++/MethodResult.h
Executable file → Normal file
58
include/sdbus-c++/MethodResult.h
Executable file → Normal file
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
|
||||
*
|
||||
* @file ConvenienceClasses.h
|
||||
* @file MethodResult.h
|
||||
*
|
||||
* Created on: Nov 8, 2016
|
||||
* Project: sdbus-c++
|
||||
@ -51,40 +51,23 @@ namespace sdbus {
|
||||
{
|
||||
protected:
|
||||
friend sdbus::internal::Object;
|
||||
|
||||
MethodResult() = default;
|
||||
MethodResult(const MethodCall& msg, sdbus::internal::Object& object);
|
||||
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:
|
||||
void send(const MethodReply& reply) const;
|
||||
|
||||
private:
|
||||
MethodCall call_;
|
||||
sdbus::internal::Object* object_{};
|
||||
};
|
||||
|
||||
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
|
||||
send(reply);
|
||||
}
|
||||
|
||||
inline void MethodResult::returnError(const Error& error) const
|
||||
{
|
||||
auto reply = call_.createErrorReply(error);
|
||||
send(reply);
|
||||
}
|
||||
|
||||
/********************************************//**
|
||||
* @class Result
|
||||
*
|
||||
@ -98,29 +81,14 @@ namespace sdbus {
|
||||
{
|
||||
public:
|
||||
Result() = default;
|
||||
Result(MethodResult result);
|
||||
Result(MethodResult&& result);
|
||||
|
||||
void returnResults(const _Results&... results) const;
|
||||
void returnError(const Error& error) const;
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#include <sdbus-c++/MethodResult.inl>
|
||||
|
||||
#endif /* SDBUS_CXX_METHODRESULT_H_ */
|
||||
|
79
include/sdbus-c++/MethodResult.inl
Normal file
79
include/sdbus-c++/MethodResult.inl
Normal file
@ -0,0 +1,79 @@
|
||||
/**
|
||||
* (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_ */
|
@ -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_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...>
|
||||
@ -375,6 +383,13 @@ namespace sdbus {
|
||||
static constexpr bool is_async = true;
|
||||
};
|
||||
|
||||
template <typename... _Args, typename... _Results>
|
||||
struct function_traits<void(Result<_Results...>&&, _Args...)>
|
||||
: public function_traits_base<std::tuple<_Results...>, _Args...>
|
||||
{
|
||||
static constexpr bool is_async = true;
|
||||
};
|
||||
|
||||
template <typename _ReturnType, typename... _Args>
|
||||
struct function_traits<_ReturnType(*)(_Args...)>
|
||||
: public function_traits<_ReturnType(_Args...)>
|
||||
@ -498,6 +513,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 +566,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_ */
|
||||
|
@ -139,20 +139,22 @@ namespace sdbus {
|
||||
{
|
||||
public:
|
||||
using std::string::string;
|
||||
using std::string::operator=;
|
||||
ObjectPath() = default; // Fixes gcc 6.3 error (default c-tor is not imported in above using declaration)
|
||||
ObjectPath(std::string path)
|
||||
: std::string(std::move(path))
|
||||
{}
|
||||
using std::string::operator=;
|
||||
};
|
||||
|
||||
class Signature : public std::string
|
||||
{
|
||||
public:
|
||||
using std::string::string;
|
||||
using std::string::operator=;
|
||||
Signature() = default; // Fixes gcc 6.3 error (default c-tor is not imported in above using declaration)
|
||||
Signature(std::string path)
|
||||
: std::string(std::move(path))
|
||||
{}
|
||||
using std::string::operator=;
|
||||
};
|
||||
|
||||
}
|
||||
|
0
sdbus-c++-config.cmake.in
Executable file → Normal file
0
sdbus-c++-config.cmake.in
Executable file → Normal file
0
sdbus-c++.pc.in
Executable file → Normal file
0
sdbus-c++.pc.in
Executable file → Normal file
139
src/Connection.cpp
Executable file → Normal file
139
src/Connection.cpp
Executable file → Normal file
@ -36,21 +36,23 @@
|
||||
namespace sdbus { namespace internal {
|
||||
|
||||
Connection::Connection(Connection::BusType type, std::unique_ptr<ISdBus>&& interface)
|
||||
: busType_(type)
|
||||
, iface_(std::move(interface))
|
||||
: iface_(std::move(interface))
|
||||
, busType_(type)
|
||||
{
|
||||
assert(iface_ != nullptr);
|
||||
|
||||
auto bus = openBus(busType_);
|
||||
bus_.reset(bus);
|
||||
|
||||
finishHandshake(bus);
|
||||
|
||||
notificationFd_ = createLoopNotificationDescriptor();
|
||||
loopExitFd_ = createProcessingLoopExitDescriptor();
|
||||
}
|
||||
|
||||
Connection::~Connection()
|
||||
{
|
||||
leaveProcessingLoop();
|
||||
closeLoopNotificationDescriptor(notificationFd_);
|
||||
closeProcessingLoopExitDescriptor(loopExitFd_);
|
||||
}
|
||||
|
||||
void Connection::requestName(const std::string& name)
|
||||
@ -76,14 +78,13 @@ void Connection::enterProcessingLoop()
|
||||
auto success = waitForNextRequest();
|
||||
if (!success)
|
||||
break; // Exit processing loop
|
||||
if (success.asyncMsgsToProcess)
|
||||
processAsynchronousMessages();
|
||||
}
|
||||
}
|
||||
|
||||
void Connection::enterProcessingLoopAsync()
|
||||
{
|
||||
asyncLoopThread_ = std::thread([this](){ enterProcessingLoop(); });
|
||||
if (!asyncLoopThread_.joinable())
|
||||
asyncLoopThread_ = std::thread([this](){ enterProcessingLoop(); });
|
||||
}
|
||||
|
||||
void Connection::leaveProcessingLoop()
|
||||
@ -92,6 +93,16 @@ void Connection::leaveProcessingLoop()
|
||||
joinWithProcessingLoop();
|
||||
}
|
||||
|
||||
const ISdBus& Connection::getSdBusInterface() const
|
||||
{
|
||||
return *iface_.get();
|
||||
}
|
||||
|
||||
ISdBus& Connection::getSdBusInterface()
|
||||
{
|
||||
return *iface_.get();
|
||||
}
|
||||
|
||||
sd_bus_slot* Connection::addObjectVTable( const std::string& objectPath
|
||||
, const std::string& interfaceName
|
||||
, const sd_bus_vtable* vtable
|
||||
@ -116,16 +127,13 @@ void Connection::removeObjectVTable(sd_bus_slot* vtableHandle)
|
||||
iface_->sd_bus_slot_unref(vtableHandle);
|
||||
}
|
||||
|
||||
sdbus::MethodCall Connection::createMethodCall( const std::string& destination
|
||||
, const std::string& objectPath
|
||||
, const std::string& interfaceName
|
||||
, const std::string& methodName ) const
|
||||
MethodCall Connection::createMethodCall( const std::string& destination
|
||||
, const std::string& objectPath
|
||||
, const std::string& interfaceName
|
||||
, const std::string& methodName ) const
|
||||
{
|
||||
sd_bus_message *sdbusMsg{};
|
||||
|
||||
// Returned message will become an owner of sdbusMsg
|
||||
SCOPE_EXIT{ iface_->sd_bus_message_unref(sdbusMsg); };
|
||||
|
||||
auto r = iface_->sd_bus_message_new_method_call( bus_.get()
|
||||
, &sdbusMsg
|
||||
, destination.c_str()
|
||||
@ -135,18 +143,15 @@ sdbus::MethodCall Connection::createMethodCall( const std::string& destination
|
||||
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to create method call", -r);
|
||||
|
||||
return MethodCall(sdbusMsg);
|
||||
return MethodCall{sdbusMsg, iface_.get(), adopt_message};
|
||||
}
|
||||
|
||||
sdbus::Signal Connection::createSignal( const std::string& objectPath
|
||||
, const std::string& interfaceName
|
||||
, const std::string& signalName ) const
|
||||
Signal Connection::createSignal( const std::string& objectPath
|
||||
, const std::string& interfaceName
|
||||
, const std::string& signalName ) const
|
||||
{
|
||||
sd_bus_message *sdbusSignal{};
|
||||
|
||||
// Returned message will become an owner of sdbusSignal
|
||||
SCOPE_EXIT{ iface_->sd_bus_message_unref(sdbusSignal); };
|
||||
|
||||
auto r = iface_->sd_bus_message_new_signal( bus_.get()
|
||||
, &sdbusSignal
|
||||
, objectPath.c_str()
|
||||
@ -155,7 +160,7 @@ sdbus::Signal Connection::createSignal( const std::string& objectPath
|
||||
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to create signal", -r);
|
||||
|
||||
return Signal(sdbusSignal);
|
||||
return Signal{sdbusSignal, iface_.get(), adopt_message};
|
||||
}
|
||||
|
||||
sd_bus_slot* Connection::registerSignalHandler( const std::string& objectPath
|
||||
@ -179,20 +184,6 @@ void Connection::unregisterSignalHandler(sd_bus_slot* handlerCookie)
|
||||
iface_->sd_bus_slot_unref(handlerCookie);
|
||||
}
|
||||
|
||||
void Connection::sendReplyAsynchronously(const sdbus::MethodReply& reply)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(mutex_);
|
||||
asyncReplies_.push(reply);
|
||||
notifyProcessingLoop();
|
||||
}
|
||||
|
||||
std::unique_ptr<sdbus::internal::IConnection> Connection::clone() const
|
||||
{
|
||||
auto interface = std::make_unique<SdBus>();
|
||||
assert(interface != nullptr);
|
||||
return std::make_unique<sdbus::internal::Connection>(busType_, std::move(interface));
|
||||
}
|
||||
|
||||
sd_bus* Connection::openBus(Connection::BusType type)
|
||||
{
|
||||
sd_bus* bus{};
|
||||
@ -223,7 +214,7 @@ void Connection::finishHandshake(sd_bus* bus)
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to flush bus on opening", -r);
|
||||
}
|
||||
|
||||
int Connection::createLoopNotificationDescriptor()
|
||||
int Connection::createProcessingLoopExitDescriptor()
|
||||
{
|
||||
auto r = eventfd(0, EFD_SEMAPHORE | EFD_CLOEXEC | EFD_NONBLOCK);
|
||||
|
||||
@ -232,26 +223,25 @@ int Connection::createLoopNotificationDescriptor()
|
||||
return r;
|
||||
}
|
||||
|
||||
void Connection::closeLoopNotificationDescriptor(int fd)
|
||||
void Connection::closeProcessingLoopExitDescriptor(int fd)
|
||||
{
|
||||
close(fd);
|
||||
}
|
||||
|
||||
void Connection::notifyProcessingLoop()
|
||||
void Connection::notifyProcessingLoopToExit()
|
||||
{
|
||||
assert(notificationFd_ >= 0);
|
||||
assert(loopExitFd_ >= 0);
|
||||
|
||||
uint64_t value = 1;
|
||||
auto r = write(notificationFd_, &value, sizeof(value));
|
||||
|
||||
auto r = write(loopExitFd_, &value, sizeof(value));
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to notify processing loop", -errno);
|
||||
}
|
||||
|
||||
void Connection::notifyProcessingLoopToExit()
|
||||
void Connection::clearExitNotification()
|
||||
{
|
||||
exitLoopThread_ = true;
|
||||
|
||||
notifyProcessingLoop();
|
||||
uint64_t value{};
|
||||
auto r = read(loopExitFd_, &value, sizeof(value));
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to read from the event descriptor", -errno);
|
||||
}
|
||||
|
||||
void Connection::joinWithProcessingLoop()
|
||||
@ -273,60 +263,35 @@ bool Connection::processPendingRequest()
|
||||
return r > 0;
|
||||
}
|
||||
|
||||
void Connection::processAsynchronousMessages()
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(mutex_);
|
||||
while (!asyncReplies_.empty())
|
||||
{
|
||||
auto reply = asyncReplies_.front();
|
||||
asyncReplies_.pop();
|
||||
reply.send();
|
||||
}
|
||||
}
|
||||
|
||||
Connection::WaitResult Connection::waitForNextRequest()
|
||||
bool Connection::waitForNextRequest()
|
||||
{
|
||||
auto bus = bus_.get();
|
||||
|
||||
assert(bus != nullptr);
|
||||
assert(notificationFd_ != 0);
|
||||
assert(loopExitFd_ != 0);
|
||||
|
||||
auto r = iface_->sd_bus_get_fd(bus);
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get bus descriptor", -r);
|
||||
auto sdbusFd = r;
|
||||
ISdBus::PollData sdbusPollData;
|
||||
auto r = iface_->sd_bus_get_poll_data(bus, &sdbusPollData);
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get bus poll data", -r);
|
||||
|
||||
r = iface_->sd_bus_get_events(bus);
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get bus events", -r);
|
||||
short int sdbusEvents = r;
|
||||
|
||||
uint64_t usec;
|
||||
iface_->sd_bus_get_timeout(bus, &usec);
|
||||
|
||||
struct pollfd fds[] = {{sdbusFd, sdbusEvents, 0}, {notificationFd_, POLLIN, 0}};
|
||||
struct pollfd fds[] = {{sdbusPollData.fd, sdbusPollData.events, 0}, {loopExitFd_, POLLIN, 0}};
|
||||
auto fdsCount = sizeof(fds)/sizeof(fds[0]);
|
||||
|
||||
r = poll(fds, fdsCount, usec == (uint64_t) -1 ? -1 : (usec+999)/1000);
|
||||
auto timeout = sdbusPollData.timeout_usec == (uint64_t) -1 ? (uint64_t)-1 : (sdbusPollData.timeout_usec+999)/1000;
|
||||
r = poll(fds, fdsCount, timeout);
|
||||
|
||||
if (r < 0 && errno == EINTR)
|
||||
return {true, false}; // Try again
|
||||
return true; // Try again
|
||||
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to wait on the bus", -errno);
|
||||
|
||||
if (fds[1].revents & POLLIN)
|
||||
{
|
||||
if (exitLoopThread_)
|
||||
return {false, false}; // Got exit notification
|
||||
|
||||
// Otherwise we have some async messages to process
|
||||
|
||||
uint64_t value{};
|
||||
auto r = read(notificationFd_, &value, sizeof(value));
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to read from the event descriptor", -errno);
|
||||
|
||||
return {false, true};
|
||||
clearExitNotification();
|
||||
return false;
|
||||
}
|
||||
|
||||
return {true, false};
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string Connection::composeSignalMatchFilter( const std::string& objectPath
|
||||
@ -361,8 +326,8 @@ std::unique_ptr<sdbus::IConnection> createSystemBusConnection()
|
||||
{
|
||||
auto interface = std::make_unique<sdbus::internal::SdBus>();
|
||||
assert(interface != nullptr);
|
||||
return std::make_unique<sdbus::internal::Connection>(sdbus::internal::Connection::BusType::eSystem,
|
||||
std::move(interface));
|
||||
return std::make_unique<sdbus::internal::Connection>( sdbus::internal::Connection::BusType::eSystem
|
||||
, std::move(interface));
|
||||
}
|
||||
|
||||
std::unique_ptr<sdbus::IConnection> createSystemBusConnection(const std::string& name)
|
||||
@ -376,8 +341,8 @@ std::unique_ptr<sdbus::IConnection> createSessionBusConnection()
|
||||
{
|
||||
auto interface = std::make_unique<sdbus::internal::SdBus>();
|
||||
assert(interface != nullptr);
|
||||
return std::make_unique<sdbus::internal::Connection>(sdbus::internal::Connection::BusType::eSession,
|
||||
std::move(interface));
|
||||
return std::make_unique<sdbus::internal::Connection>( sdbus::internal::Connection::BusType::eSession
|
||||
, std::move(interface));
|
||||
}
|
||||
|
||||
std::unique_ptr<sdbus::IConnection> createSessionBusConnection(const std::string& name)
|
||||
|
52
src/Connection.h
Executable file → Normal file
52
src/Connection.h
Executable file → Normal file
@ -29,14 +29,11 @@
|
||||
#include <sdbus-c++/IConnection.h>
|
||||
#include <sdbus-c++/Message.h>
|
||||
#include "IConnection.h"
|
||||
#include "ScopeGuard.h"
|
||||
#include "ISdBus.h"
|
||||
|
||||
#include <systemd/sd-bus.h>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
|
||||
namespace sdbus { namespace internal {
|
||||
|
||||
@ -60,19 +57,22 @@ namespace sdbus { namespace internal {
|
||||
void enterProcessingLoopAsync() override;
|
||||
void leaveProcessingLoop() override;
|
||||
|
||||
const ISdBus& getSdBusInterface() const override;
|
||||
ISdBus& getSdBusInterface() override;
|
||||
|
||||
sd_bus_slot* addObjectVTable( const std::string& objectPath
|
||||
, const std::string& interfaceName
|
||||
, const sd_bus_vtable* vtable
|
||||
, void* userData ) override;
|
||||
void removeObjectVTable(sd_bus_slot* vtableHandle) override;
|
||||
|
||||
sdbus::MethodCall createMethodCall( const std::string& destination
|
||||
, const std::string& objectPath
|
||||
, const std::string& interfaceName
|
||||
, const std::string& methodName ) const override;
|
||||
sdbus::Signal createSignal( const std::string& objectPath
|
||||
, const std::string& interfaceName
|
||||
, const std::string& signalName ) const override;
|
||||
MethodCall createMethodCall( const std::string& destination
|
||||
, const std::string& objectPath
|
||||
, const std::string& interfaceName
|
||||
, const std::string& methodName ) const override;
|
||||
Signal createSignal( const std::string& objectPath
|
||||
, const std::string& interfaceName
|
||||
, const std::string& signalName ) const override;
|
||||
|
||||
sd_bus_slot* registerSignalHandler( const std::string& objectPath
|
||||
, const std::string& interfaceName
|
||||
@ -81,32 +81,18 @@ namespace sdbus { namespace internal {
|
||||
, void* userData ) override;
|
||||
void unregisterSignalHandler(sd_bus_slot* handlerCookie) override;
|
||||
|
||||
void sendReplyAsynchronously(const sdbus::MethodReply& reply) override;
|
||||
|
||||
std::unique_ptr<sdbus::internal::IConnection> clone() const override;
|
||||
|
||||
private:
|
||||
struct WaitResult
|
||||
{
|
||||
bool msgsToProcess;
|
||||
bool asyncMsgsToProcess;
|
||||
operator bool()
|
||||
{
|
||||
return msgsToProcess || asyncMsgsToProcess;
|
||||
}
|
||||
};
|
||||
sd_bus* openBus(Connection::BusType type);
|
||||
void finishHandshake(sd_bus* bus);
|
||||
static int createLoopNotificationDescriptor();
|
||||
static void closeLoopNotificationDescriptor(int fd);
|
||||
static int createProcessingLoopExitDescriptor();
|
||||
static void closeProcessingLoopExitDescriptor(int fd);
|
||||
bool processPendingRequest();
|
||||
void processAsynchronousMessages();
|
||||
WaitResult waitForNextRequest();
|
||||
bool waitForNextRequest();
|
||||
static std::string composeSignalMatchFilter( const std::string& objectPath
|
||||
, const std::string& interfaceName
|
||||
, const std::string& signalName );
|
||||
void notifyProcessingLoop();
|
||||
void notifyProcessingLoopToExit();
|
||||
void clearExitNotification();
|
||||
void joinWithProcessingLoop();
|
||||
|
||||
private:
|
||||
@ -115,14 +101,10 @@ namespace sdbus { namespace internal {
|
||||
{
|
||||
return iface_->sd_bus_flush_close_unref(bus);
|
||||
}};
|
||||
std::thread asyncLoopThread_;
|
||||
std::mutex mutex_;
|
||||
std::queue<MethodReply> asyncReplies_;
|
||||
std::atomic<bool> exitLoopThread_;
|
||||
int notificationFd_{-1};
|
||||
BusType busType_;
|
||||
|
||||
static constexpr const uint64_t POLL_TIMEOUT_USEC = 500000;
|
||||
std::thread asyncLoopThread_;
|
||||
int loopExitFd_{-1};
|
||||
};
|
||||
|
||||
}}
|
||||
|
25
src/IConnection.h
Executable file → Normal file
25
src/IConnection.h
Executable file → Normal file
@ -33,8 +33,12 @@
|
||||
// Forward declaration
|
||||
namespace sdbus {
|
||||
class MethodCall;
|
||||
class AsyncMethodCall;
|
||||
class MethodReply;
|
||||
class Signal;
|
||||
namespace internal {
|
||||
class ISdBus;
|
||||
}
|
||||
}
|
||||
|
||||
namespace sdbus {
|
||||
@ -43,20 +47,23 @@ namespace internal {
|
||||
class IConnection
|
||||
{
|
||||
public:
|
||||
virtual const ISdBus& getSdBusInterface() const = 0;
|
||||
virtual ISdBus& getSdBusInterface() = 0;
|
||||
|
||||
virtual sd_bus_slot* addObjectVTable( const std::string& objectPath
|
||||
, const std::string& interfaceName
|
||||
, const sd_bus_vtable* vtable
|
||||
, void* userData ) = 0;
|
||||
virtual void removeObjectVTable(sd_bus_slot* vtableHandle) = 0;
|
||||
|
||||
virtual sdbus::MethodCall createMethodCall( const std::string& destination
|
||||
, const std::string& objectPath
|
||||
, const std::string& interfaceName
|
||||
, const std::string& methodName ) const = 0;
|
||||
virtual MethodCall createMethodCall( const std::string& destination
|
||||
, const std::string& objectPath
|
||||
, const std::string& interfaceName
|
||||
, const std::string& methodName ) const = 0;
|
||||
|
||||
virtual sdbus::Signal createSignal( const std::string& objectPath
|
||||
, const std::string& interfaceName
|
||||
, const std::string& signalName ) const = 0;
|
||||
virtual Signal createSignal( const std::string& objectPath
|
||||
, const std::string& interfaceName
|
||||
, const std::string& signalName ) const = 0;
|
||||
|
||||
virtual sd_bus_slot* registerSignalHandler( const std::string& objectPath
|
||||
, const std::string& interfaceName
|
||||
@ -68,10 +75,6 @@ namespace internal {
|
||||
virtual void enterProcessingLoopAsync() = 0;
|
||||
virtual void leaveProcessingLoop() = 0;
|
||||
|
||||
virtual void sendReplyAsynchronously(const sdbus::MethodReply& reply) = 0;
|
||||
|
||||
virtual std::unique_ptr<sdbus::internal::IConnection> clone() const = 0;
|
||||
|
||||
virtual ~IConnection() = default;
|
||||
};
|
||||
|
||||
|
36
src/ISdBus.h
36
src/ISdBus.h
@ -34,21 +34,37 @@ namespace sdbus { namespace internal {
|
||||
class ISdBus
|
||||
{
|
||||
public:
|
||||
struct PollData
|
||||
{
|
||||
int fd;
|
||||
short int events;
|
||||
uint64_t timeout_usec;
|
||||
};
|
||||
|
||||
virtual sd_bus_message* sd_bus_message_ref(sd_bus_message *m) = 0;
|
||||
virtual sd_bus_message* sd_bus_message_unref(sd_bus_message *m) = 0;
|
||||
|
||||
virtual int sd_bus_send(sd_bus *bus, sd_bus_message *m, uint64_t *cookie) = 0;
|
||||
virtual int sd_bus_call(sd_bus *bus, sd_bus_message *m, uint64_t usec, sd_bus_error *ret_error, sd_bus_message **reply) = 0;
|
||||
virtual int sd_bus_call_async(sd_bus *bus, sd_bus_slot **slot, sd_bus_message *m, sd_bus_message_handler_t callback, void *userdata, uint64_t usec) = 0;
|
||||
|
||||
virtual int sd_bus_message_new_method_call(sd_bus *bus, sd_bus_message **m, const char *destination, const char *path, const char *interface, const char *member) = 0;
|
||||
virtual int sd_bus_message_new_signal(sd_bus *bus, sd_bus_message **m, const char *path, const char *interface, const char *member) = 0;
|
||||
virtual int sd_bus_message_new_method_return(sd_bus_message *call, sd_bus_message **m) = 0;
|
||||
virtual int sd_bus_message_new_method_error(sd_bus_message *call, sd_bus_message **m, const sd_bus_error *e) = 0;
|
||||
|
||||
virtual int sd_bus_open_user(sd_bus **ret) = 0;
|
||||
virtual int sd_bus_open_system(sd_bus **ret) = 0;
|
||||
virtual int sd_bus_request_name(sd_bus *bus, const char *name, uint64_t flags) = 0;
|
||||
virtual int sd_bus_release_name(sd_bus *bus, const char *name) = 0;
|
||||
virtual int sd_bus_add_object_vtable(sd_bus *bus, sd_bus_slot **slot, const char *path, const char *interface, const sd_bus_vtable *vtable, void *userdata) = 0;
|
||||
virtual sd_bus_slot* sd_bus_slot_unref(sd_bus_slot *slot) = 0;
|
||||
virtual int sd_bus_message_new_method_call(sd_bus *bus, sd_bus_message **m, const char *destination, const char *path, const char *interface, const char *member) = 0;
|
||||
virtual sd_bus_message* sd_bus_message_unref(sd_bus_message *m) = 0;
|
||||
virtual int sd_bus_message_new_signal(sd_bus *bus, sd_bus_message **m, const char *path, const char *interface, const char *member) = 0;
|
||||
virtual int sd_bus_add_match(sd_bus *bus, sd_bus_slot **slot, const char *match, sd_bus_message_handler_t callback, void *userdata) = 0;
|
||||
virtual int sd_bus_open_user(sd_bus **ret) = 0;
|
||||
virtual int sd_bus_open_system(sd_bus **ret) = 0;
|
||||
virtual int sd_bus_flush(sd_bus *bus) = 0;
|
||||
virtual sd_bus_slot* sd_bus_slot_unref(sd_bus_slot *slot) = 0;
|
||||
|
||||
virtual int sd_bus_process(sd_bus *bus, sd_bus_message **r) = 0;
|
||||
virtual int sd_bus_get_fd(sd_bus *bus) = 0;
|
||||
virtual int sd_bus_get_events(sd_bus *bus) = 0;
|
||||
virtual int sd_bus_get_timeout(sd_bus *bus, uint64_t *timeout_usec) = 0;
|
||||
virtual int sd_bus_get_poll_data(sd_bus *bus, PollData* data) = 0;
|
||||
|
||||
virtual int sd_bus_flush(sd_bus *bus) = 0;
|
||||
virtual sd_bus *sd_bus_flush_close_unref(sd_bus *bus) = 0;
|
||||
|
||||
virtual ~ISdBus() = default;
|
||||
|
102
src/Message.cpp
Executable file → Normal file
102
src/Message.cpp
Executable file → Normal file
@ -27,17 +27,34 @@
|
||||
#include <sdbus-c++/Types.h>
|
||||
#include <sdbus-c++/Error.h>
|
||||
#include "MessageUtils.h"
|
||||
#include "SdBus.h"
|
||||
#include "ScopeGuard.h"
|
||||
#include <systemd/sd-bus.h>
|
||||
#include <cassert>
|
||||
|
||||
namespace sdbus {
|
||||
|
||||
Message::Message(void *msg) noexcept
|
||||
Message::Message(internal::ISdBus* sdbus) noexcept
|
||||
: sdbus_(sdbus)
|
||||
{
|
||||
assert(sdbus_ != nullptr);
|
||||
}
|
||||
|
||||
Message::Message(void *msg, internal::ISdBus* sdbus) noexcept
|
||||
: msg_(msg)
|
||||
, sdbus_(sdbus)
|
||||
{
|
||||
assert(msg_ != nullptr);
|
||||
sd_bus_message_ref((sd_bus_message*)msg_);
|
||||
assert(sdbus_ != nullptr);
|
||||
sdbus_->sd_bus_message_ref((sd_bus_message*)msg_);
|
||||
}
|
||||
|
||||
Message::Message(void *msg, internal::ISdBus* sdbus, adopt_message_t) noexcept
|
||||
: msg_(msg)
|
||||
, sdbus_(sdbus)
|
||||
{
|
||||
assert(msg_ != nullptr);
|
||||
assert(sdbus_ != nullptr);
|
||||
}
|
||||
|
||||
Message::Message(const Message& other) noexcept
|
||||
@ -48,12 +65,13 @@ Message::Message(const Message& other) noexcept
|
||||
Message& Message::operator=(const Message& other) noexcept
|
||||
{
|
||||
if (msg_)
|
||||
sd_bus_message_unref((sd_bus_message*)msg_);
|
||||
sdbus_->sd_bus_message_unref((sd_bus_message*)msg_);
|
||||
|
||||
msg_ = other.msg_;
|
||||
sdbus_ = other.sdbus_;
|
||||
ok_ = other.ok_;
|
||||
|
||||
sd_bus_message_ref((sd_bus_message*)msg_);
|
||||
sdbus_->sd_bus_message_ref((sd_bus_message*)msg_);
|
||||
|
||||
return *this;
|
||||
}
|
||||
@ -66,10 +84,12 @@ Message::Message(Message&& other) noexcept
|
||||
Message& Message::operator=(Message&& other) noexcept
|
||||
{
|
||||
if (msg_)
|
||||
sd_bus_message_unref((sd_bus_message*)msg_);
|
||||
sdbus_->sd_bus_message_unref((sd_bus_message*)msg_);
|
||||
|
||||
msg_ = other.msg_;
|
||||
other.msg_ = nullptr;
|
||||
sdbus_ = other.sdbus_;
|
||||
other.sdbus_ = nullptr;
|
||||
ok_ = other.ok_;
|
||||
other.ok_ = true;
|
||||
|
||||
@ -79,7 +99,7 @@ Message& Message::operator=(Message&& other) noexcept
|
||||
Message::~Message()
|
||||
{
|
||||
if (msg_)
|
||||
sd_bus_message_unref((sd_bus_message*)msg_);
|
||||
sdbus_->sd_bus_message_unref((sd_bus_message*)msg_);
|
||||
}
|
||||
|
||||
Message& Message::operator<<(bool item)
|
||||
@ -558,7 +578,7 @@ void Message::peekType(std::string& type, std::string& contents) const
|
||||
|
||||
bool Message::isValid() const
|
||||
{
|
||||
return msg_ != nullptr;
|
||||
return msg_ != nullptr && sdbus_ != nullptr;
|
||||
}
|
||||
|
||||
bool Message::isEmpty() const
|
||||
@ -566,20 +586,15 @@ bool Message::isEmpty() const
|
||||
return sd_bus_message_is_empty((sd_bus_message*)msg_);
|
||||
}
|
||||
|
||||
void* Message::getMsg() const
|
||||
{
|
||||
return msg_;
|
||||
}
|
||||
|
||||
void MethodCall::dontExpectReply()
|
||||
{
|
||||
auto r = sd_bus_message_set_expect_reply((sd_bus_message*)getMsg(), 0);
|
||||
auto r = sd_bus_message_set_expect_reply((sd_bus_message*)msg_, 0);
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to set the dont-expect-reply flag", -r);
|
||||
}
|
||||
|
||||
bool MethodCall::doesntExpectReply() const
|
||||
{
|
||||
auto r = sd_bus_message_get_expect_reply((sd_bus_message*)getMsg());
|
||||
auto r = sd_bus_message_get_expect_reply((sd_bus_message*)msg_);
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get the dont-expect-reply flag", -r);
|
||||
return r > 0 ? false : true;
|
||||
}
|
||||
@ -594,41 +609,35 @@ MethodReply MethodCall::send() const
|
||||
|
||||
MethodReply MethodCall::sendWithReply() const
|
||||
{
|
||||
sd_bus_message* sdbusReply{};
|
||||
SCOPE_EXIT{ sd_bus_message_unref(sdbusReply); }; // Returned message will become an owner of sdbusReply
|
||||
sd_bus_error sdbusError = SD_BUS_ERROR_NULL;
|
||||
SCOPE_EXIT{ sd_bus_error_free(&sdbusError); };
|
||||
|
||||
auto r = sd_bus_call(nullptr, (sd_bus_message*)getMsg(), 0, &sdbusError, &sdbusReply);
|
||||
sd_bus_message* sdbusReply{};
|
||||
auto r = sdbus_->sd_bus_call(nullptr, (sd_bus_message*)msg_, 0, &sdbusError, &sdbusReply);
|
||||
|
||||
if (sd_bus_error_is_set(&sdbusError))
|
||||
{
|
||||
throw sdbus::Error(sdbusError.name, sdbusError.message);
|
||||
}
|
||||
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to call method", -r);
|
||||
|
||||
return MethodReply(sdbusReply);
|
||||
return MethodReply{sdbusReply, sdbus_, adopt_message};
|
||||
}
|
||||
|
||||
MethodReply MethodCall::sendWithNoReply() const
|
||||
{
|
||||
auto r = sd_bus_send(nullptr, (sd_bus_message*)getMsg(), nullptr);
|
||||
auto r = sdbus_->sd_bus_send(nullptr, (sd_bus_message*)msg_, nullptr);
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to call method with no reply", -r);
|
||||
|
||||
return MethodReply{}; // No reply
|
||||
}
|
||||
|
||||
MethodReply MethodCall::createReply() const
|
||||
{
|
||||
sd_bus_message *sdbusReply{};
|
||||
SCOPE_EXIT{ sd_bus_message_unref(sdbusReply); }; // Returned message will become an owner of sdbusReply
|
||||
|
||||
auto r = sd_bus_message_new_method_return((sd_bus_message*)getMsg(), &sdbusReply);
|
||||
sd_bus_message* sdbusReply{};
|
||||
auto r = sdbus_->sd_bus_message_new_method_return((sd_bus_message*)msg_, &sdbusReply);
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to create method reply", -r);
|
||||
|
||||
assert(sdbusReply != nullptr);
|
||||
|
||||
return MethodReply(sdbusReply);
|
||||
return MethodReply{sdbusReply, sdbus_, adopt_message};
|
||||
}
|
||||
|
||||
MethodReply MethodCall::createErrorReply(const Error& error) const
|
||||
@ -637,26 +646,33 @@ MethodReply MethodCall::createErrorReply(const Error& error) const
|
||||
SCOPE_EXIT{ sd_bus_error_free(&sdbusError); };
|
||||
sd_bus_error_set(&sdbusError, error.getName().c_str(), error.getMessage().c_str());
|
||||
|
||||
sd_bus_message *sdbusErrorReply{};
|
||||
SCOPE_EXIT{ sd_bus_message_unref(sdbusErrorReply); }; // Returned message will become an owner of sdbusErrorReply
|
||||
|
||||
auto r = sd_bus_message_new_method_error((sd_bus_message*)getMsg(), &sdbusErrorReply, &sdbusError);
|
||||
sd_bus_message* sdbusErrorReply{};
|
||||
auto r = sdbus_->sd_bus_message_new_method_error((sd_bus_message*)msg_, &sdbusErrorReply, &sdbusError);
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to create method error reply", -r);
|
||||
|
||||
assert(sdbusErrorReply != nullptr);
|
||||
return MethodReply{sdbusErrorReply, sdbus_, adopt_message};
|
||||
}
|
||||
|
||||
return MethodReply(sdbusErrorReply);
|
||||
AsyncMethodCall::AsyncMethodCall(MethodCall&& call) noexcept
|
||||
: Message(std::move(call))
|
||||
{
|
||||
}
|
||||
|
||||
void AsyncMethodCall::send(void* callback, void* userData) const
|
||||
{
|
||||
auto r = sdbus_->sd_bus_call_async(nullptr, nullptr, (sd_bus_message*)msg_, (sd_bus_message_handler_t)callback, userData, 0);
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to call method asynchronously", -r);
|
||||
}
|
||||
|
||||
void MethodReply::send() const
|
||||
{
|
||||
auto r = sd_bus_send(nullptr, (sd_bus_message*)getMsg(), nullptr);
|
||||
auto r = sdbus_->sd_bus_send(nullptr, (sd_bus_message*)msg_, nullptr);
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to send reply", -r);
|
||||
}
|
||||
|
||||
void Signal::send() const
|
||||
{
|
||||
auto r = sd_bus_send(nullptr, (sd_bus_message*)getMsg(), nullptr);
|
||||
auto r = sdbus_->sd_bus_send(nullptr, (sd_bus_message*)msg_, nullptr);
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to emit signal", -r);
|
||||
}
|
||||
|
||||
@ -665,16 +681,24 @@ Message createPlainMessage()
|
||||
int r;
|
||||
|
||||
sd_bus* bus{};
|
||||
SCOPE_EXIT{ sd_bus_unref(bus); }; // sdbusMsg will hold reference to the bus
|
||||
SCOPE_EXIT{ sd_bus_unref(bus); };
|
||||
r = sd_bus_default_system(&bus);
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get default system bus", -r);
|
||||
|
||||
thread_local struct BusReferenceKeeper
|
||||
{
|
||||
BusReferenceKeeper(sd_bus* bus) : bus_(bus) {}
|
||||
~BusReferenceKeeper() { sd_bus_unref(bus_); }
|
||||
sd_bus* bus_{};
|
||||
} busReferenceKeeper{bus};
|
||||
|
||||
sd_bus_message* sdbusMsg{};
|
||||
SCOPE_EXIT{ sd_bus_message_unref(sdbusMsg); }; // Returned message will become an owner of sdbusMsg
|
||||
r = sd_bus_message_new(bus, &sdbusMsg, _SD_BUS_MESSAGE_TYPE_INVALID);
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to create a new message", -r);
|
||||
|
||||
return Message(sdbusMsg);
|
||||
thread_local internal::SdBus sdbus;
|
||||
|
||||
return Message{sdbusMsg, &sdbus, adopt_message};
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,43 +0,0 @@
|
||||
/**
|
||||
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
|
||||
*
|
||||
* @file Object.cpp
|
||||
*
|
||||
* Created on: Nov 8, 2016
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#include <sdbus-c++/MethodResult.h>
|
||||
#include "Object.h"
|
||||
|
||||
namespace sdbus {
|
||||
|
||||
MethodResult::MethodResult(const MethodCall& msg, sdbus::internal::Object& object)
|
||||
: call_(msg)
|
||||
, object_(&object)
|
||||
{
|
||||
}
|
||||
|
||||
void MethodResult::send(const MethodReply& reply) const
|
||||
{
|
||||
assert(object_ != nullptr);
|
||||
object_->sendReplyAsynchronously(reply);
|
||||
}
|
||||
|
||||
}
|
32
src/Object.cpp
Executable file → Normal file
32
src/Object.cpp
Executable file → Normal file
@ -74,10 +74,10 @@ void Object::registerMethod( const std::string& interfaceName
|
||||
{
|
||||
SDBUS_THROW_ERROR_IF(!asyncMethodCallback, "Invalid method callback provided", EINVAL);
|
||||
|
||||
auto asyncCallback = [this, callback = std::move(asyncMethodCallback)](MethodCall& msg)
|
||||
auto asyncCallback = [callback = std::move(asyncMethodCallback)](MethodCall& msg)
|
||||
{
|
||||
MethodResult result{msg, *this};
|
||||
callback(msg, result);
|
||||
MethodResult result{msg};
|
||||
callback(std::move(msg), std::move(result));
|
||||
};
|
||||
|
||||
auto& interface = interfaces_[interfaceName];
|
||||
@ -156,17 +156,9 @@ sdbus::Signal Object::createSignal(const std::string& interfaceName, const std::
|
||||
|
||||
void Object::emitSignal(const sdbus::Signal& message)
|
||||
{
|
||||
// TODO: Make signal emitting asynchronous. Now signal can probably be emitted only from user code
|
||||
// handled within the D-Bus processing loop thread, but not from any thread. In principle it will
|
||||
// be the same as async replies.
|
||||
message.send();
|
||||
}
|
||||
|
||||
void Object::sendReplyAsynchronously(const MethodReply& reply)
|
||||
{
|
||||
connection_.sendReplyAsynchronously(reply);
|
||||
}
|
||||
|
||||
const std::vector<sd_bus_vtable>& Object::createInterfaceVTable(InterfaceData& interfaceData)
|
||||
{
|
||||
auto& vtable = interfaceData.vtable_;
|
||||
@ -242,9 +234,11 @@ void Object::activateInterfaceVTable( const std::string& interfaceName
|
||||
|
||||
int Object::sdbus_method_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError)
|
||||
{
|
||||
MethodCall message(sdbusMessage);
|
||||
|
||||
auto* object = static_cast<Object*>(userData);
|
||||
assert(object != nullptr);
|
||||
|
||||
MethodCall message{sdbusMessage, &object->connection_.getSdBusInterface()};
|
||||
|
||||
// Note: The lookup can be optimized by using sorted vectors instead of associative containers
|
||||
auto& callback = object->interfaces_[message.getInterfaceName()].methods_[message.getMemberName()].callback_;
|
||||
assert(callback);
|
||||
@ -269,9 +263,9 @@ int Object::sdbus_property_get_callback( sd_bus */*bus*/
|
||||
, void *userData
|
||||
, sd_bus_error *retError )
|
||||
{
|
||||
Message reply(sdbusReply);
|
||||
|
||||
auto* object = static_cast<Object*>(userData);
|
||||
assert(object != nullptr);
|
||||
|
||||
// Note: The lookup can be optimized by using sorted vectors instead of associative containers
|
||||
auto& callback = object->interfaces_[interface].properties_[property].getCallback_;
|
||||
// Getter can be empty - the case of "write-only" property
|
||||
@ -281,6 +275,8 @@ int Object::sdbus_property_get_callback( sd_bus */*bus*/
|
||||
return 1;
|
||||
}
|
||||
|
||||
Message reply{sdbusReply, &object->connection_.getSdBusInterface()};
|
||||
|
||||
try
|
||||
{
|
||||
callback(reply);
|
||||
@ -301,13 +297,15 @@ int Object::sdbus_property_set_callback( sd_bus */*bus*/
|
||||
, void *userData
|
||||
, sd_bus_error *retError )
|
||||
{
|
||||
Message value(sdbusValue);
|
||||
|
||||
auto* object = static_cast<Object*>(userData);
|
||||
assert(object != nullptr);
|
||||
|
||||
// Note: The lookup can be optimized by using sorted vectors instead of associative containers
|
||||
auto& callback = object->interfaces_[interface].properties_[property].setCallback_;
|
||||
assert(callback);
|
||||
|
||||
Message value{sdbusValue, &object->connection_.getSdBusInterface()};
|
||||
|
||||
try
|
||||
{
|
||||
callback(value);
|
||||
|
@ -84,8 +84,6 @@ namespace internal {
|
||||
sdbus::Signal createSignal(const std::string& interfaceName, const std::string& signalName) override;
|
||||
void emitSignal(const sdbus::Signal& message) override;
|
||||
|
||||
void sendReplyAsynchronously(const MethodReply& reply);
|
||||
|
||||
private:
|
||||
using InterfaceName = std::string;
|
||||
struct InterfaceData
|
||||
|
87
src/ObjectProxy.cpp
Executable file → Normal file
87
src/ObjectProxy.cpp
Executable file → Normal file
@ -30,47 +30,56 @@
|
||||
#include "IConnection.h"
|
||||
#include <systemd/sd-bus.h>
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
namespace sdbus { namespace internal {
|
||||
|
||||
ObjectProxy::ObjectProxy(sdbus::internal::IConnection& connection, std::string destination, std::string objectPath)
|
||||
: connection_(&connection, [](sdbus::internal::IConnection *){ /* Intentionally left empty */ })
|
||||
, ownConnection_(false)
|
||||
, destination_(std::move(destination))
|
||||
, objectPath_(std::move(objectPath))
|
||||
{
|
||||
// The connection is not ours only, it is managed by the client and we just reference it here,
|
||||
// so we expect the client to manage the event loop upon this connection themselves.
|
||||
}
|
||||
|
||||
ObjectProxy::ObjectProxy( std::unique_ptr<sdbus::internal::IConnection>&& connection
|
||||
, std::string destination
|
||||
, std::string objectPath )
|
||||
: connection_(std::move(connection))
|
||||
, ownConnection_(true)
|
||||
, destination_(std::move(destination))
|
||||
, objectPath_(std::move(objectPath))
|
||||
{
|
||||
}
|
||||
|
||||
ObjectProxy::~ObjectProxy()
|
||||
{
|
||||
// If the dedicated connection for signals is used, we have to stop the processing loop
|
||||
// upon this connection prior to unregistering signal slots in the interfaces_ container,
|
||||
// otherwise we might have a race condition of two threads working upon one connection.
|
||||
if (signalConnection_ != nullptr)
|
||||
signalConnection_->leaveProcessingLoop();
|
||||
// The connection is ours only, so we have to manage event loop upon this connection,
|
||||
// so we get signals, async replies, and other messages from D-Bus.
|
||||
connection_->enterProcessingLoopAsync();
|
||||
}
|
||||
|
||||
MethodCall ObjectProxy::createMethodCall(const std::string& interfaceName, const std::string& methodName)
|
||||
{
|
||||
// Tell, don't ask
|
||||
return connection_->createMethodCall(destination_, objectPath_, interfaceName, methodName);
|
||||
}
|
||||
|
||||
AsyncMethodCall ObjectProxy::createAsyncMethodCall(const std::string& interfaceName, const std::string& methodName)
|
||||
{
|
||||
return AsyncMethodCall{ObjectProxy::createMethodCall(interfaceName, methodName)};
|
||||
}
|
||||
|
||||
MethodReply ObjectProxy::callMethod(const MethodCall& message)
|
||||
{
|
||||
return message.send();
|
||||
}
|
||||
|
||||
void ObjectProxy::callMethod(const AsyncMethodCall& message, async_reply_handler asyncReplyCallback)
|
||||
{
|
||||
auto callback = (void*)&ObjectProxy::sdbus_async_reply_handler;
|
||||
// Allocated userData gets deleted in the sdbus_async_reply_handler
|
||||
auto userData = new AsyncReplyUserData{*this, std::move(asyncReplyCallback)};
|
||||
|
||||
message.send(callback, userData);
|
||||
}
|
||||
|
||||
void ObjectProxy::registerSignalHandler( const std::string& interfaceName
|
||||
, const std::string& signalName
|
||||
, signal_handler signalHandler )
|
||||
@ -88,30 +97,7 @@ void ObjectProxy::registerSignalHandler( const std::string& interfaceName
|
||||
|
||||
void ObjectProxy::finishRegistration()
|
||||
{
|
||||
bool hasSignals = listensToSignals();
|
||||
|
||||
if (hasSignals && ownConnection_)
|
||||
{
|
||||
// Let's use dedicated signalConnection_ for signals,
|
||||
// which will then be used by the processing loop thread.
|
||||
signalConnection_ = connection_->clone();
|
||||
registerSignalHandlers(*signalConnection_);
|
||||
signalConnection_->enterProcessingLoopAsync();
|
||||
}
|
||||
else if (hasSignals)
|
||||
{
|
||||
// Let's used connection provided from the outside.
|
||||
registerSignalHandlers(*connection_);
|
||||
}
|
||||
}
|
||||
|
||||
bool ObjectProxy::listensToSignals() const
|
||||
{
|
||||
for (auto& interfaceItem : interfaces_)
|
||||
if (!interfaceItem.second.signals_.empty())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
registerSignalHandlers(*connection_);
|
||||
}
|
||||
|
||||
void ObjectProxy::registerSignalHandlers(sdbus::internal::IConnection& connection)
|
||||
@ -136,13 +122,36 @@ void ObjectProxy::registerSignalHandlers(sdbus::internal::IConnection& connectio
|
||||
}
|
||||
}
|
||||
|
||||
int ObjectProxy::sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError)
|
||||
{
|
||||
// We are assuming the ownership of the async reply handler pointer passed here
|
||||
std::unique_ptr<AsyncReplyUserData> asyncReplyUserData{static_cast<AsyncReplyUserData*>(userData)};
|
||||
assert(asyncReplyUserData != nullptr);
|
||||
assert(asyncReplyUserData->callback);
|
||||
|
||||
MethodReply message{sdbusMessage, &asyncReplyUserData->proxy.connection_->getSdBusInterface()};
|
||||
|
||||
const auto* error = sd_bus_message_get_error(sdbusMessage);
|
||||
if (error == nullptr)
|
||||
{
|
||||
asyncReplyUserData->callback(message, nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
sdbus::Error exception(error->name, error->message);
|
||||
asyncReplyUserData->callback(message, &exception);
|
||||
}
|
||||
}
|
||||
|
||||
int ObjectProxy::sdbus_signal_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error */*retError*/)
|
||||
{
|
||||
Signal message(sdbusMessage);
|
||||
auto* proxy = static_cast<ObjectProxy*>(userData);
|
||||
assert(proxy != nullptr);
|
||||
|
||||
Signal message{sdbusMessage, &proxy->connection_->getSdBusInterface()};
|
||||
|
||||
auto* object = static_cast<ObjectProxy*>(userData);
|
||||
// Note: The lookup can be optimized by using sorted vectors instead of associative containers
|
||||
auto& callback = object->interfaces_[message.getInterfaceName()].signals_[message.getMemberName()].callback_;
|
||||
auto& callback = proxy->interfaces_[message.getInterfaceName()].signals_[message.getMemberName()].callback_;
|
||||
assert(callback);
|
||||
|
||||
callback(message);
|
||||
|
13
src/ObjectProxy.h
Executable file → Normal file
13
src/ObjectProxy.h
Executable file → Normal file
@ -50,10 +50,11 @@ namespace internal {
|
||||
ObjectProxy( std::unique_ptr<sdbus::internal::IConnection>&& connection
|
||||
, std::string destination
|
||||
, std::string objectPath );
|
||||
~ObjectProxy() override;
|
||||
|
||||
MethodCall createMethodCall(const std::string& interfaceName, const std::string& methodName) override;
|
||||
AsyncMethodCall createAsyncMethodCall(const std::string& interfaceName, const std::string& methodName) override;
|
||||
MethodReply callMethod(const MethodCall& message) override;
|
||||
void callMethod(const AsyncMethodCall& message, async_reply_handler asyncReplyCallback) override;
|
||||
|
||||
void registerSignalHandler( const std::string& interfaceName
|
||||
, const std::string& signalName
|
||||
@ -61,16 +62,20 @@ namespace internal {
|
||||
void finishRegistration() override;
|
||||
|
||||
private:
|
||||
bool listensToSignals() const;
|
||||
struct AsyncReplyUserData
|
||||
{
|
||||
ObjectProxy& proxy;
|
||||
async_reply_handler callback;
|
||||
};
|
||||
|
||||
void registerSignalHandlers(sdbus::internal::IConnection& connection);
|
||||
static int sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError);
|
||||
static int sdbus_signal_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError);
|
||||
|
||||
private:
|
||||
std::unique_ptr< sdbus::internal::IConnection
|
||||
, std::function<void(sdbus::internal::IConnection*)>
|
||||
> connection_;
|
||||
bool ownConnection_{};
|
||||
std::unique_ptr<sdbus::internal::IConnection> signalConnection_;
|
||||
std::string destination_;
|
||||
std::string objectPath_;
|
||||
|
||||
|
132
src/SdBus.cpp
132
src/SdBus.cpp
@ -28,44 +28,67 @@
|
||||
|
||||
namespace sdbus { namespace internal {
|
||||
|
||||
int SdBus::sd_bus_request_name(sd_bus *bus, const char *name, uint64_t flags)
|
||||
sd_bus_message* SdBus::sd_bus_message_ref(sd_bus_message *m)
|
||||
{
|
||||
return ::sd_bus_request_name(bus, name, flags);
|
||||
}
|
||||
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
|
||||
|
||||
int SdBus::sd_bus_release_name(sd_bus *bus, const char *name)
|
||||
{
|
||||
return ::sd_bus_release_name(bus, name);
|
||||
}
|
||||
|
||||
int SdBus::sd_bus_add_object_vtable(sd_bus *bus, sd_bus_slot **slot, const char *path, const char *interface, const sd_bus_vtable *vtable, void *userdata)
|
||||
{
|
||||
return ::sd_bus_add_object_vtable(bus, slot, path, interface, vtable, userdata);
|
||||
}
|
||||
|
||||
sd_bus_slot* SdBus::sd_bus_slot_unref(sd_bus_slot *slot)
|
||||
{
|
||||
return ::sd_bus_slot_unref(slot);
|
||||
}
|
||||
|
||||
int SdBus::sd_bus_message_new_method_call(sd_bus *bus, sd_bus_message **m, const char *destination, const char *path, const char *interface, const char *member)
|
||||
{
|
||||
return ::sd_bus_message_new_method_call(bus, m, destination, path, interface, member);
|
||||
return ::sd_bus_message_ref(m);
|
||||
}
|
||||
|
||||
sd_bus_message* SdBus::sd_bus_message_unref(sd_bus_message *m)
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
|
||||
|
||||
return ::sd_bus_message_unref(m);
|
||||
}
|
||||
|
||||
int SdBus::sd_bus_send(sd_bus *bus, sd_bus_message *m, uint64_t *cookie)
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
|
||||
|
||||
return ::sd_bus_send(bus, m, cookie);
|
||||
}
|
||||
|
||||
int SdBus::sd_bus_call(sd_bus *bus, sd_bus_message *m, uint64_t usec, sd_bus_error *ret_error, sd_bus_message **reply)
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
|
||||
|
||||
return ::sd_bus_call(bus, m, usec, ret_error, reply);
|
||||
}
|
||||
|
||||
int SdBus::sd_bus_call_async(sd_bus *bus, sd_bus_slot **slot, sd_bus_message *m, sd_bus_message_handler_t callback, void *userdata, uint64_t usec)
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
|
||||
|
||||
return ::sd_bus_call_async(bus, slot, m, callback, userdata, usec);
|
||||
}
|
||||
|
||||
int SdBus::sd_bus_message_new_method_call(sd_bus *bus, sd_bus_message **m, const char *destination, const char *path, const char *interface, const char *member)
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
|
||||
|
||||
return ::sd_bus_message_new_method_call(bus, m, destination, path, interface, member);
|
||||
}
|
||||
|
||||
int SdBus::sd_bus_message_new_signal(sd_bus *bus, sd_bus_message **m, const char *path, const char *interface, const char *member)
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
|
||||
|
||||
return ::sd_bus_message_new_signal(bus, m, path, interface, member);
|
||||
}
|
||||
|
||||
int SdBus::sd_bus_add_match(sd_bus *bus, sd_bus_slot **slot, const char *match, sd_bus_message_handler_t callback, void *userdata)
|
||||
int SdBus::sd_bus_message_new_method_return(sd_bus_message *call, sd_bus_message **m)
|
||||
{
|
||||
return :: sd_bus_add_match(bus, slot, match, callback, userdata);
|
||||
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
|
||||
|
||||
return ::sd_bus_message_new_method_return(call, m);
|
||||
}
|
||||
|
||||
int SdBus::sd_bus_message_new_method_error(sd_bus_message *call, sd_bus_message **m, const sd_bus_error *e)
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
|
||||
|
||||
return ::sd_bus_message_new_method_error(call, m, e);
|
||||
}
|
||||
|
||||
int SdBus::sd_bus_open_user(sd_bus **ret)
|
||||
@ -78,29 +101,70 @@ int SdBus::sd_bus_open_system(sd_bus **ret)
|
||||
return ::sd_bus_open_system(ret);
|
||||
}
|
||||
|
||||
int SdBus::sd_bus_flush(sd_bus *bus)
|
||||
int SdBus::sd_bus_request_name(sd_bus *bus, const char *name, uint64_t flags)
|
||||
{
|
||||
return ::sd_bus_flush(bus);
|
||||
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
|
||||
|
||||
return ::sd_bus_request_name(bus, name, flags);
|
||||
}
|
||||
|
||||
int SdBus::sd_bus_release_name(sd_bus *bus, const char *name)
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
|
||||
|
||||
return ::sd_bus_release_name(bus, name);
|
||||
}
|
||||
|
||||
int SdBus::sd_bus_add_object_vtable(sd_bus *bus, sd_bus_slot **slot, const char *path, const char *interface, const sd_bus_vtable *vtable, void *userdata)
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
|
||||
|
||||
return ::sd_bus_add_object_vtable(bus, slot, path, interface, vtable, userdata);
|
||||
}
|
||||
|
||||
int SdBus::sd_bus_add_match(sd_bus *bus, sd_bus_slot **slot, const char *match, sd_bus_message_handler_t callback, void *userdata)
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
|
||||
|
||||
return :: sd_bus_add_match(bus, slot, match, callback, userdata);
|
||||
}
|
||||
|
||||
sd_bus_slot* SdBus::sd_bus_slot_unref(sd_bus_slot *slot)
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
|
||||
|
||||
return ::sd_bus_slot_unref(slot);
|
||||
}
|
||||
|
||||
int SdBus::sd_bus_process(sd_bus *bus, sd_bus_message **r)
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
|
||||
|
||||
return ::sd_bus_process(bus, r);
|
||||
}
|
||||
|
||||
int SdBus::sd_bus_get_fd(sd_bus *bus)
|
||||
int SdBus::sd_bus_get_poll_data(sd_bus *bus, PollData* data)
|
||||
{
|
||||
return ::sd_bus_get_fd(bus);
|
||||
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
|
||||
|
||||
auto r = ::sd_bus_get_fd(bus);
|
||||
if (r < 0)
|
||||
return r;
|
||||
data->fd = r;
|
||||
|
||||
r = ::sd_bus_get_events(bus);
|
||||
if (r < 0)
|
||||
return r;
|
||||
data->events = static_cast<short int>(r);
|
||||
|
||||
r = ::sd_bus_get_timeout(bus, &data->timeout_usec);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
int SdBus::sd_bus_get_events(sd_bus *bus)
|
||||
int SdBus::sd_bus_flush(sd_bus *bus)
|
||||
{
|
||||
return ::sd_bus_get_events(bus);
|
||||
}
|
||||
|
||||
int SdBus::sd_bus_get_timeout(sd_bus *bus, uint64_t *timeout_usec)
|
||||
{
|
||||
return ::sd_bus_get_timeout(bus, timeout_usec);
|
||||
return ::sd_bus_flush(bus);
|
||||
}
|
||||
|
||||
sd_bus* SdBus::sd_bus_flush_close_unref(sd_bus *bus)
|
||||
|
47
src/SdBus.h
47
src/SdBus.h
@ -28,28 +28,41 @@
|
||||
#define SDBUS_CXX_SDBUS_H
|
||||
|
||||
#include "ISdBus.h"
|
||||
#include <mutex>
|
||||
|
||||
namespace sdbus { namespace internal {
|
||||
|
||||
class SdBus : public ISdBus
|
||||
class SdBus final : public ISdBus
|
||||
{
|
||||
public:
|
||||
int sd_bus_request_name(sd_bus *bus, const char *name, uint64_t flags) override;
|
||||
int sd_bus_release_name(sd_bus *bus, const char *name) override;
|
||||
int sd_bus_add_object_vtable(sd_bus *bus, sd_bus_slot **slot, const char *path, const char *interface, const sd_bus_vtable *vtable, void *userdata) override;
|
||||
sd_bus_slot* sd_bus_slot_unref(sd_bus_slot *slot) override;
|
||||
int sd_bus_message_new_method_call(sd_bus *bus, sd_bus_message **m, const char *destination, const char *path, const char *interface, const char *member) override;
|
||||
sd_bus_message* sd_bus_message_unref(sd_bus_message *m) override;
|
||||
int sd_bus_message_new_signal(sd_bus *bus, sd_bus_message **m, const char *path, const char *interface, const char *member) override;
|
||||
int sd_bus_add_match(sd_bus *bus, sd_bus_slot **slot, const char *match, sd_bus_message_handler_t callback, void *userdata) override;
|
||||
int sd_bus_open_user(sd_bus **ret) override;
|
||||
int sd_bus_open_system(sd_bus **ret) override;
|
||||
int sd_bus_flush(sd_bus *bus) override;
|
||||
int sd_bus_process(sd_bus *bus, sd_bus_message **r) override;
|
||||
int sd_bus_get_fd(sd_bus *bus) override;
|
||||
int sd_bus_get_events(sd_bus *bus) override;
|
||||
int sd_bus_get_timeout(sd_bus *bus, uint64_t *timeout_usec) override;
|
||||
sd_bus *sd_bus_flush_close_unref(sd_bus *bus) override;
|
||||
virtual sd_bus_message* sd_bus_message_ref(sd_bus_message *m) override;
|
||||
virtual sd_bus_message* sd_bus_message_unref(sd_bus_message *m) override;
|
||||
|
||||
virtual int sd_bus_send(sd_bus *bus, sd_bus_message *m, uint64_t *cookie) override;
|
||||
virtual int sd_bus_call(sd_bus *bus, sd_bus_message *m, uint64_t usec, sd_bus_error *ret_error, sd_bus_message **reply) override;
|
||||
virtual int sd_bus_call_async(sd_bus *bus, sd_bus_slot **slot, sd_bus_message *m, sd_bus_message_handler_t callback, void *userdata, uint64_t usec) override;
|
||||
|
||||
virtual int sd_bus_message_new_method_call(sd_bus *bus, sd_bus_message **m, const char *destination, const char *path, const char *interface, const char *member) override;
|
||||
virtual int sd_bus_message_new_signal(sd_bus *bus, sd_bus_message **m, const char *path, const char *interface, const char *member) override;
|
||||
virtual int sd_bus_message_new_method_return(sd_bus_message *call, sd_bus_message **m) override;
|
||||
virtual int sd_bus_message_new_method_error(sd_bus_message *call, sd_bus_message **m, const sd_bus_error *e) override;
|
||||
|
||||
virtual int sd_bus_open_user(sd_bus **ret) override;
|
||||
virtual int sd_bus_open_system(sd_bus **ret) override;
|
||||
virtual int sd_bus_request_name(sd_bus *bus, const char *name, uint64_t flags) override;
|
||||
virtual int sd_bus_release_name(sd_bus *bus, const char *name) override;
|
||||
virtual int sd_bus_add_object_vtable(sd_bus *bus, sd_bus_slot **slot, const char *path, const char *interface, const sd_bus_vtable *vtable, void *userdata) override;
|
||||
virtual int sd_bus_add_match(sd_bus *bus, sd_bus_slot **slot, const char *match, sd_bus_message_handler_t callback, void *userdata) override;
|
||||
virtual sd_bus_slot* sd_bus_slot_unref(sd_bus_slot *slot) override;
|
||||
|
||||
virtual int sd_bus_process(sd_bus *bus, sd_bus_message **r) override;
|
||||
virtual int sd_bus_get_poll_data(sd_bus *bus, PollData* data) override;
|
||||
|
||||
virtual int sd_bus_flush(sd_bus *bus) override;
|
||||
virtual sd_bus *sd_bus_flush_close_unref(sd_bus *bus) override;
|
||||
|
||||
private:
|
||||
std::recursive_mutex sdbusMutex_;
|
||||
};
|
||||
|
||||
}}
|
||||
|
@ -29,7 +29,7 @@
|
||||
#include <systemd/sd-bus.h>
|
||||
#include <cassert>
|
||||
|
||||
namespace sdbus { /*namespace internal {*/
|
||||
namespace sdbus {
|
||||
|
||||
Variant::Variant()
|
||||
: msg_(createPlainMessage())
|
||||
|
@ -169,17 +169,31 @@ std::tuple<std::string, std::string> AdaptorGenerator::processMethods(const Node
|
||||
const auto& annotationName = annotation.first;
|
||||
const auto& annotationValue = annotation.second;
|
||||
|
||||
if (annotationName == "org.freedesktop.DBus.Deprecated" && annotationValue == "true")
|
||||
annotationRegistration += ".markAsDeprecated()";
|
||||
else if (annotationName == "org.freedesktop.DBus.Method.NoReply" && annotationValue == "true")
|
||||
annotationRegistration += ".withNoReply()";
|
||||
else if (annotationName == "org.freedesktop.DBus.Method.Async" && (annotationValue == "server" || annotationValue == "clientserver"))
|
||||
async = true;
|
||||
else if (annotationName == "org.freedesktop.systemd1.Privileged" && annotationValue == "true")
|
||||
annotationRegistration += ".markAsPrivileged()";
|
||||
if (annotationName == "org.freedesktop.DBus.Deprecated")
|
||||
{
|
||||
if (annotationValue == "true")
|
||||
annotationRegistration += ".markAsDeprecated()";
|
||||
}
|
||||
else if (annotationName == "org.freedesktop.DBus.Method.NoReply")
|
||||
{
|
||||
if (annotationValue == "true")
|
||||
annotationRegistration += ".withNoReply()";
|
||||
}
|
||||
else if (annotationName == "org.freedesktop.DBus.Method.Async")
|
||||
{
|
||||
if (annotationValue == "server" || annotationValue == "clientserver")
|
||||
async = true;
|
||||
}
|
||||
else if (annotationName == "org.freedesktop.systemd1.Privileged")
|
||||
{
|
||||
if (annotationValue == "true")
|
||||
annotationRegistration += ".markAsPrivileged()";
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Node: " << methodName << ": "
|
||||
<< "Option '" << annotationName << "' not allowed or supported in this context! Option ignored..." << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
Nodes args = (*method)["arg"];
|
||||
@ -187,7 +201,7 @@ std::tuple<std::string, std::string> AdaptorGenerator::processMethods(const Node
|
||||
Nodes outArgs = args.select("direction" , "out");
|
||||
|
||||
std::string argStr, argTypeStr;
|
||||
std::tie(argStr, argTypeStr, std::ignore) = argsToNamesAndTypes(inArgs);
|
||||
std::tie(argStr, argTypeStr, std::ignore) = argsToNamesAndTypes(inArgs, async);
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
@ -196,7 +210,7 @@ std::tuple<std::string, std::string> AdaptorGenerator::processMethods(const Node
|
||||
<< ".onInterface(interfaceName)"
|
||||
<< ".implementedAs("
|
||||
<< "[this]("
|
||||
<< (async ? "sdbus::Result<" + outArgsToType(outArgs, true) + "> result" + (argTypeStr.empty() ? "" : ", ") : "")
|
||||
<< (async ? "sdbus::Result<" + outArgsToType(outArgs, true) + ">&& result" + (argTypeStr.empty() ? "" : ", ") : "")
|
||||
<< argTypeStr
|
||||
<< "){ " << (async ? "" : "return ") << "this->" << methodName << "("
|
||||
<< (async ? "std::move(result)"s + (argTypeStr.empty() ? "" : ", ") : "")
|
||||
@ -208,7 +222,7 @@ std::tuple<std::string, std::string> AdaptorGenerator::processMethods(const Node
|
||||
<< (async ? "void" : outArgsToType(outArgs))
|
||||
<< " " << methodName
|
||||
<< "("
|
||||
<< (async ? "sdbus::Result<" + outArgsToType(outArgs, true) + "> result" + (argTypeStr.empty() ? "" : ", ") : "")
|
||||
<< (async ? "sdbus::Result<" + outArgsToType(outArgs, true) + ">&& result" + (argTypeStr.empty() ? "" : ", ") : "")
|
||||
<< argTypeStr
|
||||
<< ") = 0;" << endl;
|
||||
}
|
||||
|
@ -104,7 +104,7 @@ std::tuple<unsigned, std::string> BaseGenerator::generateNamespaces(const std::s
|
||||
}
|
||||
|
||||
|
||||
std::tuple<std::string, std::string, std::string> BaseGenerator::argsToNamesAndTypes(const Nodes& args) const
|
||||
std::tuple<std::string, std::string, std::string> BaseGenerator::argsToNamesAndTypes(const Nodes& args, bool async) const
|
||||
{
|
||||
std::ostringstream argSS, argTypeSS, typeSS;
|
||||
|
||||
@ -124,8 +124,16 @@ std::tuple<std::string, std::string, std::string> BaseGenerator::argsToNamesAndT
|
||||
argName = "arg" + std::to_string(i);
|
||||
}
|
||||
auto type = signature_to_type(arg->get("type"));
|
||||
argSS << argName;
|
||||
argTypeSS << "const " << type << "& " << argName;
|
||||
if (!async)
|
||||
{
|
||||
argSS << argName;
|
||||
argTypeSS << "const " << type << "& " << argName;
|
||||
}
|
||||
else
|
||||
{
|
||||
argSS << "std::move(" << argName << ")";
|
||||
argTypeSS << type << " " << argName;
|
||||
}
|
||||
typeSS << type;
|
||||
}
|
||||
|
||||
|
@ -87,7 +87,7 @@ protected:
|
||||
* @param args
|
||||
* @return tuple: argument names, argument types and names, argument types
|
||||
*/
|
||||
std::tuple<std::string, std::string, std::string> argsToNamesAndTypes(const sdbuscpp::xml::Nodes& args) const;
|
||||
std::tuple<std::string, std::string, std::string> argsToNamesAndTypes(const sdbuscpp::xml::Nodes& args, bool async = false) const;
|
||||
|
||||
/**
|
||||
* Output arguments to return type
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
project(sdbuscpp-xml2cpp)
|
||||
project(sdbus-c++-xml2cpp)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
|
@ -94,10 +94,18 @@ std::string ProxyGenerator::processInterface(Node& interface) const
|
||||
|
||||
body << tab << "{" << endl
|
||||
<< registration
|
||||
<< tab << "}" << endl << endl
|
||||
<< declaration << endl;
|
||||
<< tab << "}" << endl << endl;
|
||||
if (!declaration.empty())
|
||||
body << declaration << endl;
|
||||
|
||||
std::string methodDefinitions, asyncDeclarations;
|
||||
std::tie(methodDefinitions, asyncDeclarations) = processMethods(methods);
|
||||
|
||||
if (!asyncDeclarations.empty())
|
||||
{
|
||||
body << asyncDeclarations << endl;
|
||||
}
|
||||
|
||||
std::string methodDefinitions = processMethods(methods);
|
||||
if (!methodDefinitions.empty())
|
||||
{
|
||||
body << "public:" << endl << methodDefinitions;
|
||||
@ -117,9 +125,10 @@ std::string ProxyGenerator::processInterface(Node& interface) const
|
||||
return body.str();
|
||||
}
|
||||
|
||||
std::string ProxyGenerator::processMethods(const Nodes& methods) const
|
||||
std::tuple<std::string, std::string> ProxyGenerator::processMethods(const Nodes& methods) const
|
||||
{
|
||||
std::ostringstream methodSS;
|
||||
std::ostringstream definitionSS, asyncDeclarationSS;
|
||||
|
||||
for (const auto& method : methods)
|
||||
{
|
||||
auto name = method->get("name");
|
||||
@ -128,15 +137,15 @@ std::string ProxyGenerator::processMethods(const Nodes& methods) const
|
||||
Nodes outArgs = args.select("direction" , "out");
|
||||
|
||||
bool dontExpectReply{false};
|
||||
bool async{false};
|
||||
Nodes annotations = (*method)["annotation"];
|
||||
for (const auto& annotation : annotations)
|
||||
{
|
||||
if (annotation->get("name") == "org.freedesktop.DBus.Method.NoReply"
|
||||
&& annotation->get("value") == "true")
|
||||
{
|
||||
if (annotation->get("name") == "org.freedesktop.DBus.Method.NoReply" && annotation->get("value") == "true")
|
||||
dontExpectReply = true;
|
||||
break;
|
||||
}
|
||||
else if (annotation->get("name") == "org.freedesktop.DBus.Method.Async"
|
||||
&& (annotation->get("value") == "client" || annotation->get("value") == "clientserver"))
|
||||
async = true;
|
||||
}
|
||||
if (dontExpectReply && outArgs.size() > 0)
|
||||
{
|
||||
@ -146,39 +155,52 @@ std::string ProxyGenerator::processMethods(const Nodes& methods) const
|
||||
}
|
||||
|
||||
auto retType = outArgsToType(outArgs);
|
||||
std::string argStr, argTypeStr;
|
||||
std::tie(argStr, argTypeStr, std::ignore) = argsToNamesAndTypes(inArgs);
|
||||
std::string inArgStr, inArgTypeStr;
|
||||
std::tie(inArgStr, inArgTypeStr, std::ignore) = argsToNamesAndTypes(inArgs);
|
||||
std::string outArgStr, outArgTypeStr;
|
||||
std::tie(outArgStr, outArgTypeStr, std::ignore) = argsToNamesAndTypes(outArgs);
|
||||
|
||||
methodSS << tab << retType << " " << name << "(" << argTypeStr << ")" << endl
|
||||
definitionSS << tab << (async ? "void" : retType) << " " << name << "(" << inArgTypeStr << ")" << endl
|
||||
<< tab << "{" << endl;
|
||||
|
||||
if (outArgs.size() > 0)
|
||||
if (outArgs.size() > 0 && !async)
|
||||
{
|
||||
methodSS << tab << tab << retType << " result;" << endl;
|
||||
definitionSS << tab << tab << retType << " result;" << endl;
|
||||
}
|
||||
|
||||
methodSS << tab << tab << "object_.callMethod(\"" << name << "\")"
|
||||
definitionSS << tab << tab << "object_.callMethod" << (async ? "Async" : "") << "(\"" << name << "\")"
|
||||
".onInterface(interfaceName)";
|
||||
|
||||
if (inArgs.size() > 0)
|
||||
{
|
||||
methodSS << ".withArguments(" << argStr << ")";
|
||||
definitionSS << ".withArguments(" << inArgStr << ")";
|
||||
}
|
||||
|
||||
if (outArgs.size() > 0)
|
||||
if (async && !dontExpectReply)
|
||||
{
|
||||
methodSS << ".storeResultsTo(result);" << endl
|
||||
<< tab << tab << "return result";
|
||||
auto nameBigFirst = name;
|
||||
nameBigFirst[0] = islower(nameBigFirst[0]) ? nameBigFirst[0] + 'A' - 'a' : nameBigFirst[0];
|
||||
|
||||
definitionSS << ".uponReplyInvoke([this](const sdbus::Error* error" << (outArgTypeStr.empty() ? "" : ", ") << outArgTypeStr << ")"
|
||||
"{ this->on" << nameBigFirst << "Reply(" << outArgStr << (outArgStr.empty() ? "" : ", ") << "error); })";
|
||||
|
||||
asyncDeclarationSS << tab << "virtual void on" << nameBigFirst << "Reply("
|
||||
<< outArgTypeStr << (outArgTypeStr.empty() ? "" : ", ") << "const sdbus::Error* error) = 0;" << endl;
|
||||
}
|
||||
else if (outArgs.size() > 0)
|
||||
{
|
||||
definitionSS << ".storeResultsTo(result);" << endl
|
||||
<< tab << tab << "return result";
|
||||
}
|
||||
else if (dontExpectReply)
|
||||
{
|
||||
methodSS << ".dontExpectReply()";
|
||||
definitionSS << ".dontExpectReply()";
|
||||
}
|
||||
|
||||
methodSS << ";" << endl << tab << "}" << endl << endl;
|
||||
definitionSS << ";" << endl << tab << "}" << endl << endl;
|
||||
}
|
||||
|
||||
return methodSS.str();
|
||||
return std::make_tuple(definitionSS.str(), asyncDeclarationSS.str());
|
||||
}
|
||||
|
||||
std::tuple<std::string, std::string> ProxyGenerator::processSignals(const Nodes& signals) const
|
||||
|
@ -56,9 +56,9 @@ private:
|
||||
/**
|
||||
* Generate method calls
|
||||
* @param methods
|
||||
* @return source code
|
||||
* @return tuple: definition of methods, declaration of virtual async reply handlers
|
||||
*/
|
||||
std::string processMethods(const sdbuscpp::xml::Nodes& methods) const;
|
||||
std::tuple<std::string, std::string> processMethods(const sdbuscpp::xml::Nodes& methods) const;
|
||||
|
||||
/**
|
||||
* Generate code for handling signals
|
||||
|
@ -17,6 +17,6 @@ std::string signature_to_type(const std::string& signature);
|
||||
|
||||
std::string underscorize(const std::string& str);
|
||||
|
||||
constexpr const char* getHeaderComment() noexcept { return "\n/*\n * This file was automatically generated by sdbuscpp-xml2cpp; DO NOT EDIT!\n */\n\n"; }
|
||||
constexpr const char* getHeaderComment() noexcept { return "\n/*\n * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!\n */\n\n"; }
|
||||
|
||||
#endif //__SDBUSCPP_TOOLS_GENERATOR_UTILS_H
|
||||
|
94
test/CMakeLists.txt
Executable file → Normal file
94
test/CMakeLists.txt
Executable file → Normal file
@ -33,7 +33,7 @@ add_subdirectory(${CMAKE_CURRENT_BINARY_DIR}/googletest-src
|
||||
|
||||
set(UNITTESTS_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/unittests)
|
||||
set(UNITTESTS_SRCS
|
||||
${UNITTESTS_SOURCE_DIR}/libsdbus-c++_unittests.cpp
|
||||
${UNITTESTS_SOURCE_DIR}/sdbus-c++-unit-tests.cpp
|
||||
${UNITTESTS_SOURCE_DIR}/Message_test.cpp
|
||||
${UNITTESTS_SOURCE_DIR}/Types_test.cpp
|
||||
${UNITTESTS_SOURCE_DIR}/TypeTraits_test.cpp
|
||||
@ -44,13 +44,29 @@ set(INTEGRATIONTESTS_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/integrationtests)
|
||||
set(INTEGRATIONTESTS_SRCS
|
||||
${INTEGRATIONTESTS_SOURCE_DIR}/AdaptorAndProxy_test.cpp
|
||||
${INTEGRATIONTESTS_SOURCE_DIR}/Connection_test.cpp
|
||||
${INTEGRATIONTESTS_SOURCE_DIR}/libsdbus-c++_integrationtests.cpp
|
||||
${INTEGRATIONTESTS_SOURCE_DIR}/sdbus-c++-integration-tests.cpp
|
||||
${INTEGRATIONTESTS_SOURCE_DIR}/adaptor-glue.h
|
||||
${INTEGRATIONTESTS_SOURCE_DIR}/defs.h
|
||||
${INTEGRATIONTESTS_SOURCE_DIR}/proxy-glue.h
|
||||
${INTEGRATIONTESTS_SOURCE_DIR}/TestingAdaptor.h
|
||||
${INTEGRATIONTESTS_SOURCE_DIR}/TestingProxy.h)
|
||||
|
||||
set(PERFTESTS_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/perftests)
|
||||
set(STRESSTESTS_CLIENT_SRCS
|
||||
${PERFTESTS_SOURCE_DIR}/client.cpp
|
||||
${PERFTESTS_SOURCE_DIR}/perftest-proxy.h)
|
||||
set(STRESSTESTS_SERVER_SRCS
|
||||
${PERFTESTS_SOURCE_DIR}/server.cpp
|
||||
${PERFTESTS_SOURCE_DIR}/perftest-adaptor.h)
|
||||
|
||||
set(STRESSTESTS_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/stresstests)
|
||||
set(STRESSTESTS_SRCS
|
||||
${STRESSTESTS_SOURCE_DIR}/sdbus-c++-stress-tests.cpp
|
||||
${STRESSTESTS_SOURCE_DIR}/fahrenheit-thermometer-adaptor.h
|
||||
${STRESSTESTS_SOURCE_DIR}/fahrenheit-thermometer-proxy.h
|
||||
${STRESSTESTS_SOURCE_DIR}/concatenator-adaptor.h
|
||||
${STRESSTESTS_SOURCE_DIR}/concatenator-proxy.h)
|
||||
|
||||
#-------------------------------
|
||||
# GENERAL COMPILER CONFIGURATION
|
||||
#-------------------------------
|
||||
@ -65,53 +81,59 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR})
|
||||
# targets even when INTERFACE_INCLUDE_DIRECTORIES is used.
|
||||
set(CMAKE_NO_SYSTEM_FROM_IMPORTED "1")
|
||||
|
||||
add_executable(libsdbus-c++_unittests ${UNITTESTS_SRCS} $<TARGET_OBJECTS:sdbuscppobjects>)
|
||||
target_link_libraries(libsdbus-c++_unittests ${SYSTEMD_LIBRARIES} gmock gmock_main)
|
||||
add_executable(sdbus-c++-unit-tests ${UNITTESTS_SRCS} $<TARGET_OBJECTS:sdbus-cpp>)
|
||||
target_link_libraries(sdbus-c++-unit-tests ${SYSTEMD_LIBRARIES} gmock gmock_main)
|
||||
|
||||
add_executable(libsdbus-c++_integrationtests ${INTEGRATIONTESTS_SRCS})
|
||||
target_link_libraries(libsdbus-c++_integrationtests sdbus-c++ gmock gmock_main)
|
||||
add_executable(sdbus-c++-integration-tests ${INTEGRATIONTESTS_SRCS})
|
||||
target_link_libraries(sdbus-c++-integration-tests sdbus-c++ gmock gmock_main)
|
||||
|
||||
# Manual performance tests
|
||||
option(ENABLE_PERFTESTS "Build and install manual performance tests (default OFF)" OFF)
|
||||
if(ENABLE_PERFTESTS)
|
||||
add_executable(libsdbus-c++_perftests_client perftests/client.cpp perftests/perftest-proxy.h)
|
||||
target_link_libraries(libsdbus-c++_perftests_client sdbus-c++)
|
||||
add_executable(libsdbus-c++_perftests_server perftests/server.cpp perftests/perftest-adaptor.h)
|
||||
target_link_libraries(libsdbus-c++_perftests_server sdbus-c++)
|
||||
# Manual performance and stress tests
|
||||
option(ENABLE_PERF_TESTS "Build and install manual performance tests (default OFF)" OFF)
|
||||
option(ENABLE_STRESS_TESTS "Build and install manual stress tests (default OFF)" OFF)
|
||||
|
||||
if(ENABLE_PERF_TESTS OR ENABLE_STRESS_TESTS)
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
if(ENABLE_PERF_TESTS)
|
||||
add_executable(sdbus-c++-perf-tests-client ${STRESSTESTS_CLIENT_SRCS})
|
||||
target_link_libraries(sdbus-c++-perf-tests-client sdbus-c++ Threads::Threads)
|
||||
add_executable(sdbus-c++-perf-tests-server ${STRESSTESTS_SERVER_SRCS})
|
||||
target_link_libraries(sdbus-c++-perf-tests-server sdbus-c++ Threads::Threads)
|
||||
endif()
|
||||
|
||||
if(ENABLE_STRESS_TESTS)
|
||||
add_executable(sdbus-c++-stress-tests ${STRESSTESTS_SRCS})
|
||||
target_link_libraries(sdbus-c++-stress-tests sdbus-c++ Threads::Threads)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
#----------------------------------
|
||||
# INSTALLATION
|
||||
#----------------------------------
|
||||
|
||||
install(TARGETS libsdbus-c++_unittests DESTINATION /opt/test/bin)
|
||||
install(TARGETS libsdbus-c++_integrationtests DESTINATION /opt/test/bin)
|
||||
install(FILES ${INTEGRATIONTESTS_SOURCE_DIR}/files/libsdbus-cpp-test.conf DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/dbus-1/system.d)
|
||||
set(TESTS_INSTALL_PATH "/opt/test/bin" CACHE STRING "Specifies where the test binaries will be installed")
|
||||
|
||||
if(ENABLE_PERFTESTS)
|
||||
install(TARGETS libsdbus-c++_perftests_client DESTINATION /opt/test/bin)
|
||||
install(TARGETS libsdbus-c++_perftests_server DESTINATION /opt/test/bin)
|
||||
install(FILES perftests/files/org.sdbuscpp.perftest.conf DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/dbus-1/system.d)
|
||||
install(TARGETS sdbus-c++-unit-tests DESTINATION ${TESTS_INSTALL_PATH})
|
||||
install(TARGETS sdbus-c++-integration-tests DESTINATION ${TESTS_INSTALL_PATH})
|
||||
install(FILES ${INTEGRATIONTESTS_SOURCE_DIR}/files/org.sdbuscpp.integrationtests.conf DESTINATION /etc/dbus-1/system.d)
|
||||
|
||||
if(ENABLE_PERF_TESTS)
|
||||
install(TARGETS sdbus-c++-perf-tests-client DESTINATION ${TESTS_INSTALL_PATH})
|
||||
install(TARGETS sdbus-c++-perf-tests-server DESTINATION ${TESTS_INSTALL_PATH})
|
||||
install(FILES ${PERFTESTS_SOURCE_DIR}/files/org.sdbuscpp.perftests.conf DESTINATION /etc/dbus-1/system.d)
|
||||
endif()
|
||||
|
||||
if(ENABLE_STRESS_TESTS)
|
||||
install(TARGETS sdbus-c++-stress-tests DESTINATION ${TESTS_INSTALL_PATH})
|
||||
install(FILES ${STRESSTESTS_SOURCE_DIR}/files/org.sdbuscpp.stresstests.conf DESTINATION /etc/dbus-1/system.d)
|
||||
endif()
|
||||
|
||||
#----------------------------------
|
||||
# RUNNING THE TESTS UPON BUILD
|
||||
#----------------------------------
|
||||
|
||||
if(CMAKE_CROSSCOMPILING)
|
||||
if(NOT DEFINED UNIT_TESTS_RUNNER)
|
||||
set(UNIT_TESTS_RUNNER $ENV{UNIT_TESTS_RUNNER})
|
||||
endif()
|
||||
if(NOT DEFINED TEST_DEVICE_IP)
|
||||
set(TEST_DEVICE_IP $ENV{TEST_DEVICE_IP})
|
||||
endif()
|
||||
|
||||
if(NOT (UNIT_TESTS_RUNNER AND TEST_DEVICE_IP))
|
||||
message(WARNING "UNIT_TESTS_RUNNER and TEST_DEVICE_IP variables must be defined to run tests remotely")
|
||||
endif()
|
||||
add_test(NAME libsdbus-c++_unittests COMMAND ${UNIT_TESTS_RUNNER} --deviceip=${TEST_DEVICE_IP} --testbin=libsdbus-c++_unittests)
|
||||
add_test(NAME libsdbus-c++_integrationtests COMMAND ${UNIT_TESTS_RUNNER} --deviceip=${TEST_DEVICE_IP} --testbin=libsdbus-c++_integrationtests)
|
||||
else()
|
||||
add_test(NAME libsdbus-c++_unittests COMMAND libsdbus-c++_unittests)
|
||||
add_test(NAME libsdbus-c++_integrationtests COMMAND libsdbus-c++_integrationtests)
|
||||
if(NOT CMAKE_CROSSCOMPILING)
|
||||
add_test(NAME sdbus-c++-unit-tests COMMAND sdbus-c++-unit-tests)
|
||||
add_test(NAME sdbus-c++-integration-tests COMMAND sdbus-c++-integration-tests)
|
||||
endif()
|
||||
|
0
test/googletest-download/CMakeLists.txt.in
Executable file → Normal file
0
test/googletest-download/CMakeLists.txt.in
Executable file → Normal file
@ -43,6 +43,7 @@
|
||||
#include <tuple>
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
#include <future>
|
||||
|
||||
using ::testing::Eq;
|
||||
using ::testing::Gt;
|
||||
@ -89,7 +90,6 @@ public:
|
||||
};
|
||||
|
||||
std::unique_ptr<sdbus::IConnection> AdaptorAndProxyFixture::s_connection = sdbus::createSystemBusConnection();
|
||||
|
||||
}
|
||||
|
||||
/*-------------------------------------*/
|
||||
@ -247,7 +247,7 @@ TEST_F(SdbusTestObject, CallsErrorThrowingMethodWithDontExpectReplySet)
|
||||
ASSERT_TRUE(m_adaptor->wasThrowErrorCalled());
|
||||
}
|
||||
|
||||
TEST_F(SdbusTestObject, DoesServerSideAsynchoronousMethodInParallel)
|
||||
TEST_F(SdbusTestObject, RunsServerSideAsynchoronousMethodAsynchronously)
|
||||
{
|
||||
// Yeah, this is kinda timing-dependent test, but times should be safe...
|
||||
std::mutex mtx;
|
||||
@ -302,6 +302,40 @@ TEST_F(SdbusTestObject, HandlesCorrectlyABulkOfParallelServerSideAsyncMethods)
|
||||
ASSERT_THAT(resultCount, Eq(1500));
|
||||
}
|
||||
|
||||
TEST_F(SdbusTestObject, InvokesMethodAsynchronouslyOnClientSide)
|
||||
{
|
||||
std::promise<uint32_t> promise;
|
||||
auto future = promise.get_future();
|
||||
m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t res, const sdbus::Error* err)
|
||||
{
|
||||
if (err == nullptr)
|
||||
promise.set_value(res);
|
||||
else
|
||||
promise.set_exception(std::make_exception_ptr(*err));
|
||||
});
|
||||
|
||||
m_proxy->doOperationClientSideAsync(100);
|
||||
|
||||
ASSERT_THAT(future.get(), Eq(100));
|
||||
}
|
||||
|
||||
TEST_F(SdbusTestObject, InvokesErroneousMethodAsynchronouslyOnClientSide)
|
||||
{
|
||||
std::promise<uint32_t> promise;
|
||||
auto future = promise.get_future();
|
||||
m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t res, const sdbus::Error* err)
|
||||
{
|
||||
if (err == nullptr)
|
||||
promise.set_value(res);
|
||||
else
|
||||
promise.set_exception(std::make_exception_ptr(*err));
|
||||
});
|
||||
|
||||
m_proxy->doErroneousOperationClientSideAsync();
|
||||
|
||||
ASSERT_THROW(future.get(), sdbus::Error);
|
||||
}
|
||||
|
||||
TEST_F(SdbusTestObject, FailsCallingNonexistentMethod)
|
||||
{
|
||||
ASSERT_THROW(m_proxy->callNonexistentMethod(), sdbus::Error);
|
||||
|
@ -45,7 +45,7 @@ public:
|
||||
|
||||
protected:
|
||||
|
||||
void noArgNoReturn() const { }
|
||||
void noArgNoReturn() const {}
|
||||
|
||||
int32_t getInt() const { return INT32_VALUE; }
|
||||
|
||||
@ -110,7 +110,7 @@ protected:
|
||||
return res;
|
||||
}
|
||||
|
||||
uint32_t doOperationSync(uint32_t param)
|
||||
uint32_t doOperation(uint32_t param)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(param));
|
||||
return param;
|
||||
|
@ -38,6 +38,11 @@ public:
|
||||
double getVariantValue() const { return m_variantValue; }
|
||||
std::map<std::string, std::string> getSignatureFromSignal() const { return m_signature; }
|
||||
|
||||
void installDoOperationClientSideAsyncReplyHandler(std::function<void(uint32_t res, const sdbus::Error* err)> handler)
|
||||
{
|
||||
m_DoOperationClientSideAsyncReplyHandler = handler;
|
||||
}
|
||||
|
||||
protected:
|
||||
void onSimpleSignal() override { ++m_simpleCallCounter; }
|
||||
|
||||
@ -53,12 +58,19 @@ protected:
|
||||
m_signature[std::get<0>(s)] = static_cast<std::string>(std::get<0>(std::get<1>(s)));
|
||||
}
|
||||
|
||||
void onDoOperationReply(uint32_t returnValue, const sdbus::Error* error) override
|
||||
{
|
||||
if (m_DoOperationClientSideAsyncReplyHandler)
|
||||
m_DoOperationClientSideAsyncReplyHandler(returnValue, error);
|
||||
}
|
||||
|
||||
private:
|
||||
int m_simpleCallCounter{};
|
||||
std::map<int32_t, std::string> m_map;
|
||||
double m_variantValue;
|
||||
std::map<std::string, std::string> m_signature;
|
||||
|
||||
std::function<void(uint32_t res, const sdbus::Error* err)> m_DoOperationClientSideAsyncReplyHandler;
|
||||
};
|
||||
|
||||
|
||||
|
@ -85,9 +85,9 @@ protected:
|
||||
return this->sumVectorItems(a, b);
|
||||
});
|
||||
|
||||
object_.registerMethod("doOperationSync").onInterface(INTERFACE_NAME).implementedAs([this](uint32_t param)
|
||||
object_.registerMethod("doOperation").onInterface(INTERFACE_NAME).implementedAs([this](uint32_t param)
|
||||
{
|
||||
return this->doOperationSync(param);
|
||||
return this->doOperation(param);
|
||||
});
|
||||
|
||||
object_.registerMethod("doOperationAsync").onInterface(INTERFACE_NAME).implementedAs([this](sdbus::Result<uint32_t> result, uint32_t param)
|
||||
@ -158,7 +158,7 @@ protected:
|
||||
virtual sdbus::Struct<std::string, sdbus::Struct<std::map<int32_t, int32_t>>> getStructInStruct() const = 0;
|
||||
virtual int32_t sumStructItems(const sdbus::Struct<uint8_t, uint16_t>& a, const sdbus::Struct<int32_t, int64_t>& b) = 0;
|
||||
virtual uint32_t sumVectorItems(const std::vector<uint16_t>& a, const std::vector<uint64_t>& b) = 0;
|
||||
virtual uint32_t doOperationSync(uint32_t param) = 0;
|
||||
virtual uint32_t doOperation(uint32_t param) = 0;
|
||||
virtual void doOperationAsync(uint32_t param, sdbus::Result<uint32_t> result) = 0;
|
||||
virtual sdbus::Signature getSignature() const = 0;
|
||||
virtual sdbus::ObjectPath getObjectPath() const = 0;
|
||||
@ -210,13 +210,13 @@ R"delimiter(<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspectio
|
||||
<arg type="as" name="invalidated_properties"/>
|
||||
</signal>
|
||||
</interface>
|
||||
<interface name="com.kistler.testsdbuscpp">
|
||||
<interface name="org.sdbuscpp.integrationtests">
|
||||
<annotation name="org.freedesktop.DBus.Deprecated" value="true"/>
|
||||
<method name="doOperationAsync">
|
||||
<method name="doOperation">
|
||||
<arg type="u" direction="in"/>
|
||||
<arg type="u" direction="out"/>
|
||||
</method>
|
||||
<method name="doOperationSync">
|
||||
<method name="doOperationAsync">
|
||||
<arg type="u" direction="in"/>
|
||||
<arg type="u" direction="out"/>
|
||||
</method>
|
||||
|
@ -28,7 +28,7 @@
|
||||
|
||||
#include "sdbus-c++/Types.h"
|
||||
|
||||
const std::string INTERFACE_NAME{"com.kistler.testsdbuscpp"};
|
||||
const std::string INTERFACE_NAME{"org.sdbuscpp.integrationtests"};
|
||||
const std::string OBJECT_PATH{"/"};
|
||||
|
||||
constexpr const uint8_t UINT8_VALUE{1};
|
||||
|
@ -8,9 +8,9 @@
|
||||
<!-- ../system.conf have denied everything, so we just punch some holes -->
|
||||
|
||||
<policy context="default">
|
||||
<allow own="com.kistler.testsdbuscpp"/>
|
||||
<allow send_destination="com.kistler.testsdbuscpp"/>
|
||||
<allow send_interface="com.kistler.testsdbuscpp"/>
|
||||
<allow own="org.sdbuscpp.integrationtests"/>
|
||||
<allow send_destination="org.sdbuscpp.integrationtests"/>
|
||||
<allow send_interface="org.sdbuscpp.integrationtests"/>
|
||||
</policy>
|
||||
|
||||
</busconfig>
|
@ -50,6 +50,8 @@ protected:
|
||||
virtual void onSignalWithVariant(const sdbus::Variant& v) = 0;
|
||||
virtual void onSignalWithoutRegistration(const sdbus::Struct<std::string, sdbus::Struct<sdbus::Signature>>& s) = 0;
|
||||
|
||||
virtual void onDoOperationReply(uint32_t returnValue, const sdbus::Error* error) = 0;
|
||||
|
||||
public:
|
||||
void noArgNoReturn()
|
||||
{
|
||||
@ -124,10 +126,10 @@ public:
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t doOperationSync(uint32_t param)
|
||||
uint32_t doOperation(uint32_t param)
|
||||
{
|
||||
uint32_t result;
|
||||
object_.callMethod("doOperationSync").onInterface(INTERFACE_NAME).withArguments(param).storeResultsTo(result);
|
||||
object_.callMethod("doOperation").onInterface(INTERFACE_NAME).withArguments(param).storeResultsTo(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -138,6 +140,25 @@ public:
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t doOperationClientSideAsync(uint32_t param)
|
||||
{
|
||||
object_.callMethodAsync("doOperation")
|
||||
.onInterface(INTERFACE_NAME)
|
||||
.withArguments(param)
|
||||
.uponReplyInvoke([this](const sdbus::Error* error, uint32_t returnValue)
|
||||
{
|
||||
this->onDoOperationReply(returnValue, error);
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t doErroneousOperationClientSideAsync()
|
||||
{
|
||||
object_.callMethodAsync("throwError").onInterface(INTERFACE_NAME).uponReplyInvoke([this](const sdbus::Error* error)
|
||||
{
|
||||
this->onDoOperationReply(0, error);
|
||||
});
|
||||
}
|
||||
|
||||
sdbus::Signature getSignature()
|
||||
{
|
||||
sdbus::Signature result;
|
||||
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
|
||||
*
|
||||
* @file libsdbus-c++_integrationtests.cpp
|
||||
* @file sdbus-c++-integration-tests.cpp
|
||||
*
|
||||
* Created on: Jan 2, 2017
|
||||
* Project: sdbus-c++
|
@ -32,6 +32,7 @@
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
@ -48,11 +49,11 @@ protected:
|
||||
{
|
||||
static unsigned int counter = 0;
|
||||
static std::chrono::time_point<std::chrono::steady_clock> startTime;
|
||||
|
||||
|
||||
assert(data.size() == m_msgSize);
|
||||
|
||||
|
||||
++counter;
|
||||
|
||||
|
||||
if (counter == 1)
|
||||
startTime = std::chrono::steady_clock::now();
|
||||
else if (counter == m_msgCount)
|
||||
@ -62,7 +63,7 @@ protected:
|
||||
counter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
unsigned int m_msgSize{};
|
||||
unsigned int m_msgCount{};
|
||||
@ -88,75 +89,75 @@ std::string createRandomString(size_t length)
|
||||
//-----------------------------------------
|
||||
int main(int /*argc*/, char */*argv*/[])
|
||||
{
|
||||
const char* destinationName = "org.sdbuscpp.perftest";
|
||||
const char* objectPath = "/org/sdbuscpp/perftest";
|
||||
const char* destinationName = "org.sdbuscpp.perftests";
|
||||
const char* objectPath = "/org/sdbuscpp/perftests";
|
||||
PerftestClient client(destinationName, objectPath);
|
||||
|
||||
const unsigned int repetitions{20};
|
||||
unsigned int msgCount = 1000;
|
||||
unsigned int msgSize{};
|
||||
|
||||
|
||||
msgSize = 20;
|
||||
std::cout << "** Measuring signals of size " << msgSize << " bytes (" << repetitions << " repetitions)..." << std::endl << std::endl;
|
||||
client.m_msgCount = msgCount; client.m_msgSize = msgSize;
|
||||
for (unsigned int r = 0; r < repetitions; ++r)
|
||||
{
|
||||
client.sendDataSignals(msgCount, msgSize);
|
||||
|
||||
|
||||
std::this_thread::sleep_for(1000ms);
|
||||
}
|
||||
|
||||
|
||||
msgSize = 1000;
|
||||
std::cout << std::endl << "** Measuring signals of size " << msgSize << " bytes (" << repetitions << " repetitions)..." << std::endl << std::endl;
|
||||
client.m_msgCount = msgCount; client.m_msgSize = msgSize;
|
||||
for (unsigned int r = 0; r < repetitions; ++r)
|
||||
{
|
||||
client.sendDataSignals(msgCount, msgSize);
|
||||
|
||||
|
||||
std::this_thread::sleep_for(1000ms);
|
||||
}
|
||||
|
||||
|
||||
msgSize = 20;
|
||||
std::cout << std::endl << "** Measuring method calls of size " << msgSize << " bytes (" << repetitions << " repetitions)..." << std::endl << std::endl;
|
||||
for (unsigned int r = 0; r < repetitions; ++r)
|
||||
{
|
||||
auto str1 = createRandomString(msgSize/2);
|
||||
auto str2 = createRandomString(msgSize/2);
|
||||
|
||||
|
||||
auto startTime = std::chrono::steady_clock::now();
|
||||
for (unsigned int i = 0; i < msgCount; i++)
|
||||
{
|
||||
auto result = client.concatenateTwoStrings(str1, str2);
|
||||
|
||||
|
||||
assert(result.size() == str1.size() + str2.size());
|
||||
assert(result.size() == msgSize);
|
||||
}
|
||||
auto stopTime = std::chrono::steady_clock::now();
|
||||
std::cout << "Called " << msgCount << " methods in: " << std::chrono::duration_cast<std::chrono::milliseconds>(stopTime - startTime).count() << " ms" << std::endl;
|
||||
|
||||
|
||||
std::this_thread::sleep_for(1000ms);
|
||||
}
|
||||
|
||||
|
||||
msgSize = 1000;
|
||||
std::cout << std::endl << "** Measuring method calls of size " << msgSize << " bytes (" << repetitions << " repetitions)..." << std::endl << std::endl;
|
||||
for (unsigned int r = 0; r < repetitions; ++r)
|
||||
{
|
||||
auto str1 = createRandomString(msgSize/2);
|
||||
auto str2 = createRandomString(msgSize/2);
|
||||
|
||||
|
||||
auto startTime = std::chrono::steady_clock::now();
|
||||
for (unsigned int i = 0; i < msgCount; i++)
|
||||
{
|
||||
auto result = client.concatenateTwoStrings(str1, str2);
|
||||
|
||||
|
||||
assert(result.size() == str1.size() + str2.size());
|
||||
assert(result.size() == msgSize);
|
||||
}
|
||||
auto stopTime = std::chrono::steady_clock::now();
|
||||
std::cout << "Called " << msgCount << " methods in: " << std::chrono::duration_cast<std::chrono::milliseconds>(stopTime - startTime).count() << " ms" << std::endl;
|
||||
|
||||
|
||||
std::this_thread::sleep_for(1000ms);
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -8,9 +8,9 @@
|
||||
<!-- ../system.conf have denied everything, so we just punch some holes -->
|
||||
|
||||
<policy context="default">
|
||||
<allow own="org.sdbuscpp.perftest"/>
|
||||
<allow send_destination="org.sdbuscpp.perftest"/>
|
||||
<allow send_interface="org.sdbuscpp.perftest"/>
|
||||
<allow own="org.sdbuscpp.perftests"/>
|
||||
<allow send_destination="org.sdbuscpp.perftests"/>
|
||||
<allow send_interface="org.sdbuscpp.perftests"/>
|
||||
</policy>
|
||||
|
||||
</busconfig>
|
@ -16,7 +16,7 @@ namespace sdbuscpp {
|
||||
class perftest_adaptor
|
||||
{
|
||||
public:
|
||||
static constexpr const char* interfaceName = "org.sdbuscpp.perftest";
|
||||
static constexpr const char* interfaceName = "org.sdbuscpp.perftests";
|
||||
|
||||
protected:
|
||||
perftest_adaptor(sdbus::IObject& object)
|
||||
|
@ -16,7 +16,7 @@ namespace sdbuscpp {
|
||||
class perftest_proxy
|
||||
{
|
||||
public:
|
||||
static constexpr const char* interfaceName = "org.sdbuscpp.perftest";
|
||||
static constexpr const char* interfaceName = "org.sdbuscpp.perftests";
|
||||
|
||||
protected:
|
||||
perftest_proxy(sdbus::IObjectProxy& object)
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
@ -84,10 +85,10 @@ std::string createRandomString(size_t length)
|
||||
//-----------------------------------------
|
||||
int main(int /*argc*/, char */*argv*/[])
|
||||
{
|
||||
const char* serviceName = "org.sdbuscpp.perftest";
|
||||
const char* serviceName = "org.sdbuscpp.perftests";
|
||||
auto connection = sdbus::createSystemBusConnection(serviceName);
|
||||
|
||||
const char* objectPath = "/org/sdbuscpp/perftest";
|
||||
const char* objectPath = "/org/sdbuscpp/perftests";
|
||||
PerftestServer server(*connection, objectPath);
|
||||
|
||||
connection->enterProcessingLoop();
|
||||
|
39
test/stresstests/celsius-thermometer-adaptor.h
Normal file
39
test/stresstests/celsius-thermometer-adaptor.h
Normal file
@ -0,0 +1,39 @@
|
||||
|
||||
/*
|
||||
* This file was automatically generated by sdbuscpp-xml2cpp; DO NOT EDIT!
|
||||
*/
|
||||
|
||||
#ifndef __sdbuscpp__celsius_thermometer_adaptor_h__adaptor__H__
|
||||
#define __sdbuscpp__celsius_thermometer_adaptor_h__adaptor__H__
|
||||
|
||||
#include <sdbus-c++/sdbus-c++.h>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
namespace org {
|
||||
namespace sdbuscpp {
|
||||
namespace stresstest {
|
||||
namespace celsius {
|
||||
|
||||
class thermometer_adaptor
|
||||
{
|
||||
public:
|
||||
static constexpr const char* interfaceName = "org.sdbuscpp.stresstests.celsius.thermometer";
|
||||
|
||||
protected:
|
||||
thermometer_adaptor(sdbus::IObject& object)
|
||||
: object_(object)
|
||||
{
|
||||
object_.registerMethod("getCurrentTemperature").onInterface(interfaceName).implementedAs([this](){ return this->getCurrentTemperature(); });
|
||||
}
|
||||
|
||||
private:
|
||||
virtual uint32_t getCurrentTemperature() = 0;
|
||||
|
||||
private:
|
||||
sdbus::IObject& object_;
|
||||
};
|
||||
|
||||
}}}} // namespaces
|
||||
|
||||
#endif
|
43
test/stresstests/celsius-thermometer-proxy.h
Normal file
43
test/stresstests/celsius-thermometer-proxy.h
Normal file
@ -0,0 +1,43 @@
|
||||
|
||||
/*
|
||||
* This file was automatically generated by sdbuscpp-xml2cpp; DO NOT EDIT!
|
||||
*/
|
||||
|
||||
#ifndef __sdbuscpp__celsius_thermometer_proxy_h__proxy__H__
|
||||
#define __sdbuscpp__celsius_thermometer_proxy_h__proxy__H__
|
||||
|
||||
#include <sdbus-c++/sdbus-c++.h>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
namespace org {
|
||||
namespace sdbuscpp {
|
||||
namespace stresstest {
|
||||
namespace celsius {
|
||||
|
||||
class thermometer_proxy
|
||||
{
|
||||
public:
|
||||
static constexpr const char* interfaceName = "org.sdbuscpp.stresstests.celsius.thermometer";
|
||||
|
||||
protected:
|
||||
thermometer_proxy(sdbus::IObjectProxy& object)
|
||||
: object_(object)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
uint32_t getCurrentTemperature()
|
||||
{
|
||||
uint32_t result;
|
||||
object_.callMethod("getCurrentTemperature").onInterface(interfaceName).storeResultsTo(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
sdbus::IObjectProxy& object_;
|
||||
};
|
||||
|
||||
}}}} // namespaces
|
||||
|
||||
#endif
|
45
test/stresstests/concatenator-adaptor.h
Normal file
45
test/stresstests/concatenator-adaptor.h
Normal file
@ -0,0 +1,45 @@
|
||||
|
||||
/*
|
||||
* This file was automatically generated by sdbuscpp-xml2cpp; DO NOT EDIT!
|
||||
*/
|
||||
|
||||
#ifndef __sdbuscpp__concatenator_adaptor_h__adaptor__H__
|
||||
#define __sdbuscpp__concatenator_adaptor_h__adaptor__H__
|
||||
|
||||
#include <sdbus-c++/sdbus-c++.h>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
namespace org {
|
||||
namespace sdbuscpp {
|
||||
namespace stresstest {
|
||||
|
||||
class concatenator_adaptor
|
||||
{
|
||||
public:
|
||||
static constexpr const char* interfaceName = "org.sdbuscpp.stresstests.concatenator";
|
||||
|
||||
protected:
|
||||
concatenator_adaptor(sdbus::IObject& object)
|
||||
: object_(object)
|
||||
{
|
||||
object_.registerMethod("concatenate").onInterface(interfaceName).implementedAs([this](sdbus::Result<std::string>&& result, std::map<std::string, sdbus::Variant> params){ this->concatenate(std::move(result), std::move(params)); });
|
||||
object_.registerSignal("concatenatedSignal").onInterface(interfaceName).withParameters<std::string>();
|
||||
}
|
||||
|
||||
public:
|
||||
void concatenatedSignal(const std::string& concatenatedString)
|
||||
{
|
||||
object_.emitSignal("concatenatedSignal").onInterface(interfaceName).withArguments(concatenatedString);
|
||||
}
|
||||
|
||||
private:
|
||||
virtual void concatenate(sdbus::Result<std::string>&& result, std::map<std::string, sdbus::Variant> params) = 0;
|
||||
|
||||
private:
|
||||
sdbus::IObject& object_;
|
||||
};
|
||||
|
||||
}}} // namespaces
|
||||
|
||||
#endif
|
45
test/stresstests/concatenator-proxy.h
Normal file
45
test/stresstests/concatenator-proxy.h
Normal file
@ -0,0 +1,45 @@
|
||||
|
||||
/*
|
||||
* This file was automatically generated by sdbuscpp-xml2cpp; DO NOT EDIT!
|
||||
*/
|
||||
|
||||
#ifndef __sdbuscpp__concatenator_proxy_h__proxy__H__
|
||||
#define __sdbuscpp__concatenator_proxy_h__proxy__H__
|
||||
|
||||
#include <sdbus-c++/sdbus-c++.h>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
namespace org {
|
||||
namespace sdbuscpp {
|
||||
namespace stresstest {
|
||||
|
||||
class concatenator_proxy
|
||||
{
|
||||
public:
|
||||
static constexpr const char* interfaceName = "org.sdbuscpp.stresstests.concatenator";
|
||||
|
||||
protected:
|
||||
concatenator_proxy(sdbus::IObjectProxy& object)
|
||||
: object_(object)
|
||||
{
|
||||
object_.uponSignal("concatenatedSignal").onInterface(interfaceName).call([this](const std::string& concatenatedString){ this->onConcatenatedSignal(concatenatedString); });
|
||||
}
|
||||
|
||||
virtual void onConcatenatedSignal(const std::string& concatenatedString) = 0;
|
||||
|
||||
virtual void onConcatenateReply(const std::string& result, const sdbus::Error* error) = 0;
|
||||
|
||||
public:
|
||||
void concatenate(const std::map<std::string, sdbus::Variant>& params)
|
||||
{
|
||||
object_.callMethodAsync("concatenate").onInterface(interfaceName).withArguments(params).uponReplyInvoke([this](const sdbus::Error* error, const std::string& result){ this->onConcatenateReply(result, error); });
|
||||
}
|
||||
|
||||
private:
|
||||
sdbus::IObjectProxy& object_;
|
||||
};
|
||||
|
||||
}}} // namespaces
|
||||
|
||||
#endif
|
39
test/stresstests/fahrenheit-thermometer-adaptor.h
Normal file
39
test/stresstests/fahrenheit-thermometer-adaptor.h
Normal file
@ -0,0 +1,39 @@
|
||||
|
||||
/*
|
||||
* This file was automatically generated by sdbuscpp-xml2cpp; DO NOT EDIT!
|
||||
*/
|
||||
|
||||
#ifndef __sdbuscpp__fahrenheit_thermometer_adaptor_h__adaptor__H__
|
||||
#define __sdbuscpp__fahrenheit_thermometer_adaptor_h__adaptor__H__
|
||||
|
||||
#include <sdbus-c++/sdbus-c++.h>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
namespace org {
|
||||
namespace sdbuscpp {
|
||||
namespace stresstest {
|
||||
namespace fahrenheit {
|
||||
|
||||
class thermometer_adaptor
|
||||
{
|
||||
public:
|
||||
static constexpr const char* interfaceName = "org.sdbuscpp.stresstests.fahrenheit.thermometer";
|
||||
|
||||
protected:
|
||||
thermometer_adaptor(sdbus::IObject& object)
|
||||
: object_(object)
|
||||
{
|
||||
object_.registerMethod("getCurrentTemperature").onInterface(interfaceName).implementedAs([this](){ return this->getCurrentTemperature(); });
|
||||
}
|
||||
|
||||
private:
|
||||
virtual uint32_t getCurrentTemperature() = 0;
|
||||
|
||||
private:
|
||||
sdbus::IObject& object_;
|
||||
};
|
||||
|
||||
}}}} // namespaces
|
||||
|
||||
#endif
|
43
test/stresstests/fahrenheit-thermometer-proxy.h
Normal file
43
test/stresstests/fahrenheit-thermometer-proxy.h
Normal file
@ -0,0 +1,43 @@
|
||||
|
||||
/*
|
||||
* This file was automatically generated by sdbuscpp-xml2cpp; DO NOT EDIT!
|
||||
*/
|
||||
|
||||
#ifndef __sdbuscpp__fahrenheit_thermometer_proxy_h__proxy__H__
|
||||
#define __sdbuscpp__fahrenheit_thermometer_proxy_h__proxy__H__
|
||||
|
||||
#include <sdbus-c++/sdbus-c++.h>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
namespace org {
|
||||
namespace sdbuscpp {
|
||||
namespace stresstest {
|
||||
namespace fahrenheit {
|
||||
|
||||
class thermometer_proxy
|
||||
{
|
||||
public:
|
||||
static constexpr const char* interfaceName = "org.sdbuscpp.stresstests.fahrenheit.thermometer";
|
||||
|
||||
protected:
|
||||
thermometer_proxy(sdbus::IObjectProxy& object)
|
||||
: object_(object)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
uint32_t getCurrentTemperature()
|
||||
{
|
||||
uint32_t result;
|
||||
object_.callMethod("getCurrentTemperature").onInterface(interfaceName).storeResultsTo(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
sdbus::IObjectProxy& object_;
|
||||
};
|
||||
|
||||
}}}} // namespaces
|
||||
|
||||
#endif
|
22
test/stresstests/files/org.sdbuscpp.stresstests.conf
Normal file
22
test/stresstests/files/org.sdbuscpp.stresstests.conf
Normal file
@ -0,0 +1,22 @@
|
||||
<!-- This configuration file specifies the required security policies
|
||||
for the Kistler DBUS example to run core daemon to work. -->
|
||||
|
||||
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
|
||||
<busconfig>
|
||||
|
||||
<!-- ../system.conf have denied everything, so we just punch some holes -->
|
||||
|
||||
<policy context="default">
|
||||
<allow own="org.sdbuscpp.stresstests.service1"/>
|
||||
<allow send_destination="org.sdbuscpp.stresstests.service1"/>
|
||||
<allow send_interface="org.sdbuscpp.stresstests.service1"/>
|
||||
</policy>
|
||||
|
||||
<policy context="default">
|
||||
<allow own="org.sdbuscpp.stresstests.service2"/>
|
||||
<allow send_destination="org.sdbuscpp.stresstests.service2"/>
|
||||
<allow send_interface="org.sdbuscpp.stresstests.service2"/>
|
||||
</policy>
|
||||
|
||||
</busconfig>
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<node name="/org/sdbuscpp/stresstest/celsius/thermometer">
|
||||
<interface name="org.sdbuscpp.stresstest.celsius.thermometer">
|
||||
<method name="getCurrentTemperature">
|
||||
<arg type="u" name="result" direction="out" />
|
||||
</method>
|
||||
</interface>
|
||||
</node>
|
14
test/stresstests/org.sdbuscpp.stresstest.concatenator.xml
Normal file
14
test/stresstests/org.sdbuscpp.stresstest.concatenator.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<node name="/org/sdbuscpp/stresstest/concatenator">
|
||||
<interface name="org.sdbuscpp.stresstest.concatenator">
|
||||
<method name="concatenate">
|
||||
<arg type="a{sv}" name="params" direction="in" />
|
||||
<arg type="s" name="result" direction="out" />
|
||||
<annotation name="org.freedesktop.DBus.Method.Async" value="clientserver" />
|
||||
</method>
|
||||
<signal name="concatenatedSignal">
|
||||
<arg type="s" name="concatenatedString" />
|
||||
</signal>
|
||||
</interface>
|
||||
</node>
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<node name="/org/sdbuscpp/stresstest/fahrenheit/thermometer">
|
||||
<interface name="org.sdbuscpp.stresstest.fahrenheit.thermometer">
|
||||
<method name="getCurrentTemperature">
|
||||
<arg type="u" name="result" direction="out" />
|
||||
</method>
|
||||
</interface>
|
||||
</node>
|
322
test/stresstests/sdbus-c++-stress-tests.cpp
Normal file
322
test/stresstests/sdbus-c++-stress-tests.cpp
Normal file
@ -0,0 +1,322 @@
|
||||
/**
|
||||
* (C) 2019 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
|
||||
*
|
||||
* @file sdbus-c++-stress-tests.cpp
|
||||
*
|
||||
* Created on: Jan 25, 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/>.
|
||||
*/
|
||||
|
||||
#include "celsius-thermometer-adaptor.h"
|
||||
#include "celsius-thermometer-proxy.h"
|
||||
#include "fahrenheit-thermometer-adaptor.h"
|
||||
#include "fahrenheit-thermometer-proxy.h"
|
||||
#include "concatenator-adaptor.h"
|
||||
#include "concatenator-proxy.h"
|
||||
#include <sdbus-c++/sdbus-c++.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <unistd.h>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <cassert>
|
||||
#include <atomic>
|
||||
#include <sstream>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <queue>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
using namespace std::string_literals;
|
||||
|
||||
#define SERVICE_1_BUS_NAME "org.sdbuscpp.stresstests.service1"
|
||||
#define SERVICE_2_BUS_NAME "org.sdbuscpp.stresstests.service2"
|
||||
#define CELSIUS_THERMOMETER_OBJECT_PATH "/org/sdbuscpp/stresstests/celsius/thermometer"
|
||||
#define FAHRENHEIT_THERMOMETER_OBJECT_PATH "/org/sdbuscpp/stresstests/fahrenheit/thermometer"
|
||||
#define CONCATENATOR_OBJECT_PATH "/org/sdbuscpp/stresstests/concatenator"
|
||||
|
||||
class CelsiusThermometerAdaptor : public sdbus::Interfaces<org::sdbuscpp::stresstest::celsius::thermometer_adaptor>
|
||||
{
|
||||
public:
|
||||
using sdbus::Interfaces<org::sdbuscpp::stresstest::celsius::thermometer_adaptor>::Interfaces;
|
||||
|
||||
protected:
|
||||
virtual uint32_t getCurrentTemperature() override
|
||||
{
|
||||
return m_currentTemperature++;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t m_currentTemperature{};
|
||||
};
|
||||
|
||||
class CelsiusThermometerProxy : public sdbus::ProxyInterfaces<org::sdbuscpp::stresstest::celsius::thermometer_proxy>
|
||||
{
|
||||
public:
|
||||
using sdbus::ProxyInterfaces<org::sdbuscpp::stresstest::celsius::thermometer_proxy>::ProxyInterfaces;
|
||||
};
|
||||
|
||||
class FahrenheitThermometerAdaptor : public sdbus::Interfaces<org::sdbuscpp::stresstest::fahrenheit::thermometer_adaptor>
|
||||
{
|
||||
public:
|
||||
FahrenheitThermometerAdaptor(sdbus::IConnection& connection, std::string objectPath)
|
||||
: sdbus::Interfaces<org::sdbuscpp::stresstest::fahrenheit::thermometer_adaptor>(connection, std::move(objectPath))
|
||||
, celsiusProxy_(connection, SERVICE_2_BUS_NAME, CELSIUS_THERMOMETER_OBJECT_PATH)
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual uint32_t getCurrentTemperature() override
|
||||
{
|
||||
// In this D-Bus call, make yet another D-Bus call to another service over the same connection
|
||||
return static_cast<uint32_t>(celsiusProxy_.getCurrentTemperature() * 1.8 + 32.);
|
||||
}
|
||||
|
||||
private:
|
||||
CelsiusThermometerProxy celsiusProxy_;
|
||||
};
|
||||
|
||||
class FahrenheitThermometerProxy : public sdbus::ProxyInterfaces<org::sdbuscpp::stresstest::fahrenheit::thermometer_proxy>
|
||||
{
|
||||
public:
|
||||
using sdbus::ProxyInterfaces<org::sdbuscpp::stresstest::fahrenheit::thermometer_proxy>::ProxyInterfaces;
|
||||
};
|
||||
|
||||
class ConcatenatorAdaptor : public sdbus::Interfaces<org::sdbuscpp::stresstest::concatenator_adaptor>
|
||||
{
|
||||
public:
|
||||
ConcatenatorAdaptor(sdbus::IConnection& connection, std::string objectPath)
|
||||
: sdbus::Interfaces<org::sdbuscpp::stresstest::concatenator_adaptor>(connection, std::move(objectPath))
|
||||
{
|
||||
unsigned int workers = std::thread::hardware_concurrency();
|
||||
if (workers < 4)
|
||||
workers = 4;
|
||||
|
||||
for (unsigned int i = 0; i < workers; ++i)
|
||||
workers_.emplace_back([this]()
|
||||
{
|
||||
//std::cout << "Created worker thread 0x" << std::hex << std::this_thread::get_id() << std::dec << std::endl;
|
||||
|
||||
while(!exit_)
|
||||
{
|
||||
// Pop a work item from the queue
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
cond_.wait(lock, [this]{return !requests_.empty() || exit_;});
|
||||
if (exit_)
|
||||
break;
|
||||
auto request = std::move(requests_.front());
|
||||
requests_.pop();
|
||||
lock.unlock();
|
||||
|
||||
// Do concatenation work, return results and fire signal
|
||||
auto aString = request.input.at("key1").get<std::string>();
|
||||
auto aNumber = request.input.at("key2").get<uint32_t>();
|
||||
auto resultString = aString + " " + std::to_string(aNumber);
|
||||
|
||||
request.result.returnResults(resultString);
|
||||
|
||||
concatenatedSignal(resultString);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
~ConcatenatorAdaptor()
|
||||
{
|
||||
exit_ = true;
|
||||
cond_.notify_all();
|
||||
for (auto& worker : workers_)
|
||||
worker.join();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void concatenate(sdbus::Result<std::string>&& result, std::map<std::string, sdbus::Variant> params) override
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
requests_.push(WorkItem{std::move(params), std::move(result)});
|
||||
lock.unlock();
|
||||
cond_.notify_one();
|
||||
}
|
||||
|
||||
private:
|
||||
struct WorkItem
|
||||
{
|
||||
std::map<std::string, sdbus::Variant> input;
|
||||
sdbus::Result<std::string> result;
|
||||
};
|
||||
std::mutex mutex_;
|
||||
std::condition_variable cond_;
|
||||
std::queue<WorkItem> requests_;
|
||||
std::vector<std::thread> workers_;
|
||||
std::atomic<bool> exit_{};
|
||||
};
|
||||
|
||||
class ConcatenatorProxy : public sdbus::ProxyInterfaces<org::sdbuscpp::stresstest::concatenator_proxy>
|
||||
{
|
||||
public:
|
||||
using sdbus::ProxyInterfaces<org::sdbuscpp::stresstest::concatenator_proxy>::ProxyInterfaces;
|
||||
|
||||
private:
|
||||
virtual void onConcatenateReply(const std::string& result, const sdbus::Error* error) override
|
||||
{
|
||||
assert(error == nullptr);
|
||||
|
||||
std::stringstream str(result);
|
||||
std::string aString;
|
||||
str >> aString;
|
||||
assert(aString == "sdbus-c++-stress-tests");
|
||||
|
||||
uint32_t aNumber;
|
||||
str >> aNumber;
|
||||
assert(aNumber >= 0);
|
||||
|
||||
++repliesReceived_;
|
||||
}
|
||||
|
||||
virtual void onConcatenatedSignal(const std::string& concatenatedString) override
|
||||
{
|
||||
std::stringstream str(concatenatedString);
|
||||
std::string aString;
|
||||
str >> aString;
|
||||
assert(aString == "sdbus-c++-stress-tests");
|
||||
|
||||
uint32_t aNumber;
|
||||
str >> aNumber;
|
||||
assert(aNumber >= 0);
|
||||
|
||||
++signalsReceived_;
|
||||
}
|
||||
|
||||
public:
|
||||
std::atomic<uint32_t> repliesReceived_;
|
||||
std::atomic<uint32_t> signalsReceived_;
|
||||
};
|
||||
|
||||
//-----------------------------------------
|
||||
int main(int /*argc*/, char */*argv*/[])
|
||||
{
|
||||
auto service2Connection = sdbus::createSystemBusConnection(SERVICE_2_BUS_NAME);
|
||||
std::thread service2Thread([&con = *service2Connection]()
|
||||
{
|
||||
CelsiusThermometerAdaptor thermometer(con, CELSIUS_THERMOMETER_OBJECT_PATH);
|
||||
con.enterProcessingLoop();
|
||||
});
|
||||
|
||||
auto service1Connection = sdbus::createSystemBusConnection(SERVICE_1_BUS_NAME);
|
||||
std::thread service1Thread([&con = *service1Connection]()
|
||||
{
|
||||
ConcatenatorAdaptor concatenator(con, CONCATENATOR_OBJECT_PATH);
|
||||
FahrenheitThermometerAdaptor thermometer(con, FAHRENHEIT_THERMOMETER_OBJECT_PATH);
|
||||
con.enterProcessingLoop();
|
||||
});
|
||||
|
||||
std::this_thread::sleep_for(100ms);
|
||||
|
||||
std::atomic<uint32_t> concatenationCallsMade{0};
|
||||
std::atomic<uint32_t> concatenationRepliesReceived{0};
|
||||
std::atomic<uint32_t> concatenationSignalsReceived{0};
|
||||
std::atomic<uint32_t> thermometerCallsMade{0};
|
||||
|
||||
auto clientConnection = sdbus::createSystemBusConnection();
|
||||
std::thread clientThread([&, &con = *clientConnection]()
|
||||
{
|
||||
std::atomic<bool> stopClients{false};
|
||||
|
||||
std::thread concatenatorThread([&]()
|
||||
{
|
||||
ConcatenatorProxy concatenator(con, SERVICE_1_BUS_NAME, CONCATENATOR_OBJECT_PATH);
|
||||
|
||||
uint32_t localCounter{};
|
||||
|
||||
// Issue async concatenate calls densely one after another
|
||||
while (!stopClients)
|
||||
{
|
||||
std::map<std::string, sdbus::Variant> param;
|
||||
param["key1"] = "sdbus-c++-stress-tests";
|
||||
param["key2"] = localCounter++;
|
||||
|
||||
concatenator.concatenate(param);
|
||||
|
||||
if ((localCounter % 10) == 0)
|
||||
{
|
||||
// Make sure the system is catching up with our async requests,
|
||||
// otherwise sleep a bit to slow down flooding the server.
|
||||
assert(localCounter >= concatenator.repliesReceived_);
|
||||
while ((localCounter - concatenator.repliesReceived_) > 20 && !stopClients)
|
||||
std::this_thread::sleep_for(2ms);
|
||||
|
||||
// Update statistics
|
||||
concatenationCallsMade = localCounter;
|
||||
concatenationRepliesReceived = (uint32_t)concatenator.repliesReceived_;
|
||||
concatenationSignalsReceived = (uint32_t)concatenator.signalsReceived_;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
std::thread thermometerThread([&]()
|
||||
{
|
||||
FahrenheitThermometerProxy thermometer(con, SERVICE_1_BUS_NAME, FAHRENHEIT_THERMOMETER_OBJECT_PATH);
|
||||
uint32_t localCounter{};
|
||||
uint32_t previousTemperature{};
|
||||
|
||||
while (!stopClients)
|
||||
{
|
||||
localCounter++;
|
||||
auto temperature = thermometer.getCurrentTemperature();
|
||||
assert(temperature >= previousTemperature); // The temperature shall rise continually
|
||||
previousTemperature = temperature;
|
||||
std::this_thread::sleep_for(5ms);
|
||||
|
||||
if ((localCounter % 10) == 0)
|
||||
thermometerCallsMade = localCounter;
|
||||
}
|
||||
});
|
||||
|
||||
con.enterProcessingLoop();
|
||||
|
||||
stopClients = true;
|
||||
concatenatorThread.join();
|
||||
thermometerThread.join();
|
||||
});
|
||||
|
||||
std::atomic<bool> exitLogger{};
|
||||
std::thread loggerThread([&]()
|
||||
{
|
||||
while (!exitLogger)
|
||||
{
|
||||
std::this_thread::sleep_for(1s);
|
||||
|
||||
std::cout << "Made " << concatenationCallsMade << " concatenation calls, received " << concatenationRepliesReceived << " replies and " << concatenationSignalsReceived << " signals so far." << std::endl;
|
||||
std::cout << "Made " << thermometerCallsMade << " thermometer calls so far." << std::endl << std::endl;
|
||||
}
|
||||
});
|
||||
|
||||
getchar();
|
||||
|
||||
exitLogger = true;
|
||||
loggerThread.join();
|
||||
clientConnection->leaveProcessingLoop();
|
||||
clientThread.join();
|
||||
service1Connection->leaveProcessingLoop();
|
||||
service1Thread.join();
|
||||
service2Connection->leaveProcessingLoop();
|
||||
service2Thread.join();
|
||||
|
||||
return 0;
|
||||
}
|
@ -141,7 +141,7 @@ TEST_P(AConnectionNameRequest, ThrowsOnFail)
|
||||
{
|
||||
EXPECT_CALL(*mock_, sd_bus_request_name(_, _, _)).WillOnce(Return(-1));
|
||||
|
||||
auto conn_ = sdbus::internal::Connection(GetParam(), std::move(mock_)) ;
|
||||
sdbus::internal::Connection conn_(GetParam(), std::move(mock_));
|
||||
ASSERT_THROW(conn_.requestName(""), sdbus::Error);
|
||||
}
|
||||
|
||||
|
@ -34,21 +34,30 @@
|
||||
class SdBusMock : public sdbus::internal::ISdBus
|
||||
{
|
||||
public:
|
||||
MOCK_METHOD1(sd_bus_message_ref, sd_bus_message*(sd_bus_message *m));
|
||||
MOCK_METHOD1(sd_bus_message_unref, sd_bus_message*(sd_bus_message *m));
|
||||
|
||||
MOCK_METHOD3(sd_bus_send, int(sd_bus *bus, sd_bus_message *m, uint64_t *cookie));
|
||||
MOCK_METHOD5(sd_bus_call, int(sd_bus *bus, sd_bus_message *m, uint64_t usec, sd_bus_error *ret_error, sd_bus_message **reply));
|
||||
MOCK_METHOD6(sd_bus_call_async, int(sd_bus *bus, sd_bus_slot **slot, sd_bus_message *m, sd_bus_message_handler_t callback, void *userdata, uint64_t usec));
|
||||
|
||||
MOCK_METHOD6(sd_bus_message_new_method_call, int(sd_bus *bus, sd_bus_message **m, const char *destination, const char *path, const char *interface, const char *member));
|
||||
MOCK_METHOD5(sd_bus_message_new_signal, int(sd_bus *bus, sd_bus_message **m, const char *path, const char *interface, const char *member));
|
||||
MOCK_METHOD2(sd_bus_message_new_method_return, int(sd_bus_message *call, sd_bus_message **m));
|
||||
MOCK_METHOD3(sd_bus_message_new_method_error, int(sd_bus_message *call, sd_bus_message **m, const sd_bus_error *e));
|
||||
|
||||
MOCK_METHOD1(sd_bus_open_user, int(sd_bus **ret));
|
||||
MOCK_METHOD1(sd_bus_open_system, int(sd_bus **ret));
|
||||
MOCK_METHOD3(sd_bus_request_name, int(sd_bus *bus, const char *name, uint64_t flags));
|
||||
MOCK_METHOD2(sd_bus_release_name, int(sd_bus *bus, const char *name));
|
||||
MOCK_METHOD6(sd_bus_add_object_vtable, int(sd_bus *bus, sd_bus_slot **slot, const char *path, const char *interface, const sd_bus_vtable *vtable, void *userdata));
|
||||
MOCK_METHOD1(sd_bus_slot_unref, sd_bus_slot*(sd_bus_slot *slot));
|
||||
MOCK_METHOD6(sd_bus_message_new_method_call, int(sd_bus *bus, sd_bus_message **m, const char *destination, const char *path, const char *interface, const char *member));
|
||||
MOCK_METHOD1(sd_bus_message_unref, sd_bus_message* (sd_bus_message *m));
|
||||
MOCK_METHOD5(sd_bus_message_new_signal, int(sd_bus *bus, sd_bus_message **m, const char *path, const char *interface, const char *member));
|
||||
MOCK_METHOD5(sd_bus_add_match, int(sd_bus *bus, sd_bus_slot **slot, const char *match, sd_bus_message_handler_t callback, void *userdata));
|
||||
MOCK_METHOD1(sd_bus_open_user, int(sd_bus **ret));
|
||||
MOCK_METHOD1(sd_bus_open_system, int(sd_bus **ret));
|
||||
MOCK_METHOD1(sd_bus_flush, int(sd_bus *bus));
|
||||
MOCK_METHOD1(sd_bus_slot_unref, sd_bus_slot*(sd_bus_slot *slot));
|
||||
|
||||
MOCK_METHOD2(sd_bus_process, int(sd_bus *bus, sd_bus_message **r));
|
||||
MOCK_METHOD1(sd_bus_get_fd, int(sd_bus *bus));
|
||||
MOCK_METHOD1(sd_bus_get_events, int(sd_bus *bus));
|
||||
MOCK_METHOD2(sd_bus_get_timeout, int(sd_bus *bus, uint64_t *timeout_usec));
|
||||
MOCK_METHOD2(sd_bus_get_poll_data, int(sd_bus *bus, PollData* data));
|
||||
|
||||
MOCK_METHOD1(sd_bus_flush, int(sd_bus *bus));
|
||||
MOCK_METHOD1(sd_bus_flush_close_unref, sd_bus *(sd_bus *bus));
|
||||
};
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
|
||||
*
|
||||
* @file libsdbus-c++_unittests.cpp
|
||||
* @file sdbus-c++-unit-tests.cpp
|
||||
*
|
||||
* Created on: Nov 27, 2016
|
||||
* Project: sdbus-c++
|
Reference in New Issue
Block a user