From 1aecb29d561170dbda6079154d2a8e37158be470 Mon Sep 17 00:00:00 2001 From: Stanislav Angelovic Date: Sat, 21 Jan 2023 01:47:24 +0100 Subject: [PATCH 01/52] refactor: simplify async D-Bus connection handling --- .github/workflows/ci.yml | 7 +- CMakeLists.txt | 6 +- ChangeLog | 15 + docs/using-sdbus-c++.md | 84 +++-- include/sdbus-c++/IConnection.h | 173 +++++----- include/sdbus-c++/IProxy.h | 29 +- include/sdbus-c++/Message.h | 4 +- src/Connection.cpp | 321 ++++++++++-------- src/Connection.h | 34 +- src/IConnection.h | 8 +- src/ISdBus.h | 2 +- src/Message.cpp | 11 - src/MessageUtils.h | 6 - src/Proxy.cpp | 70 +--- src/Proxy.h | 15 - src/SdBus.cpp | 11 + src/SdBus.h | 2 +- src/Utils.h | 11 + tests/CMakeLists.txt | 1 + .../integrationtests/DBusConnectionTests.cpp | 44 +-- tests/integrationtests/TestProxy.cpp | 2 +- tests/integrationtests/TestProxy.h | 2 +- tests/unittests/PollData_test.cpp | 125 +++++++ tests/unittests/mocks/SdBusMock.h | 2 +- 24 files changed, 544 insertions(+), 441 deletions(-) create mode 100644 tests/unittests/PollData_test.cpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6e73c1f7..3add702b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,9 +16,12 @@ jobs: compiler: [g++, clang] build: [shared-libsystemd] include: - - os: ubuntu-20.04 + - os: ubuntu-22.04 compiler: g++ build: embedded-static-libsystemd + - os: ubuntu-22.04 + compiler: clang + build: embedded-static-libsystemd steps: - uses: actions/checkout@v3 - name: install-libsystemd-toolchain @@ -60,7 +63,7 @@ jobs: run: | mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_VERBOSE_MAKEFILE=ON -DBUILD_TESTS=ON -DINSTALL_TESTS=ON -DENABLE_PERF_TESTS=ON -DENABLE_STRESS_TESTS=ON -DBUILD_CODE_GEN=ON -DBUILD_LIBSYSTEMD=ON -DLIBSYSTEMD_VERSION=244 .. + cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_VERBOSE_MAKEFILE=ON -DBUILD_TESTS=ON -DINSTALL_TESTS=ON -DENABLE_PERF_TESTS=ON -DENABLE_STRESS_TESTS=ON -DBUILD_CODE_GEN=ON -DBUILD_LIBSYSTEMD=ON -DLIBSYSTEMD_VERSION=252 .. - name: make run: | cd build diff --git a/CMakeLists.txt b/CMakeLists.txt index b4cbf150..9b735d19 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,10 +19,10 @@ option(BUILD_LIBSYSTEMD "Build libsystemd static library and incorporate it into if(NOT BUILD_LIBSYSTEMD) find_package(PkgConfig REQUIRED) - pkg_check_modules(Systemd IMPORTED_TARGET GLOBAL libsystemd>=236) + pkg_check_modules(Systemd IMPORTED_TARGET GLOBAL libsystemd>=238) if(NOT TARGET PkgConfig::Systemd) message(WARNING "libsystemd not found, checking for libelogind instead") - pkg_check_modules(Systemd IMPORTED_TARGET GLOBAL libelogind>=236) + pkg_check_modules(Systemd IMPORTED_TARGET GLOBAL libelogind>=238) if(TARGET PkgConfig::Systemd) set(LIBSYSTEMD_IMPL "elogind") set(LIBSYSTEMD_LIB "libelogind") @@ -38,7 +38,7 @@ if(NOT BUILD_LIBSYSTEMD) endif() endif() if(NOT TARGET PkgConfig::Systemd) - message(FATAL_ERROR "libsystemd of version at least 236 is required, but was not found " + message(FATAL_ERROR "libsystemd of version at least 238 is required, but was not found " "(if you have systemd in your OS, you may want to install package containing pkgconfig " " files for libsystemd library. On Ubuntu, that is libsystemd-dev. " " Alternatively, you may turn BUILD_LIBSYSTEMD on for sdbus-c++ to download, build " diff --git a/ChangeLog b/ChangeLog index b6c8de2a..a3b0685a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -254,3 +254,18 @@ v1.6.0 - Add support for std::variant as an alternative C++ type for D-Bus Variant - Add support for implicit conversions between std::variant and sdbus::Variant - Fix missing includes + +v2.0.0 +- Breaking changes in API/ABI/behavior: + - In *synchronous* D-Bus calls, the proxy now **always** blocks the connection for concurrent use until the call finishes (with either a received reply, + an error, or time out). If this creates a connection contention issue in your multi-threaded design, use short-lived, light-weight proxies, or call + the method in an asynchronous way. + - The `PollData` struct has been extended with a new data member: `eventFd`. All hooks with external event loops shall be modified to poll on this fd as well. + - `PollData::timeout_usec` was renamed to `PollData::timeout` and its type has been changed to `std::chrono::microseconds`. This member now holds directly + what before had to be obtained through `PollData::getAbsoluteTimeout()` call. + - `PollData::getRelativeTimeout()` return type was changed to `std::chrono::microseconds`. + - `IConnection::processPendingRequest()` was renamed to `IConnection::processPendingEvent()`. +- Systemd of at least v238 is required to compile sdbus-c++ +- A proper fix for timeout handling +- Fix for external event loops in which the event loop thread ID was not correctly initialized (now fixed and simplified by not needing the thread ID anymore) +- Other simplifications, improvements and fixes springing out from the above refactoring diff --git a/docs/using-sdbus-c++.md b/docs/using-sdbus-c++.md index 6fb510da..7fca6990 100644 --- a/docs/using-sdbus-c++.md +++ b/docs/using-sdbus-c++.md @@ -22,7 +22,8 @@ Using sdbus-c++ library 17. [Representing D-Bus Types in sdbus-c++](#representing-d-bus-types-in-sdbus-c) 18. [Support for match rules](#support-for-match-rules) 19. [Using direct (peer-to-peer) D-Bus connections](#using-direct-peer-to-peer-d-bus-connections) -20. [Conclusion](#conclusion) +20. [Using sdbus-c++ in external event loops](#using-sdbus-c-in-external-event-loops) +21. [Conclusion](#conclusion) Introduction ------------ @@ -386,7 +387,7 @@ int main(int argc, char *argv[]) } ``` -In simple cases, we don't need to create D-Bus connection explicitly for our proxies. Unless a connection is provided to a proxy object 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 proxy ready for use. +In simple cases, we don't need to create D-Bus connection explicitly for our proxies. Unless a connection is provided to a proxy object explicitly via factory parameter, the proxy will create a connection of his own (unless it is a light-weight, short-lived proxy created with `dont_run_event_loop_thread_t`), 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 proxy ready for use. 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. @@ -423,41 +424,51 @@ How shall we use connections in relation to D-Bus objects and object proxies? A D-Bus connection is represented by a `IConnection` 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 one but also multiple D-Bus connections (we just have to make certain that the connections with assigned bus names don't share a common name; the name must be unique). -A typical use case for most services is **one** D-Bus connection in the application. The application runs event loop on that connection. When creating objects or proxies, the application provides reference of that connection to those objects and proxies. This means all these objects and proxies share the same connection. This is nicely scalable, because with whatever number of objects or proxies, there is only one connection and one event loop thread. Yet, services that provide objects at various bus names have to create and maintain multiple D-Bus connections, each with the unique bus name. +A typical use case for most services is **one** D-Bus connection in the application. The application runs an (internal or external, see below) event loop on that connection. When creating objects or proxies, the application provides reference of that connection to those objects and proxies. This means all these objects and proxies share the same connection. This is nicely scalable, because with whatever number of objects or proxies, there is only one connection and one event loop thread. Yet, services that provide objects at various bus names have to create and maintain multiple D-Bus connections, each with the unique bus name. -The connection is thread-safe and objects and proxies can invoke operations on it from multiple threads simultaneously, but the operations are serialized. This means, for example, that if an object's callback for an incoming remote method call is going to be invoked in an event loop thread, 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 connection is thread-safe and objects and proxies can invoke operations on it from multiple threads simultaneously, but the operations are serialized. Access to the connection is mutually exclusive. This means, for example, that if an object's callback for an incoming remote method call is going to be invoked in an event loop thread, 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. -We should bear that in mind when designing more complex, multi-threaded services with high parallelism. If we have undesired contention on a connection, creating a specific, dedicated connection for a hot spot helps to increase concurrency. sdbus-c++ provides us freedom to create as many connections as we want and assign objects and proxies to those connections at our will. We, as application developers, choose whatever approach is more suitable to us at quite a fine granularity. +When a `poll()` sleeps upon the connection, the connection can be used by other threads without blocking. When calling a D-Bus method through a proxy synchronously, the proxy blocks the connection from concurrent use until it gets from the peer a reply (or an error, the call times out). Async D-Bus method calls don't block the connection while the call is pending (the connection is only "locked" while the call message is sent out and while the reply handler is executed for an already arrived reply message, but not in between while the call is pending). See doxygen documentation for `IProxy::callMethod()` overloads for more details. + +We should bear these design aspects of sdbus-c++ in mind when designing more complex, multi-threaded services with high parallelism. If we have undesired contention on a connection, creating a separate, dedicated connection for a hot spot helps to increase concurrency. sdbus-c++ provides us freedom to create as many connections as we want and assign objects and proxies to those connections at our will. We, as application developers, choose whatever approach is more suitable to us at quite a fine granularity. So, more technically, how can we use connections from the server and the client perspective? #### Using D-Bus connections on the server side -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: +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: - * either in a blocking way, through `enterEventLoop()`, - * or in a non-blocking async way, through `enterEventLoopAsync()`, - * or, when we have our own implementation of an event loop (e.g. we are using sd-event event loop), we can ask the connection for its underlying fd, I/O events and timeouts through `getEventLoopPollData()` and use that data in our event loop mechanism. + * its internal event loop + * either in a blocking way, through `enterEventLoop()`, + * or in a non-blocking async way, through `enterEventLoopAsync()`, + * or an external event loop. This is suitable if we use in our application an event loop implementation of our choice (e.g., GLib Event Loop, boost::asio, ...) and we want to hook up our sdbus-c++ connections with it. See [Using sdbus-c++ in external event loops](#using-sdbus-c-in-external-event-loops) section for more information. -The object takes the D-Bus connection as a reference in its constructor. This is the only way to wire connection and object together. We must make sure the connection exists as long as objects using it exist. +The object takes the D-Bus connection as a reference in its constructor. This is the only way to wire the connection and the object together. We must make sure the connection exists as long as objects using it exist. Of course, 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. +*Note:* There may be both objects and proxies hooked to a single connection, of course. A D-Bus server application may also be a client to another D-Bus server application, and share one D-Bus connection for the D-Bus interface it exports as well as for the proxies towards other D-Bus interfaces. + #### Using D-Bus connections on the client side On the **client** side we likewise need a connection -- just that unlike on the server side, we don't need to request a unique bus name on it. We have more options here when creating a proxy: - * Pass an already existing connection as a reference. This is the typical approach when the application already maintains a D-Bus connection (maybe it provide D-Bus API on it, and/or it already has some proxies hooked on it). The proxy will share the connection with others. With this approach we must of course ensure that the connection exists as long as the proxy exists. + * Pass an already existing connection as a reference. This is the typical approach when the application already maintains a D-Bus connection (maybe it provide D-Bus API on it, and/or it already has some proxies hooked on it). The proxy will share the connection with others. With this approach we must of course ensure that the connection exists as long as the proxy exists. For discussion on options for running an event loop on that connection, see above section [Using D-Bus connections on the server side](#using-d-bus-connections-on-the-server-side). + + * Or -- and this is a simpler approach for simple D-Bus client applications -- we have another option: we let the proxy maintain its own connection (and potentially an associated event loop thread, see below). We have two options here: - * Or -- and this is typical when we have a simple D-Bus client application -- we have another option: we let the proxy maintain its own connection (and potentially an associated event loop thread, see below): + * We either create the connection ourselves and `std::move` it to the proxy object factory. The proxy becomes an owner of this connection, and it will be his dedicated connection. This has the advantage that we may choose the type of connection (system, session, remote). Additionally, - * We either create the connection ourselves and `std::move` it to the proxy object factory. The proxy becomes an owner of this connection, and will run the event loop on that connection. This had the advantage that we may choose the type of connection (system, session, remote). + * when created **without** `dont_run_event_loop_thread_t` tag, the proxy **will start** a dedicated event loop thread on that connection; + * or, when created **with** `dont_run_event_loop_thread_t` tag, the proxy will start **no** event loop thread on that connection. - * Or we don't bother about any connection at all when creating a proxy (the factory overload with no connection parameter). Under the hood, the proxy creates its own *system bus* connection, creates a separate thread and runs an event loop in it. Quite **simple**, but as you can see, this hurts scalability in case of many proxies, as each would spawn and maintain its own event loop thread (see discussion higher above). But we don't necessarily need an event loop thread, in case our proxy doesn't need to listen to signals or async method call replies. Read on. + * Or we don't care about connnections at all (proxy factory overloads with no connection parameter). Under the hood, the proxy creates its own *system bus* connection. Additionally: + * when created **without** `dont_run_event_loop_thread_t` tag, the proxy **will start** a dedicated event loop thread on that connection; + * or, when created **with** `dont_run_event_loop_thread_t` tag, the proxy will start **no** event loop thread on that connection. - It's also possible in this case to instruct the proxy to **not spawn an event loop thread** for its connection. There are many situations that we want to quickly create a proxy, carry out one or a few (synchronous) D-Bus calls, and let go of proxy. We call them light-weight proxies. For that purpose, spawning a new event loop thread comes with time and resource penalty, for nothing. To create such **a light-weight proxy**, use the factory/constructor overload with `dont_run_event_loop_thread_t`. All in above two bullet sub-points holds; the proxy just won't spawn a thread with an event loop in it. Note that such a proxy can be used only for synchronous D-Bus calls; it may not receive signals or async call replies. + A proxy needs an event loop if it's a "**long-lived**" proxy that listens on incoming messages like signals, async call replies, atc. Sharing one connection with its one event loop is more scalable. Starting a dedicated event loop in a proxy is simpler from API perspective, but comes at a performance and resource cost for each proxy creation/destruction, and it hurts scalability. A simple and scalable option are "**short-lived, light-weight**" proxies. Quite a typical use case is that we occasionally need to carry out one or a few D-Bus calls and that's it. We may create a proxy, do the calls, and let go of proxy. Such a light-weight proxy is created when `dont_run_event_loop_thread_t` tag is passed to the proxy factory. Such a proxy **does not spawn** an event loop thread. It only support synchronous D-Bus calls (no signals, no async calls...), and is meant to be created, used right away, and then destroyed immediately. -#### Stopping I/O event loops graciously +#### Stopping internal I/O event loops graciously A connection with an asynchronous event loop (i.e. one initiated through `enterEventLoopAsync()`) will stop and join its event loop thread automatically in its destructor. An event loop that blocks in the synchronous `enterEventLoop()` call can be unblocked through `leaveEventLoop()` call on the respective bus connection issued from a different thread or from an OS signal handler. @@ -1502,23 +1513,23 @@ sdbus-c++ provides many default, pre-defined C++ type representations for D-Bus | Category | Code | Code ASCII | Conventional Name | C++ Type | |---------------------|-------------|------------|--------------------|---------------------------------| | reserved | 0 | NUL | INVALID | - | -| fixed, basic | 121 | y | BYTE | `uint8_t` | -| fixed, basic | 98 | b | BOOLEAN | `bool` | -| fixed, basic | 110 | n | INT16 | `int16_t` | -| fixed, basic | 113 | q | UINT16 | `uint16_t` | -| fixed, basic | 105 | i | INT32 | `int32_t` | -| fixed, basic | 117 | u | UINT32 | `uint32_t` | -| fixed, basic | 120 | x | INT64 | `int64_t` | -| fixed, basic | 116 | t | UINT64 | `uint64_t` | -| fixed, basic | 100 | d | DOUBLE | `double` | -| string-like, basic | 115 | s | STRING | `const char*`, `std::string` | -| string-like, basic | 111 | o | OBJECT_PATH | `sdbus::ObjectPath` | -| string-like, basic | 103 | g | SIGNATURE | `sdbus::Signature` | +| fixed, basic | 121 | y | BYTE | `uint8_t` | +| fixed, basic | 98 | b | BOOLEAN | `bool` | +| fixed, basic | 110 | n | INT16 | `int16_t` | +| fixed, basic | 113 | q | UINT16 | `uint16_t` | +| fixed, basic | 105 | i | INT32 | `int32_t` | +| fixed, basic | 117 | u | UINT32 | `uint32_t` | +| fixed, basic | 120 | x | INT64 | `int64_t` | +| fixed, basic | 116 | t | UINT64 | `uint64_t` | +| fixed, basic | 100 | d | DOUBLE | `double` | +| string-like, basic | 115 | s | STRING | `const char*`, `std::string` | +| string-like, basic | 111 | o | OBJECT_PATH | `sdbus::ObjectPath` | +| string-like, basic | 103 | g | SIGNATURE | `sdbus::Signature` | | container | 97 | a | ARRAY | `std::vector`, `std::array`, `std::span` - if used as an array followed by a single complete type `T`
`std::map`, `std::unordered_map` - if used as an array of dict entries | | container | 114,40,41 | r() | STRUCT | `sdbus::Struct` variadic class template | | container | 118 | v | VARIANT | `sdbus::Variant`, `std::variant` | | container | 101,123,125 | e{} | DICT_ENTRY | - | -| fixed, basic | 104 | h | UNIX_FD | `sdbus::UnixFd` | +| fixed, basic | 104 | h | UNIX_FD | `sdbus::UnixFd` | | reserved | 109 | m | (reserved) | - | | reserved | 42 | * | (reserved) | - | | reserved | 63 | ? | (reserved) | - | @@ -1699,6 +1710,21 @@ int main(int argc, char *argv[]) > **_Note_:** The example above explicitly stops the event loops on both sides, before the connection objects are destroyed. This avoids potential `Connection reset by peer` errors caused when one side closes its socket while the other side is still working on the counterpart socket. This is a recommended workflow for closing direct D-Bus connections. +Using sdbus-c++ in external event loops +--------------------------------------- + +sdbus-c++ connections can be hooked up with an external (like GMainLoop, boost::asio, etc.) or manual event loop involving `poll()` or a similar I/O polling call. The following describes how to integrate it correctly: + +Before **each** invocation of the I/O polling call, `IConnection::getEventLoopPollData()` function should be invoked. Returned `PollData::fd` file descriptor should be polled for the events indicated by `PollData::events`, and the I/O call should block up to the returned `PollData::timeout`. Additionally, returned `PollData::eventFd` should be polled for POLLIN events. + +After each I/O polling call (for both `PollData::fd` and `PollData::eventFd` events), the `IConnection::processPendingEvent()` method should be invoked. This enables the bus connection to process any incoming or outgoing D-Bus messages. + +Note that the returned timeout should be considered only a maximum sleeping time. It is permissible (and even expected) that shorter timeouts are used by the calling program, in case other event sources are polled in the same event loop. Note that the returned time-value is absolute, based of `CLOCK_MONOTONIC` and specified in microseconds. Use `PollData::getPollTimeout()` to have the timeout value converted into a form that can be passed to `poll()`. + +`PollData::fd` is a bus I/O fd. `PollData::eventFd` is an sdbus-c++ internal fd for communicating important changes from other threads to the event loop thread, so the event loop retrieves new poll data (with updated timeout, for example) and, potentially, processes pending D-Bus messages (like signals that came in during a blocking synchronous call from other thread, or queued outgoing messages that are very big to be able to have been sent in one shot from another thread), before the next poll. + +Consult `IConnection::PollData` and `IConnection::getEventLoopPollData()` documentation for more potentially more information. + Conclusion ---------- diff --git a/include/sdbus-c++/IConnection.h b/include/sdbus-c++/IConnection.h index 1719ea04..05c5af9f 100644 --- a/include/sdbus-c++/IConnection.h +++ b/include/sdbus-c++/IConnection.h @@ -51,62 +51,7 @@ namespace sdbus { class IConnection { public: - /*! - * Poll Data for external event loop implementations. - * - * To integrate sdbus with your app's own custom event handling system - * you can use this method to query which file descriptors, poll events - * and timeouts you should add to your app's poll(2), or select(2) - * call in your main event loop. - * - * If you are unsure what this all means then use - * enterEventLoop() or enterEventLoopAsync() instead. - * - * See: getEventLoopPollData() - */ - struct PollData - { - /*! - * The read fd to be monitored by the event loop. - */ - int fd; - /*! - * The events to use for poll(2) alongside fd. - */ - short int events; - - /*! - * Absolute timeout value in micro seconds and based of CLOCK_MONOTONIC. - */ - uint64_t timeout_usec; - - /*! - * Get the event poll timeout. - * - * The timeout is an absolute value based of CLOCK_MONOTONIC. - * - * @return a duration since the CLOCK_MONOTONIC epoch started. - */ - [[nodiscard]] std::chrono::microseconds getAbsoluteTimeout() const - { - return std::chrono::microseconds(timeout_usec); - } - - /*! - * Get the timeout as relative value from now - * - * @return std::nullopt if the timeout is indefinite. A duration otherwise. - */ - [[nodiscard]] std::optional getRelativeTimeout() const; - - /*! - * Get a converted, relative timeout which can be passed as argument 'timeout' to poll(2) - * - * @return -1 if the timeout is indefinite. 0 if the poll(2) shouldn't block. An integer in milli - * seconds otherwise. - */ - [[nodiscard]] int getPollTimeout() const; - }; + struct PollData; virtual ~IConnection() = default; @@ -129,7 +74,7 @@ namespace sdbus { virtual void releaseName(const std::string& name) = 0; /*! - * @brief Retrieve the unique name of a connection. E.g. ":1.xx" + * @brief Retrieves the unique name of a connection. E.g. ":1.xx" * * @throws sdbus::Error in case of failure */ @@ -180,44 +125,57 @@ namespace sdbus { [[deprecated("Use one of other addObjectManager overloads")]] virtual void addObjectManager(const std::string& objectPath) = 0; /*! - * @brief Returns fd, I/O events and timeout data you can pass to poll + * @brief Returns fd's, I/O events and timeout data to be used in an external event loop + * + * This function is useful to hook up a bus connection object with an + * external (like GMainLoop, boost::asio, etc.) or manual event loop + * involving poll() or a similar I/O polling call. + * + * Before **each** invocation of the I/O polling call, this function + * should be invoked. Returned PollData::fd file descriptor should + * be polled for the events indicated by PollData::events, and the I/O + * call should block for that up to the returned PollData::timeout. + * + * Additionally, returned PollData::eventFd should be polled for POLLIN + * events. * - * To integrate sdbus with your app's own custom event handling system - * (without the requirement of an extra thread), you can use this - * method to query which file descriptors, poll events and timeouts you - * should add to your app's poll call in your main event loop. If these - * file descriptors signal, then you should call processPendingRequest - * to process the event. This means that all of sdbus's callbacks will - * arrive on your app's main event thread (opposed to on a thread created - * by sdbus-c++). If you are unsure what this all means then use - * enterEventLoop() or enterEventLoopAsync() instead. + * After each I/O polling call the bus connection needs to process + * incoming or outgoing data, by invoking processPendingEvent(). * - * To integrate sdbus-c++ into a gtk app, pass the file descriptor returned - * by this method to g_main_context_add_poll. + * Note that the returned timeout should be considered only a maximum + * sleeping time. It is permissible (and even expected) that shorter + * timeouts are used by the calling program, in case other event sources + * are polled in the same event loop. Note that the returned time-value + * is absolute, based of CLOCK_MONOTONIC and specified in microseconds. + * Use PollData::getPollTimeout() to have the timeout value converted + * in a form that can be passed to poll(2). * * @throws sdbus::Error in case of failure */ - virtual PollData getEventLoopPollData() const = 0; + [[nodiscard]] virtual PollData getEventLoopPollData() const = 0; /*! - * @brief Process a pending request + * @brief Processes a pending event * - * @returns true if an event was processed, false if poll should be called + * @returns True if an event was processed, false if no operations were pending * - * Processes a single dbus event. All of sdbus-c++'s callbacks will be called - * from within this method. This method should ONLY be used in conjuction - * with getEventLoopPollData(). - * This method returns true if an I/O message was processed. This you can try - * to call this method again before going to poll on I/O events. The method - * returns false if no operations were pending, and the caller should then - * poll for I/O events before calling this method again. - * enterEventLoop() and enterEventLoopAsync() will call this method for you, - * so there is no need to call it when using these. If you are unsure what - * this all means then don't use this method. + * This function drives the D-Bus connection. It processes pending I/O events. + * Queued outgoing messages (or parts thereof) are sent out. Queued incoming + * messages are dispatched to registered callbacks. Timeouts are recalculated. + * + * It returns false when no operations were pending and true if a message was + * processed. When false is returned the caller should synchronously poll for + * I/O events before calling into processPendingEvent() again. + * Don't forget to call getEventLoopPollData() each time before the next poll. + * + * You don't need to directly call this method or getEventLoopPollData() method + * when using convenient, internal bus connection event loops through + * enterEventLoop() or enterEventLoopAsync() calls. + * It is invoked automatically when necessary. * * @throws sdbus::Error in case of failure */ - virtual bool processPendingRequest() = 0; + virtual bool processPendingEvent() = 0; /*! * @brief Sets general method call timeout @@ -373,6 +331,55 @@ namespace sdbus { * @deprecated This function has been replaced by getEventLoopPollData() */ [[deprecated("This function has been replaced by getEventLoopPollData()")]] PollData getProcessLoopPollData() const; + + /*! + * @struct PollData + * + * Carries poll data needed for integration with external event loop implementations. + * + * See getEventLoopPollData() for more info. + */ + struct PollData + { + /*! + * The read fd to be monitored by the event loop. + */ + int fd; + + /*! + * The events to use for poll(2) alongside fd. + */ + short int events; + + /*! + * Absolute timeout value in microseconds, based of CLOCK_MONOTONIC. + * + * Call getPollTimeout() to get timeout recalculated to relative timeout that can be passed to poll(2). + */ + std::chrono::microseconds timeout; + + /*! + * An additional event fd to be monitored by the event loop for POLLIN events. + */ + int eventFd; + + /*! + * Returns the timeout as relative value from now. + * + * Returned value is std::chrono::microseconds::max() if the timeout is indefinite. + * + * @return Relative timeout as a time duration + */ + [[nodiscard]] std::chrono::microseconds getRelativeTimeout() const; + + /*! + * Returns relative timeout in the form which can be passed as argument 'timeout' to poll(2) + * + * @return -1 if the timeout is indefinite. 0 if the poll(2) shouldn't block. + * An integer in milliseconds otherwise. + */ + [[nodiscard]] int getPollTimeout() const; + }; }; template diff --git a/include/sdbus-c++/IProxy.h b/include/sdbus-c++/IProxy.h index 11762b74..4a89e0f8 100644 --- a/include/sdbus-c++/IProxy.h +++ b/include/sdbus-c++/IProxy.h @@ -83,20 +83,29 @@ namespace sdbus { virtual MethodCall createMethodCall(const std::string& interfaceName, const std::string& methodName) = 0; /*! - * @brief Calls method on the D-Bus object + * @brief Calls method on the remote D-Bus object * * @param[in] message Message representing a method call * @param[in] timeout Timeout for dbus call in microseconds * @return A method reply message * - * Normally, the call is blocking, i.e. it waits for the remote method to finish with either - * a return value or an error. + * The call does not block if the method call has dont-expect-reply flag set. In that case, + * the call returns immediately and the return value is an empty, invalid method reply. * - * If the method call argument is set to not expect reply, the call will not wait for the remote - * method to finish, i.e. the call will be non-blocking, and the function will return an empty, - * invalid MethodReply object (representing void). + * The call blocks otherwise, waiting for the remote peer to send back a reply, or an error, + * or until the call times out. * - * Note: To avoid messing with messages, use higher-level API defined below. + * While blocking, other concurrent operations (in other threads) on the underlying bus + * connection are stalled until the call returns. This is not an issue in vast majority of + * (simple, single-threaded) applications. In asynchronous, multi-threaded designs involving + * shared bus connections, this may be an issue. It is advised to instead use an asynchronous + * callMethod() function overload, which does not block the bus connection, or do the synchronous + * call from another Proxy instance created just before the call and then destroyed (which is + * anyway quite a typical approach in D-Bus implementations). Such proxy instance must have + * its own bus connection. Slim proxies created with `dont_run_event_loop_thread` tag are + * designed for exactly that purpose. + * + * Note: To avoid messing with messages, use API on a higher level of abstraction defined below. * * @throws sdbus::Error in case of failure */ @@ -117,10 +126,10 @@ namespace sdbus { * @return Cookie for the the pending asynchronous call * * 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 - * I/O event loop thread. + * the provided async reply handler will get invoked from the context of the bus + * connection I/O event loop thread. * - * Note: To avoid messing with messages, use higher-level API defined below. + * Note: To avoid messing with messages, use API on a higher level of abstraction defined below. * * @throws sdbus::Error in case of failure */ diff --git a/include/sdbus-c++/Message.h b/include/sdbus-c++/Message.h index b8247fb8..50fd528a 100644 --- a/include/sdbus-c++/Message.h +++ b/include/sdbus-c++/Message.h @@ -56,7 +56,6 @@ namespace sdbus { class MethodReply; namespace internal { class ISdBus; - class IConnection; } } @@ -258,12 +257,11 @@ namespace sdbus { bool doesntExpectReply() const; protected: - MethodCall(void *msg, internal::ISdBus* sdbus, const internal::IConnection* connection, adopt_message_t) noexcept; + MethodCall(void *msg, internal::ISdBus* sdbus, adopt_message_t) noexcept; private: MethodReply sendWithReply(uint64_t timeout = 0) const; MethodReply sendWithNoReply() const; - const internal::IConnection* connection_{}; }; class MethodReply : public Message diff --git a/src/Connection.cpp b/src/Connection.cpp index 8eeb158e..ea4d6647 100644 --- a/src/Connection.cpp +++ b/src/Connection.cpp @@ -35,53 +35,54 @@ #include #include #include +#include namespace sdbus::internal { Connection::Connection(std::unique_ptr&& interface, const BusFactory& busFactory) - : iface_(std::move(interface)) + : sdbus_(std::move(interface)) , bus_(openBus(busFactory)) { - assert(iface_ != nullptr); + assert(sdbus_ != nullptr); } Connection::Connection(std::unique_ptr&& interface, default_bus_t) - : Connection(std::move(interface), [this](sd_bus** bus){ return iface_->sd_bus_open(bus); }) + : Connection(std::move(interface), [this](sd_bus** bus){ return sdbus_->sd_bus_open(bus); }) { } Connection::Connection(std::unique_ptr&& interface, system_bus_t) - : Connection(std::move(interface), [this](sd_bus** bus){ return iface_->sd_bus_open_system(bus); }) + : Connection(std::move(interface), [this](sd_bus** bus){ return sdbus_->sd_bus_open_system(bus); }) { } Connection::Connection(std::unique_ptr&& interface, session_bus_t) - : Connection(std::move(interface), [this](sd_bus** bus){ return iface_->sd_bus_open_user(bus); }) + : Connection(std::move(interface), [this](sd_bus** bus){ return sdbus_->sd_bus_open_user(bus); }) { } Connection::Connection(std::unique_ptr&& interface, custom_session_bus_t, const std::string& address) - : Connection(std::move(interface), [&](sd_bus** bus) { return iface_->sd_bus_open_user_with_address(bus, address.c_str()); }) + : Connection(std::move(interface), [&](sd_bus** bus) { return sdbus_->sd_bus_open_user_with_address(bus, address.c_str()); }) { } Connection::Connection(std::unique_ptr&& interface, remote_system_bus_t, const std::string& host) - : Connection(std::move(interface), [this, &host](sd_bus** bus){ return iface_->sd_bus_open_system_remote(bus, host.c_str()); }) + : Connection(std::move(interface), [this, &host](sd_bus** bus){ return sdbus_->sd_bus_open_system_remote(bus, host.c_str()); }) { } Connection::Connection(std::unique_ptr&& interface, private_bus_t, const std::string& address) - : Connection(std::move(interface), [&](sd_bus** bus) { return iface_->sd_bus_open_direct(bus, address.c_str()); }) + : Connection(std::move(interface), [&](sd_bus** bus) { return sdbus_->sd_bus_open_direct(bus, address.c_str()); }) { } Connection::Connection(std::unique_ptr&& interface, private_bus_t, int fd) - : Connection(std::move(interface), [&](sd_bus** bus) { return iface_->sd_bus_open_direct(bus, fd); }) + : Connection(std::move(interface), [&](sd_bus** bus) { return sdbus_->sd_bus_open_direct(bus, fd); }) { } Connection::Connection(std::unique_ptr&& interface, server_bus_t, int fd) - : Connection(std::move(interface), [&](sd_bus** bus) { return iface_->sd_bus_open_server(bus, fd); }) + : Connection(std::move(interface), [&](sd_bus** bus) { return sdbus_->sd_bus_open_server(bus, fd); }) { } @@ -91,10 +92,10 @@ Connection::Connection(std::unique_ptr&& interface, sdbus_bus_t, sd_bus } Connection::Connection(std::unique_ptr&& interface, pseudo_bus_t) - : iface_(std::move(interface)) + : sdbus_(std::move(interface)) , bus_(openPseudoBus()) { - assert(iface_ != nullptr); + assert(sdbus_ != nullptr); } Connection::~Connection() @@ -106,46 +107,42 @@ void Connection::requestName(const std::string& name) { SDBUS_CHECK_SERVICE_NAME(name); - auto r = iface_->sd_bus_request_name(bus_.get(), name.c_str(), 0); + auto r = sdbus_->sd_bus_request_name(bus_.get(), name.c_str(), 0); SDBUS_THROW_ERROR_IF(r < 0, "Failed to request bus name", -r); // In some cases we need to explicitly notify the event loop - // to process messages that may have arrived while executing the call. - notifyEventLoop(eventFd_.fd); + // to process messages that may have arrived while executing the call + wakeUpEventLoopIfMessagesInQueue(); } void Connection::releaseName(const std::string& name) { - auto r = iface_->sd_bus_release_name(bus_.get(), name.c_str()); + auto r = sdbus_->sd_bus_release_name(bus_.get(), name.c_str()); SDBUS_THROW_ERROR_IF(r < 0, "Failed to release bus name", -r); // In some cases we need to explicitly notify the event loop - // to process messages that may have arrived while executing the call. - notifyEventLoop(eventFd_.fd); + // to process messages that may have arrived while executing the call + wakeUpEventLoopIfMessagesInQueue(); } std::string Connection::getUniqueName() const { const char* unique = nullptr; - auto r = iface_->sd_bus_get_unique_name(bus_.get(), &unique); + auto r = sdbus_->sd_bus_get_unique_name(bus_.get(), &unique); SDBUS_THROW_ERROR_IF(r < 0 || unique == nullptr, "Failed to get unique bus name", -r); return unique; } void Connection::enterEventLoop() { - loopThreadId_ = std::this_thread::get_id(); - SCOPE_EXIT{ loopThreadId_ = std::thread::id{}; }; - - std::lock_guard guard(loopMutex_); - while (true) { - auto processed = processPendingRequest(); - if (processed) - continue; // Process next one + // Process one pending event + (void)processPendingEvent(); - auto success = waitForNextRequest(); + // And go to poll(), which wakes us up right away + // if there's another pending event, or sleeps otherwise. + auto success = waitForNextEvent(); if (!success) break; // Exit I/O event loop } @@ -166,20 +163,24 @@ void Connection::leaveEventLoop() Connection::PollData Connection::getEventLoopPollData() const { ISdBus::PollData pollData{}; - auto r = iface_->sd_bus_get_poll_data(bus_.get(), &pollData); + auto r = sdbus_->sd_bus_get_poll_data(bus_.get(), &pollData); SDBUS_THROW_ERROR_IF(r < 0, "Failed to get bus poll data", -r); - return {pollData.fd, pollData.events, pollData.timeout_usec}; + assert(eventFd_.fd >= 0); + + auto timeout = pollData.timeout_usec == UINT64_MAX ? std::chrono::microseconds::max() : std::chrono::microseconds(pollData.timeout_usec); + + return {pollData.fd, pollData.events, timeout, eventFd_.fd}; } const ISdBus& Connection::getSdBusInterface() const { - return *iface_.get(); + return *sdbus_.get(); } ISdBus& Connection::getSdBusInterface() { - return *iface_.get(); + return *sdbus_.get(); } void Connection::addObjectManager(const std::string& objectPath) @@ -189,7 +190,7 @@ void Connection::addObjectManager(const std::string& objectPath) void Connection::addObjectManager(const std::string& objectPath, floating_slot_t) { - auto r = iface_->sd_bus_add_object_manager(bus_.get(), nullptr, objectPath.c_str()); + auto r = sdbus_->sd_bus_add_object_manager(bus_.get(), nullptr, objectPath.c_str()); SDBUS_THROW_ERROR_IF(r < 0, "Failed to add object manager", -r); } @@ -198,16 +199,16 @@ Slot Connection::addObjectManager(const std::string& objectPath, request_slot_t) { sd_bus_slot *slot{}; - auto r = iface_->sd_bus_add_object_manager(bus_.get(), &slot, objectPath.c_str()); + auto r = sdbus_->sd_bus_add_object_manager(bus_.get(), &slot, objectPath.c_str()); SDBUS_THROW_ERROR_IF(r < 0, "Failed to add object manager", -r); - return {slot, [this](void *slot){ iface_->sd_bus_slot_unref((sd_bus_slot*)slot); }}; + return {slot, [this](void *slot){ sdbus_->sd_bus_slot_unref((sd_bus_slot*)slot); }}; } void Connection::setMethodCallTimeout(uint64_t timeout) { - auto r = iface_->sd_bus_set_method_call_timeout(bus_.get(), timeout); + auto r = sdbus_->sd_bus_set_method_call_timeout(bus_.get(), timeout); SDBUS_THROW_ERROR_IF(r < 0, "Failed to set method call timeout", -r); } @@ -216,7 +217,7 @@ uint64_t Connection::getMethodCallTimeout() const { uint64_t timeout; - auto r = iface_->sd_bus_get_method_call_timeout(bus_.get(), &timeout); + auto r = sdbus_->sd_bus_get_method_call_timeout(bus_.get(), &timeout); SDBUS_THROW_ERROR_IF(r < 0, "Failed to get method call timeout", -r); @@ -229,13 +230,13 @@ Slot Connection::addMatch(const std::string& match, message_handler callback) auto matchInfo = std::make_unique(MatchInfo{std::move(callback), {}, *this, {}}); - auto r = iface_->sd_bus_add_match(bus_.get(), &matchInfo->slot, match.c_str(), &Connection::sdbus_match_callback, matchInfo.get()); + auto r = sdbus_->sd_bus_add_match(bus_.get(), &matchInfo->slot, match.c_str(), &Connection::sdbus_match_callback, matchInfo.get()); SDBUS_THROW_ERROR_IF(r < 0, "Failed to add match", -r); return {matchInfo.release(), [this](void *ptr) { auto* matchInfo = static_cast(ptr); - iface_->sd_bus_slot_unref(matchInfo->slot); + sdbus_->sd_bus_slot_unref(matchInfo->slot); std::default_delete{}(matchInfo); }}; } @@ -252,7 +253,7 @@ Slot Connection::addMatchAsync(const std::string& match, message_handler callbac sd_bus_message_handler_t sdbusInstallCallback = installCallback ? &Connection::sdbus_match_install_callback : nullptr; auto matchInfo = std::make_unique(MatchInfo{std::move(callback), std::move(installCallback), *this, {}}); - auto r = iface_->sd_bus_add_match_async( bus_.get() + auto r = sdbus_->sd_bus_add_match_async( bus_.get() , &matchInfo->slot , match.c_str() , &Connection::sdbus_match_callback @@ -263,7 +264,7 @@ Slot Connection::addMatchAsync(const std::string& match, message_handler callbac return {matchInfo.release(), [this](void *ptr) { auto* matchInfo = static_cast(ptr); - iface_->sd_bus_slot_unref(matchInfo->slot); + sdbus_->sd_bus_slot_unref(matchInfo->slot); std::default_delete{}(matchInfo); }}; } @@ -280,7 +281,7 @@ Slot Connection::addObjectVTable( const std::string& objectPath { sd_bus_slot *slot{}; - auto r = iface_->sd_bus_add_object_vtable( bus_.get() + auto r = sdbus_->sd_bus_add_object_vtable(bus_.get() , &slot , objectPath.c_str() , interfaceName.c_str() @@ -289,18 +290,18 @@ Slot Connection::addObjectVTable( const std::string& objectPath SDBUS_THROW_ERROR_IF(r < 0, "Failed to register object vtable", -r); - return {slot, [this](void *slot){ iface_->sd_bus_slot_unref((sd_bus_slot*)slot); }}; + return {slot, [this](void *slot){ sdbus_->sd_bus_slot_unref((sd_bus_slot*)slot); }}; } PlainMessage Connection::createPlainMessage() const { sd_bus_message* sdbusMsg{}; - auto r = iface_->sd_bus_message_new(bus_.get(), &sdbusMsg, _SD_BUS_MESSAGE_TYPE_INVALID); + auto r = sdbus_->sd_bus_message_new(bus_.get(), &sdbusMsg, _SD_BUS_MESSAGE_TYPE_INVALID); SDBUS_THROW_ERROR_IF(r < 0, "Failed to create a plain message", -r); - return Message::Factory::create(sdbusMsg, iface_.get(), adopt_message); + return Message::Factory::create(sdbusMsg, sdbus_.get(), adopt_message); } MethodCall Connection::createMethodCall( const std::string& destination @@ -310,7 +311,7 @@ MethodCall Connection::createMethodCall( const std::string& destination { sd_bus_message *sdbusMsg{}; - auto r = iface_->sd_bus_message_new_method_call( bus_.get() + auto r = sdbus_->sd_bus_message_new_method_call(bus_.get() , &sdbusMsg , destination.empty() ? nullptr : destination.c_str() , objectPath.c_str() @@ -319,7 +320,7 @@ MethodCall Connection::createMethodCall( const std::string& destination SDBUS_THROW_ERROR_IF(r < 0, "Failed to create method call", -r); - return Message::Factory::create(sdbusMsg, iface_.get(), this, adopt_message); + return Message::Factory::create(sdbusMsg, sdbus_.get(), adopt_message); } Signal Connection::createSignal( const std::string& objectPath @@ -328,7 +329,7 @@ Signal Connection::createSignal( const std::string& objectPath { sd_bus_message *sdbusMsg{}; - auto r = iface_->sd_bus_message_new_signal( bus_.get() + auto r = sdbus_->sd_bus_message_new_signal(bus_.get() , &sdbusMsg , objectPath.c_str() , interfaceName.c_str() @@ -336,7 +337,47 @@ Signal Connection::createSignal( const std::string& objectPath SDBUS_THROW_ERROR_IF(r < 0, "Failed to create signal", -r); - return Message::Factory::create(sdbusMsg, iface_.get(), adopt_message); + return Message::Factory::create(sdbusMsg, sdbus_.get(), adopt_message); +} + +MethodReply Connection::callMethod(const MethodCall& message, uint64_t timeout) +{ + // If the call expects reply, this call will block the bus connection from + // serving other messages until the reply arrives or the call times out. + auto reply = message.send(timeout); + + // Wake up event loop to process messages that may have arrived in the meantime... + wakeUpEventLoopIfMessagesInQueue(); + + return reply; +} + +void Connection::callMethod(const MethodCall& message, void* callback, void* userData, uint64_t timeout, floating_slot_t) +{ + // TODO: Think of ways of optimizing these three locking/unlocking of sdbus mutex (merge into one call?) + auto timeoutBefore = getEventLoopPollData().timeout; + message.send(callback, userData, timeout, floating_slot); + auto timeoutAfter = getEventLoopPollData().timeout; + + // An event loop may wait in poll with timeout `t1', while in another thread an async call is made with + // timeout `t2'. If `t2' < `t1', then we have to wake up the event loop thread to update its poll timeout. + if (timeoutAfter < timeoutBefore) + notifyEventLoopToWakeUpFromPoll(); +} + +Slot Connection::callMethod(const MethodCall& message, void* callback, void* userData, uint64_t timeout) +{ + // TODO: Think of ways of optimizing these three locking/unlocking of sdbus mutex (merge into one call?) + auto timeoutBefore = getEventLoopPollData().timeout; + auto slot = message.send(callback, userData, timeout); + auto timeoutAfter = getEventLoopPollData().timeout; + + // An event loop may wait in poll with timeout `t1', while in another thread an async call is made with + // timeout `t2'. If `t2' < `t1', then we have to wake up the event loop thread to update its poll timeout. + if (timeoutAfter < timeoutBefore) + notifyEventLoopToWakeUpFromPoll(); + + return slot; } void Connection::emitPropertiesChangedSignal( const std::string& objectPath @@ -345,7 +386,7 @@ void Connection::emitPropertiesChangedSignal( const std::string& objectPath { auto names = to_strv(propNames); - auto r = iface_->sd_bus_emit_properties_changed_strv( bus_.get() + auto r = sdbus_->sd_bus_emit_properties_changed_strv(bus_.get() , objectPath.c_str() , interfaceName.c_str() , propNames.empty() ? nullptr : &names[0] ); @@ -355,7 +396,7 @@ void Connection::emitPropertiesChangedSignal( const std::string& objectPath void Connection::emitInterfacesAddedSignal(const std::string& objectPath) { - auto r = iface_->sd_bus_emit_object_added(bus_.get(), objectPath.c_str()); + auto r = sdbus_->sd_bus_emit_object_added(bus_.get(), objectPath.c_str()); SDBUS_THROW_ERROR_IF(r < 0, "Failed to emit InterfacesAdded signal for all registered interfaces", -r); } @@ -365,7 +406,7 @@ void Connection::emitInterfacesAddedSignal( const std::string& objectPath { auto names = to_strv(interfaces); - auto r = iface_->sd_bus_emit_interfaces_added_strv( bus_.get() + auto r = sdbus_->sd_bus_emit_interfaces_added_strv(bus_.get() , objectPath.c_str() , interfaces.empty() ? nullptr : &names[0] ); @@ -374,7 +415,7 @@ void Connection::emitInterfacesAddedSignal( const std::string& objectPath void Connection::emitInterfacesRemovedSignal(const std::string& objectPath) { - auto r = iface_->sd_bus_emit_object_removed(bus_.get(), objectPath.c_str()); + auto r = sdbus_->sd_bus_emit_object_removed(bus_.get(), objectPath.c_str()); SDBUS_THROW_ERROR_IF(r < 0, "Failed to emit InterfacesRemoved signal for all registered interfaces", -r); } @@ -384,7 +425,7 @@ void Connection::emitInterfacesRemovedSignal( const std::string& objectPath { auto names = to_strv(interfaces); - auto r = iface_->sd_bus_emit_interfaces_removed_strv( bus_.get() + auto r = sdbus_->sd_bus_emit_interfaces_removed_strv(bus_.get() , objectPath.c_str() , interfaces.empty() ? nullptr : &names[0] ); @@ -404,40 +445,11 @@ Slot Connection::registerSignalHandler( const std::string& sender // https://www.freedesktop.org/software/systemd/man/sd_bus_add_match.html . // But this would require libsystemd v237 or higher. auto filter = composeSignalMatchFilter(sender, objectPath, interfaceName, signalName); - auto r = iface_->sd_bus_add_match(bus_.get(), &slot, filter.c_str(), callback, userData); + auto r = sdbus_->sd_bus_add_match(bus_.get(), &slot, filter.c_str(), callback, userData); SDBUS_THROW_ERROR_IF(r < 0, "Failed to register signal handler", -r); - return {slot, [this](void *slot){ iface_->sd_bus_slot_unref((sd_bus_slot*)slot); }}; -} - -MethodReply Connection::tryCallMethodSynchronously(const MethodCall& message, uint64_t timeout) -{ - auto loopThreadId = loopThreadId_.load(std::memory_order_relaxed); - - // Is the loop not yet on? => Go make synchronous call - while (loopThreadId == std::thread::id{}) - { - // Did the loop begin in the meantime? Or try_lock() failed spuriously? - if (!loopMutex_.try_lock()) - { - loopThreadId = loopThreadId_.load(std::memory_order_relaxed); - continue; - } - - // Synchronous D-Bus call - std::lock_guard guard(loopMutex_, std::adopt_lock); - return message.send(timeout); - } - - // Is the loop on and we are in the same thread? => Go for synchronous call - if (loopThreadId == std::this_thread::get_id()) - { - assert(!loopMutex_.try_lock()); - return message.send(timeout); - } - - return {}; + return {slot, [this](void *slot){ sdbus_->sd_bus_slot_unref((sd_bus_slot*)slot); }}; } Connection::BusPtr Connection::openBus(const BusFactory& busFactory) @@ -446,7 +458,7 @@ Connection::BusPtr Connection::openBus(const BusFactory& busFactory) int r = busFactory(&bus); SDBUS_THROW_ERROR_IF(r < 0, "Failed to open bus", -r); - BusPtr busPtr{bus, [this](sd_bus* bus){ return iface_->sd_bus_flush_close_unref(bus); }}; + BusPtr busPtr{bus, [this](sd_bus* bus){ return sdbus_->sd_bus_flush_close_unref(bus); }}; finishHandshake(busPtr.get()); return busPtr; } @@ -455,17 +467,17 @@ Connection::BusPtr Connection::openPseudoBus() { sd_bus* bus{}; - int r = iface_->sd_bus_new(&bus); + int r = sdbus_->sd_bus_new(&bus); SDBUS_THROW_ERROR_IF(r < 0, "Failed to open pseudo bus", -r); - (void)iface_->sd_bus_start(bus); + (void)sdbus_->sd_bus_start(bus); // It is expected that sd_bus_start has failed here, returning -EINVAL, due to having // not set a bus address, but it will leave the bus in an OPENING state, which enables // us to create plain D-Bus messages as a local data storage (for Variant, for example), // without dependency on real IPC communication with the D-Bus broker daemon. SDBUS_THROW_ERROR_IF(r < 0 && r != -EINVAL, "Failed to start pseudo bus", -r); - return {bus, [this](sd_bus* bus){ return iface_->sd_bus_close_unref(bus); }}; + return {bus, [this](sd_bus* bus){ return sdbus_->sd_bus_close_unref(bus); }}; } void Connection::finishHandshake(sd_bus* bus) @@ -476,43 +488,29 @@ void Connection::finishHandshake(sd_bus* bus) assert(bus != nullptr); - auto r = iface_->sd_bus_flush(bus); + auto r = sdbus_->sd_bus_flush(bus); SDBUS_THROW_ERROR_IF(r < 0, "Failed to flush bus on opening", -r); } -void Connection::notifyEventLoop(int fd) const +void Connection::notifyEventLoopToExit() { - assert(fd >= 0); - - uint64_t value = 1; - auto r = write(fd, &value, sizeof(value)); - SDBUS_THROW_ERROR_IF(r < 0, "Failed to notify event loop", -errno); + loopExitFd_.notify(); } -void Connection::notifyEventLoopToExit() const +void Connection::notifyEventLoopToWakeUpFromPoll() { - notifyEventLoop(loopExitFd_.fd); -} - -void Connection::notifyEventLoopNewTimeout() const -{ - // The extra notifications for new timeouts are only needed if calls are made asynchronously to the event loop. - // Are we in the same thread as the event loop? Note that it's ok to fail this check because the event loop isn't yet started. - if (loopThreadId_.load(std::memory_order_relaxed) == std::this_thread::get_id()) - return; - - // Get the new timeout from sd-bus - auto sdbusPollData = getEventLoopPollData(); - if (sdbusPollData.timeout_usec < activeTimeout_.load(std::memory_order_relaxed)) - notifyEventLoop(eventFd_.fd); + eventFd_.notify(); } -void Connection::clearEventLoopNotification(int fd) const +void Connection::wakeUpEventLoopIfMessagesInQueue() { - uint64_t value{}; - auto r = read(fd, &value, sizeof(value)); - SDBUS_THROW_ERROR_IF(r < 0, "Failed to read from the event descriptor", -errno); + // When doing a sync call, other D-Bus messages may have arrived, waiting in the read queue. + // In case an event loop is inside a poll in another thread, or an external event loop polls in the + // same thread but as an unrelated event source, then we need to wake up the poll explicitly so the + // event loop 1. processes all messages in the read queue, 2. updates poll timeout before next poll. + if (arePendingMessagesInReadQueue()) + notifyEventLoopToWakeUpFromPoll(); } void Connection::joinWithEventLoop() @@ -521,32 +519,36 @@ void Connection::joinWithEventLoop() asyncLoopThread_.join(); } -bool Connection::processPendingRequest() +bool Connection::processPendingEvent() { auto bus = bus_.get(); assert(bus != nullptr); - int r = iface_->sd_bus_process(bus, nullptr); + int r = sdbus_->sd_bus_process(bus, nullptr); SDBUS_THROW_ERROR_IF(r < 0, "Failed to process bus requests", -r); + // In correct use of sdbus-c++ API, r can be 0 only when processPendingEvent() + // is called from an external event loop as a reaction to event fd being signalled. + // If there are no more D-Bus messages to process, we know we have to clear event fd. + if (r == 0) + eventFd_.clear(); + return r > 0; } -bool Connection::waitForNextRequest() +bool Connection::waitForNextEvent() { assert(bus_ != nullptr); + assert(loopExitFd_.fd >= 0); assert(eventFd_.fd >= 0); auto sdbusPollData = getEventLoopPollData(); - struct pollfd fds[] = { - {sdbusPollData.fd, sdbusPollData.events, 0}, - {eventFd_.fd, POLLIN, 0}, - {loopExitFd_.fd, POLLIN, 0} - }; - auto fdsCount = sizeof(fds)/sizeof(fds[0]); + struct pollfd fds[] = { {sdbusPollData.fd, sdbusPollData.events, 0} + , {eventFd_.fd, POLLIN, 0} + , {loopExitFd_.fd, POLLIN, 0} }; + constexpr auto fdsCount = sizeof(fds)/sizeof(fds[0]); auto timeout = sdbusPollData.getPollTimeout(); - activeTimeout_.store(sdbusPollData.timeout_usec, std::memory_order_relaxed); auto r = poll(fds, fdsCount, timeout); if (r < 0 && errno == EINTR) @@ -554,21 +556,35 @@ bool Connection::waitForNextRequest() SDBUS_THROW_ERROR_IF(r < 0, "Failed to wait on the bus", -errno); - // new timeout notification + // Wake up notification, in order that we re-enter poll with freshly read PollData (namely, new poll timeout thereof) if (fds[1].revents & POLLIN) { - clearEventLoopNotification(fds[1].fd); + auto cleared = eventFd_.clear(); + SDBUS_THROW_ERROR_IF(!cleared, "Failed to read from the event descriptor", -errno); + // Go poll() again, but with up-to-date timeout (which will wake poll() up right away if there are messages to process) + return waitForNextEvent(); } - // loop exit notification + // Loop exit notification if (fds[2].revents & POLLIN) { - clearEventLoopNotification(fds[2].fd); + auto cleared = loopExitFd_.clear(); + SDBUS_THROW_ERROR_IF(!cleared, "Failed to read from the loop exit descriptor", -errno); return false; } return true; } +bool Connection::arePendingMessagesInReadQueue() const +{ + uint64_t readQueueSize{}; + + auto r = sdbus_->sd_bus_get_n_queued_read(bus_.get(), &readQueueSize); + SDBUS_THROW_ERROR_IF(r < 0, "Failed to get number of pending messages in read queue", -r); + + return readQueueSize > 0; +} + std::string Connection::composeSignalMatchFilter( const std::string &sender , const std::string &objectPath , const std::string &interfaceName @@ -623,33 +639,48 @@ Connection::EventFd::~EventFd() close(fd); } +void Connection::EventFd::notify() +{ + assert(fd >= 0); + auto r = eventfd_write(fd, 1); + SDBUS_THROW_ERROR_IF(r < 0, "Failed to notify event descriptor", -errno); +} + +bool Connection::EventFd::clear() +{ + assert(fd >= 0); + + uint64_t value{}; + auto r = eventfd_read(fd, &value); + return r >= 0; +} + } // namespace sdbus::internal namespace sdbus { -std::optional IConnection::PollData::getRelativeTimeout() const +std::chrono::microseconds IConnection::PollData::getRelativeTimeout() const { constexpr auto zero = std::chrono::microseconds::zero(); - if (timeout_usec == 0) - return zero; - else if (timeout_usec == UINT64_MAX) - return std::nullopt; + constexpr auto max = std::chrono::microseconds::max(); + using internal::now; - // We need C so that we use the same clock as the underlying sd-bus lib. - // We use POSIX's clock_gettime in favour of std::chrono::steady_clock to ensure this. - struct timespec ts{}; - auto r = clock_gettime(CLOCK_MONOTONIC, &ts); - SDBUS_THROW_ERROR_IF(r < 0, "clock_gettime failed: ", -errno); - auto now = std::chrono::nanoseconds(ts.tv_nsec) + std::chrono::seconds(ts.tv_sec); - auto absTimeout = std::chrono::microseconds(timeout_usec); - auto result = std::chrono::duration_cast(absTimeout - now); - return std::max(result, zero); + if (timeout == zero) + return zero; + else if (timeout == max) + return max; + else + return std::max(std::chrono::duration_cast(timeout - now()), zero); } int IConnection::PollData::getPollTimeout() const { - auto timeout = getRelativeTimeout(); - return timeout ? static_cast(std::chrono::ceil(timeout.value()).count()) : -1; + const auto relativeTimeout = getRelativeTimeout(); + + if (relativeTimeout == decltype(relativeTimeout)::max()) + return -1; + else + return static_cast(std::chrono::ceil(relativeTimeout).count()); } } // namespace sdbus diff --git a/src/Connection.h b/src/Connection.h index 1343d058..f288e399 100644 --- a/src/Connection.h +++ b/src/Connection.h @@ -37,8 +37,6 @@ #include #include #include -#include -#include namespace sdbus::internal { @@ -85,7 +83,7 @@ namespace sdbus::internal { void enterEventLoopAsync() override; void leaveEventLoop() override; PollData getEventLoopPollData() const override; - bool processPendingRequest() override; + bool processPendingEvent() override; void addObjectManager(const std::string& objectPath) override; void addObjectManager(const std::string& objectPath, floating_slot_t) override; @@ -116,6 +114,10 @@ namespace sdbus::internal { , const std::string& interfaceName , const std::string& signalName ) const override; + MethodReply callMethod(const MethodCall& message, uint64_t timeout) override; + void callMethod(const MethodCall& message, void* callback, void* userData, uint64_t timeout, floating_slot_t) override; + Slot callMethod(const MethodCall& message, void* callback, void* userData, uint64_t timeout) override; + void emitPropertiesChangedSignal( const std::string& objectPath , const std::string& interfaceName , const std::vector& propNames ) override; @@ -133,8 +135,6 @@ namespace sdbus::internal { , sd_bus_message_handler_t callback , void* userData ) override; - MethodReply tryCallMethodSynchronously(const MethodCall& message, uint64_t timeout) override; - private: using BusFactory = std::function; using BusPtr = std::unique_ptr>; @@ -143,17 +143,19 @@ namespace sdbus::internal { BusPtr openBus(const std::function& busFactory); BusPtr openPseudoBus(); void finishHandshake(sd_bus* bus); - bool waitForNextRequest(); + bool waitForNextEvent(); + + bool arePendingMessagesInReadQueue() const; static std::string composeSignalMatchFilter( const std::string &sender , const std::string &objectPath , const std::string &interfaceName , const std::string &signalName); - void notifyEventLoop(int fd) const; - void notifyEventLoopToExit() const; - void clearEventLoopNotification(int fd) const; - void notifyEventLoopNewTimeout() const override; + void notifyEventLoopToExit(); + void notifyEventLoopToWakeUpFromPoll(); + void wakeUpEventLoopIfMessagesInQueue(); void joinWithEventLoop(); + static std::vector to_strv(const std::vector& strings); static int sdbus_match_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError); @@ -164,6 +166,9 @@ namespace sdbus::internal { { EventFd(); ~EventFd(); + void notify(); + bool clear(); + int fd{-1}; }; @@ -176,14 +181,11 @@ namespace sdbus::internal { }; private: - std::unique_ptr iface_; + std::unique_ptr sdbus_; BusPtr bus_; std::thread asyncLoopThread_; - std::atomic loopThreadId_; - std::mutex loopMutex_; - EventFd loopExitFd_; - EventFd eventFd_; - std::atomic activeTimeout_{}; + EventFd loopExitFd_; // To wake up event loop I/O polling to exit + EventFd eventFd_; // To wake up event loop I/O polling to re-enter poll with fresh PollData values std::vector floatingMatchRules_; }; diff --git a/src/IConnection.h b/src/IConnection.h index 0c07b20d..a1227c26 100644 --- a/src/IConnection.h +++ b/src/IConnection.h @@ -28,6 +28,7 @@ #define SDBUS_CXX_INTERNAL_ICONNECTION_H_ #include +#include #include SDBUS_HEADER #include #include @@ -70,6 +71,10 @@ namespace sdbus::internal { , const std::string& interfaceName , const std::string& signalName ) const = 0; + virtual MethodReply callMethod(const MethodCall& message, uint64_t timeout) = 0; + virtual void callMethod(const MethodCall& message, void* callback, void* userData, uint64_t timeout, floating_slot_t) = 0; + virtual Slot callMethod(const MethodCall& message, void* callback, void* userData, uint64_t timeout) = 0; + virtual void emitPropertiesChangedSignal( const std::string& objectPath , const std::string& interfaceName , const std::vector& propNames ) = 0; @@ -89,9 +94,6 @@ namespace sdbus::internal { , const std::string& signalName , sd_bus_message_handler_t callback , void* userData ) = 0; - - virtual void notifyEventLoopNewTimeout() const = 0; - virtual MethodReply tryCallMethodSynchronously(const MethodCall& message, uint64_t timeout) = 0; }; [[nodiscard]] std::unique_ptr createConnection(); diff --git a/src/ISdBus.h b/src/ISdBus.h index c83972f1..e38aa290 100644 --- a/src/ISdBus.h +++ b/src/ISdBus.h @@ -88,7 +88,7 @@ namespace sdbus::internal { virtual int sd_bus_process(sd_bus *bus, sd_bus_message **r) = 0; virtual int sd_bus_get_poll_data(sd_bus *bus, PollData* data) = 0; - + virtual int sd_bus_get_n_queued_read(sd_bus *bus, uint64_t *ret) = 0; virtual int sd_bus_flush(sd_bus *bus) = 0; virtual sd_bus *sd_bus_flush_close_unref(sd_bus *bus) = 0; virtual sd_bus *sd_bus_close_unref(sd_bus *bus) = 0; diff --git a/src/Message.cpp b/src/Message.cpp index 934f3c20..2e9ce321 100644 --- a/src/Message.cpp +++ b/src/Message.cpp @@ -763,12 +763,9 @@ std::string Message::getSELinuxContext() const MethodCall::MethodCall( void *msg , internal::ISdBus *sdbus - , const internal::IConnection *connection , adopt_message_t) noexcept : Message(msg, sdbus, adopt_message) - , connection_(connection) { - assert(connection_ != nullptr); } void MethodCall::dontExpectReply() @@ -825,10 +822,6 @@ void MethodCall::send(void* callback, void* userData, uint64_t timeout, floating { auto r = sdbus_->sd_bus_call_async(nullptr, nullptr, (sd_bus_message*)msg_, (sd_bus_message_handler_t)callback, userData, timeout); SDBUS_THROW_ERROR_IF(r < 0, "Failed to call method", -r); - - // Force event loop to re-enter polling with the async call timeout if that is less than the one used in current poll - SDBUS_THROW_ERROR_IF(connection_ == nullptr, "Invalid use of MethodCall API", ENOTSUP); - connection_->notifyEventLoopNewTimeout(); } Slot MethodCall::send(void* callback, void* userData, uint64_t timeout) const @@ -838,10 +831,6 @@ Slot MethodCall::send(void* callback, void* userData, uint64_t timeout) const auto r = sdbus_->sd_bus_call_async(nullptr, &slot, (sd_bus_message*)msg_, (sd_bus_message_handler_t)callback, userData, timeout); SDBUS_THROW_ERROR_IF(r < 0, "Failed to call method asynchronously", -r); - // Force event loop to re-enter polling with the async call timeout if that is less than the one used in current poll - SDBUS_THROW_ERROR_IF(connection_ == nullptr, "Invalid use of MethodCall API", ENOTSUP); - connection_->notifyEventLoopNewTimeout(); - return {slot, [sdbus_ = sdbus_](void *slot){ sdbus_->sd_bus_slot_unref((sd_bus_slot*)slot); }}; } diff --git a/src/MessageUtils.h b/src/MessageUtils.h index 8a477030..4fb2f146 100644 --- a/src/MessageUtils.h +++ b/src/MessageUtils.h @@ -57,12 +57,6 @@ namespace sdbus { return _Msg{msg, sdbus, adopt_message}; } - - template - static _Msg create(void *msg, internal::ISdBus* sdbus, const internal::IConnection* connection, adopt_message_t) - { - return _Msg{msg, sdbus, connection, adopt_message}; - } }; PlainMessage createPlainMessage(); diff --git a/src/Proxy.cpp b/src/Proxy.cpp index 7cd6eae8..fd91dfe6 100644 --- a/src/Proxy.cpp +++ b/src/Proxy.cpp @@ -88,31 +88,9 @@ MethodCall Proxy::createMethodCall(const std::string& interfaceName, const std:: MethodReply Proxy::callMethod(const MethodCall& message, uint64_t timeout) { - // Sending method call synchronously is the only operation that blocks, waiting for the method - // reply message among the incoming messages on the sd-bus connection socket. But typically there - // already is somebody that generally handles incoming D-Bus messages -- the connection event loop - // running typically in its own thread. We have to avoid polling on socket from several threads. - // So we have to branch here: either we are within the context of the event loop thread, then we - // can send the message simply via sd_bus_call, which blocks. Or we are in another thread, then - // we can perform the send operation of the method call message from here (because that is thread- - // safe like other sd-bus API accesses), but the incoming reply we have to get through the event - // loop thread, because this is the only rightful listener on the sd-bus connection socket. - // So, technically, we use async means to wait here for reply received by the event loop thread. - SDBUS_THROW_ERROR_IF(!message.isValid(), "Invalid method call message provided", EINVAL); - // If we don't need to wait for any reply, we can send the message now irrespective of the context - if (message.doesntExpectReply()) - return message.send(timeout); - - // If we are in the context of event loop thread, we can send the D-Bus call synchronously - // and wait blockingly for the reply, because we are the exclusive listeners on the socket - auto reply = connection_->tryCallMethodSynchronously(message, timeout); - if (reply.isValid()) - return reply; - - // Otherwise we send the call asynchronously and do blocking wait for the reply from the event loop thread - return sendMethodCallMessageAndWaitForReply(message, timeout); + return connection_->callMethod(message, timeout); } PendingAsyncCall Proxy::callMethod(const MethodCall& message, async_reply_handler asyncReplyCallback, uint64_t timeout) @@ -123,10 +101,11 @@ PendingAsyncCall Proxy::callMethod(const MethodCall& message, async_reply_handle auto callData = std::make_shared(AsyncCalls::CallData{*this, std::move(asyncReplyCallback), {}, AsyncCalls::CallData::State::RUNNING}); auto weakData = std::weak_ptr{callData}; - callData->slot = message.send(callback, callData.get(), timeout); + callData->slot = connection_->callMethod(message, callback, callData.get(), timeout); pendingAsyncCalls_.addCall(std::move(callData)); + // TODO: Instead of PendingAsyncCall consider using Slot implementation for simplicity and consistency return {weakData}; } @@ -153,49 +132,6 @@ std::future Proxy::callMethod(const MethodCall& message, uint64_t t return future; } -MethodReply Proxy::sendMethodCallMessageAndWaitForReply(const MethodCall& message, uint64_t timeout) -{ - /*thread_local*/ SyncCallReplyData syncCallReplyData; - - async_reply_handler asyncReplyCallback = [&syncCallReplyData](MethodReply& reply, const Error* error) - { - syncCallReplyData.sendMethodReplyToWaitingThread(reply, error); - }; - auto callback = (void*)&Proxy::sdbus_async_reply_handler; - AsyncCalls::CallData callData{*this, std::move(asyncReplyCallback), {}, AsyncCalls::CallData::State::NOT_ASYNC}; - - message.send(callback, &callData, timeout, floating_slot); - - return syncCallReplyData.waitForMethodReply(); -} - -void Proxy::SyncCallReplyData::sendMethodReplyToWaitingThread(MethodReply& reply, const Error* error) -{ - std::unique_lock lock{mutex_}; - SCOPE_EXIT{ cond_.notify_one(); }; // This must happen before unlocking the mutex to avoid potential data race on spurious wakeup in the waiting thread - SCOPE_EXIT{ arrived_ = true; }; - - //error_ = nullptr; // Necessary if SyncCallReplyData instance is thread_local - - if (error == nullptr) - reply_ = std::move(reply); - else - error_ = std::make_unique(*error); -} - -MethodReply Proxy::SyncCallReplyData::waitForMethodReply() -{ - std::unique_lock lock{mutex_}; - cond_.wait(lock, [this](){ return arrived_; }); - - //arrived_ = false; // Necessary if SyncCallReplyData instance is thread_local - - if (error_) - throw *error_; - - return std::move(reply_); -} - void Proxy::registerSignalHandler( const std::string& interfaceName , const std::string& signalName , signal_handler signalHandler ) diff --git a/src/Proxy.h b/src/Proxy.h index a507f804..13b2e280 100644 --- a/src/Proxy.h +++ b/src/Proxy.h @@ -75,21 +75,6 @@ namespace sdbus::internal { const Message* getCurrentlyProcessedMessage() const override; private: - class SyncCallReplyData - { - public: - void sendMethodReplyToWaitingThread(MethodReply& reply, const Error* error); - MethodReply waitForMethodReply(); - - private: - std::mutex mutex_; - std::condition_variable cond_; - bool arrived_{}; - MethodReply reply_; - std::unique_ptr error_; - }; - - MethodReply sendMethodCallMessageAndWaitForReply(const MethodCall& message, uint64_t timeout); 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_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError); diff --git a/src/SdBus.cpp b/src/SdBus.cpp index f253cc37..b5a55251 100644 --- a/src/SdBus.cpp +++ b/src/SdBus.cpp @@ -53,6 +53,8 @@ int SdBus::sd_bus_send(sd_bus *bus, sd_bus_message *m, uint64_t *cookie) return r; // Make sure long messages are not only stored in outgoing queues but also really sent out + // TODO: This is a workaround. We should not block here until everything is physically sent out. + // Refactor: if sd_bus_get_n_queued_write() > 0 then wake up event loop through event fd ::sd_bus_flush(bus != nullptr ? bus : ::sd_bus_message_get_bus(m)); return r; @@ -74,6 +76,8 @@ int SdBus::sd_bus_call_async(sd_bus *bus, sd_bus_slot **slot, sd_bus_message *m, return r; // Make sure long messages are not only stored in outgoing queues but also really sent out + // TODO: This is a workaround. We should not block here until everything is physically sent out. + // Refactor: if sd_bus_get_n_queued_write() > 0 then wake up event loop through event fd ::sd_bus_flush(bus != nullptr ? bus : ::sd_bus_message_get_bus(m)); return r; @@ -395,6 +399,13 @@ int SdBus::sd_bus_get_poll_data(sd_bus *bus, PollData* data) return r; } +int SdBus::sd_bus_get_n_queued_read(sd_bus *bus, uint64_t *ret) +{ + std::lock_guard lock(sdbusMutex_); + + return ::sd_bus_get_n_queued_read(bus, ret); +} + int SdBus::sd_bus_flush(sd_bus *bus) { return ::sd_bus_flush(bus); diff --git a/src/SdBus.h b/src/SdBus.h index f321d074..dc91b377 100644 --- a/src/SdBus.h +++ b/src/SdBus.h @@ -80,7 +80,7 @@ class SdBus final : public ISdBus 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_get_n_queued_read(sd_bus *bus, uint64_t *ret) override; virtual int sd_bus_flush(sd_bus *bus) override; virtual sd_bus *sd_bus_flush_close_unref(sd_bus *bus) override; virtual sd_bus *sd_bus_close_unref(sd_bus *bus) override; diff --git a/src/Utils.h b/src/Utils.h index 82247dac..e50667b0 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -78,6 +78,17 @@ namespace sdbus::internal { return true; } + // Returns time since epoch based of POSIX CLOCK_MONOTONIC, + // so we use the very same clock as underlying sd-bus library. + [[nodiscard]] inline auto now() + { + struct timespec ts{}; + auto r = clock_gettime(CLOCK_MONOTONIC, &ts); + SDBUS_THROW_ERROR_IF(r < 0, "clock_gettime failed: ", -errno); + + return std::chrono::nanoseconds(ts.tv_nsec) + std::chrono::seconds(ts.tv_sec); + } + // Implementation of the overload pattern for variant visitation template struct overload : Ts... { using Ts::operator()...; }; template overload(Ts...) -> overload; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 87dd4736..b7a20e5c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -50,6 +50,7 @@ set(UNITTESTS_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/unittests) set(UNITTESTS_SRCS ${UNITTESTS_SOURCE_DIR}/sdbus-c++-unit-tests.cpp ${UNITTESTS_SOURCE_DIR}/Message_test.cpp + ${UNITTESTS_SOURCE_DIR}/PollData_test.cpp ${UNITTESTS_SOURCE_DIR}/Types_test.cpp ${UNITTESTS_SOURCE_DIR}/TypeTraits_test.cpp ${UNITTESTS_SOURCE_DIR}/Connection_test.cpp diff --git a/tests/integrationtests/DBusConnectionTests.cpp b/tests/integrationtests/DBusConnectionTests.cpp index 9147341f..e75db26b 100644 --- a/tests/integrationtests/DBusConnectionTests.cpp +++ b/tests/integrationtests/DBusConnectionTests.cpp @@ -41,7 +41,6 @@ using ::testing::Eq; using namespace sdbus::test; -using namespace std::chrono_literals; /*-------------------------------------*/ /* -- TEST CASES -- */ @@ -80,7 +79,7 @@ TEST(Connection, CannotReleaseNonrequestedName) ASSERT_THROW(connection->releaseName("some.random.nonrequested.name"), sdbus::Error); } -TEST(Connection, CanEnterAndLeaveEventLoop) +TEST(Connection, CanEnterAndLeaveInternalEventLoop) { auto connection = sdbus::createConnection(); connection->requestName(BUS_NAME); @@ -90,44 +89,3 @@ TEST(Connection, CanEnterAndLeaveEventLoop) t.join(); } - -TEST(Connection, PollDataGetZeroTimeout) -{ - sdbus::IConnection::PollData pd{}; - pd.timeout_usec = 0; - ASSERT_TRUE(pd.getRelativeTimeout().has_value()); - EXPECT_THAT(pd.getRelativeTimeout().value(), Eq(std::chrono::microseconds::zero())); - EXPECT_THAT(pd.getPollTimeout(), Eq(0)); -} - -TEST(Connection, PollDataGetInfiniteTimeout) -{ - sdbus::IConnection::PollData pd{}; - pd.timeout_usec = UINT64_MAX; - ASSERT_FALSE(pd.getRelativeTimeout().has_value()); - EXPECT_THAT(pd.getPollTimeout(), Eq(-1)); -} - -TEST(Connection, PollDataGetZeroRelativeTimeoutForPast) -{ - sdbus::IConnection::PollData pd{}; - auto past = std::chrono::steady_clock::now() - 10s; - pd.timeout_usec = std::chrono::duration_cast(past.time_since_epoch()).count(); - ASSERT_TRUE(pd.getRelativeTimeout().has_value()); - EXPECT_THAT(pd.getRelativeTimeout().value(), Eq(0us)); - EXPECT_THAT(pd.getPollTimeout(), Eq(0)); -} - -TEST(Connection, PollDataGetRelativeTimeoutInTolerance) -{ - sdbus::IConnection::PollData pd{}; - constexpr auto TIMEOUT = 1s; - constexpr auto TOLERANCE = 100ms; - auto future = std::chrono::steady_clock::now() + TIMEOUT; - pd.timeout_usec = std::chrono::duration_cast(future.time_since_epoch()).count(); - ASSERT_TRUE(pd.getRelativeTimeout().has_value()); - EXPECT_GE(pd.getRelativeTimeout().value(), TIMEOUT - TOLERANCE); - EXPECT_LE(pd.getRelativeTimeout().value(), TIMEOUT + TOLERANCE); - EXPECT_GE(pd.getPollTimeout(), 900); - EXPECT_LE(pd.getPollTimeout(), 1100); -} diff --git a/tests/integrationtests/TestProxy.cpp b/tests/integrationtests/TestProxy.cpp index 5e3ed706..1d8908be 100644 --- a/tests/integrationtests/TestProxy.cpp +++ b/tests/integrationtests/TestProxy.cpp @@ -2,7 +2,7 @@ * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2024 Stanislav Angelovic * - * @file TestAdaptor.cpp + * @file TestProxy.cpp * * Created on: May 23, 2020 * Project: sdbus-c++ diff --git a/tests/integrationtests/TestProxy.h b/tests/integrationtests/TestProxy.h index 367b6c97..72a50e82 100644 --- a/tests/integrationtests/TestProxy.h +++ b/tests/integrationtests/TestProxy.h @@ -2,7 +2,7 @@ * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2024 Stanislav Angelovic * - * @file TestAdaptor.h + * @file TestProxy.h * * Created on: Jan 2, 2017 * Project: sdbus-c++ diff --git a/tests/unittests/PollData_test.cpp b/tests/unittests/PollData_test.cpp new file mode 100644 index 00000000..68259d01 --- /dev/null +++ b/tests/unittests/PollData_test.cpp @@ -0,0 +1,125 @@ +/** + * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland + * (C) 2016 - 2022 Stanislav Angelovic + * + * @file PollData_test.cpp + * + * Created on: Jan 19, 2023 + * 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 . + */ + +#include +#include +#include +#include + +using ::testing::Eq; +using ::testing::Ge; +using ::testing::Le; +using ::testing::AllOf; +using namespace std::string_literals; +using namespace std::chrono_literals; + +/*-------------------------------------*/ +/* -- TEST CASES -- */ +/*-------------------------------------*/ + +TEST(PollData, ReturnsZeroRelativeTimeoutForZeroAbsoluteTimeout) +{ + sdbus::IConnection::PollData pd; + pd.timeout = std::chrono::microseconds::zero(); + + auto relativeTimeout = pd.getRelativeTimeout(); + + EXPECT_THAT(relativeTimeout, Eq(std::chrono::microseconds::zero())); +} + +TEST(PollData, ReturnsZeroPollTimeoutForZeroAbsoluteTimeout) +{ + sdbus::IConnection::PollData pd; + pd.timeout = std::chrono::microseconds::zero(); + + auto pollTimeout = pd.getPollTimeout(); + + EXPECT_THAT(pollTimeout, Eq(0)); +} + +TEST(PollData, ReturnsInfiniteRelativeTimeoutForInfiniteAbsoluteTimeout) +{ + sdbus::IConnection::PollData pd; + pd.timeout = std::chrono::microseconds::max(); + + auto relativeTimeout = pd.getRelativeTimeout(); + + EXPECT_THAT(relativeTimeout, Eq(std::chrono::microseconds::max())); +} + +TEST(PollData, ReturnsNegativePollTimeoutForInfiniteAbsoluteTimeout) +{ + sdbus::IConnection::PollData pd; + pd.timeout = std::chrono::microseconds::max(); + + auto pollTimeout = pd.getPollTimeout(); + + EXPECT_THAT(pollTimeout, Eq(-1)); +} + +TEST(PollData, ReturnsZeroRelativeTimeoutForPastAbsoluteTimeout) +{ + sdbus::IConnection::PollData pd; + auto past = std::chrono::steady_clock::now() - 10s; + pd.timeout = std::chrono::duration_cast(past.time_since_epoch()); + + auto relativeTimeout = pd.getRelativeTimeout(); + + EXPECT_THAT(relativeTimeout, Eq(0us)); +} + +TEST(PollData, ReturnsZeroPollTimeoutForPastAbsoluteTimeout) +{ + sdbus::IConnection::PollData pd; + auto past = std::chrono::steady_clock::now() - 10s; + pd.timeout = std::chrono::duration_cast(past.time_since_epoch()); + + auto pollTimeout = pd.getPollTimeout(); + + EXPECT_THAT(pollTimeout, Eq(0)); +} + +TEST(PollData, ReturnsCorrectRelativeTimeoutForFutureAbsoluteTimeout) +{ + sdbus::IConnection::PollData pd; + auto future = std::chrono::steady_clock::now() + 1s; + pd.timeout = std::chrono::duration_cast(future.time_since_epoch()); + + auto relativeTimeout = pd.getRelativeTimeout(); + + EXPECT_THAT(relativeTimeout, AllOf(Ge(900ms), Le(1100ms))); +} + +TEST(PollData, ReturnsCorrectPollTimeoutForFutureAbsoluteTimeout) +{ + sdbus::IConnection::PollData pd; + auto future = std::chrono::steady_clock::now() + 1s; + pd.timeout = std::chrono::duration_cast(future.time_since_epoch()); + + auto pollTimeout = pd.getPollTimeout(); + + EXPECT_THAT(pollTimeout, AllOf(Ge(900), Le(1100))); +} diff --git a/tests/unittests/mocks/SdBusMock.h b/tests/unittests/mocks/SdBusMock.h index 41567418..587b8d96 100644 --- a/tests/unittests/mocks/SdBusMock.h +++ b/tests/unittests/mocks/SdBusMock.h @@ -79,7 +79,7 @@ class SdBusMock : public sdbus::internal::ISdBus MOCK_METHOD2(sd_bus_process, int(sd_bus *bus, sd_bus_message **r)); MOCK_METHOD2(sd_bus_get_poll_data, int(sd_bus *bus, PollData* data)); - + MOCK_METHOD2(sd_bus_get_n_queued_read, int(sd_bus *bus, uint64_t *ret)); MOCK_METHOD1(sd_bus_flush, int(sd_bus *bus)); MOCK_METHOD1(sd_bus_flush_close_unref, sd_bus *(sd_bus *bus)); MOCK_METHOD1(sd_bus_close_unref, sd_bus *(sd_bus *bus)); From 8d0c7e7c3153e4018c0609d463f0650cba5656eb Mon Sep 17 00:00:00 2001 From: Stanislav Angelovic Date: Wed, 25 Jan 2023 00:28:28 +0100 Subject: [PATCH 02/52] chore: enable actions on release/v2.0 branch --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3add702b..9ebf12f8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,7 +4,9 @@ on: push: branches: [ master ] pull_request: - branches: [ master ] + branches: + - master + - release/v2.0 jobs: build: From b0a13e08b139d347d4f69e83c1f2d625b7e4f2c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Wed, 25 Jan 2023 00:02:51 +0100 Subject: [PATCH 03/52] feat: introduce sd-event integration --- docs/using-sdbus-c++.md | 10 +- include/sdbus-c++/IConnection.h | 34 +++- src/Connection.cpp | 186 ++++++++++++++++++ src/Connection.h | 29 +++ src/SdBus.cpp | 8 +- tests/CMakeLists.txt | 13 +- .../DBusAsyncMethodsTests.cpp | 66 +++---- tests/integrationtests/DBusGeneralTests.cpp | 57 +++--- tests/integrationtests/DBusMethodsTests.cpp | 140 +++++++------ .../integrationtests/DBusPropertiesTests.cpp | 24 ++- tests/integrationtests/DBusSignalsTests.cpp | 103 +++++----- .../DBusStandardInterfacesTests.cpp | 142 +++++++------ tests/integrationtests/TestFixture.cpp | 16 +- tests/integrationtests/TestFixture.h | 115 ++++++++++- 14 files changed, 654 insertions(+), 289 deletions(-) diff --git a/docs/using-sdbus-c++.md b/docs/using-sdbus-c++.md index 7fca6990..9ff8e3da 100644 --- a/docs/using-sdbus-c++.md +++ b/docs/using-sdbus-c++.md @@ -441,7 +441,7 @@ On the **server** side, we generally need to create D-Bus objects and publish th * its internal event loop * either in a blocking way, through `enterEventLoop()`, * or in a non-blocking async way, through `enterEventLoopAsync()`, - * or an external event loop. This is suitable if we use in our application an event loop implementation of our choice (e.g., GLib Event Loop, boost::asio, ...) and we want to hook up our sdbus-c++ connections with it. See [Using sdbus-c++ in external event loops](#using-sdbus-c-in-external-event-loops) section for more information. + * or an external event loop. This is suitable if we use in our application an event loop implementation of our choice (e.g., sd-event, GLib Event Loop, boost::asio, ...) and we want to hook up our sdbus-c++ connections with it. See [Using sdbus-c++ in external event loops](#using-sdbus-c-in-external-event-loops) section for more information. The object takes the D-Bus connection as a reference in its constructor. This is the only way to wire the connection and the object together. We must make sure the connection exists as long as objects using it exist. @@ -1723,7 +1723,13 @@ Note that the returned timeout should be considered only a maximum sleeping time `PollData::fd` is a bus I/O fd. `PollData::eventFd` is an sdbus-c++ internal fd for communicating important changes from other threads to the event loop thread, so the event loop retrieves new poll data (with updated timeout, for example) and, potentially, processes pending D-Bus messages (like signals that came in during a blocking synchronous call from other thread, or queued outgoing messages that are very big to be able to have been sent in one shot from another thread), before the next poll. -Consult `IConnection::PollData` and `IConnection::getEventLoopPollData()` documentation for more potentially more information. +Consult `IConnection::PollData` and `IConnection::getEventLoopPollData()` documentation for potentially more information. + +### Integration of sd-event event loop + +sdbus-c++ provides built-in integration of sd-event, which makes it very convenient to hook sdbus-c++ connection up with an sd-event event loop. + +See documentation of `IConnection::attachSdEventLoop()`, `IConnection::detachSdEventLoop()`, and `IConnection::getSdEventLoop()` methods, or sdbus-c++ integration tests for an example of use. These methods are sdbus-c++ counterparts to and mimic the behavior of these underlying sd-bus functions: `sd_bus_attach_event()`, `sd_bus_detach_event()`, and `sd_bus_get_event()`. Their manual pages provide much more details about their behavior. Conclusion ---------- diff --git a/include/sdbus-c++/IConnection.h b/include/sdbus-c++/IConnection.h index 05c5af9f..854414e8 100644 --- a/include/sdbus-c++/IConnection.h +++ b/include/sdbus-c++/IConnection.h @@ -35,6 +35,7 @@ #include struct sd_bus; +struct sd_event; namespace sdbus { @@ -107,6 +108,32 @@ namespace sdbus { */ virtual void leaveEventLoop() = 0; + /*! + * @brief Attaches the bus connection to an sd-event event loop + * + * @param[in] event sd-event event loop object + * @param[in] priority Specified priority + * + * @throws sdbus::Error in case of failure + * + * See `man sd_bus_attach_event'. + */ + virtual void attachSdEventLoop(sd_event *event, int priority = 0) = 0; + + /*! + * @brief Detaches the bus connection from an sd-event event loop + * + * @throws sdbus::Error in case of failure + */ + virtual void detachSdEventLoop() = 0; + + /*! + * @brief Gets current sd-event event loop for the bus connection + * + * @return Pointer to event loop object if attached, nullptr otherwise + */ + virtual sd_event *getSdEventLoop() = 0; + /*! * @brief Adds an ObjectManager at the specified D-Bus object path * @@ -150,6 +177,10 @@ namespace sdbus { * Use PollData::getPollTimeout() to have the timeout value converted * in a form that can be passed to poll(2). * + * The bus connection conveniently integrates sd-event event loop. + * To attach the bus connection to an sd-event event loop, use + * attachSdEventLoop() function. + * * @throws sdbus::Error in case of failure */ [[nodiscard]] virtual PollData getEventLoopPollData() const = 0; @@ -170,7 +201,8 @@ namespace sdbus { * * You don't need to directly call this method or getEventLoopPollData() method * when using convenient, internal bus connection event loops through - * enterEventLoop() or enterEventLoopAsync() calls. + * enterEventLoop() or enterEventLoopAsync() calls, or when the bus is + * connected to an sd-event event loop through attachSdEventLoop(). * It is invoked automatically when necessary. * * @throws sdbus::Error in case of failure diff --git a/src/Connection.cpp b/src/Connection.cpp index ea4d6647..af4a03a7 100644 --- a/src/Connection.cpp +++ b/src/Connection.cpp @@ -32,6 +32,9 @@ #include #include "ScopeGuard.h" #include SDBUS_HEADER +#ifndef SDBUS_basu // sd_event integration is not supported in basu-based sdbus-c++ +#include +#endif #include #include #include @@ -274,6 +277,189 @@ void Connection::addMatchAsync(const std::string& match, message_handler callbac floatingMatchRules_.push_back(addMatchAsync(match, std::move(callback), std::move(installCallback))); } +void Connection::attachSdEventLoop(sd_event *event, int priority) +{ +#ifndef SDBUS_basu + auto pollData = getEventLoopPollData(); + + auto sdEvent = createSdEventSlot(event); + auto sdTimeEventSource = createSdTimeEventSourceSlot(event, priority); + auto sdIoEventSource = createSdIoEventSourceSlot(event, pollData.fd, priority); + auto sdInternalEventSource = createSdInternalEventSourceSlot(event, pollData.eventFd, priority); + + sdEvent_ = std::make_unique(SdEvent{ std::move(sdEvent) + , std::move(sdTimeEventSource) + , std::move(sdIoEventSource) + , std::move(sdInternalEventSource) }); +#else + (void)event; + (void)priority; + SDBUS_THROW_ERROR("sd_event integration is not supported on this platform", EOPNOTSUPP); +#endif +} + +void Connection::detachSdEventLoop() +{ + sdEvent_.reset(); +} + +sd_event *Connection::getSdEventLoop() +{ + return sdEvent_ ? static_cast(sdEvent_->sdEvent.get()) : nullptr; +} + +#ifndef SDBUS_basu + +Slot Connection::createSdEventSlot(sd_event *event) +{ + // Get default event if no event is provided by the caller + if (event != nullptr) + event = sd_event_ref(event); + else + (void)sd_event_default(&event); + SDBUS_THROW_ERROR_IF(!event, "Invalid sd_event handle", EINVAL); + + return Slot{event, [](void* event){ sd_event_unref((sd_event*)event); }}; +} + +Slot Connection::createSdTimeEventSourceSlot(sd_event *event, int priority) +{ + sd_event_source *timeEventSource{}; + auto r = sd_event_add_time(event, &timeEventSource, CLOCK_MONOTONIC, 0, 0, onSdTimerEvent, this); + SDBUS_THROW_ERROR_IF(r < 0, "Failed to add timer event", -r); + Slot sdTimeEventSource{timeEventSource, [](void* source){ deleteSdEventSource((sd_event_source*)source); }}; + + r = sd_event_source_set_priority(timeEventSource, priority); + SDBUS_THROW_ERROR_IF(r < 0, "Failed to set time event priority", -r); + + r = sd_event_source_set_description(timeEventSource, "bus-time"); + SDBUS_THROW_ERROR_IF(r < 0, "Failed to set time event description", -r); + + return sdTimeEventSource; +} + +Slot Connection::createSdIoEventSourceSlot(sd_event *event, int fd, int priority) +{ + sd_event_source *ioEventSource{}; + auto r = sd_event_add_io(event, &ioEventSource, fd, 0, onSdIoEvent, this); + SDBUS_THROW_ERROR_IF(r < 0, "Failed to add io event", -r); + Slot sdIoEventSource{ioEventSource, [](void* source){ deleteSdEventSource((sd_event_source*)source); }}; + + r = sd_event_source_set_prepare(ioEventSource, onSdEventPrepare); + SDBUS_THROW_ERROR_IF(r < 0, "Failed to set prepare callback for IO event", -r); + + r = sd_event_source_set_priority(ioEventSource, priority); + SDBUS_THROW_ERROR_IF(r < 0, "Failed to set priority for IO event", -r); + + r = sd_event_source_set_description(ioEventSource, "bus-input"); + SDBUS_THROW_ERROR_IF(r < 0, "Failed to set priority for IO event", -r); + + return sdIoEventSource; +} + +Slot Connection::createSdInternalEventSourceSlot(sd_event *event, int fd, int priority) +{ + sd_event_source *internalEventSource{}; + auto r = sd_event_add_io(event, &internalEventSource, fd, 0, onSdInternalEvent, this); + SDBUS_THROW_ERROR_IF(r < 0, "Failed to add internal event", -r); + Slot sdInternalEventSource{internalEventSource, [](void* source){ deleteSdEventSource((sd_event_source*)source); }}; + + // sd-event loop calls prepare callbacks for all event sources, not just for the one that fired now. + // So since onSdEventPrepare is already registered on ioEventSource, we don't need to duplicate it here. + //r = sd_event_source_set_prepare(internalEventSource, onSdEventPrepare); + //SDBUS_THROW_ERROR_IF(r < 0, "Failed to set prepare callback for internal event", -r); + + r = sd_event_source_set_priority(internalEventSource, priority); + SDBUS_THROW_ERROR_IF(r < 0, "Failed to set priority for internal event", -r); + + r = sd_event_source_set_description(internalEventSource, "internal-event"); + SDBUS_THROW_ERROR_IF(r < 0, "Failed to set priority for IO event", -r); + + return sdInternalEventSource; +} + +int Connection::onSdTimerEvent(sd_event_source */*s*/, uint64_t /*usec*/, void *userdata) +{ + auto connection = static_cast(userdata); + assert(connection != nullptr); + + (void)connection->processPendingEvent(); + + return 1; +} + +int Connection::onSdIoEvent(sd_event_source */*s*/, int /*fd*/, uint32_t /*revents*/, void *userdata) +{ + auto connection = static_cast(userdata); + assert(connection != nullptr); + + (void)connection->processPendingEvent(); + + return 1; +} + +int Connection::onSdInternalEvent(sd_event_source */*s*/, int /*fd*/, uint32_t /*revents*/, void *userdata) +{ + auto connection = static_cast(userdata); + assert(connection != nullptr); + + // It's not really necessary to processPendingEvent() here. We just clear the event fd. + // The sd-event loop will before the next poll call prepare callbacks for all event sources, + // including I/O bus fd. This will get up-to-date poll timeout, which will be zero if there + // are pending D-Bus messages in the read queue, which will immediately wake up next poll + // and go to onSdIoEvent() handler, which calls processPendingEvent(). Viola. + // For external event loops that only have access to public sdbus-c++ API, processPendingEvent() + // is the only option to clear event fd (it comes at a little extra cost but on the other hand + // the solution is simpler for clients -- we don't provide an extra method for just clearing + // the event fd. There is one method for both fd's -- and that's processPendingEvent(). + + // Kept here so that potential readers know what to do in their custom external event loops. + //(void)connection->processPendingEvent(); + + connection->eventFd_.clear(); + + return 1; +} + +int Connection::onSdEventPrepare(sd_event_source */*s*/, void *userdata) +{ + auto connection = static_cast(userdata); + assert(connection != nullptr); + + auto sdbusPollData = connection->getEventLoopPollData(); + + // Set poll events to watch out for on I/O fd + auto* sdIoEventSource = static_cast(connection->sdEvent_->sdIoEventSource.get()); + auto r = sd_event_source_set_io_events(sdIoEventSource, sdbusPollData.events); + SDBUS_THROW_ERROR_IF(r < 0, "Failed to set poll events for IO event source", -r); + + // Set poll events to watch out for on internal event fd + auto* sdInternalEventSource = static_cast(connection->sdEvent_->sdInternalEventSource.get()); + r = sd_event_source_set_io_events(sdInternalEventSource, POLLIN); + SDBUS_THROW_ERROR_IF(r < 0, "Failed to set poll events for internal event source", -r); + + // Set current timeout to the time event source (it may be zero if there are messages in the sd-bus queues to be processed) + auto* sdTimeEventSource = static_cast(connection->sdEvent_->sdTimeEventSource.get()); + r = sd_event_source_set_time(sdTimeEventSource, static_cast(sdbusPollData.timeout.count())); + SDBUS_THROW_ERROR_IF(r < 0, "Failed to set timeout for time event source", -r); + r = sd_event_source_set_enabled(sdTimeEventSource, SD_EVENT_ON); + SDBUS_THROW_ERROR_IF(r < 0, "Failed to enable time event source", -r); + + return 1; +} + +void Connection::deleteSdEventSource(sd_event_source *s) +{ +#if LIBSYSTEMD_VERSION>=243 + sd_event_source_disable_unref(s); +#else + sd_event_source_set_enabled(s, SD_EVENT_OFF); + sd_event_source_unref(s); +#endif +} + +#endif // SDBUS_basu + Slot Connection::addObjectVTable( const std::string& objectPath , const std::string& interfaceName , const sd_bus_vtable* vtable diff --git a/src/Connection.h b/src/Connection.h index f288e399..b42c5748 100644 --- a/src/Connection.h +++ b/src/Connection.h @@ -38,6 +38,8 @@ #include #include +struct sd_event_source; + namespace sdbus::internal { class Connection final @@ -97,6 +99,10 @@ namespace sdbus::internal { [[nodiscard]] Slot addMatchAsync(const std::string& match, message_handler callback, message_handler installCallback) override; void addMatchAsync(const std::string& match, message_handler callback, message_handler installCallback, floating_slot_t) override; + void attachSdEventLoop(sd_event *event, int priority) override; + void detachSdEventLoop() override; + sd_event *getSdEventLoop() override; + const ISdBus& getSdBusInterface() const override; ISdBus& getSdBusInterface() override; @@ -162,6 +168,19 @@ namespace sdbus::internal { static int sdbus_match_install_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError); private: +#ifndef SDBUS_basu // sd_event integration is not supported if instead of libsystemd we are based on basu + Slot createSdEventSlot(sd_event *event); + Slot createSdTimeEventSourceSlot(sd_event *event, int priority); + Slot createSdIoEventSourceSlot(sd_event *event, int fd, int priority); + Slot createSdInternalEventSourceSlot(sd_event *event, int fd, int priority); + static void deleteSdEventSource(sd_event_source *s); + + static int onSdTimerEvent(sd_event_source *s, uint64_t usec, void *userdata); + static int onSdIoEvent(sd_event_source *s, int fd, uint32_t revents, void *userdata); + static int onSdInternalEvent(sd_event_source *s, int fd, uint32_t revents, void *userdata); + static int onSdEventPrepare(sd_event_source *s, void *userdata); +#endif + struct EventFd { EventFd(); @@ -180,6 +199,15 @@ namespace sdbus::internal { sd_bus_slot *slot; }; + // sd-event integration + struct SdEvent + { + Slot sdEvent; + Slot sdTimeEventSource; + Slot sdIoEventSource; + Slot sdInternalEventSource; + }; + private: std::unique_ptr sdbus_; BusPtr bus_; @@ -187,6 +215,7 @@ namespace sdbus::internal { EventFd loopExitFd_; // To wake up event loop I/O polling to exit EventFd eventFd_; // To wake up event loop I/O polling to re-enter poll with fresh PollData values std::vector floatingMatchRules_; + std::unique_ptr sdEvent_; // Integration of systemd sd-event event loop implementation }; } diff --git a/src/SdBus.cpp b/src/SdBus.cpp index b5a55251..728add2f 100644 --- a/src/SdBus.cpp +++ b/src/SdBus.cpp @@ -300,11 +300,13 @@ int SdBus::sd_bus_open_server(sd_bus **ret, int fd) int SdBus::sd_bus_open_system_remote(sd_bus **ret, const char *host) { -#ifdef SDBUS_basu +#ifndef SDBUS_basu + return ::sd_bus_open_system_remote(ret, host); +#else + (void)ret; + (void)host; // https://git.sr.ht/~emersion/basu/commit/01d33b244eb6 return -EOPNOTSUPP; -#else - return ::sd_bus_open_system_remote(ret, host); #endif } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b7a20e5c..fb082369 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -107,12 +107,21 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}) add_executable(sdbus-c++-unit-tests ${UNITTESTS_SRCS}) target_compile_definitions(sdbus-c++-unit-tests PRIVATE LIBSYSTEMD_VERSION=${LIBSYSTEMD_VERSION} + SDBUS_${LIBSYSTEMD_IMPL} SDBUS_HEADER=<${LIBSYSTEMD_IMPL}/sd-bus.h>) target_link_libraries(sdbus-c++-unit-tests sdbus-c++-objlib GTest::gmock) add_executable(sdbus-c++-integration-tests ${INTEGRATIONTESTS_SRCS}) -target_compile_definitions(sdbus-c++-integration-tests PRIVATE LIBSYSTEMD_VERSION=${LIBSYSTEMD_VERSION}) -target_link_libraries(sdbus-c++-integration-tests sdbus-c++ GTest::gmock) +target_compile_definitions(sdbus-c++-integration-tests PRIVATE + LIBSYSTEMD_VERSION=${LIBSYSTEMD_VERSION} + SDBUS_${LIBSYSTEMD}) +if(NOT LIBSYSTEMD STREQUAL "basu") + # Systemd::Libsystemd is included because integration tests use sd-event. Otherwise sdbus-c++ encapsulates and hides libsystemd. + target_link_libraries(sdbus-c++-integration-tests sdbus-c++ Systemd::Libsystemd GTest::gmock) +else() + # sd-event implementation is not part of basu, so its integration tests will be skipped + target_link_libraries(sdbus-c++-integration-tests sdbus-c++ GTest::gmock) +endif() # Manual performance and stress tests option(ENABLE_PERF_TESTS "Build and install manual performance tests (default OFF)" OFF) diff --git a/tests/integrationtests/DBusAsyncMethodsTests.cpp b/tests/integrationtests/DBusAsyncMethodsTests.cpp index 10766df4..20401357 100644 --- a/tests/integrationtests/DBusAsyncMethodsTests.cpp +++ b/tests/integrationtests/DBusAsyncMethodsTests.cpp @@ -49,20 +49,18 @@ using ::testing::SizeIs; using namespace std::chrono_literals; using namespace sdbus::test; -using SdbusTestObject = TestFixture; - /*-------------------------------------*/ /* -- TEST CASES -- */ /*-------------------------------------*/ -TEST_F(SdbusTestObject, ThrowsTimeoutErrorWhenClientSideAsyncMethodTimesOut) +TYPED_TEST(AsyncSdbusTestObject, ThrowsTimeoutErrorWhenClientSideAsyncMethodTimesOut) { std::chrono::time_point start; try { std::promise promise; auto future = promise.get_future(); - m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t res, const sdbus::Error* err) + this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t res, const sdbus::Error* err) { if (err == nullptr) promise.set_value(res); @@ -71,7 +69,7 @@ TEST_F(SdbusTestObject, ThrowsTimeoutErrorWhenClientSideAsyncMethodTimesOut) }); start = std::chrono::steady_clock::now(); - m_proxy->doOperationClientSideAsyncWithTimeout(1us, 1000); // The operation will take 1s, but the timeout is 500ms, so we should time out + this->m_proxy->doOperationClientSideAsyncWithTimeout(1us, (1s).count()); // The operation will take 1s, but the timeout is 1us, so we should time out future.get(); FAIL() << "Expected sdbus::Error exception"; @@ -89,7 +87,7 @@ TEST_F(SdbusTestObject, ThrowsTimeoutErrorWhenClientSideAsyncMethodTimesOut) } } -TEST_F(SdbusTestObject, RunsServerSideAsynchoronousMethodAsynchronously) +TYPED_TEST(AsyncSdbusTestObject, RunsServerSideAsynchoronousMethodAsynchronously) { // Yeah, this is kinda timing-dependent test, but times should be safe... std::mutex mtx; @@ -114,7 +112,7 @@ TEST_F(SdbusTestObject, RunsServerSideAsynchoronousMethodAsynchronously) ASSERT_THAT(results, ElementsAre(500, 1000, 1500)); } -TEST_F(SdbusTestObject, HandlesCorrectlyABulkOfParallelServerSideAsyncMethods) +TYPED_TEST(AsyncSdbusTestObject, HandlesCorrectlyABulkOfParallelServerSideAsyncMethods) { std::atomic resultCount{}; std::atomic invoke{}; @@ -144,11 +142,11 @@ TEST_F(SdbusTestObject, HandlesCorrectlyABulkOfParallelServerSideAsyncMethods) ASSERT_THAT(resultCount, Eq(1500)); } -TEST_F(SdbusTestObject, InvokesMethodAsynchronouslyOnClientSide) +TYPED_TEST(AsyncSdbusTestObject, InvokesMethodAsynchronouslyOnClientSide) { std::promise promise; auto future = promise.get_future(); - m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t res, const sdbus::Error* err) + this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t res, const sdbus::Error* err) { if (err == nullptr) promise.set_value(res); @@ -156,21 +154,21 @@ TEST_F(SdbusTestObject, InvokesMethodAsynchronouslyOnClientSide) promise.set_exception(std::make_exception_ptr(*err)); }); - m_proxy->doOperationClientSideAsync(100); + this->m_proxy->doOperationClientSideAsync(100); ASSERT_THAT(future.get(), Eq(100)); } -TEST_F(SdbusTestObject, InvokesMethodAsynchronouslyOnClientSideWithFuture) +TYPED_TEST(AsyncSdbusTestObject, InvokesMethodAsynchronouslyOnClientSideWithFuture) { - auto future = m_proxy->doOperationClientSideAsync(100, sdbus::with_future); + auto future = this->m_proxy->doOperationClientSideAsync(100, sdbus::with_future); ASSERT_THAT(future.get(), Eq(100)); } -TEST_F(SdbusTestObject, InvokesMethodAsynchronouslyOnClientSideWithFutureOnBasicAPILevel) +TYPED_TEST(AsyncSdbusTestObject, InvokesMethodAsynchronouslyOnClientSideWithFutureOnBasicAPILevel) { - auto future = m_proxy->doOperationClientSideAsyncOnBasicAPILevel(100); + auto future = this->m_proxy->doOperationClientSideAsyncOnBasicAPILevel(100); auto methodReply = future.get(); uint32_t returnValue{}; @@ -179,72 +177,72 @@ TEST_F(SdbusTestObject, InvokesMethodAsynchronouslyOnClientSideWithFutureOnBasic ASSERT_THAT(returnValue, Eq(100)); } -TEST_F(SdbusTestObject, AnswersThatAsyncCallIsPendingIfItIsInProgress) +TYPED_TEST(AsyncSdbusTestObject, AnswersThatAsyncCallIsPendingIfItIsInProgress) { - m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t /*res*/, const sdbus::Error* /*err*/){}); + this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t /*res*/, const sdbus::Error* /*err*/){}); - auto call = m_proxy->doOperationClientSideAsync(100); + auto call = this->m_proxy->doOperationClientSideAsync(100); ASSERT_TRUE(call.isPending()); } -TEST_F(SdbusTestObject, CancelsPendingAsyncCallOnClientSide) +TYPED_TEST(AsyncSdbusTestObject, CancelsPendingAsyncCallOnClientSide) { std::promise promise; auto future = promise.get_future(); - m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t /*res*/, const sdbus::Error* /*err*/){ promise.set_value(1); }); - auto call = m_proxy->doOperationClientSideAsync(100); + this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t /*res*/, const sdbus::Error* /*err*/){ promise.set_value(1); }); + auto call = this->m_proxy->doOperationClientSideAsync(100); call.cancel(); ASSERT_THAT(future.wait_for(300ms), Eq(std::future_status::timeout)); } -TEST_F(SdbusTestObject, AnswersThatAsyncCallIsNotPendingAfterItHasBeenCancelled) +TYPED_TEST(AsyncSdbusTestObject, AnswersThatAsyncCallIsNotPendingAfterItHasBeenCancelled) { std::promise promise; auto future = promise.get_future(); - m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t /*res*/, const sdbus::Error* /*err*/){ promise.set_value(1); }); - auto call = m_proxy->doOperationClientSideAsync(100); + this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t /*res*/, const sdbus::Error* /*err*/){ promise.set_value(1); }); + auto call = this->m_proxy->doOperationClientSideAsync(100); call.cancel(); ASSERT_FALSE(call.isPending()); } -TEST_F(SdbusTestObject, AnswersThatAsyncCallIsNotPendingAfterItHasBeenCompleted) +TYPED_TEST(AsyncSdbusTestObject, AnswersThatAsyncCallIsNotPendingAfterItHasBeenCompleted) { std::promise promise; auto future = promise.get_future(); - m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t /*res*/, const sdbus::Error* /*err*/){ promise.set_value(1); }); + this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t /*res*/, const sdbus::Error* /*err*/){ promise.set_value(1); }); - auto call = m_proxy->doOperationClientSideAsync(0); + auto call = this->m_proxy->doOperationClientSideAsync(0); (void) future.get(); // Wait for the call to finish ASSERT_TRUE(waitUntil([&call](){ return !call.isPending(); })); } -TEST_F(SdbusTestObject, AnswersThatDefaultConstructedAsyncCallIsNotPending) +TYPED_TEST(AsyncSdbusTestObject, AnswersThatDefaultConstructedAsyncCallIsNotPending) { sdbus::PendingAsyncCall call; ASSERT_FALSE(call.isPending()); } -TEST_F(SdbusTestObject, SupportsAsyncCallCopyAssignment) +TYPED_TEST(AsyncSdbusTestObject, SupportsAsyncCallCopyAssignment) { sdbus::PendingAsyncCall call; - call = m_proxy->doOperationClientSideAsync(100); + call = this->m_proxy->doOperationClientSideAsync(100); ASSERT_TRUE(call.isPending()); } -TEST_F(SdbusTestObject, ReturnsNonnullErrorWhenAsynchronousMethodCallFails) +TYPED_TEST(AsyncSdbusTestObject, ReturnsNonnullErrorWhenAsynchronousMethodCallFails) { std::promise promise; auto future = promise.get_future(); - m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t res, const sdbus::Error* err) + this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t res, const sdbus::Error* err) { if (err == nullptr) promise.set_value(res); @@ -252,14 +250,14 @@ TEST_F(SdbusTestObject, ReturnsNonnullErrorWhenAsynchronousMethodCallFails) promise.set_exception(std::make_exception_ptr(*err)); }); - m_proxy->doErroneousOperationClientSideAsync(); + this->m_proxy->doErroneousOperationClientSideAsync(); ASSERT_THROW(future.get(), sdbus::Error); } -TEST_F(SdbusTestObject, ThrowsErrorWhenClientSideAsynchronousMethodCallWithFutureFails) +TYPED_TEST(AsyncSdbusTestObject, ThrowsErrorWhenClientSideAsynchronousMethodCallWithFutureFails) { - auto future = m_proxy->doErroneousOperationClientSideAsync(sdbus::with_future); + auto future = this->m_proxy->doErroneousOperationClientSideAsync(sdbus::with_future); ASSERT_THROW(future.get(), sdbus::Error); } diff --git a/tests/integrationtests/DBusGeneralTests.cpp b/tests/integrationtests/DBusGeneralTests.cpp index b55c68a7..c14e72e4 100644 --- a/tests/integrationtests/DBusGeneralTests.cpp +++ b/tests/integrationtests/DBusGeneralTests.cpp @@ -45,7 +45,6 @@ using ::testing::Eq; using namespace std::chrono_literals; using namespace sdbus::test; -using AConnection = TestFixture; using ADirectConnection = TestFixtureWithDirectConnection; /*-------------------------------------*/ @@ -73,61 +72,61 @@ TEST(AnAdaptor, SupportsMoveSemantics) static_assert(std::is_move_assignable_v); } -TEST_F(AConnection, WillCallCallbackHandlerForIncomingMessageMatchingMatchRule) +TYPED_TEST(AConnection, WillCallCallbackHandlerForIncomingMessageMatchingMatchRule) { auto matchRule = "sender='" + BUS_NAME + "',path='" + OBJECT_PATH + "'"; std::atomic matchingMessageReceived{false}; - auto slot = s_proxyConnection->addMatch(matchRule, [&](sdbus::Message& msg) + auto slot = this->s_proxyConnection->addMatch(matchRule, [&](sdbus::Message& msg) { if(msg.getPath() == OBJECT_PATH) matchingMessageReceived = true; }); - m_adaptor->emitSimpleSignal(); + this->m_adaptor->emitSimpleSignal(); ASSERT_TRUE(waitUntil(matchingMessageReceived)); } -TEST_F(AConnection, CanInstallMatchRuleAsynchronously) +TYPED_TEST(AConnection, CanInstallMatchRuleAsynchronously) { auto matchRule = "sender='" + BUS_NAME + "',path='" + OBJECT_PATH + "'"; std::atomic matchingMessageReceived{false}; std::atomic matchRuleInstalled{false}; - auto slot = s_proxyConnection->addMatchAsync( matchRule - , [&](sdbus::Message& msg) - { - if(msg.getPath() == OBJECT_PATH) - matchingMessageReceived = true; - } - , [&](sdbus::Message& /*msg*/) - { - matchRuleInstalled = true; - } ); + auto slot = this->s_proxyConnection->addMatchAsync( matchRule + , [&](sdbus::Message& msg) + { + if(msg.getPath() == OBJECT_PATH) + matchingMessageReceived = true; + } + , [&](sdbus::Message& /*msg*/) + { + matchRuleInstalled = true; + } ); EXPECT_TRUE(waitUntil(matchRuleInstalled)); - m_adaptor->emitSimpleSignal(); + this->m_adaptor->emitSimpleSignal(); ASSERT_TRUE(waitUntil(matchingMessageReceived)); } -TEST_F(AConnection, WillUnsubscribeMatchRuleWhenClientDestroysTheAssociatedSlot) +TYPED_TEST(AConnection, WillUnsubscribeMatchRuleWhenClientDestroysTheAssociatedSlot) { auto matchRule = "sender='" + BUS_NAME + "',path='" + OBJECT_PATH + "'"; std::atomic matchingMessageReceived{false}; - auto slot = s_proxyConnection->addMatch(matchRule, [&](sdbus::Message& msg) + auto slot = this->s_proxyConnection->addMatch(matchRule, [&](sdbus::Message& msg) { if(msg.getPath() == OBJECT_PATH) matchingMessageReceived = true; }); slot.reset(); - m_adaptor->emitSimpleSignal(); + this->m_adaptor->emitSimpleSignal(); - ASSERT_FALSE(waitUntil(matchingMessageReceived, 2s)); + ASSERT_FALSE(waitUntil(matchingMessageReceived, 1s)); } -TEST_F(AConnection, CanAddFloatingMatchRule) +TYPED_TEST(AConnection, CanAddFloatingMatchRule) { auto matchRule = "sender='" + BUS_NAME + "',path='" + OBJECT_PATH + "'"; std::atomic matchingMessageReceived{false}; @@ -139,31 +138,31 @@ TEST_F(AConnection, CanAddFloatingMatchRule) matchingMessageReceived = true; }; con->addMatch(matchRule, std::move(callback), sdbus::floating_slot); - m_adaptor->emitSimpleSignal(); + this->m_adaptor->emitSimpleSignal(); [[maybe_unused]] auto gotMessage = waitUntil(matchingMessageReceived, 2s); assert(gotMessage); matchingMessageReceived = false; con.reset(); - m_adaptor->emitSimpleSignal(); + this->m_adaptor->emitSimpleSignal(); - ASSERT_FALSE(waitUntil(matchingMessageReceived, 2s)); + ASSERT_FALSE(waitUntil(matchingMessageReceived, 1s)); } -TEST_F(AConnection, WillNotPassToMatchCallbackMessagesThatDoNotMatchTheRule) +TYPED_TEST(AConnection, WillNotPassToMatchCallbackMessagesThatDoNotMatchTheRule) { auto matchRule = "type='signal',interface='" + INTERFACE_NAME + "',member='simpleSignal'"; std::atomic numberOfMatchingMessages{}; - auto slot = s_proxyConnection->addMatch(matchRule, [&](sdbus::Message& msg) + auto slot = this->s_proxyConnection->addMatch(matchRule, [&](sdbus::Message& msg) { if(msg.getMemberName() == "simpleSignal") numberOfMatchingMessages++; }); - auto adaptor2 = std::make_unique(*s_adaptorConnection, OBJECT_PATH_2); + auto adaptor2 = std::make_unique(*this->s_adaptorConnection, OBJECT_PATH_2); - m_adaptor->emitSignalWithMap({}); + this->m_adaptor->emitSignalWithMap({}); adaptor2->emitSimpleSignal(); - m_adaptor->emitSimpleSignal(); + this->m_adaptor->emitSimpleSignal(); ASSERT_TRUE(waitUntil([&](){ return numberOfMatchingMessages == 2; })); ASSERT_FALSE(waitUntil([&](){ return numberOfMatchingMessages > 2; }, 1s)); diff --git a/tests/integrationtests/DBusMethodsTests.cpp b/tests/integrationtests/DBusMethodsTests.cpp index 7e852592..8d712b7c 100644 --- a/tests/integrationtests/DBusMethodsTests.cpp +++ b/tests/integrationtests/DBusMethodsTests.cpp @@ -50,66 +50,64 @@ using ::testing::NotNull; using namespace std::chrono_literals; using namespace sdbus::test; -using SdbusTestObject = TestFixture; - /*-------------------------------------*/ /* -- TEST CASES -- */ /*-------------------------------------*/ -TEST_F(SdbusTestObject, CallsEmptyMethodSuccesfully) +TYPED_TEST(SdbusTestObject, CallsEmptyMethodSuccesfully) { - ASSERT_NO_THROW(m_proxy->noArgNoReturn()); + ASSERT_NO_THROW(this->m_proxy->noArgNoReturn()); } -TEST_F(SdbusTestObject, CallsMethodsWithBaseTypesSuccesfully) +TYPED_TEST(SdbusTestObject, CallsMethodsWithBaseTypesSuccesfully) { - auto resInt = m_proxy->getInt(); + auto resInt = this->m_proxy->getInt(); ASSERT_THAT(resInt, Eq(INT32_VALUE)); - auto multiplyRes = m_proxy->multiply(INT64_VALUE, DOUBLE_VALUE); + auto multiplyRes = this->m_proxy->multiply(INT64_VALUE, DOUBLE_VALUE); ASSERT_THAT(multiplyRes, Eq(INT64_VALUE * DOUBLE_VALUE)); } -TEST_F(SdbusTestObject, CallsMethodsWithTuplesSuccesfully) +TYPED_TEST(SdbusTestObject, CallsMethodsWithTuplesSuccesfully) { - auto resTuple = m_proxy->getTuple(); + auto resTuple = this->m_proxy->getTuple(); ASSERT_THAT(std::get<0>(resTuple), Eq(UINT32_VALUE)); ASSERT_THAT(std::get<1>(resTuple), Eq(STRING_VALUE)); } -TEST_F(SdbusTestObject, CallsMethodsWithStructSuccesfully) +TYPED_TEST(SdbusTestObject, CallsMethodsWithStructSuccesfully) { sdbus::Struct> a{}; - auto vectorRes = m_proxy->getInts16FromStruct(a); + auto vectorRes = this->m_proxy->getInts16FromStruct(a); ASSERT_THAT(vectorRes, Eq(std::vector{0})); // because second item is by default initialized to 0 sdbus::Struct> b{ UINT8_VALUE, INT16_VALUE, DOUBLE_VALUE, STRING_VALUE, {INT16_VALUE, -INT16_VALUE} }; - vectorRes = m_proxy->getInts16FromStruct(b); + vectorRes = this->m_proxy->getInts16FromStruct(b); ASSERT_THAT(vectorRes, Eq(std::vector{INT16_VALUE, INT16_VALUE, -INT16_VALUE})); } -TEST_F(SdbusTestObject, CallsMethodWithVariantSuccesfully) +TYPED_TEST(SdbusTestObject, CallsMethodWithVariantSuccesfully) { sdbus::Variant v{DOUBLE_VALUE}; - auto variantRes = m_proxy->processVariant(v); + sdbus::Variant variantRes = this->m_proxy->processVariant(v); ASSERT_THAT(variantRes.get(), Eq(static_cast(DOUBLE_VALUE))); } -TEST_F(SdbusTestObject, CallsMethodWithStdVariantSuccesfully) +TYPED_TEST(SdbusTestObject, CallsMethodWithStdVariantSuccesfully) { std::variant v{DOUBLE_VALUE}; - auto variantRes = m_proxy->processVariant(v); + auto variantRes = this->m_proxy->processVariant(v); ASSERT_THAT(std::get(variantRes), Eq(static_cast(DOUBLE_VALUE))); } -TEST_F(SdbusTestObject, CallsMethodWithStructVariantsAndGetMapSuccesfully) +TYPED_TEST(SdbusTestObject, CallsMethodWithStructVariantsAndGetMapSuccesfully) { std::vector x{-2, 0, 2}; sdbus::Struct y{false, true}; - auto mapOfVariants = m_proxy->getMapOfVariants(x, y); + std::map mapOfVariants = this->m_proxy->getMapOfVariants(x, y); decltype(mapOfVariants) res{ {sdbus::Variant{-2}, sdbus::Variant{false}} , {sdbus::Variant{0}, sdbus::Variant{false}} , {sdbus::Variant{2}, sdbus::Variant{true}}}; @@ -119,69 +117,69 @@ TEST_F(SdbusTestObject, CallsMethodWithStructVariantsAndGetMapSuccesfully) ASSERT_THAT(mapOfVariants[2].get(), Eq(res[2].get())); } -TEST_F(SdbusTestObject, CallsMethodWithStructInStructSuccesfully) +TYPED_TEST(SdbusTestObject, CallsMethodWithStructInStructSuccesfully) { - auto val = m_proxy->getStructInStruct(); - ASSERT_THAT(val.get<0>(), Eq(STRING_VALUE)); + auto val = this->m_proxy->getStructInStruct(); + ASSERT_THAT(val.template get<0>(), Eq(STRING_VALUE)); ASSERT_THAT(std::get<0>(std::get<1>(val))[INT32_VALUE], Eq(INT32_VALUE)); } -TEST_F(SdbusTestObject, CallsMethodWithTwoStructsSuccesfully) +TYPED_TEST(SdbusTestObject, CallsMethodWithTwoStructsSuccesfully) { - auto val = m_proxy->sumStructItems({1, 2}, {3, 4}); + auto val = this->m_proxy->sumStructItems({1, 2}, {3, 4}); ASSERT_THAT(val, Eq(1 + 2 + 3 + 4)); } -TEST_F(SdbusTestObject, CallsMethodWithTwoVectorsSuccesfully) +TYPED_TEST(SdbusTestObject, CallsMethodWithTwoVectorsSuccesfully) { - auto val = m_proxy->sumArrayItems({1, 7}, {2, 3, 4}); + auto val = this->m_proxy->sumArrayItems({1, 7}, {2, 3, 4}); ASSERT_THAT(val, Eq(1 + 7 + 2 + 3 + 4)); } -TEST_F(SdbusTestObject, CallsMethodWithSignatureSuccesfully) +TYPED_TEST(SdbusTestObject, CallsMethodWithSignatureSuccesfully) { - auto resSignature = m_proxy->getSignature(); + auto resSignature = this->m_proxy->getSignature(); ASSERT_THAT(resSignature, Eq(SIGNATURE_VALUE)); } -TEST_F(SdbusTestObject, CallsMethodWithObjectPathSuccesfully) +TYPED_TEST(SdbusTestObject, CallsMethodWithObjectPathSuccesfully) { - auto resObjectPath = m_proxy->getObjPath(); + auto resObjectPath = this->m_proxy->getObjPath(); ASSERT_THAT(resObjectPath, Eq(OBJECT_PATH_VALUE)); } -TEST_F(SdbusTestObject, CallsMethodWithUnixFdSuccesfully) +TYPED_TEST(SdbusTestObject, CallsMethodWithUnixFdSuccesfully) { - auto resUnixFd = m_proxy->getUnixFd(); + auto resUnixFd = this->m_proxy->getUnixFd(); ASSERT_THAT(resUnixFd.get(), Gt(UNIX_FD_VALUE)); } -TEST_F(SdbusTestObject, CallsMethodWithComplexTypeSuccesfully) +TYPED_TEST(SdbusTestObject, CallsMethodWithComplexTypeSuccesfully) { - auto resComplex = m_proxy->getComplex(); + auto resComplex = this->m_proxy->getComplex(); ASSERT_THAT(resComplex.count(0), Eq(1)); } -TEST_F(SdbusTestObject, CallsMultiplyMethodWithNoReplyFlag) +TYPED_TEST(SdbusTestObject, CallsMultiplyMethodWithNoReplyFlag) { - m_proxy->multiplyWithNoReply(INT64_VALUE, DOUBLE_VALUE); + this->m_proxy->multiplyWithNoReply(INT64_VALUE, DOUBLE_VALUE); - ASSERT_TRUE(waitUntil(m_adaptor->m_wasMultiplyCalled)); - ASSERT_THAT(m_adaptor->m_multiplyResult, Eq(INT64_VALUE * DOUBLE_VALUE)); + ASSERT_TRUE(waitUntil(this->m_adaptor->m_wasMultiplyCalled)); + ASSERT_THAT(this->m_adaptor->m_multiplyResult, Eq(INT64_VALUE * DOUBLE_VALUE)); } -TEST_F(SdbusTestObject, CallsMethodWithCustomTimeoutSuccessfully) +TYPED_TEST(SdbusTestObject, CallsMethodWithCustomTimeoutSuccessfully) { - auto res = m_proxy->doOperationWithTimeout(500ms, 20); // The operation will take 20ms, but the timeout is 500ms, so we are fine + auto res = this->m_proxy->doOperationWithTimeout(500ms, (20ms).count()); // The operation will take 20ms, but the timeout is 500ms, so we are fine ASSERT_THAT(res, Eq(20)); } -TEST_F(SdbusTestObject, ThrowsTimeoutErrorWhenMethodTimesOut) +TYPED_TEST(SdbusTestObject, ThrowsTimeoutErrorWhenMethodTimesOut) { auto start = std::chrono::steady_clock::now(); try { - m_proxy->doOperationWithTimeout(1us, 1000); // The operation will take 1s, but the timeout is 1us, so we should time out + this->m_proxy->doOperationWithTimeout(1us, (1s).count()); // The operation will take 1s, but the timeout is 1us, so we should time out FAIL() << "Expected sdbus::Error exception"; } catch (const sdbus::Error& e) @@ -197,11 +195,11 @@ TEST_F(SdbusTestObject, ThrowsTimeoutErrorWhenMethodTimesOut) } } -TEST_F(SdbusTestObject, CallsMethodThatThrowsError) +TYPED_TEST(SdbusTestObject, CallsMethodThatThrowsError) { try { - m_proxy->throwError(); + this->m_proxy->throwError(); FAIL() << "Expected sdbus::Error exception"; } catch (const sdbus::Error& e) @@ -215,74 +213,74 @@ TEST_F(SdbusTestObject, CallsMethodThatThrowsError) } } -TEST_F(SdbusTestObject, CallsErrorThrowingMethodWithDontExpectReplySet) +TYPED_TEST(SdbusTestObject, CallsErrorThrowingMethodWithDontExpectReplySet) { - ASSERT_NO_THROW(m_proxy->throwErrorWithNoReply()); + ASSERT_NO_THROW(this->m_proxy->throwErrorWithNoReply()); - ASSERT_TRUE(waitUntil(m_adaptor->m_wasThrowErrorCalled)); + ASSERT_TRUE(waitUntil(this->m_adaptor->m_wasThrowErrorCalled)); } -TEST_F(SdbusTestObject, FailsCallingNonexistentMethod) +TYPED_TEST(SdbusTestObject, FailsCallingNonexistentMethod) { - ASSERT_THROW(m_proxy->callNonexistentMethod(), sdbus::Error); + ASSERT_THROW(this->m_proxy->callNonexistentMethod(), sdbus::Error); } -TEST_F(SdbusTestObject, FailsCallingMethodOnNonexistentInterface) +TYPED_TEST(SdbusTestObject, FailsCallingMethodOnNonexistentInterface) { - ASSERT_THROW(m_proxy->callMethodOnNonexistentInterface(), sdbus::Error); + ASSERT_THROW(this->m_proxy->callMethodOnNonexistentInterface(), sdbus::Error); } -TEST_F(SdbusTestObject, FailsCallingMethodOnNonexistentDestination) +TYPED_TEST(SdbusTestObject, FailsCallingMethodOnNonexistentDestination) { TestProxy proxy("sdbuscpp.destination.that.does.not.exist", OBJECT_PATH); ASSERT_THROW(proxy.getInt(), sdbus::Error); } -TEST_F(SdbusTestObject, FailsCallingMethodOnNonexistentObject) +TYPED_TEST(SdbusTestObject, FailsCallingMethodOnNonexistentObject) { TestProxy proxy(BUS_NAME, "/sdbuscpp/path/that/does/not/exist"); ASSERT_THROW(proxy.getInt(), sdbus::Error); } -TEST_F(SdbusTestObject, CanReceiveSignalWhileMakingMethodCall) +TYPED_TEST(SdbusTestObject, CanReceiveSignalWhileMakingMethodCall) { - m_proxy->emitTwoSimpleSignals(); + this->m_proxy->emitTwoSimpleSignals(); - ASSERT_TRUE(waitUntil(m_proxy->m_gotSimpleSignal)); - ASSERT_TRUE(waitUntil(m_proxy->m_gotSignalWithMap)); + EXPECT_TRUE(waitUntil(this->m_proxy->m_gotSimpleSignal)); + EXPECT_TRUE(waitUntil(this->m_proxy->m_gotSignalWithMap)); } -TEST_F(SdbusTestObject, CanAccessAssociatedMethodCallMessageInMethodCallHandler) +TYPED_TEST(SdbusTestObject, CanAccessAssociatedMethodCallMessageInMethodCallHandler) { - m_proxy->doOperation(10); // This will save pointer to method call message on server side + this->m_proxy->doOperation(10); // This will save pointer to method call message on server side - ASSERT_THAT(m_adaptor->m_methodCallMsg, NotNull()); - ASSERT_THAT(m_adaptor->m_methodCallMemberName, Eq("doOperation")); + ASSERT_THAT(this->m_adaptor->m_methodCallMsg, NotNull()); + ASSERT_THAT(this->m_adaptor->m_methodCallMemberName, Eq("doOperation")); } -TEST_F(SdbusTestObject, CanAccessAssociatedMethodCallMessageInAsyncMethodCallHandler) +TYPED_TEST(SdbusTestObject, CanAccessAssociatedMethodCallMessageInAsyncMethodCallHandler) { - m_proxy->doOperationAsync(10); // This will save pointer to method call message on server side + this->m_proxy->doOperationAsync(10); // This will save pointer to method call message on server side - ASSERT_THAT(m_adaptor->m_methodCallMsg, NotNull()); - ASSERT_THAT(m_adaptor->m_methodCallMemberName, Eq("doOperationAsync")); + ASSERT_THAT(this->m_adaptor->m_methodCallMsg, NotNull()); + ASSERT_THAT(this->m_adaptor->m_methodCallMemberName, Eq("doOperationAsync")); } #if LIBSYSTEMD_VERSION>=240 -TEST_F(SdbusTestObject, CanSetGeneralMethodTimeoutWithLibsystemdVersionGreaterThan239) +TYPED_TEST(SdbusTestObject, CanSetGeneralMethodTimeoutWithLibsystemdVersionGreaterThan239) { - s_adaptorConnection->setMethodCallTimeout(5000000); - ASSERT_THAT(s_adaptorConnection->getMethodCallTimeout(), Eq(5000000)); + this->s_adaptorConnection->setMethodCallTimeout(5000000); + ASSERT_THAT(this->s_adaptorConnection->getMethodCallTimeout(), Eq(5000000)); } #else -TEST_F(SdbusTestObject, CannotSetGeneralMethodTimeoutWithLibsystemdVersionLessThan240) +TYPED_TEST(SdbusTestObject, CannotSetGeneralMethodTimeoutWithLibsystemdVersionLessThan240) { - ASSERT_THROW(s_adaptorConnection->setMethodCallTimeout(5000000), sdbus::Error); - ASSERT_THROW(s_adaptorConnection->getMethodCallTimeout(), sdbus::Error); + ASSERT_THROW(this->s_adaptorConnection->setMethodCallTimeout(5000000), sdbus::Error); + ASSERT_THROW(this->s_adaptorConnection->getMethodCallTimeout(), sdbus::Error); } #endif -TEST_F(SdbusTestObject, CanCallMethodSynchronouslyWithoutAnEventLoopThread) +TYPED_TEST(SdbusTestObject, CanCallMethodSynchronouslyWithoutAnEventLoopThread) { #if defined(__clang__) && defined(__FreeBSD__) GTEST_SKIP() << "https://github.com/Kistler-Group/sdbus-cpp/issues/359"; diff --git a/tests/integrationtests/DBusPropertiesTests.cpp b/tests/integrationtests/DBusPropertiesTests.cpp index b98bbc01..f65d1916 100644 --- a/tests/integrationtests/DBusPropertiesTests.cpp +++ b/tests/integrationtests/DBusPropertiesTests.cpp @@ -51,35 +51,33 @@ using ::testing::IsEmpty; using namespace std::chrono_literals; using namespace sdbus::test; -using SdbusTestObject = TestFixture; - /*-------------------------------------*/ /* -- TEST CASES -- */ /*-------------------------------------*/ -TEST_F(SdbusTestObject, ReadsReadOnlyPropertySuccesfully) +TYPED_TEST(SdbusTestObject, ReadsReadOnlyPropertySuccesfully) { - ASSERT_THAT(m_proxy->state(), Eq(DEFAULT_STATE_VALUE)); + ASSERT_THAT(this->m_proxy->state(), Eq(DEFAULT_STATE_VALUE)); } -TEST_F(SdbusTestObject, FailsWritingToReadOnlyProperty) +TYPED_TEST(SdbusTestObject, FailsWritingToReadOnlyProperty) { - ASSERT_THROW(m_proxy->setStateProperty("new_value"), sdbus::Error); + ASSERT_THROW(this->m_proxy->setStateProperty("new_value"), sdbus::Error); } -TEST_F(SdbusTestObject, WritesAndReadsReadWritePropertySuccesfully) +TYPED_TEST(SdbusTestObject, WritesAndReadsReadWritePropertySuccesfully) { uint32_t newActionValue = 5678; - m_proxy->action(newActionValue); + this->m_proxy->action(newActionValue); - ASSERT_THAT(m_proxy->action(), Eq(newActionValue)); + ASSERT_THAT(this->m_proxy->action(), Eq(newActionValue)); } -TEST_F(SdbusTestObject, CanAccessAssociatedPropertySetMessageInPropertySetHandler) +TYPED_TEST(SdbusTestObject, CanAccessAssociatedPropertySetMessageInPropertySetHandler) { - m_proxy->blocking(true); // This will save pointer to property get message on server side + this->m_proxy->blocking(true); // This will save pointer to property get message on server side - ASSERT_THAT(m_adaptor->m_propertySetMsg, NotNull()); - ASSERT_THAT(m_adaptor->m_propertySetSender, Not(IsEmpty())); + ASSERT_THAT(this->m_adaptor->m_propertySetMsg, NotNull()); + ASSERT_THAT(this->m_adaptor->m_propertySetSender, Not(IsEmpty())); } diff --git a/tests/integrationtests/DBusSignalsTests.cpp b/tests/integrationtests/DBusSignalsTests.cpp index 487ad247..c3575eea 100644 --- a/tests/integrationtests/DBusSignalsTests.cpp +++ b/tests/integrationtests/DBusSignalsTests.cpp @@ -44,32 +44,30 @@ using ::testing::NotNull; using namespace std::chrono_literals; using namespace sdbus::test; -using SdbusTestObject = TestFixture; - /*-------------------------------------*/ /* -- TEST CASES -- */ /*-------------------------------------*/ -TEST_F(SdbusTestObject, EmitsSimpleSignalSuccesfully) +TYPED_TEST(SdbusTestObject, EmitsSimpleSignalSuccesfully) { - m_adaptor->emitSimpleSignal(); + this->m_adaptor->emitSimpleSignal(); - ASSERT_TRUE(waitUntil(m_proxy->m_gotSimpleSignal)); + ASSERT_TRUE(waitUntil(this->m_proxy->m_gotSimpleSignal)); } -TEST_F(SdbusTestObject, EmitsSimpleSignalToMultipleProxiesSuccesfully) +TYPED_TEST(SdbusTestObject, EmitsSimpleSignalToMultipleProxiesSuccesfully) { - auto proxy1 = std::make_unique(*s_adaptorConnection, BUS_NAME, OBJECT_PATH); - auto proxy2 = std::make_unique(*s_adaptorConnection, BUS_NAME, OBJECT_PATH); + auto proxy1 = std::make_unique(*this->s_adaptorConnection, BUS_NAME, OBJECT_PATH); + auto proxy2 = std::make_unique(*this->s_adaptorConnection, BUS_NAME, OBJECT_PATH); - m_adaptor->emitSimpleSignal(); + this->m_adaptor->emitSimpleSignal(); - ASSERT_TRUE(waitUntil(m_proxy->m_gotSimpleSignal)); + ASSERT_TRUE(waitUntil(this->m_proxy->m_gotSimpleSignal)); ASSERT_TRUE(waitUntil(proxy1->m_gotSimpleSignal)); ASSERT_TRUE(waitUntil(proxy2->m_gotSimpleSignal)); } -TEST_F(SdbusTestObject, ProxyDoesNotReceiveSignalFromOtherBusName) +TYPED_TEST(SdbusTestObject, ProxyDoesNotReceiveSignalFromOtherBusName) { auto otherBusName = BUS_NAME + "2"; auto connection2 = sdbus::createConnection(otherBusName); @@ -77,93 +75,94 @@ TEST_F(SdbusTestObject, ProxyDoesNotReceiveSignalFromOtherBusName) adaptor2->emitSimpleSignal(); - ASSERT_FALSE(waitUntil(m_proxy->m_gotSimpleSignal, 2s)); + ASSERT_FALSE(waitUntil(this->m_proxy->m_gotSimpleSignal, 1s)); } -TEST_F(SdbusTestObject, EmitsSignalWithMapSuccesfully) +TYPED_TEST(SdbusTestObject, EmitsSignalWithMapSuccesfully) { - m_adaptor->emitSignalWithMap({{0, "zero"}, {1, "one"}}); + this->m_adaptor->emitSignalWithMap({{0, "zero"}, {1, "one"}}); - ASSERT_TRUE(waitUntil(m_proxy->m_gotSignalWithMap)); - ASSERT_THAT(m_proxy->m_mapFromSignal[0], Eq("zero")); - ASSERT_THAT(m_proxy->m_mapFromSignal[1], Eq("one")); + ASSERT_TRUE(waitUntil(this->m_proxy->m_gotSignalWithMap)); + ASSERT_THAT(this->m_proxy->m_mapFromSignal[0], Eq("zero")); + ASSERT_THAT(this->m_proxy->m_mapFromSignal[1], Eq("one")); } -TEST_F(SdbusTestObject, EmitsSignalWithLargeMapSuccesfully) +TYPED_TEST(SdbusTestObject, EmitsSignalWithLargeMapSuccesfully) { std::map largeMap; for (int32_t i = 0; i < 20'000; ++i) largeMap.emplace(i, "This is string nr. " + std::to_string(i+1)); - m_adaptor->emitSignalWithMap(largeMap); + this->m_adaptor->emitSignalWithMap(largeMap); - ASSERT_TRUE(waitUntil(m_proxy->m_gotSignalWithMap)); - ASSERT_THAT(m_proxy->m_mapFromSignal[0], Eq("This is string nr. 1")); - ASSERT_THAT(m_proxy->m_mapFromSignal[1], Eq("This is string nr. 2")); + ASSERT_TRUE(waitUntil(this->m_proxy->m_gotSignalWithMap)); + ASSERT_THAT(this->m_proxy->m_mapFromSignal[0], Eq("This is string nr. 1")); + ASSERT_THAT(this->m_proxy->m_mapFromSignal[1], Eq("This is string nr. 2")); } -TEST_F(SdbusTestObject, EmitsSignalWithVariantSuccesfully) +TYPED_TEST(SdbusTestObject, EmitsSignalWithVariantSuccesfully) { double d = 3.14; - m_adaptor->emitSignalWithVariant(sdbus::Variant{d}); + this->m_adaptor->emitSignalWithVariant(sdbus::Variant{d}); + this->m_adaptor->emitSignalWithVariant(d); - ASSERT_TRUE(waitUntil(m_proxy->m_gotSignalWithVariant)); - ASSERT_THAT(m_proxy->m_variantFromSignal, DoubleEq(d)); + ASSERT_TRUE(waitUntil(this->m_proxy->m_gotSignalWithVariant)); + ASSERT_THAT(this->m_proxy->m_variantFromSignal, DoubleEq(d)); } -TEST_F(SdbusTestObject, EmitsSignalWithoutRegistrationSuccesfully) +TYPED_TEST(SdbusTestObject, EmitsSignalWithoutRegistrationSuccesfully) { - m_adaptor->emitSignalWithoutRegistration({"platform", {"av"}}); + this->m_adaptor->emitSignalWithoutRegistration({"platform", {"av"}}); - ASSERT_TRUE(waitUntil(m_proxy->m_gotSignalWithSignature)); - ASSERT_THAT(m_proxy->m_signatureFromSignal["platform"], Eq("av")); + ASSERT_TRUE(waitUntil(this->m_proxy->m_gotSignalWithSignature)); + ASSERT_THAT(this->m_proxy->m_signatureFromSignal["platform"], Eq("av")); } -TEST_F(SdbusTestObject, CanAccessAssociatedSignalMessageInSignalHandler) +TYPED_TEST(SdbusTestObject, CanAccessAssociatedSignalMessageInSignalHandler) { - m_adaptor->emitSimpleSignal(); + this->m_adaptor->emitSimpleSignal(); - waitUntil(m_proxy->m_gotSimpleSignal); + waitUntil(this->m_proxy->m_gotSimpleSignal); - ASSERT_THAT(m_proxy->m_signalMsg, NotNull()); - ASSERT_THAT(m_proxy->m_signalMemberName, Eq("simpleSignal")); + ASSERT_THAT(this->m_proxy->m_signalMsg, NotNull()); + ASSERT_THAT(this->m_proxy->m_signalMemberName, Eq("simpleSignal")); } -TEST_F(SdbusTestObject, UnregistersSignalHandler) +TYPED_TEST(SdbusTestObject, UnregistersSignalHandler) { - ASSERT_NO_THROW(m_proxy->unregisterSimpleSignalHandler()); + ASSERT_NO_THROW(this->m_proxy->unregisterSimpleSignalHandler()); - m_adaptor->emitSimpleSignal(); + this->m_adaptor->emitSimpleSignal(); - ASSERT_FALSE(waitUntil(m_proxy->m_gotSimpleSignal, 2s)); + ASSERT_FALSE(waitUntil(this->m_proxy->m_gotSimpleSignal, 1s)); } -TEST_F(SdbusTestObject, UnregistersSignalHandlerForSomeProxies) +TYPED_TEST(SdbusTestObject, UnregistersSignalHandlerForSomeProxies) { - auto proxy1 = std::make_unique(*s_adaptorConnection, BUS_NAME, OBJECT_PATH); - auto proxy2 = std::make_unique(*s_adaptorConnection, BUS_NAME, OBJECT_PATH); + auto proxy1 = std::make_unique(*this->s_adaptorConnection, BUS_NAME, OBJECT_PATH); + auto proxy2 = std::make_unique(*this->s_adaptorConnection, BUS_NAME, OBJECT_PATH); - ASSERT_NO_THROW(m_proxy->unregisterSimpleSignalHandler()); + ASSERT_NO_THROW(this->m_proxy->unregisterSimpleSignalHandler()); - m_adaptor->emitSimpleSignal(); + this->m_adaptor->emitSimpleSignal(); ASSERT_TRUE(waitUntil(proxy1->m_gotSimpleSignal)); ASSERT_TRUE(waitUntil(proxy2->m_gotSimpleSignal)); - ASSERT_FALSE(waitUntil(m_proxy->m_gotSimpleSignal, 2s)); + ASSERT_FALSE(waitUntil(this->m_proxy->m_gotSimpleSignal, 1s)); } -TEST_F(SdbusTestObject, ReRegistersSignalHandler) +TYPED_TEST(SdbusTestObject, ReRegistersSignalHandler) { // unregister simple-signal handler - ASSERT_NO_THROW(m_proxy->unregisterSimpleSignalHandler()); + ASSERT_NO_THROW(this->m_proxy->unregisterSimpleSignalHandler()); - m_adaptor->emitSimpleSignal(); + this->m_adaptor->emitSimpleSignal(); - ASSERT_FALSE(waitUntil(m_proxy->m_gotSimpleSignal, 2s)); + ASSERT_FALSE(waitUntil(this->m_proxy->m_gotSimpleSignal, 1s)); // re-register simple-signal handler - ASSERT_NO_THROW(m_proxy->reRegisterSimpleSignalHandler()); + ASSERT_NO_THROW(this->m_proxy->reRegisterSimpleSignalHandler()); - m_adaptor->emitSimpleSignal(); + this->m_adaptor->emitSimpleSignal(); - ASSERT_TRUE(waitUntil(m_proxy->m_gotSimpleSignal)); + ASSERT_TRUE(waitUntil(this->m_proxy->m_gotSimpleSignal)); } diff --git a/tests/integrationtests/DBusStandardInterfacesTests.cpp b/tests/integrationtests/DBusStandardInterfacesTests.cpp index 66183dcf..f9eaabc6 100644 --- a/tests/integrationtests/DBusStandardInterfacesTests.cpp +++ b/tests/integrationtests/DBusStandardInterfacesTests.cpp @@ -48,43 +48,41 @@ using ::testing::SizeIs; using namespace std::chrono_literals; using namespace sdbus::test; -using SdbusTestObject = TestFixture; - /*-------------------------------------*/ /* -- TEST CASES -- */ /*-------------------------------------*/ -TEST_F(SdbusTestObject, PingsViaPeerInterface) +TYPED_TEST(SdbusTestObject, PingsViaPeerInterface) { - ASSERT_NO_THROW(m_proxy->Ping()); + ASSERT_NO_THROW(this->m_proxy->Ping()); } -TEST_F(SdbusTestObject, AnswersMachineUuidViaPeerInterface) +TYPED_TEST(SdbusTestObject, AnswersMachineUuidViaPeerInterface) { if (::access("/etc/machine-id", F_OK) == -1 && ::access("/var/lib/dbus/machine-id", F_OK) == -1) GTEST_SKIP() << "/etc/machine-id and /var/lib/dbus/machine-id files do not exist, GetMachineId() will not work"; - ASSERT_NO_THROW(m_proxy->GetMachineId()); + ASSERT_NO_THROW(this->m_proxy->GetMachineId()); } // TODO: Adjust expected xml and uncomment this test -//TEST_F(SdbusTestObject, AnswersXmlApiDescriptionViaIntrospectableInterface) +//TYPED_TEST(SdbusTestObject, AnswersXmlApiDescriptionViaIntrospectableInterface) //{ -// ASSERT_THAT(m_proxy->Introspect(), Eq(m_adaptor->getExpectedXmlApiDescription())); +// ASSERT_THAT(this->m_proxy->Introspect(), Eq(this->m_adaptor->getExpectedXmlApiDescription())); //} -TEST_F(SdbusTestObject, GetsPropertyViaPropertiesInterface) +TYPED_TEST(SdbusTestObject, GetsPropertyViaPropertiesInterface) { - ASSERT_THAT(m_proxy->Get(INTERFACE_NAME, "state").get(), Eq(DEFAULT_STATE_VALUE)); + ASSERT_THAT(this->m_proxy->Get(INTERFACE_NAME, "state").template get(), Eq(DEFAULT_STATE_VALUE)); } -TEST_F(SdbusTestObject, GetsPropertyAsynchronouslyViaPropertiesInterface) +TYPED_TEST(SdbusTestObject, GetsPropertyAsynchronouslyViaPropertiesInterface) { std::promise promise; auto future = promise.get_future(); - m_proxy->GetAsync(INTERFACE_NAME, "state", [&](const sdbus::Error* err, sdbus::Variant value) + this->m_proxy->GetAsync(INTERFACE_NAME, "state", [&](const sdbus::Error* err, sdbus::Variant value) { if (err == nullptr) promise.set_value(value.get()); @@ -95,29 +93,29 @@ TEST_F(SdbusTestObject, GetsPropertyAsynchronouslyViaPropertiesInterface) ASSERT_THAT(future.get(), Eq(DEFAULT_STATE_VALUE)); } -TEST_F(SdbusTestObject, GetsPropertyAsynchronouslyViaPropertiesInterfaceWithFuture) +TYPED_TEST(SdbusTestObject, GetsPropertyAsynchronouslyViaPropertiesInterfaceWithFuture) { - auto future = m_proxy->GetAsync(INTERFACE_NAME, "state", sdbus::with_future); + auto future = this->m_proxy->GetAsync(INTERFACE_NAME, "state", sdbus::with_future); - ASSERT_THAT(future.get().get(), Eq(DEFAULT_STATE_VALUE)); + ASSERT_THAT(future.get().template get(), Eq(DEFAULT_STATE_VALUE)); } -TEST_F(SdbusTestObject, SetsPropertyViaPropertiesInterface) +TYPED_TEST(SdbusTestObject, SetsPropertyViaPropertiesInterface) { uint32_t newActionValue = 2345; - m_proxy->Set(INTERFACE_NAME, "action", sdbus::Variant{newActionValue}); + this->m_proxy->Set(INTERFACE_NAME, "action", sdbus::Variant{newActionValue}); - ASSERT_THAT(m_proxy->action(), Eq(newActionValue)); + ASSERT_THAT(this->m_proxy->action(), Eq(newActionValue)); } -TEST_F(SdbusTestObject, SetsPropertyAsynchronouslyViaPropertiesInterface) +TYPED_TEST(SdbusTestObject, SetsPropertyAsynchronouslyViaPropertiesInterface) { uint32_t newActionValue = 2346; std::promise promise; auto future = promise.get_future(); - m_proxy->SetAsync(INTERFACE_NAME, "action", sdbus::Variant{newActionValue}, [&](const sdbus::Error* err) + this->m_proxy->SetAsync(INTERFACE_NAME, "action", sdbus::Variant{newActionValue}, [&](const sdbus::Error* err) { if (err == nullptr) promise.set_value(); @@ -126,35 +124,35 @@ TEST_F(SdbusTestObject, SetsPropertyAsynchronouslyViaPropertiesInterface) }); ASSERT_NO_THROW(future.get()); - ASSERT_THAT(m_proxy->action(), Eq(newActionValue)); + ASSERT_THAT(this->m_proxy->action(), Eq(newActionValue)); } -TEST_F(SdbusTestObject, SetsPropertyAsynchronouslyViaPropertiesInterfaceWithFuture) +TYPED_TEST(SdbusTestObject, SetsPropertyAsynchronouslyViaPropertiesInterfaceWithFuture) { uint32_t newActionValue = 2347; - auto future = m_proxy->SetAsync(INTERFACE_NAME, "action", sdbus::Variant{newActionValue}, sdbus::with_future); + auto future = this->m_proxy->SetAsync(INTERFACE_NAME, "action", sdbus::Variant{newActionValue}, sdbus::with_future); ASSERT_NO_THROW(future.get()); - ASSERT_THAT(m_proxy->action(), Eq(newActionValue)); + ASSERT_THAT(this->m_proxy->action(), Eq(newActionValue)); } -TEST_F(SdbusTestObject, GetsAllPropertiesViaPropertiesInterface) +TYPED_TEST(SdbusTestObject, GetsAllPropertiesViaPropertiesInterface) { - const auto properties = m_proxy->GetAll(INTERFACE_NAME); + const auto properties = this->m_proxy->GetAll(INTERFACE_NAME); ASSERT_THAT(properties, SizeIs(3)); - EXPECT_THAT(properties.at("state").get(), Eq(DEFAULT_STATE_VALUE)); - EXPECT_THAT(properties.at("action").get(), Eq(DEFAULT_ACTION_VALUE)); - EXPECT_THAT(properties.at("blocking").get(), Eq(DEFAULT_BLOCKING_VALUE)); + EXPECT_THAT(properties.at("state").template get(), Eq(DEFAULT_STATE_VALUE)); + EXPECT_THAT(properties.at("action").template get(), Eq(DEFAULT_ACTION_VALUE)); + EXPECT_THAT(properties.at("blocking").template get(), Eq(DEFAULT_BLOCKING_VALUE)); } -TEST_F(SdbusTestObject, GetsAllPropertiesAsynchronouslyViaPropertiesInterface) +TYPED_TEST(SdbusTestObject, GetsAllPropertiesAsynchronouslyViaPropertiesInterface) { std::promise> promise; auto future = promise.get_future(); - m_proxy->GetAllAsync(INTERFACE_NAME, [&](const sdbus::Error* err, std::map value) + this->m_proxy->GetAllAsync(INTERFACE_NAME, [&](const sdbus::Error* err, std::map value) { if (err == nullptr) promise.set_value(std::move(value)); @@ -169,24 +167,24 @@ TEST_F(SdbusTestObject, GetsAllPropertiesAsynchronouslyViaPropertiesInterface) EXPECT_THAT(properties.at("blocking").get(), Eq(DEFAULT_BLOCKING_VALUE)); } -TEST_F(SdbusTestObject, GetsAllPropertiesAsynchronouslyViaPropertiesInterfaceWithFuture) +TYPED_TEST(SdbusTestObject, GetsAllPropertiesAsynchronouslyViaPropertiesInterfaceWithFuture) { - auto future = m_proxy->GetAllAsync(INTERFACE_NAME, sdbus::with_future); + auto future = this->m_proxy->GetAllAsync(INTERFACE_NAME, sdbus::with_future); auto properties = future.get(); ASSERT_THAT(properties, SizeIs(3)); - EXPECT_THAT(properties.at("state").get(), Eq(DEFAULT_STATE_VALUE)); - EXPECT_THAT(properties.at("action").get(), Eq(DEFAULT_ACTION_VALUE)); - EXPECT_THAT(properties.at("blocking").get(), Eq(DEFAULT_BLOCKING_VALUE)); + EXPECT_THAT(properties.at("state").template get(), Eq(DEFAULT_STATE_VALUE)); + EXPECT_THAT(properties.at("action").template get(), Eq(DEFAULT_ACTION_VALUE)); + EXPECT_THAT(properties.at("blocking").template get(), Eq(DEFAULT_BLOCKING_VALUE)); } -TEST_F(SdbusTestObject, EmitsPropertyChangedSignalForSelectedProperties) +TYPED_TEST(SdbusTestObject, EmitsPropertyChangedSignalForSelectedProperties) { std::atomic signalReceived{false}; - m_proxy->m_onPropertiesChangedHandler = [&signalReceived]( const std::string& interfaceName - , const std::map& changedProperties - , const std::vector& /*invalidatedProperties*/ ) + this->m_proxy->m_onPropertiesChangedHandler = [&signalReceived]( const std::string& interfaceName + , const std::map& changedProperties + , const std::vector& /*invalidatedProperties*/ ) { EXPECT_THAT(interfaceName, Eq(INTERFACE_NAME)); EXPECT_THAT(changedProperties, SizeIs(1)); @@ -194,19 +192,19 @@ TEST_F(SdbusTestObject, EmitsPropertyChangedSignalForSelectedProperties) signalReceived = true; }; - m_proxy->blocking(!DEFAULT_BLOCKING_VALUE); - m_proxy->action(DEFAULT_ACTION_VALUE*2); - m_adaptor->emitPropertiesChangedSignal(INTERFACE_NAME, {"blocking"}); + this->m_proxy->blocking(!DEFAULT_BLOCKING_VALUE); + this->m_proxy->action(DEFAULT_ACTION_VALUE*2); + this->m_adaptor->emitPropertiesChangedSignal(INTERFACE_NAME, {"blocking"}); ASSERT_TRUE(waitUntil(signalReceived)); } -TEST_F(SdbusTestObject, EmitsPropertyChangedSignalForAllProperties) +TYPED_TEST(SdbusTestObject, EmitsPropertyChangedSignalForAllProperties) { std::atomic signalReceived{false}; - m_proxy->m_onPropertiesChangedHandler = [&signalReceived]( const std::string& interfaceName - , const std::map& changedProperties - , const std::vector& invalidatedProperties ) + this->m_proxy->m_onPropertiesChangedHandler = [&signalReceived]( const std::string& interfaceName + , const std::map& changedProperties + , const std::vector& invalidatedProperties ) { EXPECT_THAT(interfaceName, Eq(INTERFACE_NAME)); EXPECT_THAT(changedProperties, SizeIs(1)); @@ -216,38 +214,38 @@ TEST_F(SdbusTestObject, EmitsPropertyChangedSignalForAllProperties) signalReceived = true; }; - m_adaptor->emitPropertiesChangedSignal(INTERFACE_NAME); + this->m_adaptor->emitPropertiesChangedSignal(INTERFACE_NAME); ASSERT_TRUE(waitUntil(signalReceived)); } -TEST_F(SdbusTestObject, GetsZeroManagedObjectsIfHasNoSubPathObjects) +TYPED_TEST(SdbusTestObject, GetsZeroManagedObjectsIfHasNoSubPathObjects) { - m_adaptor.reset(); - const auto objectsInterfacesAndProperties = m_objectManagerProxy->GetManagedObjects(); + this->m_adaptor.reset(); + const auto objectsInterfacesAndProperties = this->m_objectManagerProxy->GetManagedObjects(); ASSERT_THAT(objectsInterfacesAndProperties, SizeIs(0)); } -TEST_F(SdbusTestObject, GetsManagedObjectsSuccessfully) +TYPED_TEST(SdbusTestObject, GetsManagedObjectsSuccessfully) { - auto adaptor2 = std::make_unique(*s_adaptorConnection, OBJECT_PATH_2); - const auto objectsInterfacesAndProperties = m_objectManagerProxy->GetManagedObjects(); + auto adaptor2 = std::make_unique(*this->s_adaptorConnection, OBJECT_PATH_2); + const auto objectsInterfacesAndProperties = this->m_objectManagerProxy->GetManagedObjects(); ASSERT_THAT(objectsInterfacesAndProperties, SizeIs(2)); EXPECT_THAT(objectsInterfacesAndProperties.at(OBJECT_PATH) .at(org::sdbuscpp::integrationtests_adaptor::INTERFACE_NAME) - .at("action").get(), Eq(DEFAULT_ACTION_VALUE)); + .at("action").template get(), Eq(DEFAULT_ACTION_VALUE)); EXPECT_THAT(objectsInterfacesAndProperties.at(OBJECT_PATH_2) .at(org::sdbuscpp::integrationtests_adaptor::INTERFACE_NAME) - .at("action").get(), Eq(DEFAULT_ACTION_VALUE)); + .at("action").template get(), Eq(DEFAULT_ACTION_VALUE)); } -TEST_F(SdbusTestObject, EmitsInterfacesAddedSignalForSelectedObjectInterfaces) +TYPED_TEST(SdbusTestObject, EmitsInterfacesAddedSignalForSelectedObjectInterfaces) { std::atomic signalReceived{false}; - m_objectManagerProxy->m_onInterfacesAddedHandler = [&signalReceived]( const sdbus::ObjectPath& objectPath - , const std::map>& interfacesAndProperties ) + this->m_objectManagerProxy->m_onInterfacesAddedHandler = [&signalReceived]( const sdbus::ObjectPath& objectPath + , const std::map>& interfacesAndProperties ) { EXPECT_THAT(objectPath, Eq(OBJECT_PATH)); EXPECT_THAT(interfacesAndProperties, SizeIs(1)); @@ -269,16 +267,16 @@ TEST_F(SdbusTestObject, EmitsInterfacesAddedSignalForSelectedObjectInterfaces) signalReceived = true; }; - m_adaptor->emitInterfacesAddedSignal({INTERFACE_NAME}); + this->m_adaptor->emitInterfacesAddedSignal({INTERFACE_NAME}); ASSERT_TRUE(waitUntil(signalReceived)); } -TEST_F(SdbusTestObject, EmitsInterfacesAddedSignalForAllObjectInterfaces) +TYPED_TEST(SdbusTestObject, EmitsInterfacesAddedSignalForAllObjectInterfaces) { std::atomic signalReceived{false}; - m_objectManagerProxy->m_onInterfacesAddedHandler = [&signalReceived]( const sdbus::ObjectPath& objectPath - , const std::map>& interfacesAndProperties ) + this->m_objectManagerProxy->m_onInterfacesAddedHandler = [&signalReceived]( const sdbus::ObjectPath& objectPath + , const std::map>& interfacesAndProperties ) { EXPECT_THAT(objectPath, Eq(OBJECT_PATH)); #if LIBSYSTEMD_VERSION<=250 @@ -305,16 +303,16 @@ TEST_F(SdbusTestObject, EmitsInterfacesAddedSignalForAllObjectInterfaces) signalReceived = true; }; - m_adaptor->emitInterfacesAddedSignal(); + this->m_adaptor->emitInterfacesAddedSignal(); ASSERT_TRUE(waitUntil(signalReceived)); } -TEST_F(SdbusTestObject, EmitsInterfacesRemovedSignalForSelectedObjectInterfaces) +TYPED_TEST(SdbusTestObject, EmitsInterfacesRemovedSignalForSelectedObjectInterfaces) { std::atomic signalReceived{false}; - m_objectManagerProxy->m_onInterfacesRemovedHandler = [&signalReceived]( const sdbus::ObjectPath& objectPath - , const std::vector& interfaces ) + this->m_objectManagerProxy->m_onInterfacesRemovedHandler = [&signalReceived]( const sdbus::ObjectPath& objectPath + , const std::vector& interfaces ) { EXPECT_THAT(objectPath, Eq(OBJECT_PATH)); ASSERT_THAT(interfaces, SizeIs(1)); @@ -322,16 +320,16 @@ TEST_F(SdbusTestObject, EmitsInterfacesRemovedSignalForSelectedObjectInterfaces) signalReceived = true; }; - m_adaptor->emitInterfacesRemovedSignal({INTERFACE_NAME}); + this->m_adaptor->emitInterfacesRemovedSignal({INTERFACE_NAME}); ASSERT_TRUE(waitUntil(signalReceived)); } -TEST_F(SdbusTestObject, EmitsInterfacesRemovedSignalForAllObjectInterfaces) +TYPED_TEST(SdbusTestObject, EmitsInterfacesRemovedSignalForAllObjectInterfaces) { std::atomic signalReceived{false}; - m_objectManagerProxy->m_onInterfacesRemovedHandler = [&signalReceived]( const sdbus::ObjectPath& objectPath - , const std::vector& interfaces ) + this->m_objectManagerProxy->m_onInterfacesRemovedHandler = [&signalReceived]( const sdbus::ObjectPath& objectPath + , const std::vector& interfaces ) { EXPECT_THAT(objectPath, Eq(OBJECT_PATH)); #if LIBSYSTEMD_VERSION<=250 @@ -344,7 +342,7 @@ TEST_F(SdbusTestObject, EmitsInterfacesRemovedSignalForAllObjectInterfaces) signalReceived = true; }; - m_adaptor->emitInterfacesRemovedSignal(); + this->m_adaptor->emitInterfacesRemovedSignal(); ASSERT_TRUE(waitUntil(signalReceived)); } diff --git a/tests/integrationtests/TestFixture.cpp b/tests/integrationtests/TestFixture.cpp index e044c2df..7769e978 100644 --- a/tests/integrationtests/TestFixture.cpp +++ b/tests/integrationtests/TestFixture.cpp @@ -2,7 +2,7 @@ * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2024 Stanislav Angelovic * - * @file TestAdaptor.cpp + * @file TestFixture.cpp * * Created on: May 23, 2020 * Project: sdbus-c++ @@ -28,7 +28,17 @@ namespace sdbus { namespace test { -std::unique_ptr TestFixture::s_adaptorConnection = sdbus::createSystemBusConnection(); -std::unique_ptr TestFixture::s_proxyConnection = sdbus::createSystemBusConnection(); +std::unique_ptr BaseTestFixture::s_adaptorConnection = sdbus::createSystemBusConnection(); +std::unique_ptr BaseTestFixture::s_proxyConnection = sdbus::createSystemBusConnection(); + +#ifndef SDBUS_basu // sd_event integration is not supported in basu-based sdbus-c++ + +std::thread TestFixture::s_adaptorEventLoopThread{}; +std::thread TestFixture::s_proxyEventLoopThread{}; +sd_event *TestFixture::s_adaptorSdEvent{}; +sd_event *TestFixture::s_proxySdEvent{}; +int TestFixture::s_eventExitFd{-1}; + +#endif // SDBUS_basu }} diff --git a/tests/integrationtests/TestFixture.h b/tests/integrationtests/TestFixture.h index c0d00416..ccbe3ddb 100644 --- a/tests/integrationtests/TestFixture.h +++ b/tests/integrationtests/TestFixture.h @@ -2,7 +2,7 @@ * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2024 Stanislav Angelovic * - * @file TestAdaptor.h + * @file TestFixture.h * * Created on: Jan 2, 2017 * Project: sdbus-c++ @@ -33,6 +33,10 @@ #include #include +#ifndef SDBUS_basu // sd_event integration is not supported in basu-based sdbus-c++ +#include +#endif // SDBUS_basu +#include #include #include @@ -46,22 +50,17 @@ namespace sdbus { namespace test { -class TestFixture : public ::testing::Test +class BaseTestFixture : public ::testing::Test { public: static void SetUpTestCase() { - s_proxyConnection->enterEventLoopAsync(); s_adaptorConnection->requestName(BUS_NAME); - s_adaptorConnection->enterEventLoopAsync(); - std::this_thread::sleep_for(std::chrono::milliseconds(50)); // Give time for the proxy connection to start listening to signals } static void TearDownTestCase() { s_adaptorConnection->releaseName(BUS_NAME); - s_adaptorConnection->leaveEventLoop(); - s_proxyConnection->leaveEventLoop(); } private: @@ -89,6 +88,108 @@ class TestFixture : public ::testing::Test std::unique_ptr m_proxy; }; +struct SdBusCppLoop{}; +struct SdEventLoop{}; + +template +class TestFixture : public BaseTestFixture{}; + +// Fixture working upon internal sdbus-c++ event loop +template <> +class TestFixture : public BaseTestFixture +{ +public: + static void SetUpTestCase() + { + BaseTestFixture::SetUpTestCase(); + s_proxyConnection->enterEventLoopAsync(); + s_adaptorConnection->enterEventLoopAsync(); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); // Give time for the proxy connection to start listening to signals + } + + static void TearDownTestCase() + { + BaseTestFixture::TearDownTestCase(); + s_adaptorConnection->leaveEventLoop(); + s_proxyConnection->leaveEventLoop(); + } +}; + +#ifndef SDBUS_basu // sd_event integration is not supported in basu-based sdbus-c++ + +// Fixture working upon attached external sd-event loop +template <> +class TestFixture : public BaseTestFixture +{ +public: + static void SetUpTestCase() + { + sd_event_new(&s_adaptorSdEvent); + sd_event_new(&s_proxySdEvent); + + s_adaptorConnection->attachSdEventLoop(s_adaptorSdEvent); + s_proxyConnection->attachSdEventLoop(s_proxySdEvent); + + s_eventExitFd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); + auto exitHandler = [](sd_event_source *s, auto...){ return sd_event_exit(sd_event_source_get_event(s), 0); }; + sd_event_add_io(s_adaptorSdEvent, nullptr, s_eventExitFd, EPOLLIN, exitHandler, nullptr); + sd_event_add_io(s_proxySdEvent, nullptr, s_eventExitFd, EPOLLIN, exitHandler, nullptr); + + s_adaptorEventLoopThread = std::thread([]() + { + sd_event_loop(s_adaptorSdEvent); + }); + s_proxyEventLoopThread = std::thread([]() + { + sd_event_loop(s_proxySdEvent); + }); + + BaseTestFixture::SetUpTestCase(); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); // Give time for the proxy connection to start listening to signals + } + + static void TearDownTestCase() + { + (void)eventfd_write(s_eventExitFd, 1); + + s_adaptorEventLoopThread.join(); + s_proxyEventLoopThread.join(); + + sd_event_unref(s_adaptorSdEvent); + sd_event_unref(s_proxySdEvent); + close(s_eventExitFd); + + BaseTestFixture::TearDownTestCase(); + } + +private: + static std::thread s_adaptorEventLoopThread; + static std::thread s_proxyEventLoopThread; + static sd_event *s_adaptorSdEvent; + static sd_event *s_proxySdEvent; + static int s_eventExitFd; +}; + +typedef ::testing::Types EventLoopTags; + +#else // SDBUS_basu +typedef ::testing::Types EventLoopTags; +#endif // SDBUS_basu + +TYPED_TEST_SUITE(TestFixture, EventLoopTags); + +template +using SdbusTestObject = TestFixture<_EventLoop>; +TYPED_TEST_SUITE(SdbusTestObject, EventLoopTags); + +template +using AsyncSdbusTestObject = TestFixture<_EventLoop>; +TYPED_TEST_SUITE(AsyncSdbusTestObject, EventLoopTags); + +template +using AConnection = TestFixture<_EventLoop>; +TYPED_TEST_SUITE(AConnection, EventLoopTags); + class TestFixtureWithDirectConnection : public ::testing::Test { private: From 053a1cf561177ad9b1edcdbcbb723b47709cb397 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Wed, 17 May 2023 19:32:52 +0200 Subject: [PATCH 04/52] ci: build googletest manually on older ubuntus --- .github/workflows/ci.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9ebf12f8..f325e10e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,9 +45,19 @@ jobs: sudo update-alternatives --remove-all c++ sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++ 10 - name: install-googletest - if: matrix.os == 'ubuntu-22.04' # On older ubuntus the libgmock-dev package is either unavailable or has faulty pkg-config file + if: matrix.os == 'ubuntu-22.04' run: | sudo apt-get install -y libgmock-dev + - name: install-googletest + if: matrix.os == 'ubuntu-20.04' # On older ubuntus the libgmock-dev package is either unavailable or has faulty pkg-config file, so we build & install manually + run: | + git clone https://github.com/google/googletest.git + cd googletest + mkdir build + cd build + cmake .. + cmake --build . -j4 + sudo cmake --build . --target install - name: configure-debug if: matrix.build == 'shared-libsystemd' && matrix.os == 'ubuntu-20.04' run: | From 75e3a082d168464c2c0ff6551d6db4e4a6354e88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Wed, 17 May 2023 19:45:38 +0200 Subject: [PATCH 05/52] test: support new googletest git tags --- tests/CMakeLists.txt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index fb082369..cc5e1b85 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -19,10 +19,16 @@ if (NOT TARGET GTest::gmock) if (NOT TARGET GTest::gmock) include(FetchContent) - message("Fetching googletest v${GOOGLETEST_VERSION}...") + if (GOOGLETEST_VERSION VERSION_GREATER_EQUAL 1.13.0) + set(GOOGLETEST_TAG "v${GOOGLETEST_VERSION}") + else() + set(GOOGLETEST_TAG "release-${GOOGLETEST_VERSION}") + endif() + + message("Manually fetching & building googletest v${GOOGLETEST_VERSION}...") FetchContent_Declare(googletest GIT_REPOSITORY ${GOOGLETEST_GIT_REPO} - GIT_TAG release-${GOOGLETEST_VERSION} + GIT_TAG ${GOOGLETEST_TAG} GIT_SHALLOW 1 UPDATE_COMMAND "") From 515fe6f4f393c50cf142998efea1b90df7f04f57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Wed, 17 May 2023 20:00:35 +0200 Subject: [PATCH 06/52] chore: require cmake v3.14 and use FetchContent_MakeAvailable --- CMakeLists.txt | 2 +- tests/CMakeLists.txt | 20 +++++++------------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b735d19..08b62b6b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ # PROJECT INFORMATION #------------------------------- -cmake_minimum_required(VERSION 3.13) +cmake_minimum_required(VERSION 3.14) project(sdbus-c++ VERSION 1.6.0 LANGUAGES C CXX) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index cc5e1b85..99aa27a3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -32,19 +32,13 @@ if (NOT TARGET GTest::gmock) GIT_SHALLOW 1 UPDATE_COMMAND "") - #FetchContent_MakeAvailable(googletest) # Not available in CMake 3.13 :-( Let's do it manually: - FetchContent_GetProperties(googletest) - if(NOT googletest_POPULATED) - FetchContent_Populate(googletest) - set(gtest_force_shared_crt ON CACHE INTERNAL "" FORCE) - set(BUILD_GMOCK ON CACHE INTERNAL "" FORCE) - set(INSTALL_GTEST OFF CACHE INTERNAL "" FORCE) - set(BUILD_SHARED_LIBS_BAK ${BUILD_SHARED_LIBS}) - set(BUILD_SHARED_LIBS OFF) - add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR}) - set(BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS_BAK}) - add_library(GTest::gmock ALIAS gmock) - endif() + set(gtest_force_shared_crt ON CACHE INTERNAL "" FORCE) + set(INSTALL_GTEST OFF CACHE INTERNAL "" FORCE) + set(BUILD_SHARED_LIBS_BAK ${BUILD_SHARED_LIBS}) + set(BUILD_SHARED_LIBS OFF) + FetchContent_MakeAvailable(googletest) + set(BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS_BAK}) + add_library(GTest::gmock ALIAS gmock) endif() endif() From 7ce9b52e72faf4704bd496836eed6c5b7ca9370f Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Wed, 17 May 2023 08:41:45 +0200 Subject: [PATCH 07/52] fix: prevent installing a time event, when there's no need for a timeout See #324 for discussion --- src/Connection.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Connection.cpp b/src/Connection.cpp index af4a03a7..8120f67b 100644 --- a/src/Connection.cpp +++ b/src/Connection.cpp @@ -442,7 +442,10 @@ int Connection::onSdEventPrepare(sd_event_source */*s*/, void *userdata) auto* sdTimeEventSource = static_cast(connection->sdEvent_->sdTimeEventSource.get()); r = sd_event_source_set_time(sdTimeEventSource, static_cast(sdbusPollData.timeout.count())); SDBUS_THROW_ERROR_IF(r < 0, "Failed to set timeout for time event source", -r); - r = sd_event_source_set_enabled(sdTimeEventSource, SD_EVENT_ON); + // In case the timeout is infinite, we disable the timer in the sd_event loop. + // This prevents a syscall error, where `timerfd_settime` returns `EINVAL`, + // because the value is too big. See #324 for details + r = sd_event_source_set_enabled(sdTimeEventSource, sdbusPollData.timeout != sdbusPollData.timeout.max() ? SD_EVENT_ONESHOT : SD_EVENT_OFF); SDBUS_THROW_ERROR_IF(r < 0, "Failed to enable time event source", -r); return 1; From c7675de317b6940739c6d78a525b86e34814d7a5 Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Mon, 15 May 2023 15:44:10 +0200 Subject: [PATCH 08/52] fix: update cmake version for library to 2.0.0 This will also install the library as `libsdbus-c++.so.2.0.0`, which represents the actual version of this library --- CMakeLists.txt | 2 +- tools/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 08b62b6b..6c8ad5b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.14) -project(sdbus-c++ VERSION 1.6.0 LANGUAGES C CXX) +project(sdbus-c++ VERSION 2.0.0 LANGUAGES CXX C) include(GNUInstallDirs) # Installation directories for `install` command and pkgconfig file diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index f63d18a6..4e9bec03 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.5) -project(sdbus-c++-tools VERSION 1.6.0) +project(sdbus-c++-tools VERSION 2.0.0) include(GNUInstallDirs) From 233ec790b471b599c5ac0feef9193ac8b3760432 Mon Sep 17 00:00:00 2001 From: Stanislav Angelovic Date: Thu, 2 Feb 2023 21:51:09 +0100 Subject: [PATCH 09/52] refactor: use sd-bus API to get current message --- include/sdbus-c++/IConnection.h | 18 +++++ include/sdbus-c++/IObject.h | 17 +++-- include/sdbus-c++/IProxy.h | 99 ++++++++++++++++---------- include/sdbus-c++/Message.h | 17 +++-- src/Connection.cpp | 7 ++ src/Connection.h | 1 + src/ISdBus.h | 1 + src/Object.cpp | 16 +---- src/Object.h | 4 +- src/Proxy.cpp | 16 +---- src/Proxy.h | 5 +- src/SdBus.cpp | 5 ++ src/SdBus.h | 1 + tests/integrationtests/TestAdaptor.cpp | 6 +- tests/integrationtests/TestAdaptor.h | 5 +- tests/integrationtests/TestProxy.cpp | 2 +- tests/integrationtests/TestProxy.h | 3 +- tests/unittests/mocks/SdBusMock.h | 1 + 18 files changed, 125 insertions(+), 99 deletions(-) diff --git a/include/sdbus-c++/IConnection.h b/include/sdbus-c++/IConnection.h index 854414e8..5ad3216c 100644 --- a/include/sdbus-c++/IConnection.h +++ b/include/sdbus-c++/IConnection.h @@ -36,6 +36,9 @@ struct sd_bus; struct sd_event; +namespace sdbus { + class Message; +} namespace sdbus { @@ -209,6 +212,21 @@ namespace sdbus { */ virtual bool processPendingEvent() = 0; + /*! + * @brief Provides access to the currently processed D-Bus message + * + * This method provides access to the currently processed incoming D-Bus message. + * "Currently processed" means that the registered callback handler(s) for that message + * are being invoked. This method is meant to be called from within a callback handler + * (e.g. from a D-Bus signal handler, or async method reply handler, etc.). In such a case it is + * guaranteed to return a valid D-Bus message instance for which the handler is called. + * If called from other contexts/threads, it may return a valid or invalid message, depending + * on whether a message was processed or not at the time of the call. + * + * @return Currently processed D-Bus message + */ + virtual Message getCurrentlyProcessedMessage() const = 0; + /*! * @brief Sets general method call timeout * diff --git a/include/sdbus-c++/IObject.h b/include/sdbus-c++/IObject.h index e567586b..9422eebb 100644 --- a/include/sdbus-c++/IObject.h +++ b/include/sdbus-c++/IObject.h @@ -442,20 +442,19 @@ namespace sdbus { virtual const std::string& getObjectPath() const = 0; /*! - * @brief Provides currently processed D-Bus message + * @brief Provides access to the currently processed D-Bus message * - * This method provides immutable access to the currently processed incoming D-Bus message. + * This method provides access to the currently processed incoming D-Bus message. * "Currently processed" means that the registered callback handler(s) for that message * are being invoked. This method is meant to be called from within a callback handler - * (e.g. D-Bus method implementation handler). In such a case it is guaranteed to return - * a valid pointer to the D-Bus message for which the handler is called. If called from other - * contexts/threads, it may return a nonzero pointer or a nullptr, depending on whether a message - * was processed at the time of call or not, but the value is nondereferencable, since the pointed-to - * message may have gone in the meantime. + * (e.g. from a D-Bus signal handler, or async method reply handler, etc.). In such a case it is + * guaranteed to return a valid D-Bus message instance for which the handler is called. + * If called from other contexts/threads, it may return a valid or invalid message, depending + * on whether a message was processed or not at the time of the call. * - * @return A pointer to the currently processed D-Bus message + * @return Currently processed D-Bus message */ - virtual const Message* getCurrentlyProcessedMessage() const = 0; + virtual Message getCurrentlyProcessedMessage() const = 0; }; // Out-of-line member definitions diff --git a/include/sdbus-c++/IProxy.h b/include/sdbus-c++/IProxy.h index 4a89e0f8..4ba39e8b 100644 --- a/include/sdbus-c++/IProxy.h +++ b/include/sdbus-c++/IProxy.h @@ -92,7 +92,7 @@ namespace sdbus { * The call does not block if the method call has dont-expect-reply flag set. In that case, * the call returns immediately and the return value is an empty, invalid method reply. * - * The call blocks otherwise, waiting for the remote peer to send back a reply, or an error, + * The call blocks otherwise, waiting for the remote peer to send back a reply or an error, * or until the call times out. * * While blocking, other concurrent operations (in other threads) on the underlying bus @@ -102,8 +102,8 @@ namespace sdbus { * callMethod() function overload, which does not block the bus connection, or do the synchronous * call from another Proxy instance created just before the call and then destroyed (which is * anyway quite a typical approach in D-Bus implementations). Such proxy instance must have - * its own bus connection. Slim proxies created with `dont_run_event_loop_thread` tag are - * designed for exactly that purpose. + * its own bus connection. So-called light-weight proxies (ones created with `dont_run_event_loop_thread` + * tag are designed for exactly that purpose. * * Note: To avoid messing with messages, use API on a higher level of abstraction defined below. * @@ -125,7 +125,9 @@ namespace sdbus { * @param[in] timeout Timeout for dbus call in microseconds * @return Cookie for the the pending asynchronous call * - * The call is non-blocking. It doesn't wait for the reply. Once the reply arrives, + * This is a callback-based way of asynchronously calling a remote D-Bus method. + * + * The call itself 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 bus * connection I/O event loop thread. * @@ -141,6 +143,53 @@ namespace sdbus { template PendingAsyncCall callMethod(const MethodCall& message, async_reply_handler asyncReplyCallback, const std::chrono::duration<_Rep, _Period>& timeout); + /*! + * @brief Calls method on the D-Bus object asynchronously + * + * @param[in] message Message representing an async method call + * @param[in] Tag denoting a std::future-based overload + * @return Future object providing access to the future method reply message + * + * This is a std::future-based way of asynchronously calling a remote D-Bus method. + * + * The call itself is non-blocking. It doesn't wait for the reply. Once the reply arrives, + * the provided future object will be set to contain the reply (or sdbus::Error + * in case the remote method threw an exception). + * + * Note: To avoid messing with messages, use higher-level API defined below. + * + * @throws sdbus::Error in case of failure + */ + virtual std::future callMethod(const MethodCall& message, with_future_t) = 0; + + /*! + * @brief Calls method on the D-Bus object asynchronously, with custom timeout + * + * @param[in] message Message representing an async method call + * @param[in] timeout Method call timeout + * @param[in] Tag denoting a std::future-based overload + * @return Future object providing access to the future method reply message + * + * This is a std::future-based way of asynchronously calling a remote D-Bus method. + * + * The call itself is non-blocking. It doesn't wait for the reply. Once the reply arrives, + * the provided future object will be set to contain the reply (or sdbus::Error + * in case the remote method threw an exception, or the call timed out). + * + * Note: To avoid messing with messages, use higher-level API defined below. + * + * @throws sdbus::Error in case of failure + */ + virtual std::future callMethod(const MethodCall& message, uint64_t timeout, with_future_t) = 0; + + /*! + * @copydoc IProxy::callMethod(const MethodCall&,uint64_t,with_future_t) + */ + template + std::future callMethod( const MethodCall& message + , const std::chrono::duration<_Rep, _Period>& timeout + , with_future_t ); + /*! * @brief Registers a handler for the desired signal emitted by the D-Bus object * @@ -397,47 +446,19 @@ namespace sdbus { virtual const std::string& getObjectPath() const = 0; /*! - * @brief Provides currently processed D-Bus message + * @brief Provides access to the currently processed D-Bus message * - * This method provides immutable access to the currently processed incoming D-Bus message. + * This method provides access to the currently processed incoming D-Bus message. * "Currently processed" means that the registered callback handler(s) for that message * are being invoked. This method is meant to be called from within a callback handler * (e.g. from a D-Bus signal handler, or async method reply handler, etc.). In such a case it is - * guaranteed to return a valid pointer to the D-Bus message for which the handler is called. - * If called from other contexts/threads, it may return a nonzero pointer or a nullptr, depending - * on whether a message was processed at the time of call or not, but the value is nondereferencable, - * since the pointed-to message may have gone in the meantime. + * guaranteed to return a valid D-Bus message instance for which the handler is called. + * If called from other contexts/threads, it may return a valid or invalid message, depending + * on whether a message was processed or not at the time of the call. * - * @return A pointer to the currently processed D-Bus message + * @return Currently processed D-Bus message */ - virtual const Message* getCurrentlyProcessedMessage() const = 0; - - /*! - * @brief Calls method on the D-Bus object asynchronously - * - * @param[in] message Message representing an async method call - * @param[in] asyncReplyCallback Handler for the async reply - * @param[in] timeout Timeout for dbus call in microseconds - * @return Cookie for the the pending asynchronous call - * - * 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 - * I/O event loop thread. - * - * Note: To avoid messing with messages, use higher-level API defined below. - * - * @throws sdbus::Error in case of failure - */ - virtual std::future callMethod(const MethodCall& message, with_future_t) = 0; - virtual std::future callMethod(const MethodCall& message, uint64_t timeout, with_future_t) = 0; - - /*! - * @copydoc IProxy::callMethod(const MethodCall&,uint64_t,with_future_t) - */ - template - std::future callMethod( const MethodCall& message - , const std::chrono::duration<_Rep, _Period>& timeout - , with_future_t ); + virtual Message getCurrentlyProcessedMessage() const = 0; }; /********************************************//** diff --git a/include/sdbus-c++/Message.h b/include/sdbus-c++/Message.h index 50fd528a..14d04adc 100644 --- a/include/sdbus-c++/Message.h +++ b/include/sdbus-c++/Message.h @@ -70,13 +70,19 @@ namespace sdbus { * Serialization and deserialization functions are provided for types supported * by D-Bus. * - * You don't need to work with this class directly if you use high-level APIs - * of @c IObject and @c IProxy. + * You mostly don't need to work with this class directly if you use high-level + * APIs of @c IObject and @c IProxy. * ***********************************************/ class [[nodiscard]] Message { public: + Message(const Message&) noexcept; + Message& operator=(const Message&) noexcept; + Message(Message&& other) noexcept; + Message& operator=(Message&& other) noexcept; + ~Message(); + Message& operator<<(bool item); Message& operator<<(int16_t item); Message& operator<<(int32_t item); @@ -222,13 +228,6 @@ namespace sdbus { 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; - Message& operator=(Message&& other) noexcept; - - ~Message(); - friend Factory; protected: diff --git a/src/Connection.cpp b/src/Connection.cpp index 8120f67b..d2ad1277 100644 --- a/src/Connection.cpp +++ b/src/Connection.cpp @@ -774,6 +774,13 @@ bool Connection::arePendingMessagesInReadQueue() const return readQueueSize > 0; } +Message Connection::getCurrentlyProcessedMessage() const +{ + auto* sdbusMsg = sdbus_->sd_bus_get_current_message(bus_.get()); + + return Message::Factory::create(sdbusMsg, sdbus_.get()); +} + std::string Connection::composeSignalMatchFilter( const std::string &sender , const std::string &objectPath , const std::string &interfaceName diff --git a/src/Connection.h b/src/Connection.h index b42c5748..721c3f43 100644 --- a/src/Connection.h +++ b/src/Connection.h @@ -86,6 +86,7 @@ namespace sdbus::internal { void leaveEventLoop() override; PollData getEventLoopPollData() const override; bool processPendingEvent() override; + Message getCurrentlyProcessedMessage() const override; void addObjectManager(const std::string& objectPath) override; void addObjectManager(const std::string& objectPath, floating_slot_t) override; diff --git a/src/ISdBus.h b/src/ISdBus.h index e38aa290..a8efe943 100644 --- a/src/ISdBus.h +++ b/src/ISdBus.h @@ -87,6 +87,7 @@ namespace sdbus::internal { virtual int sd_bus_start(sd_bus *bus) = 0; virtual int sd_bus_process(sd_bus *bus, sd_bus_message **r) = 0; + virtual sd_bus_message* sd_bus_get_current_message(sd_bus *bus) = 0; virtual int sd_bus_get_poll_data(sd_bus *bus, PollData* data) = 0; virtual int sd_bus_get_n_queued_read(sd_bus *bus, uint64_t *ret) = 0; virtual int sd_bus_flush(sd_bus *bus) = 0; diff --git a/src/Object.cpp b/src/Object.cpp index 097da5a4..66ff8676 100644 --- a/src/Object.cpp +++ b/src/Object.cpp @@ -240,9 +240,9 @@ const std::string& Object::getObjectPath() const return objectPath_; } -const Message* Object::getCurrentlyProcessedMessage() const +Message Object::getCurrentlyProcessedMessage() const { - return m_CurrentlyProcessedMessage.load(std::memory_order_relaxed); + return connection_.getCurrentlyProcessedMessage(); } Object::InterfaceData& Object::getInterface(const std::string& interfaceName) @@ -338,12 +338,6 @@ int Object::sdbus_method_callback(sd_bus_message *sdbusMessage, void *userData, auto message = Message::Factory::create(sdbusMessage, &object.connection_.getSdBusInterface()); - object.m_CurrentlyProcessedMessage.store(&message, std::memory_order_relaxed); - SCOPE_EXIT - { - object.m_CurrentlyProcessedMessage.store(nullptr, std::memory_order_relaxed); - }; - auto& callback = interfaceData->methods[message.getMemberName()].callback; assert(callback); @@ -396,12 +390,6 @@ int Object::sdbus_property_set_callback( sd_bus */*bus*/ auto value = Message::Factory::create(sdbusValue, &object.connection_.getSdBusInterface()); - object.m_CurrentlyProcessedMessage.store(&value, std::memory_order_relaxed); - SCOPE_EXIT - { - object.m_CurrentlyProcessedMessage.store(nullptr, std::memory_order_relaxed); - }; - auto ok = invokeHandlerAndCatchErrors([&](){ callback(value); }, retError); return ok ? 1 : -1; diff --git a/src/Object.h b/src/Object.h index d38b1c0d..50cd7b95 100644 --- a/src/Object.h +++ b/src/Object.h @@ -35,7 +35,6 @@ #include #include #include -#include #include namespace sdbus::internal { @@ -103,7 +102,7 @@ namespace sdbus::internal { sdbus::IConnection& getConnection() const override; const std::string& getObjectPath() const override; - const Message* getCurrentlyProcessedMessage() const override; + Message getCurrentlyProcessedMessage() const override; private: using InterfaceName = std::string; @@ -178,7 +177,6 @@ namespace sdbus::internal { std::string objectPath_; std::map interfaces_; Slot objectManagerSlot_; - std::atomic m_CurrentlyProcessedMessage{nullptr}; }; } diff --git a/src/Proxy.cpp b/src/Proxy.cpp index fd91dfe6..fab35d2c 100644 --- a/src/Proxy.cpp +++ b/src/Proxy.cpp @@ -200,9 +200,9 @@ const std::string& Proxy::getObjectPath() const return objectPath_; } -const Message* Proxy::getCurrentlyProcessedMessage() const +Message Proxy::getCurrentlyProcessedMessage() const { - return m_CurrentlyProcessedMessage.load(std::memory_order_relaxed); + return connection_->getCurrentlyProcessedMessage(); } int Proxy::sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError) @@ -225,12 +225,6 @@ int Proxy::sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userDat auto message = Message::Factory::create(sdbusMessage, &proxy.connection_->getSdBusInterface()); - proxy.m_CurrentlyProcessedMessage.store(&message, std::memory_order_relaxed); - SCOPE_EXIT - { - proxy.m_CurrentlyProcessedMessage.store(nullptr, std::memory_order_relaxed); - }; - auto ok = invokeHandlerAndCatchErrors([&] { const auto* error = sd_bus_message_get_error(sdbusMessage); @@ -256,12 +250,6 @@ int Proxy::sdbus_signal_handler(sd_bus_message *sdbusMessage, void *userData, sd auto message = Message::Factory::create(sdbusMessage, &signalData->proxy.connection_->getSdBusInterface()); - signalData->proxy.m_CurrentlyProcessedMessage.store(&message, std::memory_order_relaxed); - SCOPE_EXIT - { - signalData->proxy.m_CurrentlyProcessedMessage.store(nullptr, std::memory_order_relaxed); - }; - auto ok = invokeHandlerAndCatchErrors([&](){ signalData->callback(message); }, retError); return ok ? 0 : -1; diff --git a/src/Proxy.h b/src/Proxy.h index 13b2e280..53d81bf6 100644 --- a/src/Proxy.h +++ b/src/Proxy.h @@ -35,7 +35,6 @@ #include #include #include -#include #include namespace sdbus::internal { @@ -72,7 +71,7 @@ namespace sdbus::internal { sdbus::IConnection& getConnection() const override; const std::string& getObjectPath() const override; - const Message* getCurrentlyProcessedMessage() const override; + Message getCurrentlyProcessedMessage() const override; private: void registerSignalHandlers(sdbus::internal::IConnection& connection); @@ -178,8 +177,6 @@ namespace sdbus::internal { std::mutex mutex_; std::deque> calls_; } pendingAsyncCalls_; - - std::atomic m_CurrentlyProcessedMessage{nullptr}; }; } diff --git a/src/SdBus.cpp b/src/SdBus.cpp index 728add2f..47fa6824 100644 --- a/src/SdBus.cpp +++ b/src/SdBus.cpp @@ -382,6 +382,11 @@ int SdBus::sd_bus_process(sd_bus *bus, sd_bus_message **r) return ::sd_bus_process(bus, r); } +sd_bus_message* SdBus::sd_bus_get_current_message(sd_bus *bus) +{ + return ::sd_bus_get_current_message(bus); +} + int SdBus::sd_bus_get_poll_data(sd_bus *bus, PollData* data) { std::lock_guard lock(sdbusMutex_); diff --git a/src/SdBus.h b/src/SdBus.h index dc91b377..1ab9e810 100644 --- a/src/SdBus.h +++ b/src/SdBus.h @@ -79,6 +79,7 @@ class SdBus final : public ISdBus virtual int sd_bus_start(sd_bus *bus) override; virtual int sd_bus_process(sd_bus *bus, sd_bus_message **r) override; + virtual sd_bus_message* sd_bus_get_current_message(sd_bus *bus) override; virtual int sd_bus_get_poll_data(sd_bus *bus, PollData* data) override; virtual int sd_bus_get_n_queued_read(sd_bus *bus, uint64_t *ret) override; virtual int sd_bus_flush(sd_bus *bus) override; diff --git a/tests/integrationtests/TestAdaptor.cpp b/tests/integrationtests/TestAdaptor.cpp index 6a8ff18b..9a13995c 100644 --- a/tests/integrationtests/TestAdaptor.cpp +++ b/tests/integrationtests/TestAdaptor.cpp @@ -122,7 +122,7 @@ uint32_t TestAdaptor::doOperation(const uint32_t& param) { std::this_thread::sleep_for(std::chrono::milliseconds(param)); - m_methodCallMsg = getObject().getCurrentlyProcessedMessage(); + m_methodCallMsg = std::make_unique(getObject().getCurrentlyProcessedMessage()); m_methodCallMemberName = m_methodCallMsg->getMemberName(); return param; @@ -130,7 +130,7 @@ uint32_t TestAdaptor::doOperation(const uint32_t& param) void TestAdaptor::doOperationAsync(sdbus::Result&& result, uint32_t param) { - m_methodCallMsg = getObject().getCurrentlyProcessedMessage(); + m_methodCallMsg = std::make_unique(getObject().getCurrentlyProcessedMessage()); m_methodCallMemberName = m_methodCallMsg->getMemberName(); if (param == 0) @@ -234,7 +234,7 @@ bool TestAdaptor::blocking() void TestAdaptor::blocking(const bool& value) { - m_propertySetMsg = getObject().getCurrentlyProcessedMessage(); + m_propertySetMsg = std::make_unique(getObject().getCurrentlyProcessedMessage()); m_propertySetSender = m_propertySetMsg->getSender(); m_blocking = value; diff --git a/tests/integrationtests/TestAdaptor.h b/tests/integrationtests/TestAdaptor.h index e2bcfb4e..2ed31fac 100644 --- a/tests/integrationtests/TestAdaptor.h +++ b/tests/integrationtests/TestAdaptor.h @@ -33,6 +33,7 @@ #include #include #include +#include namespace sdbus { namespace test { @@ -103,9 +104,9 @@ class TestAdaptor final : public sdbus::AdaptorInterfaces< org::sdbuscpp::integr mutable double m_multiplyResult{}; mutable std::atomic m_wasThrowErrorCalled{false}; - const Message* m_methodCallMsg{}; + std::unique_ptr m_methodCallMsg; std::string m_methodCallMemberName; - const Message* m_propertySetMsg{}; + std::unique_ptr m_propertySetMsg; std::string m_propertySetSender; }; diff --git a/tests/integrationtests/TestProxy.cpp b/tests/integrationtests/TestProxy.cpp index 1d8908be..f20cbc31 100644 --- a/tests/integrationtests/TestProxy.cpp +++ b/tests/integrationtests/TestProxy.cpp @@ -61,7 +61,7 @@ TestProxy::~TestProxy() void TestProxy::onSimpleSignal() { - m_signalMsg = getProxy().getCurrentlyProcessedMessage(); + m_signalMsg = std::make_unique(getProxy().getCurrentlyProcessedMessage()); m_signalMemberName = m_signalMsg->getMemberName(); m_gotSimpleSignal = true; diff --git a/tests/integrationtests/TestProxy.h b/tests/integrationtests/TestProxy.h index 72a50e82..cebbce86 100644 --- a/tests/integrationtests/TestProxy.h +++ b/tests/integrationtests/TestProxy.h @@ -33,6 +33,7 @@ #include #include #include +#include namespace sdbus { namespace test { @@ -118,7 +119,7 @@ class TestProxy final : public sdbus::ProxyInterfaces< org::sdbuscpp::integratio std::function m_DoOperationClientSideAsyncReplyHandler; std::function&, const std::vector&)> m_onPropertiesChangedHandler; - const Message* m_signalMsg{}; + std::unique_ptr m_signalMsg; std::string m_signalMemberName; }; diff --git a/tests/unittests/mocks/SdBusMock.h b/tests/unittests/mocks/SdBusMock.h index 587b8d96..353b74df 100644 --- a/tests/unittests/mocks/SdBusMock.h +++ b/tests/unittests/mocks/SdBusMock.h @@ -78,6 +78,7 @@ class SdBusMock : public sdbus::internal::ISdBus MOCK_METHOD1(sd_bus_start, int(sd_bus *bus)); MOCK_METHOD2(sd_bus_process, int(sd_bus *bus, sd_bus_message **r)); + MOCK_METHOD1(sd_bus_get_current_message, sd_bus_message*(sd_bus *bus)); MOCK_METHOD2(sd_bus_get_poll_data, int(sd_bus *bus, PollData* data)); MOCK_METHOD2(sd_bus_get_n_queued_read, int(sd_bus *bus, uint64_t *ret)); MOCK_METHOD1(sd_bus_flush, int(sd_bus *bus)); From 538c4bd2643de06f841683eacdd7647af8c02158 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Tue, 24 Oct 2023 21:56:47 +0200 Subject: [PATCH 10/52] refactor: let callbacks take message objects by value (#367) Signatures of callbacks async_reply_handler, signal_handler, message_handler and property_set_callback were modified to take input message objects by value, as opposed to non-const ref. The callee assumes ownership of the message. This API is more idiomatic, more expressive, cleaner and safer. Move semantics is used to pass messages to the callback handlers. In some cases, this also improves performance. --- ChangeLog | 18 ++++++++++-------- docs/using-sdbus-c++.md | 6 +++--- include/sdbus-c++/ConvenienceApiClasses.inl | 6 +++--- include/sdbus-c++/IConnection.h | 2 +- include/sdbus-c++/TypeTraits.h | 8 ++++---- src/Connection.cpp | 4 ++-- src/Object.cpp | 4 ++-- src/Proxy.cpp | 10 +++++----- src/Proxy.h | 2 +- tests/integrationtests/DBusGeneralTests.cpp | 12 ++++++------ 10 files changed, 37 insertions(+), 35 deletions(-) diff --git a/ChangeLog b/ChangeLog index a3b0685a..549b633a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -257,14 +257,16 @@ v1.6.0 v2.0.0 - Breaking changes in API/ABI/behavior: - - In *synchronous* D-Bus calls, the proxy now **always** blocks the connection for concurrent use until the call finishes (with either a received reply, - an error, or time out). If this creates a connection contention issue in your multi-threaded design, use short-lived, light-weight proxies, or call - the method in an asynchronous way. - - The `PollData` struct has been extended with a new data member: `eventFd`. All hooks with external event loops shall be modified to poll on this fd as well. - - `PollData::timeout_usec` was renamed to `PollData::timeout` and its type has been changed to `std::chrono::microseconds`. This member now holds directly - what before had to be obtained through `PollData::getAbsoluteTimeout()` call. - - `PollData::getRelativeTimeout()` return type was changed to `std::chrono::microseconds`. - - `IConnection::processPendingRequest()` was renamed to `IConnection::processPendingEvent()`. + - In *synchronous* D-Bus calls, the proxy now **always** blocks the connection for concurrent use until the call finishes (with either a received reply, + an error, or time out). If this creates a connection contention issue in your multi-threaded design, use short-lived, light-weight proxies, or call + the method in an asynchronous way. + - Signatures of callbacks `async_reply_handler`, `signal_handler`, `message_handler` and `property_set_callback` were modified to take input message objects + by value, as opposed to non-const ref. Callee assumes ownership of the message. This API is more idiomatic, cleaner and safer. + - The `PollData` struct has been extended with a new data member: `eventFd`. All hooks with external event loops shall be modified to poll on this fd as well. + - `PollData::timeout_usec` was renamed to `PollData::timeout` and its type has been changed to `std::chrono::microseconds`. This member now holds directly + what before had to be obtained through `PollData::getAbsoluteTimeout()` call. + - `PollData::getRelativeTimeout()` return type was changed to `std::chrono::microseconds`. + - `IConnection::processPendingRequest()` was renamed to `IConnection::processPendingEvent()`. - Systemd of at least v238 is required to compile sdbus-c++ - A proper fix for timeout handling - Fix for external event loops in which the event loop thread ID was not correctly initialized (now fixed and simplified by not needing the thread ID anymore) diff --git a/docs/using-sdbus-c++.md b/docs/using-sdbus-c++.md index 9ff8e3da..abcaee8e 100644 --- a/docs/using-sdbus-c++.md +++ b/docs/using-sdbus-c++.md @@ -330,7 +330,7 @@ Please note that we can create and destroy D-Bus objects on a connection dynamic #include #include -void onConcatenated(sdbus::Signal& signal) +void onConcatenated(sdbus::Signal signal) { std::string concatenatedString; signal >> concatenatedString; @@ -389,7 +389,7 @@ int main(int argc, char *argv[]) In simple cases, we don't need to create D-Bus connection explicitly for our proxies. Unless a connection is provided to a proxy object explicitly via factory parameter, the proxy will create a connection of his own (unless it is a light-weight, short-lived proxy created with `dont_run_event_loop_thread_t`), 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 proxy ready for use. -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. +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. 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. @@ -1117,7 +1117,7 @@ int main(int argc, char *argv[]) { /* ... */ - auto callback = [](MethodReply& reply, const sdbus::Error* error) + auto callback = [](MethodReply reply, const sdbus::Error* error) { if (error == nullptr) // No error { diff --git a/include/sdbus-c++/ConvenienceApiClasses.inl b/include/sdbus-c++/ConvenienceApiClasses.inl index b3eeb5f8..0cb2d6b6 100644 --- a/include/sdbus-c++/ConvenienceApiClasses.inl +++ b/include/sdbus-c++/ConvenienceApiClasses.inl @@ -317,7 +317,7 @@ namespace sdbus { if (propertySignature_.empty()) propertySignature_ = signature_of_function_input_arguments<_Function>::str(); - setter_ = [callback = std::forward<_Function>(callback)](PropertySetCall& call) + setter_ = [callback = std::forward<_Function>(callback)](PropertySetCall call) { // Default-construct property value using property_type = function_argument_t<_Function, 0>; @@ -580,7 +580,7 @@ namespace sdbus { { assert(method_.isValid()); // onInterface() must be placed/called prior to this function - auto asyncReplyHandler = [callback = std::forward<_Function>(callback)](MethodReply& reply, const Error* error) + auto asyncReplyHandler = [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. @@ -656,7 +656,7 @@ namespace sdbus { proxy_.registerSignalHandler( interfaceName_ , signalName_ - , [callback = std::forward<_Function>(callback)](Signal& signal) + , [callback = std::forward<_Function>(callback)](Signal signal) { // Create a tuple of callback input arguments' types, which will be used // as a storage for the argument values deserialized from the signal message. diff --git a/include/sdbus-c++/IConnection.h b/include/sdbus-c++/IConnection.h index 5ad3216c..c3c8a0c3 100644 --- a/include/sdbus-c++/IConnection.h +++ b/include/sdbus-c++/IConnection.h @@ -286,7 +286,7 @@ namespace sdbus { * The syntax of the match rule expression passed in match is described in the D-Bus specification. * The specified handler function callback is called for each incoming message matching the specified * expression. The match is installed synchronously when connected to a bus broker, i.e. the call - * sends a control message requested the match to be added to the broker and waits until the broker + * sends a control message requesting the match to be added to the broker and waits until the broker * confirms the match has been installed successfully. * * Simply let go of the slot instance to uninstall the match rule from the bus connection. The slot diff --git a/include/sdbus-c++/TypeTraits.h b/include/sdbus-c++/TypeTraits.h index 42ab1b51..bec6148a 100644 --- a/include/sdbus-c++/TypeTraits.h +++ b/include/sdbus-c++/TypeTraits.h @@ -63,10 +63,10 @@ namespace sdbus { // Callbacks from sdbus-c++ using method_callback = std::function; - using async_reply_handler = std::function; - using signal_handler = std::function; - using message_handler = std::function; - using property_set_callback = std::function; + using async_reply_handler = std::function; + using signal_handler = std::function; + using message_handler = std::function; + using property_set_callback = std::function; using property_get_callback = std::function; // Type-erased RAII-style handle to callbacks/subscriptions registered to sdbus-c++ diff --git a/src/Connection.cpp b/src/Connection.cpp index d2ad1277..c3433f51 100644 --- a/src/Connection.cpp +++ b/src/Connection.cpp @@ -811,7 +811,7 @@ int Connection::sdbus_match_callback(sd_bus_message *sdbusMessage, void *userDat { auto* matchInfo = static_cast(userData); auto message = Message::Factory::create(sdbusMessage, &matchInfo->connection.getSdBusInterface()); - auto ok = invokeHandlerAndCatchErrors([&](){ matchInfo->callback(message); }, retError); + auto ok = invokeHandlerAndCatchErrors([&](){ matchInfo->callback(std::move(message)); }, retError); return ok ? 0 : -1; } @@ -819,7 +819,7 @@ int Connection::sdbus_match_install_callback(sd_bus_message *sdbusMessage, void { auto* matchInfo = static_cast(userData); auto message = Message::Factory::create(sdbusMessage, &matchInfo->connection.getSdBusInterface()); - auto ok = invokeHandlerAndCatchErrors([&](){ matchInfo->installCallback(message); }, retError); + auto ok = invokeHandlerAndCatchErrors([&](){ matchInfo->installCallback(std::move(message)); }, retError); return ok ? 0 : -1; } diff --git a/src/Object.cpp b/src/Object.cpp index 66ff8676..e8e32fdb 100644 --- a/src/Object.cpp +++ b/src/Object.cpp @@ -341,7 +341,7 @@ int Object::sdbus_method_callback(sd_bus_message *sdbusMessage, void *userData, auto& callback = interfaceData->methods[message.getMemberName()].callback; assert(callback); - auto ok = invokeHandlerAndCatchErrors([&](){ callback(message); }, retError); + auto ok = invokeHandlerAndCatchErrors([&](){ callback(std::move(message)); }, retError); return ok ? 1 : -1; } @@ -390,7 +390,7 @@ int Object::sdbus_property_set_callback( sd_bus */*bus*/ auto value = Message::Factory::create(sdbusValue, &object.connection_.getSdBusInterface()); - auto ok = invokeHandlerAndCatchErrors([&](){ callback(value); }, retError); + auto ok = invokeHandlerAndCatchErrors([&](){ callback(std::move(value)); }, retError); return ok ? 1 : -1; } diff --git a/src/Proxy.cpp b/src/Proxy.cpp index fab35d2c..b9ae9f5c 100644 --- a/src/Proxy.cpp +++ b/src/Proxy.cpp @@ -119,10 +119,10 @@ std::future Proxy::callMethod(const MethodCall& message, uint64_t t auto promise = std::make_shared>(); auto future = promise->get_future(); - async_reply_handler asyncReplyCallback = [promise = std::move(promise)](MethodReply& reply, const Error* error) noexcept + async_reply_handler asyncReplyCallback = [promise = std::move(promise)](MethodReply reply, const Error* error) noexcept { if (error == nullptr) - promise->set_value(reply); + promise->set_value(std::move(reply)); else promise->set_exception(std::make_exception_ptr(*error)); }; @@ -230,12 +230,12 @@ int Proxy::sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userDat const auto* error = sd_bus_message_get_error(sdbusMessage); if (error == nullptr) { - asyncCallData->callback(message, nullptr); + asyncCallData->callback(std::move(message), nullptr); } else { Error exception(error->name, error->message); - asyncCallData->callback(message, &exception); + asyncCallData->callback(std::move(message), &exception); } }, retError); @@ -250,7 +250,7 @@ int Proxy::sdbus_signal_handler(sd_bus_message *sdbusMessage, void *userData, sd auto message = Message::Factory::create(sdbusMessage, &signalData->proxy.connection_->getSdBusInterface()); - auto ok = invokeHandlerAndCatchErrors([&](){ signalData->callback(message); }, retError); + auto ok = invokeHandlerAndCatchErrors([&](){ signalData->callback(std::move(message)); }, retError); return ok ? 0 : -1; } diff --git a/src/Proxy.h b/src/Proxy.h index 53d81bf6..0c35de43 100644 --- a/src/Proxy.h +++ b/src/Proxy.h @@ -119,7 +119,7 @@ namespace sdbus::internal { public: struct CallData { - enum class State + enum class State // TODO: In release/v2.0, we no more have sync-in-terms-of-async, we can simplify code with just bool finished like before { NOT_ASYNC , RUNNING , FINISHED diff --git a/tests/integrationtests/DBusGeneralTests.cpp b/tests/integrationtests/DBusGeneralTests.cpp index c14e72e4..d5f225d9 100644 --- a/tests/integrationtests/DBusGeneralTests.cpp +++ b/tests/integrationtests/DBusGeneralTests.cpp @@ -76,7 +76,7 @@ TYPED_TEST(AConnection, WillCallCallbackHandlerForIncomingMessageMatchingMatchRu { auto matchRule = "sender='" + BUS_NAME + "',path='" + OBJECT_PATH + "'"; std::atomic matchingMessageReceived{false}; - auto slot = this->s_proxyConnection->addMatch(matchRule, [&](sdbus::Message& msg) + auto slot = this->s_proxyConnection->addMatch(matchRule, [&](sdbus::Message msg) { if(msg.getPath() == OBJECT_PATH) matchingMessageReceived = true; @@ -93,12 +93,12 @@ TYPED_TEST(AConnection, CanInstallMatchRuleAsynchronously) std::atomic matchingMessageReceived{false}; std::atomic matchRuleInstalled{false}; auto slot = this->s_proxyConnection->addMatchAsync( matchRule - , [&](sdbus::Message& msg) + , [&](sdbus::Message msg) { if(msg.getPath() == OBJECT_PATH) matchingMessageReceived = true; } - , [&](sdbus::Message& /*msg*/) + , [&](sdbus::Message /*msg*/) { matchRuleInstalled = true; } ); @@ -114,7 +114,7 @@ TYPED_TEST(AConnection, WillUnsubscribeMatchRuleWhenClientDestroysTheAssociatedS { auto matchRule = "sender='" + BUS_NAME + "',path='" + OBJECT_PATH + "'"; std::atomic matchingMessageReceived{false}; - auto slot = this->s_proxyConnection->addMatch(matchRule, [&](sdbus::Message& msg) + auto slot = this->s_proxyConnection->addMatch(matchRule, [&](sdbus::Message msg) { if(msg.getPath() == OBJECT_PATH) matchingMessageReceived = true; @@ -132,7 +132,7 @@ TYPED_TEST(AConnection, CanAddFloatingMatchRule) std::atomic matchingMessageReceived{false}; auto con = sdbus::createSystemBusConnection(); con->enterEventLoopAsync(); - auto callback = [&](sdbus::Message& msg) + auto callback = [&](sdbus::Message msg) { if(msg.getPath() == OBJECT_PATH) matchingMessageReceived = true; @@ -153,7 +153,7 @@ TYPED_TEST(AConnection, WillNotPassToMatchCallbackMessagesThatDoNotMatchTheRule) { auto matchRule = "type='signal',interface='" + INTERFACE_NAME + "',member='simpleSignal'"; std::atomic numberOfMatchingMessages{}; - auto slot = this->s_proxyConnection->addMatch(matchRule, [&](sdbus::Message& msg) + auto slot = this->s_proxyConnection->addMatch(matchRule, [&](sdbus::Message msg) { if(msg.getMemberName() == "simpleSignal") numberOfMatchingMessages++; From dd468eb423acd92bcb423ce6ce2dbfff0966a252 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Tue, 24 Oct 2023 22:13:02 +0200 Subject: [PATCH 11/52] refactor: simplify async call state flag (#368) Since synchronous D-Bus calls are simplified in v2.0 and no more implemented in terms of an async call, the issue reported in #362 is no more relevant, and we can return to the simple boolean flag for indicating a finished call. --- src/Proxy.cpp | 7 ++----- src/Proxy.h | 14 ++++---------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/src/Proxy.cpp b/src/Proxy.cpp index b9ae9f5c..89d94b7b 100644 --- a/src/Proxy.cpp +++ b/src/Proxy.cpp @@ -98,7 +98,7 @@ PendingAsyncCall Proxy::callMethod(const MethodCall& message, async_reply_handle SDBUS_THROW_ERROR_IF(!message.isValid(), "Invalid async method call message provided", EINVAL); auto callback = (void*)&Proxy::sdbus_async_reply_handler; - auto callData = std::make_shared(AsyncCalls::CallData{*this, std::move(asyncReplyCallback), {}, AsyncCalls::CallData::State::RUNNING}); + auto callData = std::make_shared(AsyncCalls::CallData{*this, std::move(asyncReplyCallback)}); auto weakData = std::weak_ptr{callData}; callData->slot = connection_->callMethod(message, callback, callData.get(), timeout); @@ -211,16 +211,13 @@ int Proxy::sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userDat assert(asyncCallData != nullptr); assert(asyncCallData->callback); auto& proxy = asyncCallData->proxy; - auto state = asyncCallData->state; // We are removing the CallData item at the complete scope exit, after the callback has been invoked. // We can't do it earlier (before callback invocation for example), because CallBack data (slot release) // is the synchronization point between callback invocation and Proxy::unregister. SCOPE_EXIT { - // Remove call meta-data if it's a real async call (a sync call done in terms of async has STATE_NOT_ASYNC) - if (state != AsyncCalls::CallData::State::NOT_ASYNC) - proxy.pendingAsyncCalls_.removeCall(asyncCallData); + proxy.pendingAsyncCalls_.removeCall(asyncCallData); }; auto message = Message::Factory::create(sdbusMessage, &proxy.connection_->getSdBusInterface()); diff --git a/src/Proxy.h b/src/Proxy.h index 0c35de43..efb5b3a1 100644 --- a/src/Proxy.h +++ b/src/Proxy.h @@ -119,16 +119,10 @@ namespace sdbus::internal { public: struct CallData { - enum class State // TODO: In release/v2.0, we no more have sync-in-terms-of-async, we can simplify code with just bool finished like before - { NOT_ASYNC - , RUNNING - , FINISHED - }; - Proxy& proxy; async_reply_handler callback; - Slot slot; - State state; + Slot slot{}; + bool finished{false}; }; ~AsyncCalls() @@ -139,14 +133,14 @@ namespace sdbus::internal { void addCall(std::shared_ptr asyncCallData) { std::lock_guard lock(mutex_); - if (asyncCallData->state != CallData::State::FINISHED) // The call may have finished in the meantime + if (!asyncCallData->finished) // The call may have finished in the meantime calls_.emplace_back(std::move(asyncCallData)); } void removeCall(CallData* data) { std::unique_lock lock(mutex_); - data->state = CallData::State::FINISHED; + data->finished = true; if (auto it = std::find_if(calls_.begin(), calls_.end(), [data](auto const& entry){ return entry.get() == data; }); it != calls_.end()) { auto callData = std::move(*it); From f9a482cdc841a8b3e88ece1b59d6b6f1948ab02b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Tue, 24 Oct 2023 22:13:36 +0200 Subject: [PATCH 12/52] fix(tests): enable skipped test for Clang and FreeBSD (#369) The test now works with Clang, libc++ and -O2 optimization, since the underlying implementation has been completely re-designed and doesn't suffer from that problem anymore. --- tests/integrationtests/DBusMethodsTests.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/integrationtests/DBusMethodsTests.cpp b/tests/integrationtests/DBusMethodsTests.cpp index 8d712b7c..a6fdc039 100644 --- a/tests/integrationtests/DBusMethodsTests.cpp +++ b/tests/integrationtests/DBusMethodsTests.cpp @@ -282,10 +282,6 @@ TYPED_TEST(SdbusTestObject, CannotSetGeneralMethodTimeoutWithLibsystemdVersionLe TYPED_TEST(SdbusTestObject, CanCallMethodSynchronouslyWithoutAnEventLoopThread) { -#if defined(__clang__) && defined(__FreeBSD__) - GTEST_SKIP() << "https://github.com/Kistler-Group/sdbus-cpp/issues/359"; -#endif - auto proxy = std::make_unique(BUS_NAME, OBJECT_PATH, sdbus::dont_run_event_loop_thread); auto multiplyRes = proxy->multiply(INT64_VALUE, DOUBLE_VALUE); From 13db519b69e36d5299e40eb687d5077650c517b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Wed, 25 Oct 2023 20:01:18 +0200 Subject: [PATCH 13/52] refactor: make Variant constructor explicit (#370) This makes the library more robust and prone to user's errors when the user writes an extension for their custom type. In case they forget to implement a serialization function for that type and yet insert an object of that type into sdbus::Message, the current behavior is that, surprisingly, the library masks the error as it resolves the call to the Variant overload, because Variant provides an implicit template converting constructor, so the library tries to construct first the Variant object from the object of custom type, and then inserting into the message that Variant object. Variant constructor serializes the underlying object into its internal message object, which resolves to the same message insertion overload, creating an infinite recursion and ultimately the stack overflow. This is undesired and plain wrong. Marking this Variant converting constructor solves these problems, plus in overall it makes the code a little safer and more verbose. With explicit Variant constructor, when the user forgets to implement a serialization function for their type, the call of such function will fail with an expressive compilation error, and will produce no undesired, surprising results. --- ChangeLog | 1 + include/sdbus-c++/Types.h | 2 +- tests/integrationtests/DBusSignalsTests.cpp | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index 549b633a..59fe430a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -267,6 +267,7 @@ v2.0.0 what before had to be obtained through `PollData::getAbsoluteTimeout()` call. - `PollData::getRelativeTimeout()` return type was changed to `std::chrono::microseconds`. - `IConnection::processPendingRequest()` was renamed to `IConnection::processPendingEvent()`. + - `Variant` constructor is now explicit. - Systemd of at least v238 is required to compile sdbus-c++ - A proper fix for timeout handling - Fix for external event loops in which the event loop thread ID was not correctly initialized (now fixed and simplified by not needing the thread ID anymore) diff --git a/include/sdbus-c++/Types.h b/include/sdbus-c++/Types.h index 5a8427e9..2ebb1467 100644 --- a/include/sdbus-c++/Types.h +++ b/include/sdbus-c++/Types.h @@ -56,7 +56,7 @@ namespace sdbus { Variant(); template - Variant(const _ValueType& value) + explicit Variant(const _ValueType& value) : Variant() { msg_.openVariant(signature_of<_ValueType>::str()); diff --git a/tests/integrationtests/DBusSignalsTests.cpp b/tests/integrationtests/DBusSignalsTests.cpp index c3575eea..779c2c7f 100644 --- a/tests/integrationtests/DBusSignalsTests.cpp +++ b/tests/integrationtests/DBusSignalsTests.cpp @@ -103,7 +103,6 @@ TYPED_TEST(SdbusTestObject, EmitsSignalWithVariantSuccesfully) { double d = 3.14; this->m_adaptor->emitSignalWithVariant(sdbus::Variant{d}); - this->m_adaptor->emitSignalWithVariant(d); ASSERT_TRUE(waitUntil(this->m_proxy->m_gotSignalWithVariant)); ASSERT_THAT(this->m_proxy->m_variantFromSignal, DoubleEq(d)); From 75bddd040e5877dd2e5bac2c93da3d959dd86b4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Tue, 31 Oct 2023 13:41:08 +0100 Subject: [PATCH 14/52] refactor: use sd_bus_match_signal() for signal registration (#372) sd_bus_match_signal() is more convenient than more general sd_bus_add_match() for signal registration, and requires less code on our side. Plus, systemd of at least v238 is required for sdbus-c++ v2, and this version already ships with sd_bus_match_signal(). --- src/Connection.cpp | 30 ++++++++---------------------- src/Connection.h | 4 ---- src/ISdBus.h | 1 + src/SdBus.cpp | 7 +++++++ src/SdBus.h | 1 + tests/unittests/mocks/SdBusMock.h | 1 + 6 files changed, 18 insertions(+), 26 deletions(-) diff --git a/src/Connection.cpp b/src/Connection.cpp index c3433f51..fe3b62f6 100644 --- a/src/Connection.cpp +++ b/src/Connection.cpp @@ -630,11 +630,14 @@ Slot Connection::registerSignalHandler( const std::string& sender { sd_bus_slot *slot{}; - // Alternatively to our own composeSignalMatchFilter() implementation, we could use sd_bus_match_signal() from - // https://www.freedesktop.org/software/systemd/man/sd_bus_add_match.html . - // But this would require libsystemd v237 or higher. - auto filter = composeSignalMatchFilter(sender, objectPath, interfaceName, signalName); - auto r = sdbus_->sd_bus_add_match(bus_.get(), &slot, filter.c_str(), callback, userData); + auto r = sdbus_->sd_bus_match_signal( bus_.get() + , &slot + , !sender.empty() ? sender.c_str() : nullptr + , !objectPath.empty() ? objectPath.c_str() : nullptr + , !interfaceName.empty() ? interfaceName.c_str() : nullptr + , !signalName.empty() ? signalName.c_str() : nullptr + , callback + , userData ); SDBUS_THROW_ERROR_IF(r < 0, "Failed to register signal handler", -r); @@ -781,23 +784,6 @@ Message Connection::getCurrentlyProcessedMessage() const return Message::Factory::create(sdbusMsg, sdbus_.get()); } -std::string Connection::composeSignalMatchFilter( const std::string &sender - , const std::string &objectPath - , const std::string &interfaceName - , const std::string &signalName ) -{ - std::string filter; - - filter += "type='signal',"; - if (!sender.empty()) - filter += "sender='" + sender + "',"; - filter += "interface='" + interfaceName + "',"; - filter += "member='" + signalName + "',"; - filter += "path='" + objectPath + "'"; - - return filter; -} - std::vector Connection::to_strv(const std::vector& strings) { std::vector strv; diff --git a/src/Connection.h b/src/Connection.h index 721c3f43..230bcbb2 100644 --- a/src/Connection.h +++ b/src/Connection.h @@ -153,10 +153,6 @@ namespace sdbus::internal { bool waitForNextEvent(); bool arePendingMessagesInReadQueue() const; - static std::string composeSignalMatchFilter( const std::string &sender - , const std::string &objectPath - , const std::string &interfaceName - , const std::string &signalName); void notifyEventLoopToExit(); void notifyEventLoopToWakeUpFromPoll(); diff --git a/src/ISdBus.h b/src/ISdBus.h index a8efe943..95eac4c6 100644 --- a/src/ISdBus.h +++ b/src/ISdBus.h @@ -81,6 +81,7 @@ namespace sdbus::internal { virtual int sd_bus_add_object_manager(sd_bus *bus, sd_bus_slot **slot, const char *path) = 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_add_match_async(sd_bus *bus, sd_bus_slot **slot, const char *match, sd_bus_message_handler_t callback, sd_bus_message_handler_t install_callback, void *userdata) = 0; + virtual int sd_bus_match_signal(sd_bus *bus, sd_bus_slot **ret, const char *sender, const char *path, const char *interface, const char *member, sd_bus_message_handler_t callback, void *userdata) = 0; virtual sd_bus_slot* sd_bus_slot_unref(sd_bus_slot *slot) = 0; virtual int sd_bus_new(sd_bus **ret) = 0; diff --git a/src/SdBus.cpp b/src/SdBus.cpp index 47fa6824..4d6228c4 100644 --- a/src/SdBus.cpp +++ b/src/SdBus.cpp @@ -358,6 +358,13 @@ int SdBus::sd_bus_add_match_async(sd_bus *bus, sd_bus_slot **slot, const char *m return ::sd_bus_add_match_async(bus, slot, match, callback, install_callback, userdata); } +int SdBus::sd_bus_match_signal(sd_bus *bus, sd_bus_slot **ret, const char *sender, const char *path, const char *interface, const char *member, sd_bus_message_handler_t callback, void *userdata) +{ + std::lock_guard lock(sdbusMutex_); + + return ::sd_bus_match_signal(bus, ret, sender, path, interface, member, callback, userdata); +} + sd_bus_slot* SdBus::sd_bus_slot_unref(sd_bus_slot *slot) { std::lock_guard lock(sdbusMutex_); diff --git a/src/SdBus.h b/src/SdBus.h index 1ab9e810..ebe903d3 100644 --- a/src/SdBus.h +++ b/src/SdBus.h @@ -73,6 +73,7 @@ class SdBus final : public ISdBus virtual int sd_bus_add_object_manager(sd_bus *bus, sd_bus_slot **slot, const char *path) 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 int sd_bus_add_match_async(sd_bus *bus, sd_bus_slot **slot, const char *match, sd_bus_message_handler_t callback, sd_bus_message_handler_t install_callback, void *userdata) override; + virtual int sd_bus_match_signal(sd_bus *bus, sd_bus_slot **ret, const char *sender, const char *path, const char *interface, const char *member, 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_new(sd_bus **ret) override; diff --git a/tests/unittests/mocks/SdBusMock.h b/tests/unittests/mocks/SdBusMock.h index 353b74df..82167052 100644 --- a/tests/unittests/mocks/SdBusMock.h +++ b/tests/unittests/mocks/SdBusMock.h @@ -72,6 +72,7 @@ class SdBusMock : public sdbus::internal::ISdBus MOCK_METHOD3(sd_bus_add_object_manager, int(sd_bus *bus, sd_bus_slot **slot, const char *path)); 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_METHOD6(sd_bus_add_match_async, int(sd_bus *bus, sd_bus_slot **slot, const char *match, sd_bus_message_handler_t callback, sd_bus_message_handler_t install_callback, void *userdata)); + MOCK_METHOD8(sd_bus_match_signal, int(sd_bus *bus, sd_bus_slot **ret, const char *sender, const char *path, const char *interface, const char *member, sd_bus_message_handler_t callback, void *userdata)); MOCK_METHOD1(sd_bus_slot_unref, sd_bus_slot*(sd_bus_slot *slot)); MOCK_METHOD1(sd_bus_new, int(sd_bus **ret)); From 299d1014ef9ba19df8e8c130a259d0accf129972 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Fri, 22 Dec 2023 23:58:01 +0100 Subject: [PATCH 15/52] fix: add missing header to ScopeGuard.h --- src/ScopeGuard.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ScopeGuard.h b/src/ScopeGuard.h index d64a5edd..07263172 100644 --- a/src/ScopeGuard.h +++ b/src/ScopeGuard.h @@ -29,6 +29,7 @@ #include #include +#include // Straightforward, modern, easy-to-use RAII utility to perform work on scope exit in an exception-safe manner. // From f61ee287427f804061fc6a487a6b87334ca7bd51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Sat, 30 Dec 2023 18:40:38 +0100 Subject: [PATCH 16/52] refactor: improve Object vtable registration (#388) This improves the D-Bus object API registration/unregistration by making it more flexible, more dynamic, closer to sd-bus API design but still on high abstraction level, and -- most importantly -- less error-prone since no `finishRegistration()` call is needed anymore. --- CMakeLists.txt | 2 + ChangeLog | 1 + docs/using-sdbus-c++.md | 24 +- .../examplemanager-planet1-server-glue.h | 18 +- .../obj-manager-server.cpp | 20 +- include/sdbus-c++/AdaptorInterfaces.h | 20 +- include/sdbus-c++/ConvenienceApiClasses.h | 101 +---- include/sdbus-c++/ConvenienceApiClasses.inl | 371 +----------------- include/sdbus-c++/IObject.h | 284 ++++---------- include/sdbus-c++/ProxyInterfaces.h | 7 +- include/sdbus-c++/StandardInterfaces.h | 20 +- include/sdbus-c++/TypeTraits.h | 10 + include/sdbus-c++/VTableItems.h | 107 +++++ include/sdbus-c++/VTableItems.inl | 285 ++++++++++++++ src/Connection.cpp | 12 +- src/Object.cpp | 350 ++++++++--------- src/Object.h | 123 +++--- src/VTableUtils.c | 42 +- src/VTableUtils.h | 42 +- tests/integrationtests/DBusMethodsTests.cpp | 32 +- .../integrationtests-adaptor.h | 61 +-- tests/perftests/perftests-adaptor.h | 11 +- .../stresstests/celsius-thermometer-adaptor.h | 6 +- tests/stresstests/concatenator-adaptor.h | 9 +- .../fahrenheit-thermometer-adaptor.h | 15 +- tools/xml2cpp-codegen/AdaptorGenerator.cpp | 130 ++++-- tools/xml2cpp-codegen/AdaptorGenerator.h | 11 +- 27 files changed, 1029 insertions(+), 1085 deletions(-) create mode 100644 include/sdbus-c++/VTableItems.h create mode 100644 include/sdbus-c++/VTableItems.inl diff --git a/CMakeLists.txt b/CMakeLists.txt index 6c8ad5b4..05cf65bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,6 +89,8 @@ set(SDBUSCPP_HDR_SRCS set(SDBUSCPP_PUBLIC_HDRS ${SDBUSCPP_INCLUDE_DIR}/ConvenienceApiClasses.h ${SDBUSCPP_INCLUDE_DIR}/ConvenienceApiClasses.inl + ${SDBUSCPP_INCLUDE_DIR}/VTableItems.h + ${SDBUSCPP_INCLUDE_DIR}/VTableItems.inl ${SDBUSCPP_INCLUDE_DIR}/Error.h ${SDBUSCPP_INCLUDE_DIR}/IConnection.h ${SDBUSCPP_INCLUDE_DIR}/AdaptorInterfaces.h diff --git a/ChangeLog b/ChangeLog index 59fe430a..255133d1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -268,6 +268,7 @@ v2.0.0 - `PollData::getRelativeTimeout()` return type was changed to `std::chrono::microseconds`. - `IConnection::processPendingRequest()` was renamed to `IConnection::processPendingEvent()`. - `Variant` constructor is now explicit. + - Object D-Bus API registration is now done through `IObject::addVTable()` method. The registration is immediate; no `finishRegistration()` call is needed anymore. - Systemd of at least v238 is required to compile sdbus-c++ - A proper fix for timeout handling - Fix for external event loops in which the event loop thread ID was not correctly initialized (now fixed and simplified by not needing the thread ID anymore) diff --git a/docs/using-sdbus-c++.md b/docs/using-sdbus-c++.md index abcaee8e..0b53038d 100644 --- a/docs/using-sdbus-c++.md +++ b/docs/using-sdbus-c++.md @@ -306,16 +306,20 @@ int main(int argc, char *argv[]) // Register D-Bus methods and signals on the concatenator object, and exports the object. const char* interfaceName = "org.sdbuscpp.Concatenator"; - concatenator->registerMethod(interfaceName, "concatenate", "ais", "s", &concatenate); - concatenator->registerSignal(interfaceName, "concatenated", "s"); - concatenator->finishRegistration(); + concatenator->addVTable( sdbus::MethodVTableItem{"concatenate", "ais", {}, "s", {}, &concatenate, {}} + , sdbus::SignalVTableItem{"concatenated", "s", {}, {}} ) + .forInterface(interfaceName); // Run the I/O event loop on the bus connection. connection->enterEventLoop(); } ``` -We establish a D-Bus system connection and request `org.sdbuscpp.concatenator` D-Bus name on it. This name will be used by D-Bus clients to find the service. We then create an object with path `/org/sdbuscpp/concatenator` on this connection. We register interfaces, its methods, signals that the object provides, and, through `finishRegistration()`, export the object (i.e., make it visible to clients) on the bus. Then we need to make sure to run the event loop upon the connection, which handles all incoming, outgoing and other requests. +We establish a D-Bus system 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 add a so-called object vtable, where we declare and describe its D-Bus API, i.e. its interface, methods, signals, properties (if any) that the object provides. Then we need to make sure to run the event loop upon the connection, which handles all incoming, outgoing and other requests. + +> **_Tip_:** There's also an overload of `addVTable()` method with `request_slot_t` tag parameter which returns a `Slot` object. The slot is a simple RAII-based handle of the associated vtable registration. As long as you keep the slot object, the vtable registration is active. When you let go of the slot, the vtable is automatically removed from the D-Bus object. This gives you the ability to implement "dynamic" D-Bus object API that is addable as well as removable at any time during object lifetime. + +> **_Note_:** A D-Bus object can have any number of vtables attached to it. Even a D-Bus interface of an object can have multiple vtables attached to it. The callback for any D-Bus object method on this level is any callable of signature `void(sdbus::MethodCall call)`. The `call` parameter is the incoming method call message. We need to deserialize our method input arguments from it. Then we can invoke the logic of the method and get the results. Then for the given `call`, we create a `reply` message, pack results into it and send it back to the caller through `send()`. (If we had a void-returning method, we'd just send an empty `reply` back.) We also fire a signal with the results. To do this, we need to create a signal message via object's `createSignal()`, serialize the results into it, and then send it out to subscribers by invoking object's `emitSignal()`. @@ -365,7 +369,7 @@ int main(int argc, char *argv[]) assert(result == "1:2:3"); } - // Invoke concatenate again, this time with no numbers and we shall get an error +/ // Invoke concatenate again, this time with no numbers and we shall get an error { auto method = concatenatorProxy->createMethodCall(interfaceName, "concatenate"); method << std::vector() << separator; @@ -526,15 +530,19 @@ int main(int argc, char *argv[]) // Register D-Bus methods and signals on the concatenator object, and exports the object. const char* interfaceName = "org.sdbuscpp.Concatenator"; - concatenator->registerMethod("concatenate").onInterface(interfaceName).implementedAs(std::move(concatenate)); - concatenator->registerSignal("concatenated").onInterface(interfaceName).withParameters(); - concatenator->finishRegistration(); + concatenator->addVTable( sdbus::registerMethod("concatenate").implementedAs(std::move(concatenate)) + , sdbus::registerSignal{"concatenated").withParameters() ) + .forInterface(interfaceName); // Run the loop on the connection. connection->enterEventLoop(); } ``` +> **_Tip_:** There's also an overload of `addVTable(...).forInterface()` method with `request_slot_t` tag parameter which returns a `Slot` object. The slot is a simple RAII-based handle of the associated vtable registration. As long as you keep the slot object, the vtable registration is active. When you let go of the slot, the vtable is automatically removed from the D-Bus object. This gives you the ability to implement "dynamic" D-Bus object API that is addable as well as removable at any time during object lifetime. + +> **_Note_:** A D-Bus object can have any number of vtables attached to it. Even a D-Bus interface of an object can have multiple vtables attached to it. + ### Client side ```c++ diff --git a/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-server-glue.h b/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-server-glue.h index db0e02c3..b2ca89ce 100644 --- a/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-server-glue.h +++ b/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-server-glue.h @@ -21,14 +21,24 @@ class Planet1_adaptor protected: Planet1_adaptor(sdbus::IObject& object) - : object_(object) + : object_(&object) { - object_.registerMethod("GetPopulation").onInterface(INTERFACE_NAME).withOutputParamNames("population").implementedAs([this](){ return this->GetPopulation(); }); - object_.registerProperty("Name").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Name(); }); } + Planet1_adaptor(const Planet1_adaptor&) = delete; + Planet1_adaptor& operator=(const Planet1_adaptor&) = delete; + Planet1_adaptor(Planet1_adaptor&&) = default; + Planet1_adaptor& operator=(Planet1_adaptor&&) = default; + ~Planet1_adaptor() = default; + void registerAdaptor() + { + object_->addVTable( sdbus::registerMethod("GetPopulation").withOutputParamNames("population").implementedAs([this](){ return this->GetPopulation(); }) + , sdbus::registerProperty("Name").withGetter([this](){ return this->Name(); }) + ).forInterface(INTERFACE_NAME); + } + private: virtual uint64_t GetPopulation() = 0; @@ -36,7 +46,7 @@ class Planet1_adaptor virtual std::string Name() = 0; private: - sdbus::IObject& object_; + sdbus::IObject* object_; }; }}} // namespaces diff --git a/examples/org.freedesktop.DBus.ObjectManager/obj-manager-server.cpp b/examples/org.freedesktop.DBus.ObjectManager/obj-manager-server.cpp index caadf525..4631ed5f 100644 --- a/examples/org.freedesktop.DBus.ObjectManager/obj-manager-server.cpp +++ b/examples/org.freedesktop.DBus.ObjectManager/obj-manager-server.cpp @@ -19,11 +19,11 @@ #include #include -class ManagerAdaptor : public sdbus::AdaptorInterfaces< sdbus::ObjectManager_adaptor > +class ManagerAdaptor : public sdbus::AdaptorInterfaces { public: ManagerAdaptor(sdbus::IConnection& connection, std::string path) - : AdaptorInterfaces(connection, std::move(path)) + : AdaptorInterfaces(connection, std::move(path)) { registerAdaptor(); } @@ -34,15 +34,15 @@ class ManagerAdaptor : public sdbus::AdaptorInterfaces< sdbus::ObjectManager_ada } }; -class PlanetAdaptor final : public sdbus::AdaptorInterfaces< org::sdbuscpp::ExampleManager::Planet1_adaptor, - sdbus::ManagedObject_adaptor, - sdbus::Properties_adaptor > +class PlanetAdaptor final : public sdbus::AdaptorInterfaces< org::sdbuscpp::ExampleManager::Planet1_adaptor + , sdbus::ManagedObject_adaptor + , sdbus::Properties_adaptor > { public: - PlanetAdaptor(sdbus::IConnection& connection, std::string path, std::string name, uint64_t poulation) - : AdaptorInterfaces(connection, std::move(path)) - , m_name(std::move(name)) - , m_population(poulation) + PlanetAdaptor(sdbus::IConnection& connection, std::string path, std::string name, uint64_t population) + : AdaptorInterfaces(connection, std::move(path)) + , m_name(std::move(name)) + , m_population(population) { registerAdaptor(); emitInterfacesAddedSignal({org::sdbuscpp::ExampleManager::Planet1_adaptor::INTERFACE_NAME}); @@ -105,4 +105,4 @@ int main() connection->releaseName("org.sdbuscpp.examplemanager"); connection->leaveEventLoop(); return 0; -} \ No newline at end of file +} diff --git a/include/sdbus-c++/AdaptorInterfaces.h b/include/sdbus-c++/AdaptorInterfaces.h index edd3cdfc..13400329 100644 --- a/include/sdbus-c++/AdaptorInterfaces.h +++ b/include/sdbus-c++/AdaptorInterfaces.h @@ -82,9 +82,10 @@ namespace sdbus { * adaptor-side interface classes representing interfaces (with methods, signals and properties) * of the D-Bus object. * - * In the final adaptor class inherited from AdaptorInterfaces, it is necessary to finish - * adaptor registration in class constructor (finishRegistration();`), and, conversely, - * unregister the adaptor in class destructor (`unregister();`). + * In the final adaptor class inherited from AdaptorInterfaces, one needs to make sure: + * 1. to call `registerAdaptor();` in the class constructor, and, conversely, + * 2. to call `unregisterAdaptor();` in the class destructor, + * so that the object API vtable is registered and unregistered at the proper time. * ***********************************************/ template @@ -108,15 +109,15 @@ namespace sdbus { } /*! - * @brief Finishes adaptor API registration and publishes the adaptor on the bus + * @brief Adds object vtable (i.e. D-Bus API) definitions for all interfaces it implements * * This function must be called in the constructor of the final adaptor class that implements AdaptorInterfaces. * - * For more information, see underlying @ref IObject::finishRegistration() + * See also @ref IObject::addVTable() */ void registerAdaptor() { - getObject().finishRegistration(); + (_Interfaces::registerAdaptor(), ...); } /*! @@ -132,12 +133,9 @@ namespace sdbus { } /*! - * @brief Returns object path of the underlying DBus object + * @brief Returns reference to the underlying IObject instance */ - const std::string& getObjectPath() const - { - return getObject().getObjectPath(); - } + using ObjectHolder::getObject; protected: using base_type = AdaptorInterfaces; diff --git a/include/sdbus-c++/ConvenienceApiClasses.h b/include/sdbus-c++/ConvenienceApiClasses.h index b62cbb13..e6522fef 100644 --- a/include/sdbus-c++/ConvenienceApiClasses.h +++ b/include/sdbus-c++/ConvenienceApiClasses.h @@ -27,13 +27,13 @@ #ifndef SDBUS_CXX_CONVENIENCEAPICLASSES_H_ #define SDBUS_CXX_CONVENIENCEAPICLASSES_H_ +#include #include #include #include -#include #include #include -#include +#include #include #include #include @@ -48,101 +48,16 @@ namespace sdbus { namespace sdbus { - class MethodRegistrator + class VTableAdder { public: - MethodRegistrator(IObject& object, std::string methodName); - MethodRegistrator(MethodRegistrator&& other) = default; - ~MethodRegistrator() noexcept(false); - - MethodRegistrator& onInterface(std::string interfaceName); - template MethodRegistrator& implementedAs(_Function&& callback); - MethodRegistrator& withInputParamNames(std::vector paramNames); - template MethodRegistrator& withInputParamNames(_String... paramNames); - MethodRegistrator& withOutputParamNames(std::vector paramNames); - template MethodRegistrator& withOutputParamNames(_String... paramNames); - MethodRegistrator& markAsDeprecated(); - MethodRegistrator& markAsPrivileged(); - MethodRegistrator& withNoReply(); + VTableAdder(IObject& object, std::vector vtable); + void forInterface(std::string interfaceName); + [[nodiscard]] Slot forInterface(std::string interfaceName, request_slot_t); private: IObject& object_; - std::string methodName_; - std::string interfaceName_; - std::string inputSignature_; - std::vector inputParamNames_; - std::string outputSignature_; - std::vector outputParamNames_; - method_callback methodCallback_; - Flags flags_; - int exceptions_{}; // Number of active exceptions when SignalRegistrator is constructed - }; - - class SignalRegistrator - { - public: - SignalRegistrator(IObject& object, std::string signalName); - SignalRegistrator(SignalRegistrator&& other) = default; - ~SignalRegistrator() noexcept(false); - - SignalRegistrator& onInterface(std::string interfaceName); - template SignalRegistrator& withParameters(); - template SignalRegistrator& withParameters(std::vector paramNames); - template SignalRegistrator& withParameters(_String... paramNames); - SignalRegistrator& markAsDeprecated(); - - private: - IObject& object_; - std::string signalName_; - std::string interfaceName_; - std::string signalSignature_; - std::vector paramNames_; - Flags flags_; - int exceptions_{}; // Number of active exceptions when SignalRegistrator is constructed - }; - - class PropertyRegistrator - { - public: - PropertyRegistrator(IObject& object, const std::string& propertyName); - PropertyRegistrator(PropertyRegistrator&& other) = default; - ~PropertyRegistrator() noexcept(false); - - PropertyRegistrator& onInterface(std::string interfaceName); - template PropertyRegistrator& withGetter(_Function&& callback); - template PropertyRegistrator& withSetter(_Function&& callback); - PropertyRegistrator& markAsDeprecated(); - PropertyRegistrator& markAsPrivileged(); - PropertyRegistrator& withUpdateBehavior(Flags::PropertyUpdateBehaviorFlags behavior); - - private: - IObject& object_; - const std::string& propertyName_; - std::string interfaceName_; - std::string propertySignature_; - property_get_callback getter_; - property_set_callback setter_; - Flags flags_; - int exceptions_{}; // Number of active exceptions when PropertyRegistrator is constructed - }; - - class InterfaceFlagsSetter - { - public: - InterfaceFlagsSetter(IObject& object, const std::string& interfaceName); - InterfaceFlagsSetter(InterfaceFlagsSetter&& other) = default; - ~InterfaceFlagsSetter() noexcept(false); - - InterfaceFlagsSetter& markAsDeprecated(); - InterfaceFlagsSetter& markAsPrivileged(); - InterfaceFlagsSetter& withNoReplyMethods(); - InterfaceFlagsSetter& withPropertyUpdateBehavior(Flags::PropertyUpdateBehaviorFlags behavior); - - private: - IObject& object_; - const std::string& interfaceName_; - Flags flags_; - int exceptions_{}; // Number of active exceptions when InterfaceFlagsSetter is constructed + std::vector vtable_; }; class SignalEmitter @@ -313,6 +228,6 @@ namespace sdbus { const std::string* interfaceName_{}; }; -} +} // namespace sdbus #endif /* SDBUS_CXX_CONVENIENCEAPICLASSES_H_ */ diff --git a/include/sdbus-c++/ConvenienceApiClasses.inl b/include/sdbus-c++/ConvenienceApiClasses.inl index 0cb2d6b6..017b9744 100644 --- a/include/sdbus-c++/ConvenienceApiClasses.inl +++ b/include/sdbus-c++/ConvenienceApiClasses.inl @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -41,374 +42,24 @@ namespace sdbus { - /*** ----------------- ***/ - /*** MethodRegistrator ***/ - /*** ----------------- ***/ - - inline MethodRegistrator::MethodRegistrator(IObject& object, std::string methodName) - : object_(object) - , methodName_(std::move(methodName)) - , exceptions_(std::uncaught_exceptions()) - { - } - - inline MethodRegistrator::~MethodRegistrator() noexcept(false) // since C++11, destructors must - { // explicitly be allowed to throw - // Don't register the method if MethodRegistrator threw an exception in one of its methods - if (std::uncaught_exceptions() != exceptions_) - return; - - assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function - assert(methodCallback_); // implementedAs() must be placed/called prior to this function - - // registerMethod() can throw. But as the MethodRegistrator shall always be used as an unnamed, - // temporary object, i.e. not as a stack-allocated object, the double-exception situation - // shall never happen. I.e. it should not happen that this destructor is directly called - // in the stack-unwinding process of another flying exception (which would lead to immediate - // termination). It can be called indirectly in the destructor of another object, but that's - // fine and safe provided that the caller catches exceptions thrown from here. - // Therefore, we can allow registerMethod() to throw even if we are in the destructor. - // Bottomline is, to be on the safe side, the caller must take care of catching and reacting - // to the exception thrown from here if the caller is a destructor itself. - object_.registerMethod( interfaceName_ - , std::move(methodName_) - , std::move(inputSignature_) - , inputParamNames_ - , std::move(outputSignature_) - , outputParamNames_ - , std::move(methodCallback_) - , std::move(flags_)); - } - - inline MethodRegistrator& MethodRegistrator::onInterface(std::string interfaceName) - { - interfaceName_ = std::move(interfaceName); - - return *this; - } - - template - MethodRegistrator& MethodRegistrator::implementedAs(_Function&& callback) - { - inputSignature_ = signature_of_function_input_arguments<_Function>::str(); - outputSignature_ = signature_of_function_output_arguments<_Function>::str(); - methodCallback_ = [callback = std::forward<_Function>(callback)](MethodCall call) - { - // 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. - call >> inputArgs; - - if constexpr (!is_async_method_v<_Function>) - { - // Invoke callback with input arguments from the tuple. - auto ret = sdbus::apply(callback, inputArgs); - - // Store output arguments to the reply message and send it back. - auto reply = call.createReply(); - reply << ret; - reply.send(); - } - else - { - // Invoke callback with input arguments from the tuple and with result object to be set later - using AsyncResult = typename function_traits<_Function>::async_result_t; - sdbus::apply(callback, AsyncResult{std::move(call)}, std::move(inputArgs)); - } - }; - - return *this; - } - - inline MethodRegistrator& MethodRegistrator::withInputParamNames(std::vector paramNames) - { - inputParamNames_ = std::move(paramNames); - - return *this; - } - - template - inline MethodRegistrator& MethodRegistrator::withInputParamNames(_String... paramNames) - { - static_assert(std::conjunction_v...>, "Parameter names must be (convertible to) strings"); - - return withInputParamNames({paramNames...}); - } - - inline MethodRegistrator& MethodRegistrator::withOutputParamNames(std::vector paramNames) - { - outputParamNames_ = std::move(paramNames); - - return *this; - } - - template - inline MethodRegistrator& MethodRegistrator::withOutputParamNames(_String... paramNames) - { - static_assert(std::conjunction_v...>, "Parameter names must be (convertible to) strings"); - - return withOutputParamNames({paramNames...}); - } - - inline MethodRegistrator& MethodRegistrator::markAsDeprecated() - { - flags_.set(Flags::DEPRECATED); - - return *this; - } - - inline MethodRegistrator& MethodRegistrator::markAsPrivileged() - { - flags_.set(Flags::PRIVILEGED); - - return *this; - } - - inline MethodRegistrator& MethodRegistrator::withNoReply() - { - flags_.set(Flags::METHOD_NO_REPLY); - - return *this; - } - - /*** ----------------- ***/ - /*** SignalRegistrator ***/ - /*** ----------------- ***/ - - inline SignalRegistrator::SignalRegistrator(IObject& object, std::string signalName) - : object_(object) - , signalName_(std::move(signalName)) - , exceptions_(std::uncaught_exceptions()) - { - } - - inline SignalRegistrator::~SignalRegistrator() noexcept(false) // since C++11, destructors must - { // explicitly be allowed to throw - // Don't register the signal if SignalRegistrator threw an exception in one of its methods - if (std::uncaught_exceptions() != exceptions_) - return; - - assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function - - // registerSignal() can throw. But as the SignalRegistrator shall always be used as an unnamed, - // temporary object, i.e. not as a stack-allocated object, the double-exception situation - // shall never happen. I.e. it should not happen that this destructor is directly called - // in the stack-unwinding process of another flying exception (which would lead to immediate - // termination). It can be called indirectly in the destructor of another object, but that's - // fine and safe provided that the caller catches exceptions thrown from here. - // Therefore, we can allow registerSignal() to throw even if we are in the destructor. - // Bottomline is, to be on the safe side, the caller must take care of catching and reacting - // to the exception thrown from here if the caller is a destructor itself. - object_.registerSignal( interfaceName_ - , std::move(signalName_) - , std::move(signalSignature_) - , paramNames_ - , std::move(flags_) ); - } - - inline SignalRegistrator& SignalRegistrator::onInterface(std::string interfaceName) - { - interfaceName_ = std::move(interfaceName); - - return *this; - } - - template - inline SignalRegistrator& SignalRegistrator::withParameters() - { - signalSignature_ = signature_of_function_input_arguments::str(); - - return *this; - } - - template - inline SignalRegistrator& SignalRegistrator::withParameters(std::vector paramNames) - { - paramNames_ = std::move(paramNames); - - return withParameters<_Args...>(); - } - - template - inline SignalRegistrator& SignalRegistrator::withParameters(_String... paramNames) - { - static_assert(std::conjunction_v...>, "Parameter names must be (convertible to) strings"); - static_assert(sizeof...(_Args) == sizeof...(_String), "Numbers of signal parameters and their names don't match"); - - return withParameters<_Args...>({paramNames...}); - } - - inline SignalRegistrator& SignalRegistrator::markAsDeprecated() - { - flags_.set(Flags::DEPRECATED); - - return *this; - } - - /*** ------------------- ***/ - /*** PropertyRegistrator ***/ - /*** ------------------- ***/ - - inline PropertyRegistrator::PropertyRegistrator(IObject& object, const std::string& propertyName) - : object_(object) - , propertyName_(propertyName) - , exceptions_(std::uncaught_exceptions()) - { - } - - inline PropertyRegistrator::~PropertyRegistrator() noexcept(false) // since C++11, destructors must - { // explicitly be allowed to throw - // Don't register the property if PropertyRegistrator threw an exception in one of its methods - if (std::uncaught_exceptions() != exceptions_) - return; - - assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function - - // registerProperty() can throw. But as the PropertyRegistrator shall always be used as an unnamed, - // temporary object, i.e. not as a stack-allocated object, the double-exception situation - // shall never happen. I.e. it should not happen that this destructor is directly called - // in the stack-unwinding process of another flying exception (which would lead to immediate - // termination). It can be called indirectly in the destructor of another object, but that's - // fine and safe provided that the caller catches exceptions thrown from here. - // Therefore, we can allow registerProperty() to throw even if we are in the destructor. - // Bottomline is, to be on the safe side, the caller must take care of catching and reacting - // to the exception thrown from here if the caller is a destructor itself. - object_.registerProperty( interfaceName_ - , propertyName_ - , propertySignature_ - , std::move(getter_) - , std::move(setter_) - , flags_ ); - } - - inline PropertyRegistrator& PropertyRegistrator::onInterface(std::string interfaceName) - { - interfaceName_ = std::move(interfaceName); - - return *this; - } - - template - inline PropertyRegistrator& PropertyRegistrator::withGetter(_Function&& callback) - { - static_assert(function_argument_count_v<_Function> == 0, "Property getter function must not take any arguments"); - static_assert(!std::is_void>::value, "Property getter function must return property value"); - - if (propertySignature_.empty()) - propertySignature_ = signature_of_function_output_arguments<_Function>::str(); - - getter_ = [callback = std::forward<_Function>(callback)](PropertyGetReply& reply) - { - // Get the propety value and serialize it into the pre-constructed reply message - reply << callback(); - }; - - return *this; - } - - template - inline PropertyRegistrator& PropertyRegistrator::withSetter(_Function&& callback) - { - static_assert(function_argument_count_v<_Function> == 1, "Property setter function must take one parameter - the property value"); - static_assert(std::is_void>::value, "Property setter function must not return any value"); - - if (propertySignature_.empty()) - propertySignature_ = signature_of_function_input_arguments<_Function>::str(); - - setter_ = [callback = std::forward<_Function>(callback)](PropertySetCall call) - { - // Default-construct property value - using property_type = function_argument_t<_Function, 0>; - std::decay_t property; - - // Deserialize property value from the incoming call message - call >> property; - - // Invoke setter with the value - callback(property); - }; - - return *this; - } - - inline PropertyRegistrator& PropertyRegistrator::markAsDeprecated() - { - flags_.set(Flags::DEPRECATED); - - return *this; - } - - inline PropertyRegistrator& PropertyRegistrator::markAsPrivileged() - { - flags_.set(Flags::PRIVILEGED); - - return *this; - } - - inline PropertyRegistrator& PropertyRegistrator::withUpdateBehavior(Flags::PropertyUpdateBehaviorFlags behavior) - { - flags_.set(behavior); - - return *this; - } - - /*** -------------------- ***/ - /*** InterfaceFlagsSetter ***/ - /*** -------------------- ***/ + /*** ------------- ***/ + /*** VTableAdder ***/ + /*** ------------- ***/ - inline InterfaceFlagsSetter::InterfaceFlagsSetter(IObject& object, const std::string& interfaceName) + inline VTableAdder::VTableAdder(IObject& object, std::vector vtable) : object_(object) - , interfaceName_(interfaceName) - , exceptions_(std::uncaught_exceptions()) + , vtable_(std::move(vtable)) { } - inline InterfaceFlagsSetter::~InterfaceFlagsSetter() noexcept(false) // since C++11, destructors must - { // explicitly be allowed to throw - // Don't set any flags if InterfaceFlagsSetter threw an exception in one of its methods - if (std::uncaught_exceptions() != exceptions_) - return; - - // setInterfaceFlags() can throw. But as the InterfaceFlagsSetter shall always be used as an unnamed, - // temporary object, i.e. not as a stack-allocated object, the double-exception situation - // shall never happen. I.e. it should not happen that this destructor is directly called - // in the stack-unwinding process of another flying exception (which would lead to immediate - // termination). It can be called indirectly in the destructor of another object, but that's - // fine and safe provided that the caller catches exceptions thrown from here. - // Therefore, we can allow setInterfaceFlags() to throw even if we are in the destructor. - // Bottomline is, to be on the safe side, the caller must take care of catching and reacting - // to the exception thrown from here if the caller is a destructor itself. - object_.setInterfaceFlags(interfaceName_, std::move(flags_)); - } - - inline InterfaceFlagsSetter& InterfaceFlagsSetter::markAsDeprecated() + inline void VTableAdder::forInterface(std::string interfaceName) { - flags_.set(Flags::DEPRECATED); - - return *this; + object_.addVTable(std::move(interfaceName), std::move(vtable_)); } - inline InterfaceFlagsSetter& InterfaceFlagsSetter::markAsPrivileged() + inline Slot VTableAdder::forInterface(std::string interfaceName, request_slot_t) { - flags_.set(Flags::PRIVILEGED); - - return *this; - } - - inline InterfaceFlagsSetter& InterfaceFlagsSetter::withNoReplyMethods() - { - flags_.set(Flags::METHOD_NO_REPLY); - - return *this; - } - - inline InterfaceFlagsSetter& InterfaceFlagsSetter::withPropertyUpdateBehavior(Flags::PropertyUpdateBehaviorFlags behavior) - { - flags_.set(behavior); - - return *this; + return object_.addVTable(std::move(interfaceName), std::move(vtable_), request_slot); } /*** ------------- ***/ @@ -930,6 +581,6 @@ namespace sdbus { .getResultAsFuture>(); } -} +} // namespace sdbus #endif /* SDBUS_CPP_CONVENIENCEAPICLASSES_INL_ */ diff --git a/include/sdbus-c++/IObject.h b/include/sdbus-c++/IObject.h index 9422eebb..a6fb9a55 100644 --- a/include/sdbus-c++/IObject.h +++ b/include/sdbus-c++/IObject.h @@ -27,6 +27,7 @@ #ifndef SDBUS_CXX_IOBJECT_H_ #define SDBUS_CXX_IOBJECT_H_ +#include #include #include #include @@ -62,144 +63,104 @@ namespace sdbus { virtual ~IObject() = default; /*! - * @brief Registers method that the object will provide on D-Bus + * @brief Adds a declaration of methods, properties and signals of the object at a given interface * - * @param[in] interfaceName Name of an interface that the method will belong to - * @param[in] methodName Name of the method - * @param[in] inputSignature D-Bus signature of method input parameters - * @param[in] outputSignature D-Bus signature of method output parameters - * @param[in] methodCallback Callback that implements the body of the method - * @param[in] flags D-Bus method flags (privileged, deprecated, or no reply) + * @param[in] interfaceName Name of an interface the the vtable is registered for + * @param[in] items Individual instances of VTable item structures * - * @throws sdbus::Error in case of failure - */ - virtual void registerMethod( const std::string& interfaceName - , std::string methodName - , std::string inputSignature - , std::string outputSignature - , method_callback methodCallback - , Flags flags = {} ) = 0; - - /*! - * @brief Registers method that the object will provide on D-Bus - * - * @param[in] interfaceName Name of an interface that the method will belong to - * @param[in] methodName Name of the method - * @param[in] inputSignature D-Bus signature of method input parameters - * @param[in] inputNames Names of input parameters - * @param[in] outputSignature D-Bus signature of method output parameters - * @param[in] outputNames Names of output parameters - * @param[in] methodCallback Callback that implements the body of the method - * @param[in] flags D-Bus method flags (privileged, deprecated, or no reply) - * - * Provided names of input and output parameters will be included in the introspection - * description (given that at least version 242 of underlying libsystemd library is - * used; otherwise, names of parameters are ignored). This usually helps better describe - * the API to the introspector. + * This method is used to declare attributes for the object under the given interface. + * Parameter `items' represents a vtable definition that may contain method declarations + * (using MethodVTableItem struct), property declarations (using PropertyVTableItem + * struct), signal declarations (using SignalVTableItem struct), or global interface + * flags (using InterfaceFlagsVTableItem struct). * - * @throws sdbus::Error in case of failure - */ - virtual void registerMethod( const std::string& interfaceName - , std::string methodName - , std::string inputSignature - , const std::vector& inputNames - , std::string outputSignature - , const std::vector& outputNames - , method_callback methodCallback - , Flags flags = {} ) = 0; - - /*! - * @brief Registers signal that the object will emit on D-Bus + * An interface can have any number of vtables attached to it. * - * @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) + * Consult manual pages for underlying `sd_bus_add_object_vtable` function for more information. + * + * The method can be called at any time during object's lifetime. For each vtable an internal + * match slot is created and its lifetime is tied to the lifetime of the Object instance. + * + * The function provides strong exception guarantee. The state of the object remains + * unmodified in face of an exception. * * @throws sdbus::Error in case of failure */ - virtual void registerSignal( const std::string& interfaceName - , std::string signalName - , std::string signature - , Flags flags = {} ) = 0; + template < typename... VTableItems + , typename = std::enable_if_t<(is_one_of_variants_types> && ...)> > + void addVTable(std::string interfaceName, VTableItems&&... items); /*! - * @brief Registers signal that the object will emit on D-Bus + * @brief Adds a declaration of methods, properties and signals of the object at a given interface * - * @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] paramNames Names of parameters of the signal - * @param[in] flags D-Bus signal flags (deprecated) + * @param[in] interfaceName Name of an interface the the vtable is registered for + * @param[in] vtable A list of individual descriptions in the form of VTable item instances * - * Provided names of signal output parameters will be included in the introspection - * description (given that at least version 242 of underlying libsystemd library is - * used; otherwise, names of parameters are ignored). This usually helps better describe - * the API to the introspector. + * This method is used to declare attributes for the object under the given interface. + * The `vtable' parameter may contain method declarations (using MethodVTableItem struct), + * property declarations (using PropertyVTableItem struct), signal declarations (using + * SignalVTableItem struct), or global interface flags (using InterfaceFlagsVTableItem struct). * - * @throws sdbus::Error in case of failure - */ - virtual void registerSignal( const std::string& interfaceName - , std::string signalName - , std::string signature - , const std::vector& paramNames - , Flags flags = {} ) = 0; - - /*! - * @brief Registers read-only property that the object will provide on D-Bus + * An interface can have any number of vtables attached to it. + * + * Consult manual pages for underlying `sd_bus_add_object_vtable` function for more information. + * + * The method can be called at any time during object's lifetime. For each vtable an internal + * match slot is created and its lifetime is tied to the lifetime of the Object instance. * - * @param[in] interfaceName Name of an interface that the property will fall under - * @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) + * The function provides strong exception guarantee. The state of the object remains + * unmodified in face of an exception. * * @throws sdbus::Error in case of failure */ - virtual void registerProperty( const std::string& interfaceName - , std::string propertyName - , std::string signature - , property_get_callback getCallback - , Flags flags = {} ) = 0; + virtual void addVTable(std::string interfaceName, std::vector vtable) = 0; /*! - * @brief Registers read/write property that the object will provide on D-Bus + * @brief Adds a declaration of methods, properties and signals of the object at a given interface + * + * @param[in] interfaceName Name of an interface the the vtable is registered for + * @param[in] vtable A list of individual descriptions in the form of VTable item instances + * + * This method is used to declare attributes for the object under the given interface. + * The `vtable' parameter may contain method declarations (using MethodVTableItem struct), + * property declarations (using PropertyVTableItem struct), signal declarations (using + * SignalVTableItem struct), or global interface flags (using InterfaceFlagsVTableItem struct). * - * @param[in] interfaceName Name of an interface that the property will fall under - * @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] setCallback Callback that implements the body of the property setter - * @param[in] flags D-Bus property flags (deprecated, property update behavior) + * An interface can have any number of vtables attached to it. + * + * Consult manual pages for underlying `sd_bus_add_object_vtable` function for more information. + * + * The method can be called at any time during object's lifetime. For each vtable an internal + * match slot is created and is returned to the caller. The returned slot should be destroyed + * when the vtable is not needed anymore. This allows for "dynamic" object API where vtables + * can be added or removed by the user at runtime. + * + * The function provides strong exception guarantee. The state of the object remains + * unmodified in face of an exception. * * @throws sdbus::Error in case of failure */ - virtual void registerProperty( const std::string& interfaceName - , std::string propertyName - , std::string signature - , property_get_callback getCallback - , property_set_callback setCallback - , Flags flags = {} ) = 0; + virtual Slot addVTable(std::string interfaceName, std::vector vtable, request_slot_t) = 0; /*! - * @brief Sets flags for a given interface + * @brief A little more convenient overload of addVTable() above * - * @param[in] interfaceName Name of an interface whose flags will be set - * @param[in] flags Flags to be set + * This version allows method chaining for a little safer and more readable VTable registration. * - * @throws sdbus::Error in case of failure + * See addVTable() overloads above for detailed documentation. */ - virtual void setInterfaceFlags(const std::string& interfaceName, Flags flags) = 0; + template < typename... VTableItems + , typename = std::enable_if_t<(is_one_of_variants_types> && ...)> > + [[nodiscard]] VTableAdder addVTable(VTableItems&&... items); /*! - * @brief Finishes object API registration and publishes the object on the bus + * @brief A little more convenient overload of addVTable() above * - * The method exports all up to now registered methods, signals and properties on D-Bus. - * Must be called after all methods, signals and properties have been registered. + * This version allows method chaining for a little safer and more readable VTable registration. * - * @throws sdbus::Error in case of failure + * See addVTable() overloads above for detailed documentation. */ - virtual void finishRegistration() = 0; + [[nodiscard]] VTableAdder addVTable(std::vector vtable); /*! * @brief Unregisters object's API and removes object from the bus @@ -276,10 +237,8 @@ namespace sdbus { * @brief Emits InterfacesAdded signal on this object path * * This emits an InterfacesAdded signal on this object path with explicitly provided list - * of registered interfaces. As sdbus-c++ does currently not supported adding/removing - * interfaces of an existing object at run time (an object has a fixed set of interfaces - * registered by the time of invoking finishRegistration()), emitInterfacesAddedSignal(void) - * is probably what you are looking for. + * of registered interfaces. Since v2.0, sdbus-c++ supports dynamically addable/removable + * object interfaces and their vtables, so this method now makes more sense. * * @throws sdbus::Error in case of failure */ @@ -300,10 +259,8 @@ namespace sdbus { * @brief Emits InterfacesRemoved signal on this object path * * This emits an InterfacesRemoved signal on the given path with explicitly provided list - * of registered interfaces. As sdbus-c++ does currently not supported adding/removing - * interfaces of an existing object at run time (an object has a fixed set of interfaces - * registered by the time of invoking finishRegistration()), emitInterfacesRemovedSignal(void) - * is probably what you are looking for. + * of registered interfaces. Since v2.0, sdbus-c++ supports dynamically addable/removable + * object interfaces and their vtables, so this method now makes more sense. * * @throws sdbus::Error in case of failure */ @@ -340,81 +297,6 @@ namespace sdbus { */ virtual sdbus::IConnection& getConnection() const = 0; - /*! - * @brief Registers method that the object will provide on D-Bus - * - * @param[in] methodName Name of the method - * @return A helper object for convenient registration of the method - * - * This is a high-level, convenience way of registering 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 parameters and return type - * of the provided native method implementation callback. - * - * Example of use: - * @code - * object.registerMethod("doFoo").onInterface("com.kistler.foo").implementedAs([this](int value){ return this->doFoo(value); }); - * @endcode - * - * @throws sdbus::Error in case of failure - */ - [[nodiscard]] MethodRegistrator registerMethod(const std::string& methodName); - - /*! - * @brief Registers signal that the object will provide on D-Bus - * - * @param[in] signalName Name of the signal - * @return A helper object for convenient registration of the signal - * - * This is a high-level, convenience way of registering D-Bus signals that abstracts - * from the D-Bus message concept. Signal arguments are automatically (de)serialized - * in a message and D-Bus signatures automatically deduced from the provided native parameters. - * - * Example of use: - * @code - * object.registerSignal("paramChange").onInterface("com.kistler.foo").withParameters>(); - * @endcode - * - * @throws sdbus::Error in case of failure - */ - [[nodiscard]] SignalRegistrator registerSignal(const std::string& signalName); - - /*! - * @brief Registers property that the object will provide on D-Bus - * - * @param[in] propertyName Name of the property - * @return A helper object for convenient registration of the property - * - * This is a high-level, convenience way of registering D-Bus properties that abstracts - * from the D-Bus message concept. Property arguments are automatically (de)serialized - * in a message and D-Bus signatures automatically deduced from the provided native callbacks. - * - * Example of use: - * @code - * object_.registerProperty("state").onInterface("com.kistler.foo").withGetter([this](){ return this->state(); }); - * @endcode - * - * @throws sdbus::Error in case of failure - */ - [[nodiscard]] PropertyRegistrator registerProperty(const std::string& propertyName); - - /*! - * @brief Sets flags (annotations) for a given interface - * - * @param[in] interfaceName Name of an interface whose flags will be set - * @return A helper object for convenient setting of Interface flags - * - * This is a high-level, convenience alternative to the other setInterfaceFlags overload. - * - * Example of use: - * @code - * object_.setInterfaceFlags("com.kistler.foo").markAsDeprecated().withPropertyUpdateBehavior(sdbus::Flags::EMITS_NO_SIGNAL); - * @endcode - * - * @throws sdbus::Error in case of failure - */ - [[nodiscard]] InterfaceFlagsSetter setInterfaceFlags(const std::string& interfaceName); - /*! * @brief Emits signal on D-Bus * @@ -459,29 +341,26 @@ namespace sdbus { // Out-of-line member definitions - inline MethodRegistrator IObject::registerMethod(const std::string& methodName) - { - return MethodRegistrator(*this, methodName); - } - - inline SignalRegistrator IObject::registerSignal(const std::string& signalName) + inline SignalEmitter IObject::emitSignal(const std::string& signalName) { - return SignalRegistrator(*this, signalName); + return SignalEmitter(*this, signalName); } - inline PropertyRegistrator IObject::registerProperty(const std::string& propertyName) + template + void IObject::addVTable(std::string interfaceName, VTableItems&&... items) { - return PropertyRegistrator(*this, propertyName); + addVTable(std::move(interfaceName), {std::forward(items)...}); } - inline InterfaceFlagsSetter IObject::setInterfaceFlags(const std::string& interfaceName) + template + VTableAdder IObject::addVTable(VTableItems&&... items) { - return InterfaceFlagsSetter(*this, interfaceName); + return addVTable(std::vector{std::forward(items)...}); } - inline SignalEmitter IObject::emitSignal(const std::string& signalName) + inline VTableAdder IObject::addVTable(std::vector vtable) { - return SignalEmitter(*this, signalName); + return VTableAdder(*this, std::move(vtable)); } /*! @@ -506,6 +385,7 @@ namespace sdbus { } +#include #include #endif /* SDBUS_CXX_IOBJECT_H_ */ diff --git a/include/sdbus-c++/ProxyInterfaces.h b/include/sdbus-c++/ProxyInterfaces.h index a48525ef..0ad98de4 100644 --- a/include/sdbus-c++/ProxyInterfaces.h +++ b/include/sdbus-c++/ProxyInterfaces.h @@ -197,12 +197,9 @@ namespace sdbus { } /*! - * @brief Returns object path of the underlying DBus object + * @brief Returns reference to the underlying IProxy instance */ - const std::string& getObjectPath() const - { - return getProxy().getObjectPath(); - } + using ProxyObjectHolder::getProxy; protected: using base_type = ProxyInterfaces; diff --git a/include/sdbus-c++/StandardInterfaces.h b/include/sdbus-c++/StandardInterfaces.h index 89937a48..d193662b 100644 --- a/include/sdbus-c++/StandardInterfaces.h +++ b/include/sdbus-c++/StandardInterfaces.h @@ -251,8 +251,7 @@ namespace sdbus { static constexpr const char* INTERFACE_NAME = "org.freedesktop.DBus.Properties"; protected: - Properties_adaptor(sdbus::IObject& object) - : object_(&object) + Properties_adaptor(sdbus::IObject& object) : object_(&object) { } @@ -263,6 +262,10 @@ namespace sdbus { ~Properties_adaptor() = default; + void registerAdaptor() + { + } + public: void emitPropertiesChangedSignal(const std::string& interfaceName, const std::vector& properties) { @@ -293,10 +296,8 @@ namespace sdbus { static constexpr const char* INTERFACE_NAME = "org.freedesktop.DBus.ObjectManager"; protected: - explicit ObjectManager_adaptor(sdbus::IObject& object) - : object_(&object) + explicit ObjectManager_adaptor(sdbus::IObject& object) : object_(&object) { - object_->addObjectManager(); } ObjectManager_adaptor(const ObjectManager_adaptor&) = delete; @@ -306,6 +307,11 @@ namespace sdbus { ~ObjectManager_adaptor() = default; + void registerAdaptor() + { + object_->addObjectManager(); + } + private: sdbus::IObject* object_; }; @@ -336,6 +342,10 @@ namespace sdbus { ~ManagedObject_adaptor() = default; + void registerAdaptor() + { + } + public: /*! * @brief Emits InterfacesAdded signal for this object path diff --git a/include/sdbus-c++/TypeTraits.h b/include/sdbus-c++/TypeTraits.h index bec6148a..a0b8825f 100644 --- a/include/sdbus-c++/TypeTraits.h +++ b/include/sdbus-c++/TypeTraits.h @@ -41,6 +41,7 @@ #include #include #include +#include // Forward declarations namespace sdbus { @@ -659,6 +660,15 @@ namespace sdbus { using future_return_t = typename future_return<_Args...>::type; + // Credit: Piotr Skotnicki (https://stackoverflow.com/a/57639506) + template + constexpr bool is_one_of_variants_types = false; + + template + constexpr bool is_one_of_variants_types, _QueriedType> + = (std::is_same_v<_QueriedType, _VariantTypes> || ...); + + namespace detail { template diff --git a/include/sdbus-c++/VTableItems.h b/include/sdbus-c++/VTableItems.h new file mode 100644 index 00000000..726fd9f3 --- /dev/null +++ b/include/sdbus-c++/VTableItems.h @@ -0,0 +1,107 @@ +/** + * (C) 2023 Stanislav Angelovic + * + * @file VTableItems.h + * + * Created on: Dec 14, 2023 + * 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 . + */ + +#ifndef SDBUS_CXX_VTABLEITEMS_H_ +#define SDBUS_CXX_VTABLEITEMS_H_ + +#include +#include +#include +#include +#include + +namespace sdbus { + + struct MethodVTableItem + { + template MethodVTableItem& implementedAs(_Function&& callback); + MethodVTableItem& withInputParamNames(std::vector names); + template MethodVTableItem& withInputParamNames(_String... names); + MethodVTableItem& withOutputParamNames(std::vector names); + template MethodVTableItem& withOutputParamNames(_String... names); + MethodVTableItem& markAsDeprecated(); + MethodVTableItem& markAsPrivileged(); + MethodVTableItem& withNoReply(); + + std::string name; + std::string inputSignature; + std::vector inputParamNames; + std::string outputSignature; + std::vector outputParamNames; + method_callback callbackHandler; + Flags flags; + }; + + MethodVTableItem registerMethod(std::string methodName); + + struct SignalVTableItem + { + template SignalVTableItem& withParameters(); + template SignalVTableItem& withParameters(std::vector names); + template SignalVTableItem& withParameters(_String... names); + SignalVTableItem& markAsDeprecated(); + + std::string name; + std::string signature; + std::vector paramNames; + Flags flags; + }; + + SignalVTableItem registerSignal(std::string signalName); + + struct PropertyVTableItem + { + template PropertyVTableItem& withGetter(_Function&& callback); + template PropertyVTableItem& withSetter(_Function&& callback); + PropertyVTableItem& markAsDeprecated(); + PropertyVTableItem& markAsPrivileged(); + PropertyVTableItem& withUpdateBehavior(Flags::PropertyUpdateBehaviorFlags behavior); + + std::string name; + std::string signature; + property_get_callback getter; + property_set_callback setter; + Flags flags; + }; + + PropertyVTableItem registerProperty(std::string propertyName); + + struct InterfaceFlagsVTableItem + { + InterfaceFlagsVTableItem& markAsDeprecated(); + InterfaceFlagsVTableItem& markAsPrivileged(); + InterfaceFlagsVTableItem& withNoReplyMethods(); + InterfaceFlagsVTableItem& withPropertyUpdateBehavior(Flags::PropertyUpdateBehaviorFlags behavior); + + Flags flags; + }; + + InterfaceFlagsVTableItem setInterfaceFlags(); + + using VTableItem = std::variant; + +} // namespace sdbus + +#endif /* SDBUS_CXX_VTABLEITEMS_H_ */ diff --git a/include/sdbus-c++/VTableItems.inl b/include/sdbus-c++/VTableItems.inl new file mode 100644 index 00000000..a989bacd --- /dev/null +++ b/include/sdbus-c++/VTableItems.inl @@ -0,0 +1,285 @@ +/** + * (C) 2023 Stanislav Angelovic + * + * @file VTableItems.inl + * + * Created on: Dec 14, 2023 + * 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 . + */ + +#ifndef SDBUS_CPP_VTABLEITEMS_INL_ +#define SDBUS_CPP_VTABLEITEMS_INL_ + +#include +#include +#include +#include +#include + +namespace sdbus { + + /*** -------------------- ***/ + /*** Method VTable Item ***/ + /*** -------------------- ***/ + + template + MethodVTableItem& MethodVTableItem::implementedAs(_Function&& callback) + { + inputSignature = signature_of_function_input_arguments<_Function>::str(); + outputSignature = signature_of_function_output_arguments<_Function>::str(); + callbackHandler = [callback = std::forward<_Function>(callback)](MethodCall call) + { + // 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. + call >> inputArgs; + + if constexpr (!is_async_method_v<_Function>) + { + // Invoke callback with input arguments from the tuple. + auto ret = sdbus::apply(callback, inputArgs); + + // Store output arguments to the reply message and send it back. + auto reply = call.createReply(); + reply << ret; + reply.send(); + } + else + { + // Invoke callback with input arguments from the tuple and with result object to be set later + using AsyncResult = typename function_traits<_Function>::async_result_t; + sdbus::apply(callback, AsyncResult{std::move(call)}, std::move(inputArgs)); + } + }; + + return *this; + } + + inline MethodVTableItem& MethodVTableItem::withInputParamNames(std::vector names) + { + inputParamNames = std::move(names); + + return *this; + } + + template + inline MethodVTableItem& MethodVTableItem::withInputParamNames(_String... names) + { + static_assert(std::conjunction_v...>, "Parameter names must be (convertible to) strings"); + + return withInputParamNames({names...}); + } + + inline MethodVTableItem& MethodVTableItem::withOutputParamNames(std::vector names) + { + outputParamNames = std::move(names); + + return *this; + } + + template + inline MethodVTableItem& MethodVTableItem::withOutputParamNames(_String... names) + { + static_assert(std::conjunction_v...>, "Parameter names must be (convertible to) strings"); + + return withOutputParamNames({names...}); + } + + inline MethodVTableItem& MethodVTableItem::markAsDeprecated() + { + flags.set(Flags::DEPRECATED); + + return *this; + } + + inline MethodVTableItem& MethodVTableItem::markAsPrivileged() + { + flags.set(Flags::PRIVILEGED); + + return *this; + } + + inline MethodVTableItem& MethodVTableItem::withNoReply() + { + flags.set(Flags::METHOD_NO_REPLY); + + return *this; + } + + inline MethodVTableItem registerMethod(std::string methodName) + { + return {std::move(methodName), {}, {}, {}, {}, {}, {}}; + } + + /*** -------------------- ***/ + /*** Signal VTable Item ***/ + /*** -------------------- ***/ + + template + inline SignalVTableItem& SignalVTableItem::withParameters() + { + signature = signature_of_function_input_arguments::str(); + + return *this; + } + + template + inline SignalVTableItem& SignalVTableItem::withParameters(std::vector names) + { + paramNames = std::move(names); + + return withParameters<_Args...>(); + } + + template + inline SignalVTableItem& SignalVTableItem::withParameters(_String... names) + { + static_assert(std::conjunction_v...>, "Parameter names must be (convertible to) strings"); + static_assert(sizeof...(_Args) == sizeof...(_String), "Numbers of signal parameters and their names don't match"); + + return withParameters<_Args...>({names...}); + } + + inline SignalVTableItem& SignalVTableItem::markAsDeprecated() + { + flags.set(Flags::DEPRECATED); + + return *this; + } + + inline SignalVTableItem registerSignal(std::string signalName) + { + return {std::move(signalName), {}, {}, {}}; + } + + /*** -------------------- ***/ + /*** Property VTable Item ***/ + /*** -------------------- ***/ + + template + inline PropertyVTableItem& PropertyVTableItem::withGetter(_Function&& callback) + { + static_assert(function_argument_count_v<_Function> == 0, "Property getter function must not take any arguments"); + static_assert(!std::is_void>::value, "Property getter function must return property value"); + + if (signature.empty()) + signature = signature_of_function_output_arguments<_Function>::str(); + + getter = [callback = std::forward<_Function>(callback)](PropertyGetReply& reply) + { + // Get the propety value and serialize it into the pre-constructed reply message + reply << callback(); + }; + + return *this; + } + + template + inline PropertyVTableItem& PropertyVTableItem::withSetter(_Function&& callback) + { + static_assert(function_argument_count_v<_Function> == 1, "Property setter function must take one parameter - the property value"); + static_assert(std::is_void>::value, "Property setter function must not return any value"); + + if (signature.empty()) + signature = signature_of_function_input_arguments<_Function>::str(); + + setter = [callback = std::forward<_Function>(callback)](PropertySetCall call) + { + // Default-construct property value + using property_type = function_argument_t<_Function, 0>; + std::decay_t property; + + // Deserialize property value from the incoming call message + call >> property; + + // Invoke setter with the value + callback(property); + }; + + return *this; + } + + inline PropertyVTableItem& PropertyVTableItem::markAsDeprecated() + { + flags.set(Flags::DEPRECATED); + + return *this; + } + + inline PropertyVTableItem& PropertyVTableItem::markAsPrivileged() + { + flags.set(Flags::PRIVILEGED); + + return *this; + } + + inline PropertyVTableItem& PropertyVTableItem::withUpdateBehavior(Flags::PropertyUpdateBehaviorFlags behavior) + { + flags.set(behavior); + + return *this; + } + + inline PropertyVTableItem registerProperty(std::string propertyName) + { + return {std::move(propertyName), {}, {}, {}, {}}; + } + + /*** --------------------------- ***/ + /*** Interface Flags VTable Item ***/ + /*** --------------------------- ***/ + + inline InterfaceFlagsVTableItem& InterfaceFlagsVTableItem::markAsDeprecated() + { + flags.set(Flags::DEPRECATED); + + return *this; + } + + inline InterfaceFlagsVTableItem& InterfaceFlagsVTableItem::markAsPrivileged() + { + flags.set(Flags::PRIVILEGED); + + return *this; + } + + inline InterfaceFlagsVTableItem& InterfaceFlagsVTableItem::withNoReplyMethods() + { + flags.set(Flags::METHOD_NO_REPLY); + + return *this; + } + + inline InterfaceFlagsVTableItem& InterfaceFlagsVTableItem::withPropertyUpdateBehavior(Flags::PropertyUpdateBehaviorFlags behavior) + { + flags.set(behavior); + + return *this; + } + + inline InterfaceFlagsVTableItem setInterfaceFlags() + { + return {}; + } + +} // namespace sdbus + +#endif /* SDBUS_CPP_VTABLEITEMS_INL_ */ diff --git a/src/Connection.cpp b/src/Connection.cpp index fe3b62f6..7eeeb848 100644 --- a/src/Connection.cpp +++ b/src/Connection.cpp @@ -470,7 +470,7 @@ Slot Connection::addObjectVTable( const std::string& objectPath { sd_bus_slot *slot{}; - auto r = sdbus_->sd_bus_add_object_vtable(bus_.get() + auto r = sdbus_->sd_bus_add_object_vtable( bus_.get() , &slot , objectPath.c_str() , interfaceName.c_str() @@ -500,7 +500,7 @@ MethodCall Connection::createMethodCall( const std::string& destination { sd_bus_message *sdbusMsg{}; - auto r = sdbus_->sd_bus_message_new_method_call(bus_.get() + auto r = sdbus_->sd_bus_message_new_method_call( bus_.get() , &sdbusMsg , destination.empty() ? nullptr : destination.c_str() , objectPath.c_str() @@ -518,7 +518,7 @@ Signal Connection::createSignal( const std::string& objectPath { sd_bus_message *sdbusMsg{}; - auto r = sdbus_->sd_bus_message_new_signal(bus_.get() + auto r = sdbus_->sd_bus_message_new_signal( bus_.get() , &sdbusMsg , objectPath.c_str() , interfaceName.c_str() @@ -575,7 +575,7 @@ void Connection::emitPropertiesChangedSignal( const std::string& objectPath { auto names = to_strv(propNames); - auto r = sdbus_->sd_bus_emit_properties_changed_strv(bus_.get() + auto r = sdbus_->sd_bus_emit_properties_changed_strv( bus_.get() , objectPath.c_str() , interfaceName.c_str() , propNames.empty() ? nullptr : &names[0] ); @@ -595,7 +595,7 @@ void Connection::emitInterfacesAddedSignal( const std::string& objectPath { auto names = to_strv(interfaces); - auto r = sdbus_->sd_bus_emit_interfaces_added_strv(bus_.get() + auto r = sdbus_->sd_bus_emit_interfaces_added_strv( bus_.get() , objectPath.c_str() , interfaces.empty() ? nullptr : &names[0] ); @@ -614,7 +614,7 @@ void Connection::emitInterfacesRemovedSignal( const std::string& objectPath { auto names = to_strv(interfaces); - auto r = sdbus_->sd_bus_emit_interfaces_removed_strv(bus_.get() + auto r = sdbus_->sd_bus_emit_interfaces_removed_strv( bus_.get() , objectPath.c_str() , interfaces.empty() ? nullptr : &names[0] ); diff --git a/src/Object.cpp b/src/Object.cpp index e8e32fdb..51caa83f 100644 --- a/src/Object.cpp +++ b/src/Object.cpp @@ -47,129 +47,33 @@ Object::Object(sdbus::internal::IConnection& connection, std::string objectPath) SDBUS_CHECK_OBJECT_PATH(objectPath_); } -void Object::registerMethod( const std::string& interfaceName - , std::string methodName - , std::string inputSignature - , std::string outputSignature - , method_callback methodCallback - , Flags flags ) -{ - registerMethod( interfaceName - , std::move(methodName) - , std::move(inputSignature) - , {} - , std::move(outputSignature) - , {} - , std::move(methodCallback) - , std::move(flags) ); -} - -void Object::registerMethod( const std::string& interfaceName - , std::string methodName - , std::string inputSignature - , const std::vector& inputNames - , std::string outputSignature - , const std::vector& outputNames - , method_callback methodCallback - , Flags flags ) +void Object::addVTable(std::string interfaceName, std::vector vtable) { - SDBUS_CHECK_INTERFACE_NAME(interfaceName); - SDBUS_CHECK_MEMBER_NAME(methodName); - SDBUS_THROW_ERROR_IF(!methodCallback, "Invalid method callback provided", EINVAL); - - auto& interface = getInterface(interfaceName); - InterfaceData::MethodData methodData{ std::move(inputSignature) - , std::move(outputSignature) - , paramNamesToString(inputNames) + paramNamesToString(outputNames) - , std::move(methodCallback) - , std::move(flags) }; - auto inserted = interface.methods.emplace(std::move(methodName), std::move(methodData)).second; - - SDBUS_THROW_ERROR_IF(!inserted, "Failed to register method: method already exists", EINVAL); -} + auto slot = Object::addVTable(std::move(interfaceName), std::move(vtable), request_slot); -void Object::registerSignal( const std::string& interfaceName - , std::string signalName - , std::string signature - , Flags flags ) -{ - registerSignal(interfaceName, std::move(signalName), std::move(signature), {}, std::move(flags)); + vtables_.push_back(std::move(slot)); } -void Object::registerSignal( const std::string& interfaceName - , std::string signalName - , std::string signature - , const std::vector& paramNames - , Flags flags ) +Slot Object::addVTable(std::string interfaceName, std::vector vtable, request_slot_t) { SDBUS_CHECK_INTERFACE_NAME(interfaceName); - SDBUS_CHECK_MEMBER_NAME(signalName); - - auto& interface = getInterface(interfaceName); - - InterfaceData::SignalData signalData{std::move(signature), paramNamesToString(paramNames), std::move(flags)}; - auto inserted = interface.signals.emplace(std::move(signalName), std::move(signalData)).second; - - SDBUS_THROW_ERROR_IF(!inserted, "Failed to register signal: signal already exists", EINVAL); -} - -void Object::registerProperty( const std::string& interfaceName - , std::string propertyName - , std::string signature - , property_get_callback getCallback - , Flags flags ) -{ - registerProperty( interfaceName - , std::move(propertyName) - , std::move(signature) - , std::move(getCallback) - , {} - , std::move(flags) ); -} -void Object::registerProperty( const std::string& interfaceName - , std::string propertyName - , std::string signature - , property_get_callback getCallback - , property_set_callback setCallback - , Flags flags ) -{ - SDBUS_CHECK_INTERFACE_NAME(interfaceName); - SDBUS_CHECK_MEMBER_NAME(propertyName); - SDBUS_THROW_ERROR_IF(!getCallback && !setCallback, "Invalid property callbacks provided", EINVAL); + // 1st pass -- create vtable structure for internal sdbus-c++ purposes + auto internalVTable = std::make_unique(createInternalVTable(std::move(interfaceName), std::move(vtable))); - auto& interface = getInterface(interfaceName); + // 2nd pass -- from internal sdbus-c++ vtable, create vtable structure in format expected by underlying sd-bus library + internalVTable->sdbusVTable = createInternalSdBusVTable(*internalVTable); - InterfaceData::PropertyData propertyData{ std::move(signature) - , std::move(getCallback) - , std::move(setCallback) - , std::move(flags) }; - auto inserted = interface.properties.emplace(std::move(propertyName), std::move(propertyData)).second; + // 3rd step -- register the vtable with sd-bus + internalVTable->slot = connection_.addObjectVTable(objectPath_, internalVTable->interfaceName, &internalVTable->sdbusVTable[0], internalVTable.get()); - SDBUS_THROW_ERROR_IF(!inserted, "Failed to register property: property already exists", EINVAL); -} - -void Object::setInterfaceFlags(const std::string& interfaceName, Flags flags) -{ - auto& interface = getInterface(interfaceName); - interface.flags = flags; -} - -void Object::finishRegistration() -{ - for (auto& item : interfaces_) - { - const auto& interfaceName = item.first; - auto& interfaceData = item.second; - - const auto& vtable = createInterfaceVTable(interfaceData); - activateInterfaceVTable(interfaceName, interfaceData, vtable); - } + // Return vtable wrapped in a Slot object + return {internalVTable.release(), [](void *ptr){ delete static_cast(ptr); }}; } void Object::unregister() { - interfaces_.clear(); + vtables_.clear(); removeObjectManager(); } @@ -245,81 +149,151 @@ Message Object::getCurrentlyProcessedMessage() const return connection_.getCurrentlyProcessedMessage(); } -Object::InterfaceData& Object::getInterface(const std::string& interfaceName) +Object::VTable Object::createInternalVTable(std::string interfaceName, std::vector vtable) +{ + VTable internalVTable; + + internalVTable.interfaceName = std::move(interfaceName); + + for (auto& vtableItem : vtable) + { + std::visit( overload{ [&](InterfaceFlagsVTableItem&& interfaceFlags){ writeInterfaceFlagsToVTable(std::move(interfaceFlags), internalVTable); } + , [&](MethodVTableItem&& method){ writeMethodRecordToVTable(std::move(method), internalVTable); } + , [&](SignalVTableItem&& signal){ writeSignalRecordToVTable(std::move(signal), internalVTable); } + , [&](PropertyVTableItem&& property){ writePropertyRecordToVTable(std::move(property), internalVTable); } } + , std::move(vtableItem) ); + } + + // Sort arrays so we can do fast searching for an item in sd-bus callback handlers + std::sort(internalVTable.methods.begin(), internalVTable.methods.end(), [](const auto& a, const auto& b){ return a.name < b.name; }); + std::sort(internalVTable.signals.begin(), internalVTable.signals.end(), [](const auto& a, const auto& b){ return a.name < b.name; }); + std::sort(internalVTable.properties.begin(), internalVTable.properties.end(), [](const auto& a, const auto& b){ return a.name < b.name; }); + + internalVTable.object = this; + + return internalVTable; +} + +void Object::writeInterfaceFlagsToVTable(InterfaceFlagsVTableItem flags, VTable& vtable) { - return interfaces_.emplace(interfaceName, *this).first->second; + vtable.interfaceFlags = std::move(flags.flags); } -const std::vector& Object::createInterfaceVTable(InterfaceData& interfaceData) +void Object::writeMethodRecordToVTable(MethodVTableItem method, VTable& vtable) { - auto& vtable = interfaceData.vtable; - assert(vtable.empty()); + SDBUS_CHECK_MEMBER_NAME(method.name); + SDBUS_THROW_ERROR_IF(!method.callbackHandler, "Invalid method callback provided", EINVAL); + + vtable.methods.push_back({ std::move(method.name) + , std::move(method.inputSignature) + , std::move(method.outputSignature) + , paramNamesToString(method.inputParamNames) + paramNamesToString(method.outputParamNames) + , std::move(method.callbackHandler) + , std::move(method.flags) }); +} - vtable.push_back(createVTableStartItem(interfaceData.flags.toSdBusInterfaceFlags())); - registerMethodsToVTable(interfaceData, vtable); - registerSignalsToVTable(interfaceData, vtable); - registerPropertiesToVTable(interfaceData, vtable); - vtable.push_back(createVTableEndItem()); +void Object::writeSignalRecordToVTable(SignalVTableItem signal, VTable& vtable) +{ + SDBUS_CHECK_MEMBER_NAME(signal.name); - return vtable; + vtable.signals.push_back({ std::move(signal.name) + , std::move(signal.signature) + , paramNamesToString(signal.paramNames) + , std::move(signal.flags) }); } -void Object::registerMethodsToVTable(const InterfaceData& interfaceData, std::vector& vtable) +void Object::writePropertyRecordToVTable(PropertyVTableItem property, VTable& vtable) { - for (const auto& item : interfaceData.methods) - { - const auto& methodName = item.first; - const auto& methodData = item.second; - - vtable.push_back(createVTableMethodItem( methodName.c_str() - , methodData.inputArgs.c_str() - , methodData.outputArgs.c_str() - , methodData.paramNames.c_str() - , &Object::sdbus_method_callback - , methodData.flags.toSdBusMethodFlags() )); - } + SDBUS_CHECK_MEMBER_NAME(property.name); + SDBUS_THROW_ERROR_IF(!property.getter && !property.setter, "Invalid property callbacks provided", EINVAL); + + vtable.properties.push_back({ std::move(property.name) + , std::move(property.signature) + , std::move(property.getter) + , std::move(property.setter) + , std::move(property.flags) }); } -void Object::registerSignalsToVTable(const InterfaceData& interfaceData, std::vector& vtable) +std::vector Object::createInternalSdBusVTable(const VTable& vtable) { - for (const auto& item : interfaceData.signals) - { - const auto& signalName = item.first; - const auto& signalData = item.second; + std::vector sdbusVTable; - vtable.push_back(createVTableSignalItem( signalName.c_str() - , signalData.signature.c_str() - , signalData.paramNames.c_str() - , signalData.flags.toSdBusSignalFlags() )); - } + startSdBusVTable(vtable.interfaceFlags, sdbusVTable); + for (const auto& methodItem : vtable.methods) + writeMethodRecordToSdBusVTable(methodItem, sdbusVTable); + for (const auto& signalItem : vtable.signals) + writeSignalRecordToSdBusVTable(signalItem, sdbusVTable); + for (const auto& propertyItem : vtable.properties) + writePropertyRecordToSdBusVTable(propertyItem, sdbusVTable); + finalizeSdBusVTable(sdbusVTable); + + return sdbusVTable; +} + +void Object::startSdBusVTable(const Flags& interfaceFlags, std::vector& vtable) +{ + auto vtableItem = createSdBusVTableStartItem(interfaceFlags.toSdBusInterfaceFlags()); + vtable.push_back(std::move(vtableItem)); } -void Object::registerPropertiesToVTable(const InterfaceData& interfaceData, std::vector& vtable) +void Object::writeMethodRecordToSdBusVTable(const VTable::MethodItem& method, std::vector& vtable) { - for (const auto& item : interfaceData.properties) + auto vtableItem = createSdBusVTableMethodItem( method.name.c_str() + , method.inputArgs.c_str() + , method.outputArgs.c_str() + , method.paramNames.c_str() + , &Object::sdbus_method_callback + , method.flags.toSdBusMethodFlags() ); + vtable.push_back(std::move(vtableItem)); +} + +void Object::writeSignalRecordToSdBusVTable(const VTable::SignalItem& signal, std::vector& vtable) +{ + auto vtableItem = createSdBusVTableSignalItem( signal.name.c_str() + , signal.signature.c_str() + , signal.paramNames.c_str() + , signal.flags.toSdBusSignalFlags() ); + vtable.push_back(std::move(vtableItem)); +} + +void Object::writePropertyRecordToSdBusVTable(const VTable::PropertyItem& property, std::vector& vtable) +{ + auto vtableItem = !property.setCallback + ? createSdBusVTableReadOnlyPropertyItem( property.name.c_str() + , property.signature.c_str() + , &Object::sdbus_property_get_callback + , property.flags.toSdBusPropertyFlags() ) + : createSdBusVTableWritablePropertyItem( property.name.c_str() + , property.signature.c_str() + , &Object::sdbus_property_get_callback + , &Object::sdbus_property_set_callback + , property.flags.toSdBusWritablePropertyFlags() ); + vtable.push_back(std::move(vtableItem)); +} + +void Object::finalizeSdBusVTable(std::vector& vtable) +{ + vtable.push_back(createSdBusVTableEndItem()); +} + +const Object::VTable::MethodItem* Object::findMethod(const VTable& vtable, const std::string& methodName) +{ + auto it = std::lower_bound(vtable.methods.begin(), vtable.methods.end(), methodName, [](const auto& methodItem, const auto& methodName) { - const auto& propertyName = item.first; - const auto& propertyData = item.second; - - if (!propertyData.setCallback) - vtable.push_back(createVTablePropertyItem( propertyName.c_str() - , propertyData.signature.c_str() - , &Object::sdbus_property_get_callback - , propertyData.flags.toSdBusPropertyFlags() )); - else - vtable.push_back(createVTableWritablePropertyItem( propertyName.c_str() - , propertyData.signature.c_str() - , &Object::sdbus_property_get_callback - , &Object::sdbus_property_set_callback - , propertyData.flags.toSdBusWritablePropertyFlags() )); - } + return methodItem.name < methodName; + }); + + return it != vtable.methods.end() && it->name == methodName ? &*it : nullptr; } -void Object::activateInterfaceVTable( const std::string& interfaceName - , InterfaceData& interfaceData - , const std::vector& vtable ) +const Object::VTable::PropertyItem* Object::findProperty(const VTable& vtable, const std::string& propertyName) { - interfaceData.slot = connection_.addObjectVTable(objectPath_, interfaceName, &vtable[0], &interfaceData); + auto it = std::lower_bound(vtable.properties.begin(), vtable.properties.end(), propertyName, [](const auto& propertyItem, const auto& propertyName) + { + return propertyItem.name < propertyName; + }); + + return it != vtable.properties.end() && it->name == propertyName ? &*it : nullptr; } std::string Object::paramNamesToString(const std::vector& paramNames) @@ -332,16 +306,17 @@ std::string Object::paramNamesToString(const std::vector& paramName int Object::sdbus_method_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError) { - auto* interfaceData = static_cast(userData); - assert(interfaceData != nullptr); - auto& object = interfaceData->object; + auto* vtable = static_cast(userData); + assert(vtable != nullptr); + assert(vtable->object != nullptr); - auto message = Message::Factory::create(sdbusMessage, &object.connection_.getSdBusInterface()); + auto message = Message::Factory::create(sdbusMessage, &vtable->object->connection_.getSdBusInterface()); - auto& callback = interfaceData->methods[message.getMemberName()].callback; - assert(callback); + const auto* methodItem = findMethod(*vtable, message.getMemberName()); + assert(methodItem != nullptr); + assert(methodItem->callback); - auto ok = invokeHandlerAndCatchErrors([&](){ callback(std::move(message)); }, retError); + auto ok = invokeHandlerAndCatchErrors([&](){ methodItem->callback(std::move(message)); }, retError); return ok ? 1 : -1; } @@ -354,21 +329,23 @@ int Object::sdbus_property_get_callback( sd_bus */*bus*/ , void *userData , sd_bus_error *retError ) { - auto* interfaceData = static_cast(userData); - assert(interfaceData != nullptr); - auto& object = interfaceData->object; + auto* vtable = static_cast(userData); + assert(vtable != nullptr); + assert(vtable->object != nullptr); + + const auto* propertyItem = findProperty(*vtable, property); + assert(propertyItem != nullptr); - auto& callback = interfaceData->properties[property].getCallback; - // Getter can be empty - the case of "write-only" property - if (!callback) + // Getter may be empty - the case of "write-only" property + if (!propertyItem->getCallback) { sd_bus_error_set(retError, "org.freedesktop.DBus.Error.Failed", "Cannot read property as it is write-only"); return 1; } - auto reply = Message::Factory::create(sdbusReply, &object.connection_.getSdBusInterface()); + auto reply = Message::Factory::create(sdbusReply, &vtable->object->connection_.getSdBusInterface()); - auto ok = invokeHandlerAndCatchErrors([&](){ callback(reply); }, retError); + auto ok = invokeHandlerAndCatchErrors([&](){ propertyItem->getCallback(reply); }, retError); return ok ? 1 : -1; } @@ -381,16 +358,17 @@ int Object::sdbus_property_set_callback( sd_bus */*bus*/ , void *userData , sd_bus_error *retError ) { - auto* interfaceData = static_cast(userData); - assert(interfaceData != nullptr); - auto& object = interfaceData->object; + auto* vtable = static_cast(userData); + assert(vtable != nullptr); + assert(vtable->object != nullptr); - auto& callback = interfaceData->properties[property].setCallback; - assert(callback); + const auto* propertyItem = findProperty(*vtable, property); + assert(propertyItem != nullptr); + assert(propertyItem->setCallback); - auto value = Message::Factory::create(sdbusValue, &object.connection_.getSdBusInterface()); + auto value = Message::Factory::create(sdbusValue, &vtable->object->connection_.getSdBusInterface()); - auto ok = invokeHandlerAndCatchErrors([&](){ callback(std::move(value)); }, retError); + auto ok = invokeHandlerAndCatchErrors([&](){ propertyItem->setCallback(std::move(value)); }, retError); return ok ? 1 : -1; } diff --git a/src/Object.h b/src/Object.h index 50cd7b95..b7c6feba 100644 --- a/src/Object.h +++ b/src/Object.h @@ -31,7 +31,7 @@ #include "IConnection.h" #include SDBUS_HEADER #include -#include +#include #include #include #include @@ -45,46 +45,8 @@ namespace sdbus::internal { public: Object(sdbus::internal::IConnection& connection, std::string objectPath); - void registerMethod( const std::string& interfaceName - , std::string methodName - , std::string inputSignature - , std::string outputSignature - , method_callback methodCallback - , Flags flags ) override; - void registerMethod( const std::string& interfaceName - , std::string methodName - , std::string inputSignature - , const std::vector& inputNames - , std::string outputSignature - , const std::vector& outputNames - , method_callback methodCallback - , Flags flags ) override; - - void registerSignal( const std::string& interfaceName - , std::string signalName - , std::string signature - , Flags flags ) override; - void registerSignal( const std::string& interfaceName - , std::string signalName - , std::string signature - , const std::vector& paramNames - , Flags flags ) override; - - void registerProperty( const std::string& interfaceName - , std::string propertyName - , std::string signature - , property_get_callback getCallback - , Flags flags ) override; - void registerProperty( const std::string& interfaceName - , std::string propertyName - , std::string signature - , property_get_callback getCallback - , property_set_callback setCallback - , Flags flags ) override; - - void setInterfaceFlags(const std::string& interfaceName, Flags flags) override; - - void finishRegistration() override; + void addVTable(std::string interfaceName, std::vector vtable) override; + Slot addVTable(std::string interfaceName, std::vector vtable, request_slot_t) override; void unregister() override; sdbus::Signal createSignal(const std::string& interfaceName, const std::string& signalName) override; @@ -105,55 +67,74 @@ namespace sdbus::internal { Message getCurrentlyProcessedMessage() const override; private: - using InterfaceName = std::string; - struct InterfaceData + // A vtable record comprising methods, signals, properties, flags. + // Once created, it cannot be modified. Only new vtables records can be added. + // An interface can have any number of vtables attached to it, not only one. + struct VTable { - InterfaceData(Object& object) : object(object) {} + std::string interfaceName; + Flags interfaceFlags; - using MethodName = std::string; - struct MethodData + struct MethodItem { - const std::string inputArgs; - const std::string outputArgs; - const std::string paramNames; + std::string name; + std::string inputArgs; + std::string outputArgs; + std::string paramNames; method_callback callback; Flags flags; }; - std::map methods; - using SignalName = std::string; - struct SignalData + // Array of method records sorted by method name + std::vector methods; + + struct SignalItem { - const std::string signature; - const std::string paramNames; + std::string name; + std::string signature; + std::string paramNames; Flags flags; }; - std::map signals; - using PropertyName = std::string; - struct PropertyData + // Array of signal records sorted by signal name + std::vector signals; + + struct PropertyItem { - const std::string signature; + std::string name; + std::string signature; property_get_callback getCallback; property_set_callback setCallback; Flags flags; }; - std::map properties; - std::vector vtable; - Flags flags; - Object& object; + // Array of signal records sorted by signal name + std::vector properties; + + // VTable structure in format required by sd-bus API + std::vector sdbusVTable; + + // Back-reference to the owning object from sd-bus callback handlers + Object* object{}; // This is intentionally the last member, because it must be destructed first, // releasing callbacks above before the callbacks themselves are destructed. Slot slot; }; - InterfaceData& getInterface(const std::string& interfaceName); - static const std::vector& createInterfaceVTable(InterfaceData& interfaceData); - static void registerMethodsToVTable(const InterfaceData& interfaceData, std::vector& vtable); - static void registerSignalsToVTable(const InterfaceData& interfaceData, std::vector& vtable); - static void registerPropertiesToVTable(const InterfaceData& interfaceData, std::vector& vtable); - void activateInterfaceVTable( const std::string& interfaceName - , InterfaceData& interfaceData - , const std::vector& vtable ); + VTable createInternalVTable(std::string interfaceName, std::vector vtable); + void writeInterfaceFlagsToVTable(InterfaceFlagsVTableItem flags, VTable& vtable); + void writeMethodRecordToVTable(MethodVTableItem method, VTable& vtable); + void writeSignalRecordToVTable(SignalVTableItem signal, VTable& vtable); + void writePropertyRecordToVTable(PropertyVTableItem property, VTable& vtable); + + std::vector createInternalSdBusVTable(const VTable& vtable); + static void startSdBusVTable(const Flags& interfaceFlags, std::vector& vtable); + static void writeMethodRecordToSdBusVTable(const VTable::MethodItem& method, std::vector& vtable); + static void writeSignalRecordToSdBusVTable(const VTable::SignalItem& signal, std::vector& vtable); + static void writePropertyRecordToSdBusVTable(const VTable::PropertyItem& property, std::vector& vtable); + static void finalizeSdBusVTable(std::vector& vtable); + + static const VTable::MethodItem* findMethod(const VTable& vtable, const std::string& methodName); + static const VTable::PropertyItem* findProperty(const VTable& vtable, const std::string& propertyName); + static std::string paramNamesToString(const std::vector& paramNames); static int sdbus_method_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError); @@ -175,7 +156,7 @@ namespace sdbus::internal { private: sdbus::internal::IConnection& connection_; std::string objectPath_; - std::map interfaces_; + std::vector vtables_; Slot objectManagerSlot_; }; diff --git a/src/VTableUtils.c b/src/VTableUtils.c index e1ad2c88..906d0699 100644 --- a/src/VTableUtils.c +++ b/src/VTableUtils.c @@ -27,18 +27,18 @@ #include "VTableUtils.h" #include SDBUS_HEADER -sd_bus_vtable createVTableStartItem(uint64_t flags) +sd_bus_vtable createSdBusVTableStartItem(uint64_t flags) { struct sd_bus_vtable vtableStart = SD_BUS_VTABLE_START(flags); return vtableStart; } -sd_bus_vtable createVTableMethodItem( const char *member - , const char *signature - , const char *result - , const char *paramNames - , sd_bus_message_handler_t handler - , uint64_t flags ) +sd_bus_vtable createSdBusVTableMethodItem( const char *member + , const char *signature + , const char *result + , const char *paramNames + , sd_bus_message_handler_t handler + , uint64_t flags ) { #if LIBSYSTEMD_VERSION>=242 // We have to expand macro SD_BUS_METHOD_WITH_NAMES manually here, because the macro expects literal char strings @@ -65,10 +65,10 @@ sd_bus_vtable createVTableMethodItem( const char *member return vtableItem; } -sd_bus_vtable createVTableSignalItem( const char *member - , const char *signature - , const char *outnames - , uint64_t flags ) +sd_bus_vtable createSdBusVTableSignalItem( const char *member + , const char *signature + , const char *outnames + , uint64_t flags ) { #if LIBSYSTEMD_VERSION>=242 struct sd_bus_vtable vtableItem = SD_BUS_SIGNAL_WITH_NAMES(member, signature, outnames, flags); @@ -79,26 +79,26 @@ sd_bus_vtable createVTableSignalItem( const char *member return vtableItem; } -sd_bus_vtable createVTablePropertyItem( const char *member - , const char *signature - , sd_bus_property_get_t getter - , uint64_t flags ) +sd_bus_vtable createSdBusVTableReadOnlyPropertyItem( const char *member + , const char *signature + , sd_bus_property_get_t getter + , uint64_t flags ) { struct sd_bus_vtable vtableItem = SD_BUS_PROPERTY(member, signature, getter, 0, flags); return vtableItem; } -sd_bus_vtable createVTableWritablePropertyItem( const char *member - , const char *signature - , sd_bus_property_get_t getter - , sd_bus_property_set_t setter - , uint64_t flags ) +sd_bus_vtable createSdBusVTableWritablePropertyItem( const char *member + , const char *signature + , sd_bus_property_get_t getter + , sd_bus_property_set_t setter + , uint64_t flags ) { struct sd_bus_vtable vtableItem = SD_BUS_WRITABLE_PROPERTY(member, signature, getter, setter, 0, flags); return vtableItem; } -sd_bus_vtable createVTableEndItem() +sd_bus_vtable createSdBusVTableEndItem() { struct sd_bus_vtable vtableEnd = SD_BUS_VTABLE_END; return vtableEnd; diff --git a/src/VTableUtils.h b/src/VTableUtils.h index c0a204af..1cd4ada7 100644 --- a/src/VTableUtils.h +++ b/src/VTableUtils.h @@ -34,27 +34,27 @@ extern "C" { #endif -sd_bus_vtable createVTableStartItem(uint64_t flags); -sd_bus_vtable createVTableMethodItem( const char *member - , const char *signature - , const char *result - , const char *paramNames - , sd_bus_message_handler_t handler - , uint64_t flags ); -sd_bus_vtable createVTableSignalItem( const char *member - , const char *signature - , const char *outnames - , uint64_t flags ); -sd_bus_vtable createVTablePropertyItem( const char *member - , const char *signature - , sd_bus_property_get_t getter - , uint64_t flags ); -sd_bus_vtable createVTableWritablePropertyItem( const char *member - , const char *signature - , sd_bus_property_get_t getter - , sd_bus_property_set_t setter - , uint64_t flags ); -sd_bus_vtable createVTableEndItem(); +sd_bus_vtable createSdBusVTableStartItem(uint64_t flags); +sd_bus_vtable createSdBusVTableMethodItem( const char *member + , const char *signature + , const char *result + , const char *paramNames + , sd_bus_message_handler_t handler + , uint64_t flags ); +sd_bus_vtable createSdBusVTableSignalItem( const char *member + , const char *signature + , const char *outnames + , uint64_t flags ); +sd_bus_vtable createSdBusVTableReadOnlyPropertyItem( const char *member + , const char *signature + , sd_bus_property_get_t getter + , uint64_t flags ); +sd_bus_vtable createSdBusVTableWritablePropertyItem( const char *member + , const char *signature + , sd_bus_property_get_t getter + , sd_bus_property_set_t setter + , uint64_t flags ); +sd_bus_vtable createSdBusVTableEndItem(); #ifdef __cplusplus } diff --git a/tests/integrationtests/DBusMethodsTests.cpp b/tests/integrationtests/DBusMethodsTests.cpp index a6fdc039..c052caf9 100644 --- a/tests/integrationtests/DBusMethodsTests.cpp +++ b/tests/integrationtests/DBusMethodsTests.cpp @@ -81,7 +81,6 @@ TYPED_TEST(SdbusTestObject, CallsMethodsWithStructSuccesfully) auto vectorRes = this->m_proxy->getInts16FromStruct(a); ASSERT_THAT(vectorRes, Eq(std::vector{0})); // because second item is by default initialized to 0 - sdbus::Struct> b{ UINT8_VALUE, INT16_VALUE, DOUBLE_VALUE, STRING_VALUE, {INT16_VALUE, -INT16_VALUE} }; @@ -288,3 +287,34 @@ TYPED_TEST(SdbusTestObject, CanCallMethodSynchronouslyWithoutAnEventLoopThread) ASSERT_THAT(multiplyRes, Eq(INT64_VALUE * DOUBLE_VALUE)); } + +TYPED_TEST(SdbusTestObject, CanRegisterAdditionalVTableDynamicallyAtAnyTime) +{ + auto& object = this->m_adaptor->getObject(); + auto vtableSlot = object.addVTable( "org.sdbuscpp.integrationtests2" + , { sdbus::registerMethod("add").implementedAs([](const int64_t& a, const double& b){ return a + b; }) + , sdbus::registerMethod("subtract").implementedAs([](const int& a, const int& b){ return a - b; })} + , sdbus::request_slot ); + + // The new remote vtable is registered as long as we keep vtableSlot, so remote method calls now should pass + auto proxy = sdbus::createProxy(BUS_NAME, OBJECT_PATH, sdbus::dont_run_event_loop_thread); + int result{}; + proxy->callMethod("subtract").onInterface("org.sdbuscpp.integrationtests2").withArguments(10, 2).storeResultsTo(result); + + ASSERT_THAT(result, Eq(8)); +} + +TYPED_TEST(SdbusTestObject, CanUnregisterAdditionallyRegisteredVTableAtAnyTime) +{ + auto& object = this->m_adaptor->getObject(); + + auto vtableSlot = object.addVTable( "org.sdbuscpp.integrationtests2" + , { sdbus::registerMethod("add").implementedAs([](const int64_t& a, const double& b){ return a + b; }) + , sdbus::registerMethod("subtract").implementedAs([](const int& a, const int& b){ return a - b; })} + , sdbus::request_slot ); + vtableSlot.reset(); // Letting the slot go means letting go the associated vtable registration + + // No such remote D-Bus method under given interface exists anymore... + auto proxy = sdbus::createProxy(BUS_NAME, OBJECT_PATH, sdbus::dont_run_event_loop_thread); + ASSERT_THROW(proxy->callMethod("subtract").onInterface("org.sdbuscpp.integrationtests2").withArguments(10, 2), sdbus::Error); +} diff --git a/tests/integrationtests/integrationtests-adaptor.h b/tests/integrationtests/integrationtests-adaptor.h index 23a74d29..a34146fa 100644 --- a/tests/integrationtests/integrationtests-adaptor.h +++ b/tests/integrationtests/integrationtests-adaptor.h @@ -22,34 +22,6 @@ class integrationtests_adaptor integrationtests_adaptor(sdbus::IObject& object) : object_(&object) { - object_->setInterfaceFlags(INTERFACE_NAME).markAsDeprecated().withPropertyUpdateBehavior(sdbus::Flags::EMITS_NO_SIGNAL); - object_->registerMethod("noArgNoReturn").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->noArgNoReturn(); }); - object_->registerMethod("getInt").onInterface(INTERFACE_NAME).withOutputParamNames("anInt").implementedAs([this](){ return this->getInt(); }); - object_->registerMethod("getTuple").onInterface(INTERFACE_NAME).withOutputParamNames("arg0", "arg1").implementedAs([this](){ return this->getTuple(); }); - object_->registerMethod("multiply").onInterface(INTERFACE_NAME).withInputParamNames("a", "b").withOutputParamNames("result").implementedAs([this](const int64_t& a, const double& b){ return this->multiply(a, b); }); - object_->registerMethod("multiplyWithNoReply").onInterface(INTERFACE_NAME).withInputParamNames("a", "b").implementedAs([this](const int64_t& a, const double& b){ return this->multiplyWithNoReply(a, b); }).markAsDeprecated().withNoReply(); - object_->registerMethod("getInts16FromStruct").onInterface(INTERFACE_NAME).withInputParamNames("arg0").withOutputParamNames("arg0").implementedAs([this](const sdbus::Struct>& arg0){ return this->getInts16FromStruct(arg0); }); - object_->registerMethod("processVariant").onInterface(INTERFACE_NAME).withInputParamNames("variant").withOutputParamNames("result").implementedAs([this](const std::variant& variant){ return this->processVariant(variant); }); - object_->registerMethod("getMapOfVariants").onInterface(INTERFACE_NAME).withInputParamNames("x", "y").withOutputParamNames("aMapOfVariants").implementedAs([this](const std::vector& x, const sdbus::Struct& y){ return this->getMapOfVariants(x, y); }); - object_->registerMethod("getStructInStruct").onInterface(INTERFACE_NAME).withOutputParamNames("aMapOfVariants").implementedAs([this](){ return this->getStructInStruct(); }); - object_->registerMethod("sumStructItems").onInterface(INTERFACE_NAME).withInputParamNames("arg0", "arg1").withOutputParamNames("arg0").implementedAs([this](const sdbus::Struct& arg0, const sdbus::Struct& arg1){ return this->sumStructItems(arg0, arg1); }); - object_->registerMethod("sumArrayItems").onInterface(INTERFACE_NAME).withInputParamNames("arg0", "arg1").withOutputParamNames("arg0").implementedAs([this](const std::vector& arg0, const std::array& arg1){ return this->sumArrayItems(arg0, arg1); }); - object_->registerMethod("doOperation").onInterface(INTERFACE_NAME).withInputParamNames("arg0").withOutputParamNames("arg0").implementedAs([this](const uint32_t& arg0){ return this->doOperation(arg0); }); - object_->registerMethod("doOperationAsync").onInterface(INTERFACE_NAME).withInputParamNames("arg0").withOutputParamNames("arg0").implementedAs([this](sdbus::Result&& result, uint32_t arg0){ this->doOperationAsync(std::move(result), std::move(arg0)); }); - object_->registerMethod("getSignature").onInterface(INTERFACE_NAME).withOutputParamNames("arg0").implementedAs([this](){ return this->getSignature(); }); - object_->registerMethod("getObjPath").onInterface(INTERFACE_NAME).withOutputParamNames("arg0").implementedAs([this](){ return this->getObjPath(); }); - object_->registerMethod("getUnixFd").onInterface(INTERFACE_NAME).withOutputParamNames("arg0").implementedAs([this](){ return this->getUnixFd(); }); - object_->registerMethod("getComplex").onInterface(INTERFACE_NAME).withOutputParamNames("arg0").implementedAs([this](){ return this->getComplex(); }).markAsDeprecated(); - object_->registerMethod("throwError").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->throwError(); }); - object_->registerMethod("throwErrorWithNoReply").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->throwErrorWithNoReply(); }).withNoReply(); - object_->registerMethod("doPrivilegedStuff").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->doPrivilegedStuff(); }).markAsPrivileged(); - object_->registerMethod("emitTwoSimpleSignals").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->emitTwoSimpleSignals(); }); - object_->registerSignal("simpleSignal").onInterface(INTERFACE_NAME).markAsDeprecated(); - object_->registerSignal("signalWithMap").onInterface(INTERFACE_NAME).withParameters>("aMap"); - object_->registerSignal("signalWithVariant").onInterface(INTERFACE_NAME).withParameters("aVariant"); - object_->registerProperty("action").onInterface(INTERFACE_NAME).withGetter([this](){ return this->action(); }).withSetter([this](const uint32_t& value){ this->action(value); }).withUpdateBehavior(sdbus::Flags::EMITS_INVALIDATION_SIGNAL); - object_->registerProperty("blocking").onInterface(INTERFACE_NAME).withGetter([this](){ return this->blocking(); }).withSetter([this](const bool& value){ this->blocking(value); }); - object_->registerProperty("state").onInterface(INTERFACE_NAME).withGetter([this](){ return this->state(); }).markAsDeprecated().withUpdateBehavior(sdbus::Flags::CONST_PROPERTY_VALUE); } integrationtests_adaptor(const integrationtests_adaptor&) = delete; @@ -59,6 +31,39 @@ class integrationtests_adaptor ~integrationtests_adaptor() = default; + void registerAdaptor() + { + object_->addVTable( sdbus::setInterfaceFlags().markAsDeprecated().withPropertyUpdateBehavior(sdbus::Flags::EMITS_NO_SIGNAL) + , sdbus::registerMethod("noArgNoReturn").implementedAs([this](){ return this->noArgNoReturn(); }) + , sdbus::registerMethod("getInt").withOutputParamNames("anInt").implementedAs([this](){ return this->getInt(); }) + , sdbus::registerMethod("getTuple").withOutputParamNames("arg0", "arg1").implementedAs([this](){ return this->getTuple(); }) + , sdbus::registerMethod("multiply").withInputParamNames("a", "b").withOutputParamNames("result").implementedAs([this](const int64_t& a, const double& b){ return this->multiply(a, b); }) + , sdbus::registerMethod("multiplyWithNoReply").withInputParamNames("a", "b").implementedAs([this](const int64_t& a, const double& b){ return this->multiplyWithNoReply(a, b); }).markAsDeprecated().withNoReply() + , sdbus::registerMethod("getInts16FromStruct").withInputParamNames("arg0").withOutputParamNames("arg0").implementedAs([this](const sdbus::Struct>& arg0){ return this->getInts16FromStruct(arg0); }) + , sdbus::registerMethod("processVariant").withInputParamNames("variant").withOutputParamNames("result").implementedAs([this](const std::variant& variant){ return this->processVariant(variant); }) + , sdbus::registerMethod("getMapOfVariants").withInputParamNames("x", "y").withOutputParamNames("aMapOfVariants").implementedAs([this](const std::vector& x, const sdbus::Struct& y){ return this->getMapOfVariants(x, y); }) + , sdbus::registerMethod("getStructInStruct").withOutputParamNames("aMapOfVariants").implementedAs([this](){ return this->getStructInStruct(); }) + , sdbus::registerMethod("sumStructItems").withInputParamNames("arg0", "arg1").withOutputParamNames("arg0").implementedAs([this](const sdbus::Struct& arg0, const sdbus::Struct& arg1){ return this->sumStructItems(arg0, arg1); }) + , sdbus::registerMethod("sumArrayItems").withInputParamNames("arg0", "arg1").withOutputParamNames("arg0").implementedAs([this](const std::vector& arg0, const std::array& arg1){ return this->sumArrayItems(arg0, arg1); }) + , sdbus::registerMethod("doOperation").withInputParamNames("arg0").withOutputParamNames("arg0").implementedAs([this](const uint32_t& arg0){ return this->doOperation(arg0); }) + , sdbus::registerMethod("doOperationAsync").withInputParamNames("arg0").withOutputParamNames("arg0").implementedAs([this](sdbus::Result&& result, uint32_t arg0){ this->doOperationAsync(std::move(result), std::move(arg0)); }) + , sdbus::registerMethod("getSignature").withOutputParamNames("arg0").implementedAs([this](){ return this->getSignature(); }) + , sdbus::registerMethod("getObjPath").withOutputParamNames("arg0").implementedAs([this](){ return this->getObjPath(); }) + , sdbus::registerMethod("getUnixFd").withOutputParamNames("arg0").implementedAs([this](){ return this->getUnixFd(); }) + , sdbus::registerMethod("getComplex").withOutputParamNames("arg0").implementedAs([this](){ return this->getComplex(); }).markAsDeprecated() + , sdbus::registerMethod("throwError").implementedAs([this](){ return this->throwError(); }) + , sdbus::registerMethod("throwErrorWithNoReply").implementedAs([this](){ return this->throwErrorWithNoReply(); }).withNoReply() + , sdbus::registerMethod("doPrivilegedStuff").implementedAs([this](){ return this->doPrivilegedStuff(); }).markAsPrivileged() + , sdbus::registerMethod("emitTwoSimpleSignals").implementedAs([this](){ return this->emitTwoSimpleSignals(); }) + , sdbus::registerSignal("simpleSignal").markAsDeprecated() + , sdbus::registerSignal("signalWithMap").withParameters>("aMap") + , sdbus::registerSignal("signalWithVariant").withParameters("aVariant") + , sdbus::registerProperty("action").withGetter([this](){ return this->action(); }).withSetter([this](const uint32_t& value){ this->action(value); }).withUpdateBehavior(sdbus::Flags::EMITS_INVALIDATION_SIGNAL) + , sdbus::registerProperty("blocking").withGetter([this](){ return this->blocking(); }).withSetter([this](const bool& value){ this->blocking(value); }) + , sdbus::registerProperty("state").withGetter([this](){ return this->state(); }).markAsDeprecated().withUpdateBehavior(sdbus::Flags::CONST_PROPERTY_VALUE) + ).forInterface(INTERFACE_NAME); + } + public: void emitSimpleSignal() { diff --git a/tests/perftests/perftests-adaptor.h b/tests/perftests/perftests-adaptor.h index ab5f5b4a..6aefa1ae 100644 --- a/tests/perftests/perftests-adaptor.h +++ b/tests/perftests/perftests-adaptor.h @@ -22,9 +22,6 @@ class perftests_adaptor perftests_adaptor(sdbus::IObject& object) : object_(&object) { - object_->registerMethod("sendDataSignals").onInterface(INTERFACE_NAME).withInputParamNames("numberOfSignals", "signalMsgSize").implementedAs([this](const uint32_t& numberOfSignals, const uint32_t& signalMsgSize){ return this->sendDataSignals(numberOfSignals, signalMsgSize); }); - object_->registerMethod("concatenateTwoStrings").onInterface(INTERFACE_NAME).withInputParamNames("string1", "string2").withOutputParamNames("result").implementedAs([this](const std::string& string1, const std::string& string2){ return this->concatenateTwoStrings(string1, string2); }); - object_->registerSignal("dataSignal").onInterface(INTERFACE_NAME).withParameters("data"); } perftests_adaptor(const perftests_adaptor&) = delete; @@ -34,6 +31,14 @@ class perftests_adaptor ~perftests_adaptor() = default; + void registerAdaptor() + { + object_->addVTable( sdbus::registerMethod("sendDataSignals").withInputParamNames("numberOfSignals", "signalMsgSize").implementedAs([this](const uint32_t& numberOfSignals, const uint32_t& signalMsgSize){ return this->sendDataSignals(numberOfSignals, signalMsgSize); }) + , sdbus::registerMethod("concatenateTwoStrings").withInputParamNames("string1", "string2").withOutputParamNames("result").implementedAs([this](const std::string& string1, const std::string& string2){ return this->concatenateTwoStrings(string1, string2); }) + , sdbus::registerSignal("dataSignal").withParameters("data") + ).forInterface(INTERFACE_NAME); + } + public: void emitDataSignal(const std::string& data) { diff --git a/tests/stresstests/celsius-thermometer-adaptor.h b/tests/stresstests/celsius-thermometer-adaptor.h index 604dc713..36a32bab 100644 --- a/tests/stresstests/celsius-thermometer-adaptor.h +++ b/tests/stresstests/celsius-thermometer-adaptor.h @@ -24,7 +24,6 @@ class thermometer_adaptor thermometer_adaptor(sdbus::IObject& object) : object_(&object) { - object_->registerMethod("getCurrentTemperature").onInterface(INTERFACE_NAME).withOutputParamNames("result").implementedAs([this](){ return this->getCurrentTemperature(); }); } thermometer_adaptor(const thermometer_adaptor&) = delete; @@ -34,6 +33,11 @@ class thermometer_adaptor ~thermometer_adaptor() = default; + void registerAdaptor() + { + object_->addVTable(sdbus::registerMethod("getCurrentTemperature").withOutputParamNames("result").implementedAs([this](){ return this->getCurrentTemperature(); })).forInterface(INTERFACE_NAME); + } + private: virtual uint32_t getCurrentTemperature() = 0; diff --git a/tests/stresstests/concatenator-adaptor.h b/tests/stresstests/concatenator-adaptor.h index 22bd21b8..0bd47e70 100644 --- a/tests/stresstests/concatenator-adaptor.h +++ b/tests/stresstests/concatenator-adaptor.h @@ -23,8 +23,6 @@ class concatenator_adaptor concatenator_adaptor(sdbus::IObject& object) : object_(&object) { - object_->registerMethod("concatenate").onInterface(INTERFACE_NAME).withInputParamNames("params").withOutputParamNames("result").implementedAs([this](sdbus::Result&& result, std::map params){ this->concatenate(std::move(result), std::move(params)); }); - object_->registerSignal("concatenatedSignal").onInterface(INTERFACE_NAME).withParameters("concatenatedString"); } concatenator_adaptor(const concatenator_adaptor&) = delete; @@ -34,6 +32,13 @@ class concatenator_adaptor ~concatenator_adaptor() = default; + void registerAdaptor() + { + object_->addVTable( sdbus::registerMethod("concatenate").withInputParamNames("params").withOutputParamNames("result").implementedAs([this](sdbus::Result&& result, std::map params){ this->concatenate(std::move(result), std::move(params)); }) + , sdbus::registerSignal("concatenatedSignal").withParameters("concatenatedString") + ).forInterface(INTERFACE_NAME); + } + public: void emitConcatenatedSignal(const std::string& concatenatedString) { diff --git a/tests/stresstests/fahrenheit-thermometer-adaptor.h b/tests/stresstests/fahrenheit-thermometer-adaptor.h index 9d5dce29..0710fbb6 100644 --- a/tests/stresstests/fahrenheit-thermometer-adaptor.h +++ b/tests/stresstests/fahrenheit-thermometer-adaptor.h @@ -24,7 +24,6 @@ class thermometer_adaptor thermometer_adaptor(sdbus::IObject& object) : object_(&object) { - object_->registerMethod("getCurrentTemperature").onInterface(INTERFACE_NAME).withOutputParamNames("result").implementedAs([this](){ return this->getCurrentTemperature(); }); } thermometer_adaptor(const thermometer_adaptor&) = delete; @@ -34,6 +33,11 @@ class thermometer_adaptor ~thermometer_adaptor() = default; + void registerAdaptor() + { + object_->addVTable(sdbus::registerMethod("getCurrentTemperature").withOutputParamNames("result").implementedAs([this](){ return this->getCurrentTemperature(); })).forInterface(INTERFACE_NAME); + } + private: virtual uint32_t getCurrentTemperature() = 0; @@ -58,8 +62,6 @@ class factory_adaptor factory_adaptor(sdbus::IObject& object) : object_(&object) { - object_->registerMethod("createDelegateObject").onInterface(INTERFACE_NAME).withOutputParamNames("delegate").implementedAs([this](sdbus::Result&& result){ this->createDelegateObject(std::move(result)); }); - object_->registerMethod("destroyDelegateObject").onInterface(INTERFACE_NAME).withInputParamNames("delegate").implementedAs([this](sdbus::Result<>&& result, sdbus::ObjectPath delegate){ this->destroyDelegateObject(std::move(result), std::move(delegate)); }).withNoReply(); } factory_adaptor(const factory_adaptor&) = delete; @@ -69,6 +71,13 @@ class factory_adaptor ~factory_adaptor() = default; + void registerAdaptor() + { + object_->addVTable( sdbus::registerMethod("createDelegateObject").withOutputParamNames("delegate").implementedAs([this](sdbus::Result&& result){ this->createDelegateObject(std::move(result)); }) + , sdbus::registerMethod("destroyDelegateObject").withInputParamNames("delegate").implementedAs([this](sdbus::Result<>&& result, sdbus::ObjectPath delegate){ this->destroyDelegateObject(std::move(result), std::move(delegate)); }).withNoReply() + ).forInterface(INTERFACE_NAME); + } + private: virtual void createDelegateObject(sdbus::Result&& result) = 0; virtual void destroyDelegateObject(sdbus::Result<>&& result, sdbus::ObjectPath delegate) = 0; diff --git a/tools/xml2cpp-codegen/AdaptorGenerator.cpp b/tools/xml2cpp-codegen/AdaptorGenerator.cpp index 25e74cb2..3c7c24ce 100644 --- a/tools/xml2cpp-codegen/AdaptorGenerator.cpp +++ b/tools/xml2cpp-codegen/AdaptorGenerator.cpp @@ -85,7 +85,17 @@ std::string AdaptorGenerator::processInterface(Node& interface) const << tab << "static constexpr const char* INTERFACE_NAME = \"" << ifaceName << "\";" << endl << endl << "protected:" << endl << tab << className << "(sdbus::IObject& object)" << endl - << tab << tab << ": object_(&object)" << endl; + << tab << tab << ": object_(&object)" << endl + << tab << "{" << endl + << tab << "}" << endl << endl; + + // Rule of Five + body << tab << className << "(const " << className << "&) = delete;" << endl; + body << tab << className << "& operator=(const " << className << "&) = delete;" << endl; + body << tab << className << "(" << className << "&&) = default;" << endl; + body << tab << className << "& operator=(" << className << "&&) = default;" << endl << endl; + + body << tab << "~" << className << "() = default;" << endl << endl; Nodes methods = interface["method"]; Nodes signals = interface["signal"]; @@ -111,33 +121,28 @@ std::string AdaptorGenerator::processInterface(Node& interface) const if(!annotationRegistration.empty()) { std::stringstream str; - str << tab << tab << "object_->setInterfaceFlags(INTERFACE_NAME)" << annotationRegistration << ";" << endl; + str << "sdbus::setInterfaceFlags()" << annotationRegistration << ";"; annotationRegistration = str.str(); } - std::string methodRegistration, methodDeclaration; - std::tie(methodRegistration, methodDeclaration) = processMethods(methods); - - std::string signalRegistration, signalMethods; - std::tie(signalRegistration, signalMethods) = processSignals(signals); + std::vector methodRegistrations; + std::string methodDeclaration; + std::tie(methodRegistrations, methodDeclaration) = processMethods(methods); - std::string propertyRegistration, propertyAccessorDeclaration; - std::tie(propertyRegistration, propertyAccessorDeclaration) = processProperties(properties); + std::vector signalRegistrations; + std::string signalMethods; + std::tie(signalRegistrations, signalMethods) = processSignals(signals); - body << tab << "{" << endl - << annotationRegistration - << methodRegistration - << signalRegistration - << propertyRegistration - << tab << "}" << endl << endl; + std::vector propertyRegistrations; + std::string propertyAccessorDeclaration; + std::tie(propertyRegistrations, propertyAccessorDeclaration) = processProperties(properties); - // Rule of Five - body << tab << className << "(const " << className << "&) = delete;" << endl; - body << tab << className << "& operator=(const " << className << "&) = delete;" << endl; - body << tab << className << "(" << className << "&&) = default;" << endl; - body << tab << className << "& operator=(" << className << "&&) = default;" << endl << endl; + auto vtableRegistration = createVTableRegistration(annotationRegistration, methodRegistrations, signalRegistrations, propertyRegistrations); - body << tab << "~" << className << "() = default;" << endl << endl; + body << tab << "void registerAdaptor()" << endl + << tab << "{" << endl + << vtableRegistration << endl + << tab << "}" << endl << endl; if (!signalMethods.empty()) { @@ -163,12 +168,16 @@ std::string AdaptorGenerator::processInterface(Node& interface) const } -std::tuple AdaptorGenerator::processMethods(const Nodes& methods) const +std::tuple, std::string> AdaptorGenerator::processMethods(const Nodes& methods) const { - std::ostringstream registrationSS, declarationSS; + std::ostringstream declarationSS; + + std::vector methodRegistrations; for (const auto& method : methods) { + std::ostringstream registrationSS; + auto methodName = method->get("name"); auto methodNameSafe = mangle_name(methodName); @@ -217,9 +226,8 @@ std::tuple AdaptorGenerator::processMethods(const Node using namespace std::string_literals; - registrationSS << tab << tab << "object_->registerMethod(\"" + registrationSS << "sdbus::registerMethod(\"" << methodName << "\")" - << ".onInterface(INTERFACE_NAME)" << (!argStringsStr.empty() ? (".withInputParamNames(" + argStringsStr + ")") : "") << (!outArgStringsStr.empty() ? (".withOutputParamNames(" + outArgStringsStr + ")") : "") << ".implementedAs(" @@ -229,7 +237,9 @@ std::tuple AdaptorGenerator::processMethods(const Node << "){ " << (async ? "" : "return ") << "this->" << methodNameSafe << "(" << (async ? "std::move(result)"s + (argTypeStr.empty() ? "" : ", ") : "") << argStr << "); })" - << annotationRegistration << ";" << endl; + << annotationRegistration; + + methodRegistrations.push_back(registrationSS.str()); declarationSS << tab << "virtual " @@ -241,16 +251,20 @@ std::tuple AdaptorGenerator::processMethods(const Node << ") = 0;" << endl; } - return std::make_tuple(registrationSS.str(), declarationSS.str()); + return std::make_tuple(methodRegistrations, declarationSS.str()); } -std::tuple AdaptorGenerator::processSignals(const Nodes& signals) const +std::tuple, std::string> AdaptorGenerator::processSignals(const Nodes& signals) const { - std::ostringstream signalRegistrationSS, signalMethodSS; + std::ostringstream signalMethodSS; + + std::vector signalRegistrations; for (const auto& signal : signals) { + std::stringstream signalRegistrationSS; + auto name = signal->get("name"); auto annotations = getAnnotations(*signal); @@ -272,9 +286,7 @@ std::tuple AdaptorGenerator::processSignals(const Node std::string argStr, argTypeStr, typeStr, argStringsStr; std::tie(argStr, argTypeStr, typeStr, argStringsStr) = argsToNamesAndTypes(args); - signalRegistrationSS << tab << tab - << "object_->registerSignal(\"" << name << "\")" - ".onInterface(INTERFACE_NAME)"; + signalRegistrationSS << "sdbus::registerSignal(\"" << name << "\")"; if (args.size() > 0) { @@ -282,7 +294,8 @@ std::tuple AdaptorGenerator::processSignals(const Node } signalRegistrationSS << annotationRegistration; - signalRegistrationSS << ";" << endl; + + signalRegistrations.push_back(signalRegistrationSS.str()); auto nameWithCapFirstLetter = name; nameWithCapFirstLetter[0] = std::toupper(nameWithCapFirstLetter[0]); @@ -302,16 +315,20 @@ std::tuple AdaptorGenerator::processSignals(const Node << tab << "}" << endl << endl; } - return std::make_tuple(signalRegistrationSS.str(), signalMethodSS.str()); + return std::make_tuple(signalRegistrations, signalMethodSS.str()); } -std::tuple AdaptorGenerator::processProperties(const Nodes& properties) const +std::tuple, std::string> AdaptorGenerator::processProperties(const Nodes& properties) const { - std::ostringstream registrationSS, declarationSS; + std::ostringstream declarationSS; + + std::vector propertyRegistrations; for (const auto& property : properties) { + std::ostringstream registrationSS; + auto propertyName = property->get("name"); auto propertyNameSafe = mangle_name(propertyName); auto propertyAccess = property->get("access"); @@ -339,9 +356,8 @@ std::tuple AdaptorGenerator::processProperties(const N << "Option '" << annotationName << "' not allowed or supported in this context! Option ignored..." << std::endl; } - registrationSS << tab << tab << "object_->registerProperty(\"" - << propertyName << "\")" - << ".onInterface(INTERFACE_NAME)"; + registrationSS << "sdbus::registerProperty(\"" + << propertyName << "\")"; if (propertyAccess == "read" || propertyAccess == "readwrite") { @@ -356,7 +372,8 @@ std::tuple AdaptorGenerator::processProperties(const N } registrationSS << annotationRegistration; - registrationSS << ";" << endl; + + propertyRegistrations.push_back(registrationSS.str()); if (propertyAccess == "read" || propertyAccess == "readwrite") declarationSS << tab << "virtual " << propertyType << " " << propertyNameSafe << "() = 0;" << endl; @@ -364,7 +381,38 @@ std::tuple AdaptorGenerator::processProperties(const N declarationSS << tab << "virtual void " << propertyNameSafe << "(" << propertyTypeArg << ") = 0;" << endl; } - return std::make_tuple(registrationSS.str(), declarationSS.str()); + return std::make_tuple(propertyRegistrations, declarationSS.str()); +} + +std::string AdaptorGenerator::createVTableRegistration(const std::string& annotationRegistration, + const std::vector& methodRegistrations, + const std::vector& signalRegistrations, + const std::vector& propertyRegistrations) const +{ + std::vector allRegistrations; + if (!annotationRegistration.empty()) + allRegistrations.push_back(annotationRegistration); + allRegistrations.insert(allRegistrations.end(), methodRegistrations.begin(), methodRegistrations.end()); + allRegistrations.insert(allRegistrations.end(), signalRegistrations.begin(), signalRegistrations.end()); + allRegistrations.insert(allRegistrations.end(), propertyRegistrations.begin(), propertyRegistrations.end()); + + if (allRegistrations.empty()) + return {}; + + std::ostringstream registrationSS; + if (allRegistrations.size() == 1) + { + registrationSS << tab << tab << "object_->addVTable(" << allRegistrations[0] << ").forInterface(INTERFACE_NAME);"; + } + else + { + registrationSS << tab << tab << "object_->addVTable( " << allRegistrations[0] << endl; + for (size_t i = 1; i < allRegistrations.size(); ++i) + registrationSS << tab << tab << " , " << allRegistrations[i] << endl; + registrationSS << tab << tab << " ).forInterface(INTERFACE_NAME);"; + } + + return registrationSS.str(); } std::map AdaptorGenerator::getAnnotations( sdbuscpp::xml::Node& node) const diff --git a/tools/xml2cpp-codegen/AdaptorGenerator.h b/tools/xml2cpp-codegen/AdaptorGenerator.h index 9ee1c49e..36c90d0c 100644 --- a/tools/xml2cpp-codegen/AdaptorGenerator.h +++ b/tools/xml2cpp-codegen/AdaptorGenerator.h @@ -59,21 +59,26 @@ class AdaptorGenerator : public BaseGenerator * @param methods * @return tuple: registration of methods, declaration of abstract methods */ - std::tuple processMethods(const sdbuscpp::xml::Nodes& methods) const; + std::tuple, std::string> processMethods(const sdbuscpp::xml::Nodes& methods) const; /** * Generate source code for signals * @param signals * @return tuple: registration of signals, definition of signal methods */ - std::tuple processSignals(const sdbuscpp::xml::Nodes& signals) const; + std::tuple, std::string> processSignals(const sdbuscpp::xml::Nodes& signals) const; /** * Generate source code for properties * @param properties * @return tuple: registration of properties, declaration of property accessor virtual methods */ - std::tuple processProperties(const sdbuscpp::xml::Nodes& properties) const; + std::tuple, std::string> processProperties(const sdbuscpp::xml::Nodes& properties) const; + + std::string createVTableRegistration(const std::string& annotationRegistration, + const std::vector& methodRegistrations, + const std::vector& signalRegistrations, + const std::vector& propertyRegistrations) const; /** * Get annotations listed for a given node From 4d8e68e92619da9dc4a0a53d7901116985011bd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Sat, 30 Dec 2023 18:57:10 +0100 Subject: [PATCH 17/52] refactor: improve Proxy signal subscription (#389) This makes D-Bus proxy signal registration more flexible, more dynamic, and less error-prone since no `finishRegistration()` call is needed. A proxy can register to a signal at any time during its lifetime, and can unregister freely by simply destroying the associated slot. --- docs/using-sdbus-c++.md | 14 ++-- .../examplemanager-planet1-client-glue.h | 17 +++-- include/sdbus-c++/ConvenienceApiClasses.h | 13 +--- include/sdbus-c++/ConvenienceApiClasses.inl | 36 +++++----- include/sdbus-c++/IProxy.h | 51 +++++--------- include/sdbus-c++/ProxyInterfaces.h | 13 ++-- include/sdbus-c++/StandardInterfaces.h | 44 ++++++++---- src/Connection.cpp | 34 +++++---- src/Connection.h | 2 +- src/Proxy.cpp | 69 +++++++------------ src/Proxy.h | 63 +++++++++-------- .../integrationtests/integrationtests-proxy.h | 16 +++-- tests/perftests/perftests-proxy.h | 6 +- tests/stresstests/celsius-thermometer-proxy.h | 4 ++ tests/stresstests/concatenator-proxy.h | 6 +- .../fahrenheit-thermometer-proxy.h | 8 +++ tools/xml2cpp-codegen/ProxyGenerator.cpp | 25 ++++--- 17 files changed, 223 insertions(+), 198 deletions(-) diff --git a/docs/using-sdbus-c++.md b/docs/using-sdbus-c++.md index 0b53038d..6fc4b58b 100644 --- a/docs/using-sdbus-c++.md +++ b/docs/using-sdbus-c++.md @@ -354,7 +354,6 @@ int main(int argc, char *argv[]) // Let's subscribe for the 'concatenated' signals const char* interfaceName = "org.sdbuscpp.Concatenator"; concatenatorProxy->registerSignalHandler(interfaceName, "concatenated", &onConcatenated); - concatenatorProxy->finishRegistration(); std::vector numbers = {1, 2, 3}; std::string separator = ":"; @@ -391,10 +390,12 @@ int main(int argc, char *argv[]) } ``` -In simple cases, we don't need to create D-Bus connection explicitly for our proxies. Unless a connection is provided to a proxy object explicitly via factory parameter, the proxy will create a connection of his own (unless it is a light-weight, short-lived proxy created with `dont_run_event_loop_thread_t`), 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 proxy ready for use. +In simple cases, we don't need to create D-Bus connection explicitly for our proxies. Unless a connection is provided to a proxy object explicitly via factory parameter, the proxy will create a connection of his own (unless it is a light-weight, short-lived proxy created with `dont_run_event_loop_thread_t`), 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 handlers for signals we are interested in (if any). 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. +> **_Tip_:** There's also an overload of `registerSignalHandler()` with `request_slot_t` tag which returns a `Slot` object. The slot is a simple RAII-based handle of the subscription. As long as you keep the slot object, the signal subscription is active. When you let go of the object, the signal handler is automatically unregistered. This gives you finer control over the lifetime of signal subscription. + 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. 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. @@ -567,7 +568,6 @@ int main(int argc, char *argv[]) // Let's subscribe for the 'concatenated' signals const char* interfaceName = "org.sdbuscpp.Concatenator"; concatenatorProxy->uponSignal("concatenated").onInterface(interfaceName).call([](const std::string& str){ onConcatenated(str); }); - concatenatorProxy->finishRegistration(); std::vector numbers = {1, 2, 3}; std::string separator = ":"; @@ -601,9 +601,11 @@ int main(int argc, char *argv[]) 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. +> **_Tip_:** There's also an overload of `uponSignal(...).call()` with `request_slot_t` tag which returns a `Slot` object. The slot is a simple RAII-based handle of the subscription. As long as you keep the slot object, the signal subscription is active. When you let go of the object, the signal handler is automatically unregistered. This gives you finer control over the lifetime of signal subscription. + We recommend that sdbus-c++ users prefer the convenience API to the lower level, basic API. When feasible, using generated adaptor and proxy C++ bindings is even better as it provides yet slightly higher abstraction built on top of the convenience API, where remote calls look simply like local, native calls of object methods. They are described in the following section. -> **_Note_:** By default, signal callback handlers are not invoked (i.e., the signal is silently dropped) if there is a signal signature mismatch. If clients want to be informed of such situations, they can prepend `const sdbus::Error*` parameter to their signal callback handler's parameter list. This argument will be `nullptr` in normal cases, and will provide access to the corresponding `sdbus::Error` object in case of deserialization failures. An example of a handler with the signature (`int`) different from the real signal contents (`string`): +> **_Note_:** By default, signal callback handlers are not invoked (i.e., the signal is silently dropped) if there is a signal signature mismatch. If you want to be informed of such situations, they can prepend `const sdbus::Error*` parameter to their signal callback handler's parameter list. This argument will be `nullptr` in normal cases, and will provide access to the corresponding `sdbus::Error` object in case of deserialization failures. An example of a handler with the signature (`int`) different from the real signal contents (`string`): > ```c++ > void onConcatenated(const sdbus::Error* e, int wrongParameter) > { @@ -1508,7 +1510,7 @@ Pre-generated `*_proxy` and `*_adaptor` convenience classes for these standard i For example, for our `Concatenator` example above in this tutorial, we may want to conveniently emit a `PropertyChanged` signal under `org.freedesktop.DBus.Properties` interface. First, we must augment our `Concatenator` class to also inherit from `org.freedesktop.DBus.Properties` interface: `class Concatenator : public sdbus::AdaptorInterfaces {...};`, and then we just issue `emitPropertiesChangedSignal` function of our adaptor object. -Note that signals of afore-mentioned standard D-Bus interfaces are not emitted by the library automatically. It's clients who are supposed to emit them. +Note that signals of afore-mentioned standard D-Bus interfaces are not emitted by the library automatically. It's you, the user of sdbus-c++, who are supposed to emit them. Working examples of using standard D-Bus interfaces can be found in [sdbus-c++ integration tests](/tests/integrationtests/DBusStandardInterfacesTests.cpp) or the [examples](/examples) directory. @@ -1555,7 +1557,7 @@ For more information on basic D-Bus types, D-Bus container types, and D-Bus type ### Extending sdbus-c++ type system -The above mapping between D-Bus and C++ types is what sdbus-c++ provides by default. However, the mapping can be extended. Clients can implement additional mapping between a D-Bus type and their custom type. +The above mapping between D-Bus and C++ types is what sdbus-c++ provides by default. However, the mapping can be extended. You can implement additional mapping between a D-Bus type and their custom type. We need two things to do that: diff --git a/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-client-glue.h b/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-client-glue.h index 020019c0..e51fd78f 100644 --- a/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-client-glue.h +++ b/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-client-glue.h @@ -21,28 +21,37 @@ class Planet1_proxy protected: Planet1_proxy(sdbus::IProxy& proxy) - : proxy_(proxy) + : proxy_(&proxy) { } + Planet1_proxy(const Planet1_proxy&) = delete; + Planet1_proxy& operator=(const Planet1_proxy&) = delete; + Planet1_proxy(Planet1_proxy&&) = default; + Planet1_proxy& operator=(Planet1_proxy&&) = default; + ~Planet1_proxy() = default; + void registerProxy() + { + } + public: uint64_t GetPopulation() { uint64_t result; - proxy_.callMethod("GetPopulation").onInterface(INTERFACE_NAME).storeResultsTo(result); + proxy_->callMethod("GetPopulation").onInterface(INTERFACE_NAME).storeResultsTo(result); return result; } public: std::string Name() { - return proxy_.getProperty("Name").onInterface(INTERFACE_NAME); + return proxy_->getProperty("Name").onInterface(INTERFACE_NAME); } private: - sdbus::IProxy& proxy_; + sdbus::IProxy* proxy_; }; }}} // namespaces diff --git a/include/sdbus-c++/ConvenienceApiClasses.h b/include/sdbus-c++/ConvenienceApiClasses.h index e6522fef..f0c5db58 100644 --- a/include/sdbus-c++/ConvenienceApiClasses.h +++ b/include/sdbus-c++/ConvenienceApiClasses.h @@ -129,22 +129,15 @@ namespace sdbus { SignalSubscriber(IProxy& proxy, const std::string& signalName); SignalSubscriber& onInterface(std::string interfaceName); template void call(_Function&& callback); + template [[nodiscard]] Slot call(_Function&& callback, request_slot_t); private: - IProxy& proxy_; - const std::string& signalName_; - std::string interfaceName_; - }; - - class SignalUnsubscriber - { - public: - SignalUnsubscriber(IProxy& proxy, const std::string& signalName); - void onInterface(const std::string& interfaceName); + template signal_handler makeSignalHandler(_Function&& callback); private: IProxy& proxy_; const std::string& signalName_; + std::string interfaceName_; }; class PropertyGetter diff --git a/include/sdbus-c++/ConvenienceApiClasses.inl b/include/sdbus-c++/ConvenienceApiClasses.inl index 017b9744..178b2775 100644 --- a/include/sdbus-c++/ConvenienceApiClasses.inl +++ b/include/sdbus-c++/ConvenienceApiClasses.inl @@ -307,7 +307,24 @@ namespace sdbus { proxy_.registerSignalHandler( interfaceName_ , signalName_ - , [callback = std::forward<_Function>(callback)](Signal signal) + , makeSignalHandler(std::forward<_Function>(callback)) ); + } + + template + inline Slot SignalSubscriber::call(_Function&& callback, request_slot_t) + { + assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function + + return proxy_.registerSignalHandler( interfaceName_ + , signalName_ + , makeSignalHandler(std::forward<_Function>(callback)) + , request_slot ); + } + + template + inline signal_handler SignalSubscriber::makeSignalHandler(_Function&& callback) + { + return [callback = std::forward<_Function>(callback)](Signal signal) { // Create a tuple of callback input arguments' types, which will be used // as a storage for the argument values deserialized from the signal message. @@ -343,22 +360,7 @@ namespace sdbus { // Invoke callback with input arguments from the tuple. sdbus::apply(callback, signalArgs); } - }); - } - - /*** ------------------ ***/ - /*** SignalUnsubscriber ***/ - /*** ------------------ ***/ - - inline SignalUnsubscriber::SignalUnsubscriber(IProxy& proxy, const std::string& signalName) - : proxy_(proxy) - , signalName_(signalName) - { - } - - inline void SignalUnsubscriber::onInterface(const std::string& interfaceName) - { - proxy_.unregisterSignalHandler(interfaceName, signalName_); + }; } /*** -------------- ***/ diff --git a/include/sdbus-c++/IProxy.h b/include/sdbus-c++/IProxy.h index 4ba39e8b..c3a22009 100644 --- a/include/sdbus-c++/IProxy.h +++ b/include/sdbus-c++/IProxy.h @@ -197,6 +197,9 @@ namespace sdbus { * @param[in] signalName Name of the signal * @param[in] signalHandler Callback that implements the body of the signal handler * + * A signal can be subscribed to and unsubscribed from at any time during proxy + * lifetime. The subscription is active immediately after the call. + * * @throws sdbus::Error in case of failure */ virtual void registerSignalHandler( const std::string& interfaceName @@ -204,28 +207,27 @@ namespace sdbus { , signal_handler signalHandler ) = 0; /*! - * @brief Unregisters the handler of the desired signal + * @brief Registers a handler for the desired signal emitted by the D-Bus object * * @param[in] interfaceName Name of an interface that the signal belongs to * @param[in] signalName Name of the signal + * @param[in] signalHandler Callback that implements the body of the signal handler * - * @throws sdbus::Error in case of failure - */ - virtual void unregisterSignalHandler( const std::string& interfaceName - , const std::string& signalName ) = 0; - - /*! - * @brief Finishes the registration of signal handlers + * @return RAII-style slot handle representing the ownership of the subscription * - * The method physically subscribes to the desired signals. - * Must be called only once, after all signals have been registered already. + * A signal can be subscribed to and unsubscribed from at any time during proxy + * lifetime. The subscription is active immediately after the call. The subscription + * is unregistered when the client destroys the returned slot object. * * @throws sdbus::Error in case of failure */ - virtual void finishRegistration() = 0; + [[nodiscard]] virtual Slot registerSignalHandler( const std::string& interfaceName + , const std::string& signalName + , signal_handler signalHandler + , request_slot_t ) = 0; /*! - * @brief Unregisters proxy's signal handlers and stops receving replies to pending async calls + * @brief Unregisters proxy's signal handlers and stops receiving replies to pending async calls * * Unregistration is done automatically also in proxy's destructor. This method makes * sense if, in the process of proxy removal, we need to make sure that callbacks @@ -291,6 +293,9 @@ namespace sdbus { * in a message and D-Bus signatures automatically deduced from the parameters * of the provided native signal callback. * + * A signal can be subscribed to and unsubscribed from at any time during proxy + * lifetime. The subscription is active immediately after the call. + * * Example of use: * @code * object_.uponSignal("fooSignal").onInterface("com.kistler.foo").call([this](int arg1, double arg2){ this->onFooSignal(arg1, arg2); }); @@ -300,23 +305,6 @@ namespace sdbus { */ [[nodiscard]] SignalSubscriber uponSignal(const std::string& signalName); - /*! - * @brief Unregisters signal handler of a given signal of the D-Bus object - * - * @param[in] signalName Name of the signal - * @return A helper object for convenient unregistration of the signal handler - * - * This is a high-level, convenience way of unregistering a D-Bus signal's handler. - * - * Example of use: - * @code - * object_.muteSignal("fooSignal").onInterface("com.kistler.foo"); - * @endcode - * - * @throws sdbus::Error in case of failure - */ - [[nodiscard]] SignalUnsubscriber muteSignal(const std::string& signalName); - /*! * @brief Gets value of a property of the D-Bus object * @@ -543,11 +531,6 @@ namespace sdbus { return SignalSubscriber(*this, signalName); } - inline SignalUnsubscriber IProxy::muteSignal(const std::string& signalName) - { - return SignalUnsubscriber(*this, signalName); - } - inline PropertyGetter IProxy::getProperty(const std::string& propertyName) { return PropertyGetter(*this, propertyName); diff --git a/include/sdbus-c++/ProxyInterfaces.h b/include/sdbus-c++/ProxyInterfaces.h index 0ad98de4..d587523b 100644 --- a/include/sdbus-c++/ProxyInterfaces.h +++ b/include/sdbus-c++/ProxyInterfaces.h @@ -83,9 +83,10 @@ namespace sdbus { * methods. So the _Interfaces template parameter is a list of sdbus-c++-xml2cpp-generated * proxy-side interface classes representing interfaces of the corresponding remote D-Bus object. * - * In the final proxy class inherited from ProxyInterfaces, it is necessary to finish proxy - * registration in class constructor (`finishRegistration();`), and, conversely, unregister - * the proxy in class destructor (`unregister();`). + * In the final adaptor class inherited from ProxyInterfaces, one needs to make sure: + * 1. to call `registerProxy();` in the class constructor, and, conversely, + * 2. to call `unregisterProxy();` in the class destructor, + * so that the signals are subscribed to and unsubscribed from at a proper time. * ***********************************************/ template @@ -173,15 +174,15 @@ namespace sdbus { } /*! - * @brief Finishes proxy registration and makes the proxy ready for use + * @brief Registers handlers for D-Bus signals of the remote object * * This function must be called in the constructor of the final proxy class that implements ProxyInterfaces. * - * For more information, see underlying @ref IProxy::finishRegistration() + * See also @ref IProxy::registerSignalHandler() */ void registerProxy() { - getProxy().finishRegistration(); + (_Interfaces::registerProxy(), ...); } /*! diff --git a/include/sdbus-c++/StandardInterfaces.h b/include/sdbus-c++/StandardInterfaces.h index d193662b..b9dee98e 100644 --- a/include/sdbus-c++/StandardInterfaces.h +++ b/include/sdbus-c++/StandardInterfaces.h @@ -54,6 +54,10 @@ namespace sdbus { ~Peer_proxy() = default; + void registerProxy() + { + } + public: void Ping() { @@ -89,6 +93,10 @@ namespace sdbus { ~Introspectable_proxy() = default; + void registerProxy() + { + } + public: std::string Introspect() { @@ -109,6 +117,17 @@ namespace sdbus { protected: Properties_proxy(sdbus::IProxy& proxy) : proxy_(&proxy) + { + } + + Properties_proxy(const Properties_proxy&) = delete; + Properties_proxy& operator=(const Properties_proxy&) = delete; + Properties_proxy(Properties_proxy&&) = default; + Properties_proxy& operator=(Properties_proxy&&) = default; + + ~Properties_proxy() = default; + + void registerProxy() { proxy_ ->uponSignal("PropertiesChanged") @@ -121,13 +140,6 @@ namespace sdbus { }); } - Properties_proxy(const Properties_proxy&) = delete; - Properties_proxy& operator=(const Properties_proxy&) = delete; - Properties_proxy(Properties_proxy&&) = default; - Properties_proxy& operator=(Properties_proxy&&) = default; - - ~Properties_proxy() = default; - virtual void onPropertiesChanged( const std::string& interfaceName , const std::map& changedProperties , const std::vector& invalidatedProperties ) = 0; @@ -198,6 +210,17 @@ namespace sdbus { protected: ObjectManager_proxy(sdbus::IProxy& proxy) : proxy_(&proxy) + { + } + + ObjectManager_proxy(const ObjectManager_proxy&) = delete; + ObjectManager_proxy& operator=(const ObjectManager_proxy&) = delete; + ObjectManager_proxy(ObjectManager_proxy&&) = default; + ObjectManager_proxy& operator=(ObjectManager_proxy&&) = default; + + ~ObjectManager_proxy() = default; + + void registerProxy() { proxy_ ->uponSignal("InterfacesAdded") @@ -217,13 +240,6 @@ namespace sdbus { }); } - ObjectManager_proxy(const ObjectManager_proxy&) = delete; - ObjectManager_proxy& operator=(const ObjectManager_proxy&) = delete; - ObjectManager_proxy(ObjectManager_proxy&&) = default; - ObjectManager_proxy& operator=(ObjectManager_proxy&&) = default; - - ~ObjectManager_proxy() = default; - virtual void onInterfacesAdded( const sdbus::ObjectPath& objectPath , const std::map>& interfacesAndProperties) = 0; virtual void onInterfacesRemoved( const sdbus::ObjectPath& objectPath diff --git a/src/Connection.cpp b/src/Connection.cpp index 7eeeb848..539dfbf7 100644 --- a/src/Connection.cpp +++ b/src/Connection.cpp @@ -233,15 +233,13 @@ Slot Connection::addMatch(const std::string& match, message_handler callback) auto matchInfo = std::make_unique(MatchInfo{std::move(callback), {}, *this, {}}); - auto r = sdbus_->sd_bus_add_match(bus_.get(), &matchInfo->slot, match.c_str(), &Connection::sdbus_match_callback, matchInfo.get()); + sd_bus_slot *slot{}; + auto r = sdbus_->sd_bus_add_match(bus_.get(), &slot, match.c_str(), &Connection::sdbus_match_callback, matchInfo.get()); SDBUS_THROW_ERROR_IF(r < 0, "Failed to add match", -r); - return {matchInfo.release(), [this](void *ptr) - { - auto* matchInfo = static_cast(ptr); - sdbus_->sd_bus_slot_unref(matchInfo->slot); - std::default_delete{}(matchInfo); - }}; + matchInfo->slot = {slot, [this](void *slot){ sdbus_->sd_bus_slot_unref((sd_bus_slot*)slot); }}; + + return {matchInfo.release(), [](void *ptr){ delete static_cast(ptr); }}; } void Connection::addMatch(const std::string& match, message_handler callback, floating_slot_t) @@ -256,20 +254,18 @@ Slot Connection::addMatchAsync(const std::string& match, message_handler callbac sd_bus_message_handler_t sdbusInstallCallback = installCallback ? &Connection::sdbus_match_install_callback : nullptr; auto matchInfo = std::make_unique(MatchInfo{std::move(callback), std::move(installCallback), *this, {}}); + sd_bus_slot *slot{}; auto r = sdbus_->sd_bus_add_match_async( bus_.get() - , &matchInfo->slot + , &slot , match.c_str() , &Connection::sdbus_match_callback , sdbusInstallCallback , matchInfo.get()); SDBUS_THROW_ERROR_IF(r < 0, "Failed to add match", -r); - return {matchInfo.release(), [this](void *ptr) - { - auto* matchInfo = static_cast(ptr); - sdbus_->sd_bus_slot_unref(matchInfo->slot); - std::default_delete{}(matchInfo); - }}; + matchInfo->slot = {slot, [this](void *slot){ sdbus_->sd_bus_slot_unref((sd_bus_slot*)slot); }}; + + return {matchInfo.release(), [](void *ptr){ delete static_cast(ptr); }}; } void Connection::addMatchAsync(const std::string& match, message_handler callback, message_handler installCallback, floating_slot_t) @@ -796,16 +792,26 @@ std::vector Connection::to_strv(const std::vector& int Connection::sdbus_match_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError) { auto* matchInfo = static_cast(userData); + assert(matchInfo != nullptr); + assert(matchInfo->callback); + auto message = Message::Factory::create(sdbusMessage, &matchInfo->connection.getSdBusInterface()); + auto ok = invokeHandlerAndCatchErrors([&](){ matchInfo->callback(std::move(message)); }, retError); + return ok ? 0 : -1; } int Connection::sdbus_match_install_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError) { auto* matchInfo = static_cast(userData); + assert(matchInfo != nullptr); + assert(matchInfo->installCallback); + auto message = Message::Factory::create(sdbusMessage, &matchInfo->connection.getSdBusInterface()); + auto ok = invokeHandlerAndCatchErrors([&](){ matchInfo->installCallback(std::move(message)); }, retError); + return ok ? 0 : -1; } diff --git a/src/Connection.h b/src/Connection.h index 230bcbb2..5c0f001a 100644 --- a/src/Connection.h +++ b/src/Connection.h @@ -193,7 +193,7 @@ namespace sdbus::internal { message_handler callback; message_handler installCallback; Connection& connection; - sd_bus_slot *slot; + Slot slot; }; // sd-event integration diff --git a/src/Proxy.cpp b/src/Proxy.cpp index 89d94b7b..390f3dd5 100644 --- a/src/Proxy.cpp +++ b/src/Proxy.cpp @@ -136,58 +136,36 @@ void Proxy::registerSignalHandler( const std::string& interfaceName , const std::string& signalName , signal_handler signalHandler ) { - SDBUS_CHECK_INTERFACE_NAME(interfaceName); - SDBUS_CHECK_MEMBER_NAME(signalName); - SDBUS_THROW_ERROR_IF(!signalHandler, "Invalid signal handler provided", EINVAL); - - auto& interface = interfaces_[interfaceName]; - - auto signalData = std::make_unique(*this, std::move(signalHandler), nullptr); - auto insertionResult = interface.signals_.emplace(signalName, std::move(signalData)); + auto slot = Proxy::registerSignalHandler(interfaceName, signalName, std::move(signalHandler), request_slot); - auto inserted = insertionResult.second; - SDBUS_THROW_ERROR_IF(!inserted, "Failed to register signal handler: handler already exists", EINVAL); + floatingSignalSlots_.push_back(std::move(slot)); } -void Proxy::unregisterSignalHandler( const std::string& interfaceName - , const std::string& signalName ) +Slot Proxy::registerSignalHandler( const std::string& interfaceName + , const std::string& signalName + , signal_handler signalHandler + , request_slot_t ) { - auto it = interfaces_.find(interfaceName); - - if (it != interfaces_.end()) - it->second.signals_.erase(signalName); -} + SDBUS_CHECK_INTERFACE_NAME(interfaceName); + SDBUS_CHECK_MEMBER_NAME(signalName); + SDBUS_THROW_ERROR_IF(!signalHandler, "Invalid signal handler provided", EINVAL); -void Proxy::finishRegistration() -{ - registerSignalHandlers(*connection_); -} + auto signalInfo = std::make_unique(SignalInfo{std::move(signalHandler), *this, {}}); -void Proxy::registerSignalHandlers(sdbus::internal::IConnection& connection) -{ - for (auto& interfaceItem : interfaces_) - { - const auto& interfaceName = interfaceItem.first; - auto& signalsOnInterface = interfaceItem.second.signals_; + signalInfo->slot = connection_->registerSignalHandler( destination_ + , objectPath_ + , interfaceName + , signalName + , &Proxy::sdbus_signal_handler + , signalInfo.get() ); - for (auto& signalItem : signalsOnInterface) - { - const auto& signalName = signalItem.first; - auto* signalData = signalItem.second.get(); - signalData->slot = connection.registerSignalHandler( destination_ - , objectPath_ - , interfaceName - , signalName - , &Proxy::sdbus_signal_handler - , signalData); - } - } + return {signalInfo.release(), [](void *ptr){ delete static_cast(ptr); }}; } void Proxy::unregister() { pendingAsyncCalls_.clear(); - interfaces_.clear(); + floatingSignalSlots_.clear(); } sdbus::IConnection& Proxy::getConnection() const @@ -241,13 +219,14 @@ int Proxy::sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userDat int Proxy::sdbus_signal_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError) { - auto* signalData = static_cast(userData); - assert(signalData != nullptr); - assert(signalData->callback); + auto* signalInfo = static_cast(userData); + assert(signalInfo != nullptr); + assert(signalInfo->callback); - auto message = Message::Factory::create(sdbusMessage, &signalData->proxy.connection_->getSdBusInterface()); + // TODO: Hide Message factory invocation under Connection API (tell, don't ask principle), then we can remove getSdBusInterface() + auto message = Message::Factory::create(sdbusMessage, &signalInfo->proxy.connection_->getSdBusInterface()); - auto ok = invokeHandlerAndCatchErrors([&](){ signalData->callback(std::move(message)); }, retError); + auto ok = invokeHandlerAndCatchErrors([&](){ signalInfo->callback(std::move(message)); }, retError); return ok ? 0 : -1; } diff --git a/src/Proxy.h b/src/Proxy.h index efb5b3a1..7ae4abe2 100644 --- a/src/Proxy.h +++ b/src/Proxy.h @@ -32,10 +32,9 @@ #include SDBUS_HEADER #include #include -#include #include +#include #include -#include namespace sdbus::internal { @@ -63,10 +62,10 @@ namespace sdbus::internal { void registerSignalHandler( const std::string& interfaceName , const std::string& signalName , signal_handler signalHandler ) override; - void unregisterSignalHandler( const std::string& interfaceName - , const std::string& signalName ) override; - - void finishRegistration() override; + Slot registerSignalHandler( const std::string& interfaceName + , const std::string& signalName + , signal_handler signalHandler + , request_slot_t ) override; void unregister() override; sdbus::IConnection& getConnection() const override; @@ -74,9 +73,8 @@ namespace sdbus::internal { Message getCurrentlyProcessedMessage() const override; private: - 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_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError); + static int sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError); private: friend PendingAsyncCall; @@ -87,28 +85,37 @@ namespace sdbus::internal { std::string destination_; std::string objectPath_; - using InterfaceName = std::string; - struct InterfaceData + std::vector floatingSignalSlots_; + + struct SignalInfo { - using SignalName = std::string; - struct SignalData - { - SignalData(Proxy& proxy, signal_handler callback, Slot slot) - : proxy(proxy) - , callback(std::move(callback)) - , slot(std::move(slot)) - {} - Proxy& proxy; - signal_handler callback; - // slot must be listed after callback to ensure that slot is destructed first. - // Destructing the slot will sd_bus_slot_unref() the callback. - // Only after sd_bus_slot_unref(), we can safely delete the callback. The bus mutex (SdBus::sdbusMutex_) - // ensures that sd_bus_slot_unref() and the callback execute sequentially. - Slot slot; - }; - std::map> signals_; + signal_handler callback; + Proxy& proxy; + Slot slot; }; - std::map interfaces_; + +// using InterfaceName = std::string; +// struct InterfaceData +// { +// using SignalName = std::string; +// struct SignalData +// { +// SignalData(Proxy& proxy, signal_handler callback, Slot slot) +// : proxy(proxy) +// , callback(std::move(callback)) +// , slot(std::move(slot)) +// {} +// Proxy& proxy; +// signal_handler callback; +// // slot must be listed after callback to ensure that slot is destructed first. +// // Destructing the slot will sd_bus_slot_unref() the callback. +// // Only after sd_bus_slot_unref(), we can safely delete the callback. The bus mutex (SdBus::sdbusMutex_) +// // ensures that sd_bus_slot_unref() and the callback execute sequentially. +// Slot slot; +// }; +// std::map> signals_; +// }; +// std::map interfaces_; // We need to keep track of pending async calls. When the proxy is being destructed, we must // remove all slots of these pending calls, otherwise in case when the connection outlives diff --git a/tests/integrationtests/integrationtests-proxy.h b/tests/integrationtests/integrationtests-proxy.h index f951ed96..c1ed8f25 100644 --- a/tests/integrationtests/integrationtests-proxy.h +++ b/tests/integrationtests/integrationtests-proxy.h @@ -22,9 +22,6 @@ class integrationtests_proxy integrationtests_proxy(sdbus::IProxy& proxy) : proxy_(&proxy) { - proxy_->uponSignal("simpleSignal").onInterface(INTERFACE_NAME).call([this](){ this->onSimpleSignal(); }); - proxy_->uponSignal("signalWithMap").onInterface(INTERFACE_NAME).call([this](const std::map& aMap){ this->onSignalWithMap(aMap); }); - proxy_->uponSignal("signalWithVariant").onInterface(INTERFACE_NAME).call([this](const sdbus::Variant& aVariant){ this->onSignalWithVariant(aVariant); }); } integrationtests_proxy(const integrationtests_proxy&) = delete; @@ -34,6 +31,13 @@ class integrationtests_proxy ~integrationtests_proxy() = default; + void registerProxy() + { + simpleSignalSlot_ = proxy_->uponSignal("simpleSignal").onInterface(INTERFACE_NAME).call([this](){ this->onSimpleSignal(); }, sdbus::request_slot); + proxy_->uponSignal("signalWithMap").onInterface(INTERFACE_NAME).call([this](const std::map& aMap){ this->onSignalWithMap(aMap); }); + proxy_->uponSignal("signalWithVariant").onInterface(INTERFACE_NAME).call([this](const sdbus::Variant& aVariant){ this->onSignalWithVariant(aVariant); }); + } + virtual void onSimpleSignal() = 0; virtual void onSignalWithMap(const std::map& aMap) = 0; virtual void onSignalWithVariant(const sdbus::Variant& aVariant) = 0; @@ -183,13 +187,12 @@ class integrationtests_proxy void unregisterSimpleSignalHandler() { - proxy_->muteSignal("simpleSignal").onInterface(INTERFACE_NAME); + simpleSignalSlot_.reset(); } void reRegisterSimpleSignalHandler() { - proxy_->uponSignal("simpleSignal").onInterface(INTERFACE_NAME).call([this](){ this->onSimpleSignal(); }); - proxy_->finishRegistration(); + simpleSignalSlot_ = proxy_->uponSignal("simpleSignal").onInterface(INTERFACE_NAME).call([this](){ this->onSimpleSignal(); }, sdbus::request_slot); } public: @@ -220,6 +223,7 @@ class integrationtests_proxy private: sdbus::IProxy* proxy_; + sdbus::Slot simpleSignalSlot_; }; }} // namespaces diff --git a/tests/perftests/perftests-proxy.h b/tests/perftests/perftests-proxy.h index df16af04..dee4bf97 100644 --- a/tests/perftests/perftests-proxy.h +++ b/tests/perftests/perftests-proxy.h @@ -22,7 +22,6 @@ class perftests_proxy perftests_proxy(sdbus::IProxy& proxy) : proxy_(&proxy) { - proxy_->uponSignal("dataSignal").onInterface(INTERFACE_NAME).call([this](const std::string& data){ this->onDataSignal(data); }); } perftests_proxy(const perftests_proxy&) = delete; @@ -32,6 +31,11 @@ class perftests_proxy ~perftests_proxy() = default; + void registerProxy() + { + proxy_->uponSignal("dataSignal").onInterface(INTERFACE_NAME).call([this](const std::string& data){ this->onDataSignal(data); }); + } + virtual void onDataSignal(const std::string& data) = 0; public: diff --git a/tests/stresstests/celsius-thermometer-proxy.h b/tests/stresstests/celsius-thermometer-proxy.h index 25c6f58f..b4a57158 100644 --- a/tests/stresstests/celsius-thermometer-proxy.h +++ b/tests/stresstests/celsius-thermometer-proxy.h @@ -33,6 +33,10 @@ class thermometer_proxy ~thermometer_proxy() = default; + void registerProxy() + { + } + public: uint32_t getCurrentTemperature() { diff --git a/tests/stresstests/concatenator-proxy.h b/tests/stresstests/concatenator-proxy.h index 60a9c783..8aa14beb 100644 --- a/tests/stresstests/concatenator-proxy.h +++ b/tests/stresstests/concatenator-proxy.h @@ -23,7 +23,6 @@ class concatenator_proxy concatenator_proxy(sdbus::IProxy& proxy) : proxy_(&proxy) { - proxy_->uponSignal("concatenatedSignal").onInterface(INTERFACE_NAME).call([this](const std::string& concatenatedString){ this->onConcatenatedSignal(concatenatedString); }); } concatenator_proxy(const concatenator_proxy&) = delete; @@ -33,6 +32,11 @@ class concatenator_proxy ~concatenator_proxy() = default; + void registerProxy() + { + proxy_->uponSignal("concatenatedSignal").onInterface(INTERFACE_NAME).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; diff --git a/tests/stresstests/fahrenheit-thermometer-proxy.h b/tests/stresstests/fahrenheit-thermometer-proxy.h index e9705722..3a1fc265 100644 --- a/tests/stresstests/fahrenheit-thermometer-proxy.h +++ b/tests/stresstests/fahrenheit-thermometer-proxy.h @@ -33,6 +33,10 @@ class thermometer_proxy ~thermometer_proxy() = default; + void registerProxy() + { + } + public: uint32_t getCurrentTemperature() { @@ -71,6 +75,10 @@ class factory_proxy ~factory_proxy() = default; + void registerProxy() + { + } + public: sdbus::ObjectPath createDelegateObject() { diff --git a/tools/xml2cpp-codegen/ProxyGenerator.cpp b/tools/xml2cpp-codegen/ProxyGenerator.cpp index 964e4da9..fec70d74 100644 --- a/tools/xml2cpp-codegen/ProxyGenerator.cpp +++ b/tools/xml2cpp-codegen/ProxyGenerator.cpp @@ -84,17 +84,8 @@ std::string ProxyGenerator::processInterface(Node& interface) const << tab << "static constexpr const char* INTERFACE_NAME = \"" << ifaceName << "\";" << endl << endl << "protected:" << endl << tab << className << "(sdbus::IProxy& proxy)" << endl - << tab << tab << ": proxy_(&proxy)" << endl; - - Nodes methods = interface["method"]; - Nodes signals = interface["signal"]; - Nodes properties = interface["property"]; - - std::string registration, declaration; - std::tie(registration, declaration) = processSignals(signals); - - body << tab << "{" << endl - << registration + << tab << tab << ": proxy_(&proxy)" << endl + << tab << "{" << endl << tab << "}" << endl << endl; // Rule of Five @@ -105,6 +96,18 @@ std::string ProxyGenerator::processInterface(Node& interface) const body << tab << "~" << className << "() = default;" << endl << endl; + Nodes methods = interface["method"]; + Nodes signals = interface["signal"]; + Nodes properties = interface["property"]; + + std::string registration, declaration; + std::tie(registration, declaration) = processSignals(signals); + + body << tab << "void registerProxy()" << endl + << tab << "{" << endl + << registration + << tab << "}" << endl << endl; + if (!declaration.empty()) body << declaration << endl; From f6a4def0d723fd5fa1e30e47071c1e913fdc5ae4 Mon Sep 17 00:00:00 2001 From: Stanislav Angelovic Date: Sat, 30 Dec 2023 19:22:45 +0100 Subject: [PATCH 18/52] refactor: rename request_slot tag to return_slot --- docs/using-sdbus-c++.md | 8 ++++---- include/sdbus-c++/ConvenienceApiClasses.h | 4 ++-- include/sdbus-c++/ConvenienceApiClasses.inl | 8 ++++---- include/sdbus-c++/IObject.h | 2 +- include/sdbus-c++/IProxy.h | 2 +- include/sdbus-c++/TypeTraits.h | 6 +++--- src/Connection.cpp | 2 +- src/Connection.h | 2 +- src/IConnection.h | 2 +- src/Object.cpp | 6 +++--- src/Object.h | 2 +- src/Proxy.cpp | 4 ++-- src/Proxy.h | 2 +- tests/integrationtests/DBusMethodsTests.cpp | 8 ++++---- tests/integrationtests/integrationtests-proxy.h | 4 ++-- 15 files changed, 31 insertions(+), 31 deletions(-) diff --git a/docs/using-sdbus-c++.md b/docs/using-sdbus-c++.md index 6fc4b58b..975907d7 100644 --- a/docs/using-sdbus-c++.md +++ b/docs/using-sdbus-c++.md @@ -317,7 +317,7 @@ int main(int argc, char *argv[]) We establish a D-Bus system 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 add a so-called object vtable, where we declare and describe its D-Bus API, i.e. its interface, methods, signals, properties (if any) that the object provides. Then we need to make sure to run the event loop upon the connection, which handles all incoming, outgoing and other requests. -> **_Tip_:** There's also an overload of `addVTable()` method with `request_slot_t` tag parameter which returns a `Slot` object. The slot is a simple RAII-based handle of the associated vtable registration. As long as you keep the slot object, the vtable registration is active. When you let go of the slot, the vtable is automatically removed from the D-Bus object. This gives you the ability to implement "dynamic" D-Bus object API that is addable as well as removable at any time during object lifetime. +> **_Tip_:** There's also an overload of `addVTable()` method with `return_slot_t` tag parameter which returns a `Slot` object. The slot is a simple RAII-based handle of the associated vtable registration. As long as you keep the slot object, the vtable registration is active. When you let go of the slot, the vtable is automatically removed from the D-Bus object. This gives you the ability to implement "dynamic" D-Bus object API that is addable as well as removable at any time during object lifetime. > **_Note_:** A D-Bus object can have any number of vtables attached to it. Even a D-Bus interface of an object can have multiple vtables attached to it. @@ -394,7 +394,7 @@ In simple cases, we don't need to create D-Bus connection explicitly for our pro 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. -> **_Tip_:** There's also an overload of `registerSignalHandler()` with `request_slot_t` tag which returns a `Slot` object. The slot is a simple RAII-based handle of the subscription. As long as you keep the slot object, the signal subscription is active. When you let go of the object, the signal handler is automatically unregistered. This gives you finer control over the lifetime of signal subscription. +> **_Tip_:** There's also an overload of `registerSignalHandler()` with `return_slot_t` tag which returns a `Slot` object. The slot is a simple RAII-based handle of the subscription. As long as you keep the slot object, the signal subscription is active. When you let go of the object, the signal handler is automatically unregistered. This gives you finer control over the lifetime of signal subscription. 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. @@ -540,7 +540,7 @@ int main(int argc, char *argv[]) } ``` -> **_Tip_:** There's also an overload of `addVTable(...).forInterface()` method with `request_slot_t` tag parameter which returns a `Slot` object. The slot is a simple RAII-based handle of the associated vtable registration. As long as you keep the slot object, the vtable registration is active. When you let go of the slot, the vtable is automatically removed from the D-Bus object. This gives you the ability to implement "dynamic" D-Bus object API that is addable as well as removable at any time during object lifetime. +> **_Tip_:** There's also an overload of `addVTable(...).forInterface()` method with `return_slot_t` tag parameter which returns a `Slot` object. The slot is a simple RAII-based handle of the associated vtable registration. As long as you keep the slot object, the vtable registration is active. When you let go of the slot, the vtable is automatically removed from the D-Bus object. This gives you the ability to implement "dynamic" D-Bus object API that is addable as well as removable at any time during object lifetime. > **_Note_:** A D-Bus object can have any number of vtables attached to it. Even a D-Bus interface of an object can have multiple vtables attached to it. @@ -601,7 +601,7 @@ int main(int argc, char *argv[]) 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. -> **_Tip_:** There's also an overload of `uponSignal(...).call()` with `request_slot_t` tag which returns a `Slot` object. The slot is a simple RAII-based handle of the subscription. As long as you keep the slot object, the signal subscription is active. When you let go of the object, the signal handler is automatically unregistered. This gives you finer control over the lifetime of signal subscription. +> **_Tip_:** There's also an overload of `uponSignal(...).call()` with `return_slot_t` tag which returns a `Slot` object. The slot is a simple RAII-based handle of the subscription. As long as you keep the slot object, the signal subscription is active. When you let go of the object, the signal handler is automatically unregistered. This gives you finer control over the lifetime of signal subscription. We recommend that sdbus-c++ users prefer the convenience API to the lower level, basic API. When feasible, using generated adaptor and proxy C++ bindings is even better as it provides yet slightly higher abstraction built on top of the convenience API, where remote calls look simply like local, native calls of object methods. They are described in the following section. diff --git a/include/sdbus-c++/ConvenienceApiClasses.h b/include/sdbus-c++/ConvenienceApiClasses.h index f0c5db58..edaab716 100644 --- a/include/sdbus-c++/ConvenienceApiClasses.h +++ b/include/sdbus-c++/ConvenienceApiClasses.h @@ -53,7 +53,7 @@ namespace sdbus { public: VTableAdder(IObject& object, std::vector vtable); void forInterface(std::string interfaceName); - [[nodiscard]] Slot forInterface(std::string interfaceName, request_slot_t); + [[nodiscard]] Slot forInterface(std::string interfaceName, return_slot_t); private: IObject& object_; @@ -129,7 +129,7 @@ namespace sdbus { SignalSubscriber(IProxy& proxy, const std::string& signalName); SignalSubscriber& onInterface(std::string interfaceName); template void call(_Function&& callback); - template [[nodiscard]] Slot call(_Function&& callback, request_slot_t); + template [[nodiscard]] Slot call(_Function&& callback, return_slot_t); private: template signal_handler makeSignalHandler(_Function&& callback); diff --git a/include/sdbus-c++/ConvenienceApiClasses.inl b/include/sdbus-c++/ConvenienceApiClasses.inl index 178b2775..b1dc8c29 100644 --- a/include/sdbus-c++/ConvenienceApiClasses.inl +++ b/include/sdbus-c++/ConvenienceApiClasses.inl @@ -57,9 +57,9 @@ namespace sdbus { object_.addVTable(std::move(interfaceName), std::move(vtable_)); } - inline Slot VTableAdder::forInterface(std::string interfaceName, request_slot_t) + inline Slot VTableAdder::forInterface(std::string interfaceName, return_slot_t) { - return object_.addVTable(std::move(interfaceName), std::move(vtable_), request_slot); + return object_.addVTable(std::move(interfaceName), std::move(vtable_), return_slot); } /*** ------------- ***/ @@ -311,14 +311,14 @@ namespace sdbus { } template - inline Slot SignalSubscriber::call(_Function&& callback, request_slot_t) + inline Slot SignalSubscriber::call(_Function&& callback, return_slot_t) { assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function return proxy_.registerSignalHandler( interfaceName_ , signalName_ , makeSignalHandler(std::forward<_Function>(callback)) - , request_slot ); + , return_slot ); } template diff --git a/include/sdbus-c++/IObject.h b/include/sdbus-c++/IObject.h index a6fb9a55..187b35a3 100644 --- a/include/sdbus-c++/IObject.h +++ b/include/sdbus-c++/IObject.h @@ -140,7 +140,7 @@ namespace sdbus { * * @throws sdbus::Error in case of failure */ - virtual Slot addVTable(std::string interfaceName, std::vector vtable, request_slot_t) = 0; + virtual Slot addVTable(std::string interfaceName, std::vector vtable, return_slot_t) = 0; /*! * @brief A little more convenient overload of addVTable() above diff --git a/include/sdbus-c++/IProxy.h b/include/sdbus-c++/IProxy.h index c3a22009..90a8ed03 100644 --- a/include/sdbus-c++/IProxy.h +++ b/include/sdbus-c++/IProxy.h @@ -224,7 +224,7 @@ namespace sdbus { [[nodiscard]] virtual Slot registerSignalHandler( const std::string& interfaceName , const std::string& signalName , signal_handler signalHandler - , request_slot_t ) = 0; + , return_slot_t ) = 0; /*! * @brief Unregisters proxy's signal handlers and stops receiving replies to pending async calls diff --git a/include/sdbus-c++/TypeTraits.h b/include/sdbus-c++/TypeTraits.h index a0b8825f..0c6e6de8 100644 --- a/include/sdbus-c++/TypeTraits.h +++ b/include/sdbus-c++/TypeTraits.h @@ -73,9 +73,9 @@ namespace sdbus { // Type-erased RAII-style handle to callbacks/subscriptions registered to sdbus-c++ using Slot = std::unique_ptr>; - // Tag specifying that an owning slot handle shall be returned from the function - struct request_slot_t { explicit request_slot_t() = default; }; - inline constexpr request_slot_t request_slot{}; + // Tag specifying that an owning slot handle shall be returned from a registration/subscription function to the caller + struct return_slot_t { explicit return_slot_t() = default; }; + inline constexpr return_slot_t return_slot{}; // Tag specifying that the library shall own the slot resulting from the call of the function (so-called floating slot) struct floating_slot_t { explicit floating_slot_t() = default; }; inline constexpr floating_slot_t floating_slot{}; diff --git a/src/Connection.cpp b/src/Connection.cpp index 539dfbf7..bc9b371e 100644 --- a/src/Connection.cpp +++ b/src/Connection.cpp @@ -198,7 +198,7 @@ void Connection::addObjectManager(const std::string& objectPath, floating_slot_t SDBUS_THROW_ERROR_IF(r < 0, "Failed to add object manager", -r); } -Slot Connection::addObjectManager(const std::string& objectPath, request_slot_t) +Slot Connection::addObjectManager(const std::string& objectPath, return_slot_t) { sd_bus_slot *slot{}; diff --git a/src/Connection.h b/src/Connection.h index 5c0f001a..323028ea 100644 --- a/src/Connection.h +++ b/src/Connection.h @@ -90,7 +90,7 @@ namespace sdbus::internal { void addObjectManager(const std::string& objectPath) override; void addObjectManager(const std::string& objectPath, floating_slot_t) override; - Slot addObjectManager(const std::string& objectPath, request_slot_t) override; + Slot addObjectManager(const std::string& objectPath, return_slot_t) override; void setMethodCallTimeout(uint64_t timeout) override; uint64_t getMethodCallTimeout() const override; diff --git a/src/IConnection.h b/src/IConnection.h index a1227c26..5145f3c0 100644 --- a/src/IConnection.h +++ b/src/IConnection.h @@ -86,7 +86,7 @@ namespace sdbus::internal { , const std::vector& interfaces ) = 0; using sdbus::IConnection::addObjectManager; - [[nodiscard]] virtual Slot addObjectManager(const std::string& objectPath, request_slot_t) = 0; + [[nodiscard]] virtual Slot addObjectManager(const std::string& objectPath, return_slot_t) = 0; [[nodiscard]] virtual Slot registerSignalHandler( const std::string& sender , const std::string& objectPath diff --git a/src/Object.cpp b/src/Object.cpp index 51caa83f..06342724 100644 --- a/src/Object.cpp +++ b/src/Object.cpp @@ -49,12 +49,12 @@ Object::Object(sdbus::internal::IConnection& connection, std::string objectPath) void Object::addVTable(std::string interfaceName, std::vector vtable) { - auto slot = Object::addVTable(std::move(interfaceName), std::move(vtable), request_slot); + auto slot = Object::addVTable(std::move(interfaceName), std::move(vtable), return_slot); vtables_.push_back(std::move(slot)); } -Slot Object::addVTable(std::string interfaceName, std::vector vtable, request_slot_t) +Slot Object::addVTable(std::string interfaceName, std::vector vtable, return_slot_t) { SDBUS_CHECK_INTERFACE_NAME(interfaceName); @@ -121,7 +121,7 @@ void Object::emitInterfacesRemovedSignal(const std::vector& interfa void Object::addObjectManager() { - objectManagerSlot_ = connection_.addObjectManager(objectPath_, request_slot); + objectManagerSlot_ = connection_.addObjectManager(objectPath_, return_slot); } void Object::removeObjectManager() diff --git a/src/Object.h b/src/Object.h index b7c6feba..91dea674 100644 --- a/src/Object.h +++ b/src/Object.h @@ -46,7 +46,7 @@ namespace sdbus::internal { Object(sdbus::internal::IConnection& connection, std::string objectPath); void addVTable(std::string interfaceName, std::vector vtable) override; - Slot addVTable(std::string interfaceName, std::vector vtable, request_slot_t) override; + Slot addVTable(std::string interfaceName, std::vector vtable, return_slot_t) override; void unregister() override; sdbus::Signal createSignal(const std::string& interfaceName, const std::string& signalName) override; diff --git a/src/Proxy.cpp b/src/Proxy.cpp index 390f3dd5..004888f1 100644 --- a/src/Proxy.cpp +++ b/src/Proxy.cpp @@ -136,7 +136,7 @@ void Proxy::registerSignalHandler( const std::string& interfaceName , const std::string& signalName , signal_handler signalHandler ) { - auto slot = Proxy::registerSignalHandler(interfaceName, signalName, std::move(signalHandler), request_slot); + auto slot = Proxy::registerSignalHandler(interfaceName, signalName, std::move(signalHandler), return_slot); floatingSignalSlots_.push_back(std::move(slot)); } @@ -144,7 +144,7 @@ void Proxy::registerSignalHandler( const std::string& interfaceName Slot Proxy::registerSignalHandler( const std::string& interfaceName , const std::string& signalName , signal_handler signalHandler - , request_slot_t ) + , return_slot_t ) { SDBUS_CHECK_INTERFACE_NAME(interfaceName); SDBUS_CHECK_MEMBER_NAME(signalName); diff --git a/src/Proxy.h b/src/Proxy.h index 7ae4abe2..d47e4fd9 100644 --- a/src/Proxy.h +++ b/src/Proxy.h @@ -65,7 +65,7 @@ namespace sdbus::internal { Slot registerSignalHandler( const std::string& interfaceName , const std::string& signalName , signal_handler signalHandler - , request_slot_t ) override; + , return_slot_t ) override; void unregister() override; sdbus::IConnection& getConnection() const override; diff --git a/tests/integrationtests/DBusMethodsTests.cpp b/tests/integrationtests/DBusMethodsTests.cpp index c052caf9..b18b5f04 100644 --- a/tests/integrationtests/DBusMethodsTests.cpp +++ b/tests/integrationtests/DBusMethodsTests.cpp @@ -293,8 +293,8 @@ TYPED_TEST(SdbusTestObject, CanRegisterAdditionalVTableDynamicallyAtAnyTime) auto& object = this->m_adaptor->getObject(); auto vtableSlot = object.addVTable( "org.sdbuscpp.integrationtests2" , { sdbus::registerMethod("add").implementedAs([](const int64_t& a, const double& b){ return a + b; }) - , sdbus::registerMethod("subtract").implementedAs([](const int& a, const int& b){ return a - b; })} - , sdbus::request_slot ); + , sdbus::registerMethod("subtract").implementedAs([](const int& a, const int& b){ return a - b; }) } + , sdbus::return_slot ); // The new remote vtable is registered as long as we keep vtableSlot, so remote method calls now should pass auto proxy = sdbus::createProxy(BUS_NAME, OBJECT_PATH, sdbus::dont_run_event_loop_thread); @@ -310,8 +310,8 @@ TYPED_TEST(SdbusTestObject, CanUnregisterAdditionallyRegisteredVTableAtAnyTime) auto vtableSlot = object.addVTable( "org.sdbuscpp.integrationtests2" , { sdbus::registerMethod("add").implementedAs([](const int64_t& a, const double& b){ return a + b; }) - , sdbus::registerMethod("subtract").implementedAs([](const int& a, const int& b){ return a - b; })} - , sdbus::request_slot ); + , sdbus::registerMethod("subtract").implementedAs([](const int& a, const int& b){ return a - b; }) } + , sdbus::return_slot ); vtableSlot.reset(); // Letting the slot go means letting go the associated vtable registration // No such remote D-Bus method under given interface exists anymore... diff --git a/tests/integrationtests/integrationtests-proxy.h b/tests/integrationtests/integrationtests-proxy.h index c1ed8f25..1c86d928 100644 --- a/tests/integrationtests/integrationtests-proxy.h +++ b/tests/integrationtests/integrationtests-proxy.h @@ -33,7 +33,7 @@ class integrationtests_proxy void registerProxy() { - simpleSignalSlot_ = proxy_->uponSignal("simpleSignal").onInterface(INTERFACE_NAME).call([this](){ this->onSimpleSignal(); }, sdbus::request_slot); + simpleSignalSlot_ = proxy_->uponSignal("simpleSignal").onInterface(INTERFACE_NAME).call([this](){ this->onSimpleSignal(); }, sdbus::return_slot); proxy_->uponSignal("signalWithMap").onInterface(INTERFACE_NAME).call([this](const std::map& aMap){ this->onSignalWithMap(aMap); }); proxy_->uponSignal("signalWithVariant").onInterface(INTERFACE_NAME).call([this](const sdbus::Variant& aVariant){ this->onSignalWithVariant(aVariant); }); } @@ -192,7 +192,7 @@ class integrationtests_proxy void reRegisterSimpleSignalHandler() { - simpleSignalSlot_ = proxy_->uponSignal("simpleSignal").onInterface(INTERFACE_NAME).call([this](){ this->onSimpleSignal(); }, sdbus::request_slot); + simpleSignalSlot_ = proxy_->uponSignal("simpleSignal").onInterface(INTERFACE_NAME).call([this](){ this->onSimpleSignal(); }, sdbus::return_slot); } public: From f263267c17655829ffa9339d6067a49688622b7b Mon Sep 17 00:00:00 2001 From: Stanislav Angelovic Date: Sat, 30 Dec 2023 19:27:22 +0100 Subject: [PATCH 19/52] refactor: remove deprecated dont_request_slot_t tag --- include/sdbus-c++/Message.h | 1 - include/sdbus-c++/TypeTraits.h | 3 --- src/Message.cpp | 5 ----- 3 files changed, 9 deletions(-) diff --git a/include/sdbus-c++/Message.h b/include/sdbus-c++/Message.h index 14d04adc..f1125158 100644 --- a/include/sdbus-c++/Message.h +++ b/include/sdbus-c++/Message.h @@ -245,7 +245,6 @@ namespace sdbus { MethodCall() = default; MethodReply send(uint64_t timeout) const; - [[deprecated("Use send overload with floating_slot instead")]] void send(void* callback, void* userData, uint64_t timeout, dont_request_slot_t) const; void send(void* callback, void* userData, uint64_t timeout, floating_slot_t) const; [[nodiscard]] Slot send(void* callback, void* userData, uint64_t timeout) const; diff --git a/include/sdbus-c++/TypeTraits.h b/include/sdbus-c++/TypeTraits.h index 0c6e6de8..1131384c 100644 --- a/include/sdbus-c++/TypeTraits.h +++ b/include/sdbus-c++/TypeTraits.h @@ -79,9 +79,6 @@ namespace sdbus { // Tag specifying that the library shall own the slot resulting from the call of the function (so-called floating slot) struct floating_slot_t { explicit floating_slot_t() = default; }; inline constexpr floating_slot_t floating_slot{}; - // Deprecated name for the above -- a floating slot - struct dont_request_slot_t { explicit dont_request_slot_t() = default; }; - [[deprecated("Replaced by floating_slot")]] inline constexpr dont_request_slot_t dont_request_slot{}; // Tag denoting the assumption that the caller has already obtained message ownership struct adopt_message_t { explicit adopt_message_t() = default; }; inline constexpr adopt_message_t adopt_message{}; diff --git a/src/Message.cpp b/src/Message.cpp index 2e9ce321..05e62269 100644 --- a/src/Message.cpp +++ b/src/Message.cpp @@ -813,11 +813,6 @@ MethodReply MethodCall::sendWithNoReply() const return Factory::create(); // No reply } -void MethodCall::send(void* callback, void* userData, uint64_t timeout, dont_request_slot_t) const -{ - MethodCall::send(callback, userData, timeout, floating_slot); -} - void MethodCall::send(void* callback, void* userData, uint64_t timeout, floating_slot_t) const { auto r = sdbus_->sd_bus_call_async(nullptr, nullptr, (sd_bus_message*)msg_, (sd_bus_message_handler_t)callback, userData, timeout); From fc7ccb0beabe3abc8974668bd828f79eb4189841 Mon Sep 17 00:00:00 2001 From: Stanislav Angelovic Date: Sat, 30 Dec 2023 19:47:08 +0100 Subject: [PATCH 20/52] chore: remove legacy comments --- src/Proxy.h | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/Proxy.h b/src/Proxy.h index d47e4fd9..4dabe9e9 100644 --- a/src/Proxy.h +++ b/src/Proxy.h @@ -94,29 +94,6 @@ namespace sdbus::internal { Slot slot; }; -// using InterfaceName = std::string; -// struct InterfaceData -// { -// using SignalName = std::string; -// struct SignalData -// { -// SignalData(Proxy& proxy, signal_handler callback, Slot slot) -// : proxy(proxy) -// , callback(std::move(callback)) -// , slot(std::move(slot)) -// {} -// Proxy& proxy; -// signal_handler callback; -// // slot must be listed after callback to ensure that slot is destructed first. -// // Destructing the slot will sd_bus_slot_unref() the callback. -// // Only after sd_bus_slot_unref(), we can safely delete the callback. The bus mutex (SdBus::sdbusMutex_) -// // ensures that sd_bus_slot_unref() and the callback execute sequentially. -// Slot slot; -// }; -// std::map> signals_; -// }; -// std::map interfaces_; - // We need to keep track of pending async calls. When the proxy is being destructed, we must // remove all slots of these pending calls, otherwise in case when the connection outlives // the proxy, we might get async reply handlers invoked for pending async calls after the proxy From d9e7e4a44525a91a25807d53095a32585dce60ac Mon Sep 17 00:00:00 2001 From: Stanislav Angelovic Date: Sat, 30 Dec 2023 19:57:48 +0100 Subject: [PATCH 21/52] refactor: add nodiscard attribute for some functions --- include/sdbus-c++/ConvenienceApiClasses.inl | 4 ++-- include/sdbus-c++/IConnection.h | 6 +++--- include/sdbus-c++/IObject.h | 12 ++++++------ include/sdbus-c++/IProxy.h | 10 +++++----- src/IConnection.h | 2 +- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/include/sdbus-c++/ConvenienceApiClasses.inl b/include/sdbus-c++/ConvenienceApiClasses.inl index b1dc8c29..5ee2258d 100644 --- a/include/sdbus-c++/ConvenienceApiClasses.inl +++ b/include/sdbus-c++/ConvenienceApiClasses.inl @@ -57,7 +57,7 @@ namespace sdbus { object_.addVTable(std::move(interfaceName), std::move(vtable_)); } - inline Slot VTableAdder::forInterface(std::string interfaceName, return_slot_t) + [[nodiscard]] inline Slot VTableAdder::forInterface(std::string interfaceName, return_slot_t) { return object_.addVTable(std::move(interfaceName), std::move(vtable_), return_slot); } @@ -311,7 +311,7 @@ namespace sdbus { } template - inline Slot SignalSubscriber::call(_Function&& callback, return_slot_t) + [[nodiscard]] inline Slot SignalSubscriber::call(_Function&& callback, return_slot_t) { assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function diff --git a/include/sdbus-c++/IConnection.h b/include/sdbus-c++/IConnection.h index c3c8a0c3..76e63519 100644 --- a/include/sdbus-c++/IConnection.h +++ b/include/sdbus-c++/IConnection.h @@ -82,7 +82,7 @@ namespace sdbus { * * @throws sdbus::Error in case of failure */ - virtual std::string getUniqueName() const = 0; + [[nodiscard]] virtual std::string getUniqueName() const = 0; /*! * @brief Enters I/O event loop on this bus connection @@ -225,7 +225,7 @@ namespace sdbus { * * @return Currently processed D-Bus message */ - virtual Message getCurrentlyProcessedMessage() const = 0; + [[nodiscard]] virtual Message getCurrentlyProcessedMessage() const = 0; /*! * @brief Sets general method call timeout @@ -256,7 +256,7 @@ namespace sdbus { * * @throws sdbus::Error in case of failure */ - virtual uint64_t getMethodCallTimeout() const = 0; + [[nodiscard]] virtual uint64_t getMethodCallTimeout() const = 0; /*! * @brief Adds an ObjectManager at the specified D-Bus object path diff --git a/include/sdbus-c++/IObject.h b/include/sdbus-c++/IObject.h index 187b35a3..53fdda03 100644 --- a/include/sdbus-c++/IObject.h +++ b/include/sdbus-c++/IObject.h @@ -140,7 +140,7 @@ namespace sdbus { * * @throws sdbus::Error in case of failure */ - virtual Slot addVTable(std::string interfaceName, std::vector vtable, return_slot_t) = 0; + [[nodiscard]] virtual Slot addVTable(std::string interfaceName, std::vector vtable, return_slot_t) = 0; /*! * @brief A little more convenient overload of addVTable() above @@ -187,7 +187,7 @@ namespace sdbus { * * @throws sdbus::Error in case of failure */ - virtual Signal createSignal(const std::string& interfaceName, const std::string& signalName) = 0; + [[nodiscard]] virtual Signal createSignal(const std::string& interfaceName, const std::string& signalName) = 0; /*! * @brief Emits signal for this object path @@ -288,14 +288,14 @@ namespace sdbus { * @brief Tests whether ObjectManager interface is added at the path of this D-Bus object * @return True if ObjectManager interface is there, false otherwise */ - virtual bool hasObjectManager() const = 0; + [[nodiscard]] virtual bool hasObjectManager() const = 0; /*! * @brief Provides D-Bus connection used by the object * * @return Reference to the D-Bus connection */ - virtual sdbus::IConnection& getConnection() const = 0; + [[nodiscard]] virtual sdbus::IConnection& getConnection() const = 0; /*! * @brief Emits signal on D-Bus @@ -321,7 +321,7 @@ namespace sdbus { /*! * @brief Returns object path of the underlying DBus object */ - virtual const std::string& getObjectPath() const = 0; + [[nodiscard]] virtual const std::string& getObjectPath() const = 0; /*! * @brief Provides access to the currently processed D-Bus message @@ -336,7 +336,7 @@ namespace sdbus { * * @return Currently processed D-Bus message */ - virtual Message getCurrentlyProcessedMessage() const = 0; + [[nodiscard]] virtual Message getCurrentlyProcessedMessage() const = 0; }; // Out-of-line member definitions diff --git a/include/sdbus-c++/IProxy.h b/include/sdbus-c++/IProxy.h index 90a8ed03..c9312c7f 100644 --- a/include/sdbus-c++/IProxy.h +++ b/include/sdbus-c++/IProxy.h @@ -80,7 +80,7 @@ namespace sdbus { * * @throws sdbus::Error in case of failure */ - virtual MethodCall createMethodCall(const std::string& interfaceName, const std::string& methodName) = 0; + [[nodiscard]] virtual MethodCall createMethodCall(const std::string& interfaceName, const std::string& methodName) = 0; /*! * @brief Calls method on the remote D-Bus object @@ -107,7 +107,7 @@ namespace sdbus { * * Note: To avoid messing with messages, use API on a higher level of abstraction defined below. * - * @throws sdbus::Error in case of failure + * @throws sdbus::Error in case of failure (also in case the remote function returned an error) */ virtual MethodReply callMethod(const MethodCall& message, uint64_t timeout = 0) = 0; @@ -426,12 +426,12 @@ namespace sdbus { * * @return Reference to the D-Bus connection */ - virtual sdbus::IConnection& getConnection() const = 0; + [[nodiscard]] virtual sdbus::IConnection& getConnection() const = 0; /*! * @brief Returns object path of the underlying DBus object */ - virtual const std::string& getObjectPath() const = 0; + [[nodiscard]] virtual const std::string& getObjectPath() const = 0; /*! * @brief Provides access to the currently processed D-Bus message @@ -446,7 +446,7 @@ namespace sdbus { * * @return Currently processed D-Bus message */ - virtual Message getCurrentlyProcessedMessage() const = 0; + [[nodiscard]] virtual Message getCurrentlyProcessedMessage() const = 0; }; /********************************************//** diff --git a/src/IConnection.h b/src/IConnection.h index 5145f3c0..6c21d863 100644 --- a/src/IConnection.h +++ b/src/IConnection.h @@ -73,7 +73,7 @@ namespace sdbus::internal { virtual MethodReply callMethod(const MethodCall& message, uint64_t timeout) = 0; virtual void callMethod(const MethodCall& message, void* callback, void* userData, uint64_t timeout, floating_slot_t) = 0; - virtual Slot callMethod(const MethodCall& message, void* callback, void* userData, uint64_t timeout) = 0; + [[nodiscard]] virtual Slot callMethod(const MethodCall& message, void* callback, void* userData, uint64_t timeout) = 0; virtual void emitPropertiesChangedSignal( const std::string& objectPath , const std::string& interfaceName From f3c2137ff325d6fede7885543ea7dca237cf002d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Mon, 8 Jan 2024 09:28:19 +0100 Subject: [PATCH 22/52] refactor: add Async suffix to async callMethod functions (#394) This makes naming consistent, so all asynchronously working functions have Async suffix --- include/sdbus-c++/ConvenienceApiClasses.inl | 2 +- include/sdbus-c++/IProxy.h | 34 +++++++++++++-------- src/Proxy.cpp | 10 +++--- src/Proxy.h | 6 ++-- tests/integrationtests/TestProxy.cpp | 2 +- 5 files changed, 31 insertions(+), 23 deletions(-) diff --git a/include/sdbus-c++/ConvenienceApiClasses.inl b/include/sdbus-c++/ConvenienceApiClasses.inl index 5ee2258d..6125a770 100644 --- a/include/sdbus-c++/ConvenienceApiClasses.inl +++ b/include/sdbus-c++/ConvenienceApiClasses.inl @@ -257,7 +257,7 @@ namespace sdbus { sdbus::apply(callback, error, args); }; - return proxy_.callMethod(method_, std::move(asyncReplyHandler), timeout_); + return proxy_.callMethodAsync(method_, std::move(asyncReplyHandler), timeout_); } template diff --git a/include/sdbus-c++/IProxy.h b/include/sdbus-c++/IProxy.h index c9312c7f..aabbf00e 100644 --- a/include/sdbus-c++/IProxy.h +++ b/include/sdbus-c++/IProxy.h @@ -135,13 +135,17 @@ namespace sdbus { * * @throws sdbus::Error in case of failure */ - virtual PendingAsyncCall callMethod(const MethodCall& message, async_reply_handler asyncReplyCallback, uint64_t timeout = 0) = 0; + virtual PendingAsyncCall callMethodAsync( const MethodCall& message + , async_reply_handler asyncReplyCallback + , uint64_t timeout = 0 ) = 0; /*! * @copydoc IProxy::callMethod(const MethodCall&,async_reply_handler,uint64_t) */ template - PendingAsyncCall callMethod(const MethodCall& message, async_reply_handler asyncReplyCallback, const std::chrono::duration<_Rep, _Period>& timeout); + PendingAsyncCall callMethodAsync( const MethodCall& message + , async_reply_handler asyncReplyCallback + , const std::chrono::duration<_Rep, _Period>& timeout ); /*! * @brief Calls method on the D-Bus object asynchronously @@ -160,7 +164,7 @@ namespace sdbus { * * @throws sdbus::Error in case of failure */ - virtual std::future callMethod(const MethodCall& message, with_future_t) = 0; + virtual std::future callMethodAsync(const MethodCall& message, with_future_t) = 0; /*! * @brief Calls method on the D-Bus object asynchronously, with custom timeout @@ -180,15 +184,17 @@ namespace sdbus { * * @throws sdbus::Error in case of failure */ - virtual std::future callMethod(const MethodCall& message, uint64_t timeout, with_future_t) = 0; + virtual std::future callMethodAsync( const MethodCall& message + , uint64_t timeout + , with_future_t ) = 0; /*! * @copydoc IProxy::callMethod(const MethodCall&,uint64_t,with_future_t) */ template - std::future callMethod( const MethodCall& message - , const std::chrono::duration<_Rep, _Period>& timeout - , with_future_t ); + std::future callMethodAsync( const MethodCall& message + , const std::chrono::duration<_Rep, _Period>& timeout + , with_future_t ); /*! * @brief Registers a handler for the desired signal emitted by the D-Bus object @@ -501,19 +507,21 @@ namespace sdbus { } template - inline PendingAsyncCall IProxy::callMethod(const MethodCall& message, async_reply_handler asyncReplyCallback, const std::chrono::duration<_Rep, _Period>& timeout) + inline PendingAsyncCall IProxy::callMethodAsync( const MethodCall& message + , async_reply_handler asyncReplyCallback + , const std::chrono::duration<_Rep, _Period>& timeout ) { auto microsecs = std::chrono::duration_cast(timeout); - return callMethod(message, std::move(asyncReplyCallback), microsecs.count()); + return callMethodAsync(message, std::move(asyncReplyCallback), microsecs.count()); } template - inline std::future IProxy::callMethod( const MethodCall& message - , const std::chrono::duration<_Rep, _Period>& timeout - , with_future_t ) + inline std::future IProxy::callMethodAsync( const MethodCall& message + , const std::chrono::duration<_Rep, _Period>& timeout + , with_future_t ) { auto microsecs = std::chrono::duration_cast(timeout); - return callMethod(message, microsecs.count(), with_future); + return callMethodAsync(message, microsecs.count(), with_future); } inline MethodInvoker IProxy::callMethod(const std::string& methodName) diff --git a/src/Proxy.cpp b/src/Proxy.cpp index 004888f1..d03f9da9 100644 --- a/src/Proxy.cpp +++ b/src/Proxy.cpp @@ -93,7 +93,7 @@ MethodReply Proxy::callMethod(const MethodCall& message, uint64_t timeout) return connection_->callMethod(message, timeout); } -PendingAsyncCall Proxy::callMethod(const MethodCall& message, async_reply_handler asyncReplyCallback, uint64_t timeout) +PendingAsyncCall Proxy::callMethodAsync(const MethodCall& message, async_reply_handler asyncReplyCallback, uint64_t timeout) { SDBUS_THROW_ERROR_IF(!message.isValid(), "Invalid async method call message provided", EINVAL); @@ -109,12 +109,12 @@ PendingAsyncCall Proxy::callMethod(const MethodCall& message, async_reply_handle return {weakData}; } -std::future Proxy::callMethod(const MethodCall& message, with_future_t) +std::future Proxy::callMethodAsync(const MethodCall& message, with_future_t) { - return Proxy::callMethod(message, {}, with_future); + return Proxy::callMethodAsync(message, {}, with_future); } -std::future Proxy::callMethod(const MethodCall& message, uint64_t timeout, with_future_t) +std::future Proxy::callMethodAsync(const MethodCall& message, uint64_t timeout, with_future_t) { auto promise = std::make_shared>(); auto future = promise->get_future(); @@ -127,7 +127,7 @@ std::future Proxy::callMethod(const MethodCall& message, uint64_t t promise->set_exception(std::make_exception_ptr(*error)); }; - (void)Proxy::callMethod(message, std::move(asyncReplyCallback), timeout); + (void)Proxy::callMethodAsync(message, std::move(asyncReplyCallback), timeout); return future; } diff --git a/src/Proxy.h b/src/Proxy.h index 4dabe9e9..01c917c3 100644 --- a/src/Proxy.h +++ b/src/Proxy.h @@ -55,9 +55,9 @@ namespace sdbus::internal { MethodCall createMethodCall(const std::string& interfaceName, const std::string& methodName) override; MethodReply callMethod(const MethodCall& message, uint64_t timeout) override; - PendingAsyncCall callMethod(const MethodCall& message, async_reply_handler asyncReplyCallback, uint64_t timeout) override; - std::future callMethod(const MethodCall& message, with_future_t) override; - std::future callMethod(const MethodCall& message, uint64_t timeout, with_future_t) override; + PendingAsyncCall callMethodAsync(const MethodCall& message, async_reply_handler asyncReplyCallback, uint64_t timeout) override; + std::future callMethodAsync(const MethodCall& message, with_future_t) override; + std::future callMethodAsync(const MethodCall& message, uint64_t timeout, with_future_t) override; void registerSignalHandler( const std::string& interfaceName , const std::string& signalName diff --git a/tests/integrationtests/TestProxy.cpp b/tests/integrationtests/TestProxy.cpp index f20cbc31..44cab9da 100644 --- a/tests/integrationtests/TestProxy.cpp +++ b/tests/integrationtests/TestProxy.cpp @@ -136,7 +136,7 @@ std::future TestProxy::doOperationClientSideAsyncOnBasicAPILevel(ui auto methodCall = getProxy().createMethodCall(sdbus::test::INTERFACE_NAME, "doOperation"); methodCall << param; - return getProxy().callMethod(methodCall, sdbus::with_future); + return getProxy().callMethodAsync(methodCall, sdbus::with_future); } void TestProxy::doErroneousOperationClientSideAsync() From 2fe7cfe5e51dfa60e48c86f01be4d7e1147435c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Tue, 9 Jan 2024 23:31:47 +0100 Subject: [PATCH 23/52] refactor: use correct header include order --- include/sdbus-c++/ConvenienceApiClasses.h | 11 ++++++----- include/sdbus-c++/ConvenienceApiClasses.inl | 11 ++++++----- include/sdbus-c++/IConnection.h | 5 +++-- include/sdbus-c++/IObject.h | 9 +++++---- include/sdbus-c++/IProxy.h | 7 ++++--- include/sdbus-c++/Message.h | 20 ++++++++++---------- include/sdbus-c++/MethodResult.h | 3 ++- include/sdbus-c++/TypeTraits.h | 17 ++++++++--------- include/sdbus-c++/Types.h | 5 +++-- include/sdbus-c++/VTableItems.h | 5 +++-- include/sdbus-c++/VTableItems.inl | 5 +++-- src/Connection.cpp | 16 +++++++++------- src/Connection.h | 13 ++++++++----- src/Error.cpp | 8 +++++--- src/IConnection.h | 12 +++++++----- src/Message.cpp | 15 +++++++++------ src/Object.cpp | 16 +++++++++------- src/Object.h | 14 ++++++++------ src/Proxy.cpp | 14 ++++++++------ src/Proxy.h | 12 +++++++----- src/ScopeGuard.h | 1 - src/Types.cpp | 16 +++++++++------- src/VTableUtils.h | 2 +- 23 files changed, 133 insertions(+), 104 deletions(-) diff --git a/include/sdbus-c++/ConvenienceApiClasses.h b/include/sdbus-c++/ConvenienceApiClasses.h index edaab716..ee4cbe75 100644 --- a/include/sdbus-c++/ConvenienceApiClasses.h +++ b/include/sdbus-c++/ConvenienceApiClasses.h @@ -27,16 +27,17 @@ #ifndef SDBUS_CXX_CONVENIENCEAPICLASSES_H_ #define SDBUS_CXX_CONVENIENCEAPICLASSES_H_ -#include #include #include #include -#include -#include -#include +#include + #include -#include #include +#include +#include +#include +#include // Forward declarations namespace sdbus { diff --git a/include/sdbus-c++/ConvenienceApiClasses.inl b/include/sdbus-c++/ConvenienceApiClasses.inl index 6125a770..46287b33 100644 --- a/include/sdbus-c++/ConvenienceApiClasses.inl +++ b/include/sdbus-c++/ConvenienceApiClasses.inl @@ -27,18 +27,19 @@ #ifndef SDBUS_CPP_CONVENIENCEAPICLASSES_INL_ #define SDBUS_CPP_CONVENIENCEAPICLASSES_INL_ +#include #include #include #include #include -#include #include -#include -#include +#include + +#include +#include #include #include -#include -#include +#include namespace sdbus { diff --git a/include/sdbus-c++/IConnection.h b/include/sdbus-c++/IConnection.h index 76e63519..fb82aad6 100644 --- a/include/sdbus-c++/IConnection.h +++ b/include/sdbus-c++/IConnection.h @@ -28,11 +28,12 @@ #define SDBUS_CXX_ICONNECTION_H_ #include -#include -#include + #include #include +#include #include +#include struct sd_bus; struct sd_event; diff --git a/include/sdbus-c++/IObject.h b/include/sdbus-c++/IObject.h index 53fdda03..6d19fdb4 100644 --- a/include/sdbus-c++/IObject.h +++ b/include/sdbus-c++/IObject.h @@ -27,13 +27,14 @@ #ifndef SDBUS_CXX_IOBJECT_H_ #define SDBUS_CXX_IOBJECT_H_ -#include #include -#include #include +#include +#include + #include -#include #include +#include #include // Forward declarations @@ -385,7 +386,7 @@ namespace sdbus { } -#include #include +#include #endif /* SDBUS_CXX_IOBJECT_H_ */ diff --git a/include/sdbus-c++/IProxy.h b/include/sdbus-c++/IProxy.h index aabbf00e..8f91cf42 100644 --- a/include/sdbus-c++/IProxy.h +++ b/include/sdbus-c++/IProxy.h @@ -29,11 +29,12 @@ #include #include -#include -#include -#include + #include +#include #include +#include +#include // Forward declarations namespace sdbus { diff --git a/include/sdbus-c++/Message.h b/include/sdbus-c++/Message.h index f1125158..743604c5 100644 --- a/include/sdbus-c++/Message.h +++ b/include/sdbus-c++/Message.h @@ -27,24 +27,24 @@ #ifndef SDBUS_CXX_MESSAGE_H_ #define SDBUS_CXX_MESSAGE_H_ -#include #include +#include + +#include #include -#include -#include -#include +#include +#include +#include +#include #if __cplusplus >= 202002L #include #endif -#include +#include +#include #include #include -#include -#include -#include -#include -#include #include +#include // Forward declarations namespace sdbus { diff --git a/include/sdbus-c++/MethodResult.h b/include/sdbus-c++/MethodResult.h index 2a6edaa3..d6f8540f 100644 --- a/include/sdbus-c++/MethodResult.h +++ b/include/sdbus-c++/MethodResult.h @@ -28,9 +28,10 @@ #define SDBUS_CXX_METHODRESULT_H_ #include + #include -// Forward declaration +// Forward declarations namespace sdbus { class Error; } diff --git a/include/sdbus-c++/TypeTraits.h b/include/sdbus-c++/TypeTraits.h index 1131384c..fd6faab2 100644 --- a/include/sdbus-c++/TypeTraits.h +++ b/include/sdbus-c++/TypeTraits.h @@ -27,21 +27,20 @@ #ifndef SDBUS_CXX_TYPETRAITS_H_ #define SDBUS_CXX_TYPETRAITS_H_ -#include -#include -#include #include -#include -#if __cplusplus >= 202002L -#include -#endif -#include -#include #include #include +#include #include +#if __cplusplus >= 202002L +#include +#endif +#include #include +#include +#include #include +#include // Forward declarations namespace sdbus { diff --git a/include/sdbus-c++/Types.h b/include/sdbus-c++/Types.h index 2ebb1467..e36d44c7 100644 --- a/include/sdbus-c++/Types.h +++ b/include/sdbus-c++/Types.h @@ -29,11 +29,12 @@ #include #include + +#include #include +#include #include #include -#include -#include #include namespace sdbus { diff --git a/include/sdbus-c++/VTableItems.h b/include/sdbus-c++/VTableItems.h index 726fd9f3..73a95e7d 100644 --- a/include/sdbus-c++/VTableItems.h +++ b/include/sdbus-c++/VTableItems.h @@ -26,11 +26,12 @@ #ifndef SDBUS_CXX_VTABLEITEMS_H_ #define SDBUS_CXX_VTABLEITEMS_H_ -#include #include +#include + #include -#include #include +#include namespace sdbus { diff --git a/include/sdbus-c++/VTableItems.inl b/include/sdbus-c++/VTableItems.inl index a989bacd..0d2f3ac5 100644 --- a/include/sdbus-c++/VTableItems.inl +++ b/include/sdbus-c++/VTableItems.inl @@ -26,10 +26,11 @@ #ifndef SDBUS_CPP_VTABLEITEMS_INL_ #define SDBUS_CPP_VTABLEITEMS_INL_ -#include #include -#include +#include + #include +#include #include namespace sdbus { diff --git a/src/Connection.cpp b/src/Connection.cpp index bc9b371e..bf8b85dd 100644 --- a/src/Connection.cpp +++ b/src/Connection.cpp @@ -25,20 +25,22 @@ */ #include "Connection.h" -#include "SdBus.h" + +#include "sdbus-c++/Error.h" +#include "sdbus-c++/Message.h" + #include "MessageUtils.h" -#include "Utils.h" -#include -#include #include "ScopeGuard.h" +#include "SdBus.h" +#include "Utils.h" + +#include +#include #include SDBUS_HEADER #ifndef SDBUS_basu // sd_event integration is not supported in basu-based sdbus-c++ #include #endif #include -#include -#include -#include namespace sdbus::internal { diff --git a/src/Connection.h b/src/Connection.h index 323028ea..778dcd23 100644 --- a/src/Connection.h +++ b/src/Connection.h @@ -27,15 +27,18 @@ #ifndef SDBUS_CXX_INTERNAL_CONNECTION_H_ #define SDBUS_CXX_INTERNAL_CONNECTION_H_ -#include -#include +#include "sdbus-c++/IConnection.h" + +#include "sdbus-c++/Message.h" + #include "IConnection.h" -#include "ScopeGuard.h" #include "ISdBus.h" -#include SDBUS_HEADER +#include "ScopeGuard.h" + #include -#include #include +#include SDBUS_HEADER +#include #include struct sd_event_source; diff --git a/src/Error.cpp b/src/Error.cpp index 210590c6..d4adfeb9 100644 --- a/src/Error.cpp +++ b/src/Error.cpp @@ -24,10 +24,12 @@ * along with sdbus-c++. If not, see . */ -#include -#include SDBUS_HEADER +#include "sdbus-c++/Error.h" + #include "ScopeGuard.h" +#include SDBUS_HEADER + namespace sdbus { sdbus::Error createError(int errNo, const std::string& customMsg) @@ -40,4 +42,4 @@ namespace sdbus std::string message(customMsg + " (" + sdbusError.message + ")"); return sdbus::Error(name, message); } -} +} // namespace sdbus diff --git a/src/IConnection.h b/src/IConnection.h index 6c21d863..380fd8a5 100644 --- a/src/IConnection.h +++ b/src/IConnection.h @@ -27,12 +27,14 @@ #ifndef SDBUS_CXX_INTERNAL_ICONNECTION_H_ #define SDBUS_CXX_INTERNAL_ICONNECTION_H_ -#include -#include -#include SDBUS_HEADER -#include -#include +#include "sdbus-c++/IConnection.h" + +#include "sdbus-c++/TypeTraits.h" + #include +#include +#include +#include SDBUS_HEADER #include // Forward declaration diff --git a/src/Message.cpp b/src/Message.cpp index 05e62269..3ac89ae3 100644 --- a/src/Message.cpp +++ b/src/Message.cpp @@ -24,15 +24,18 @@ * along with sdbus-c++. If not, see . */ -#include -#include -#include -#include "MessageUtils.h" -#include "ISdBus.h" +#include "sdbus-c++/Message.h" + +#include "sdbus-c++/Error.h" +#include "sdbus-c++/Types.h" + #include "IConnection.h" +#include "ISdBus.h" +#include "MessageUtils.h" #include "ScopeGuard.h" -#include SDBUS_HEADER + #include +#include SDBUS_HEADER namespace sdbus { diff --git a/src/Object.cpp b/src/Object.cpp index 06342724..6305f92e 100644 --- a/src/Object.cpp +++ b/src/Object.cpp @@ -25,19 +25,21 @@ */ #include "Object.h" + +#include "sdbus-c++/Error.h" +#include "sdbus-c++/Flags.h" +#include "sdbus-c++/IConnection.h" +#include "sdbus-c++/Message.h" + +#include "IConnection.h" #include "MessageUtils.h" -#include -#include -#include -#include -#include #include "ScopeGuard.h" -#include "IConnection.h" #include "Utils.h" #include "VTableUtils.h" + +#include #include SDBUS_HEADER #include -#include namespace sdbus::internal { diff --git a/src/Object.h b/src/Object.h index 91dea674..f36fed7a 100644 --- a/src/Object.h +++ b/src/Object.h @@ -27,15 +27,17 @@ #ifndef SDBUS_CXX_INTERNAL_OBJECT_H_ #define SDBUS_CXX_INTERNAL_OBJECT_H_ -#include +#include "sdbus-c++/IObject.h" + #include "IConnection.h" -#include SDBUS_HEADER -#include -#include -#include + +#include #include +#include #include -#include +#include +#include SDBUS_HEADER +#include namespace sdbus::internal { diff --git a/src/Proxy.cpp b/src/Proxy.cpp index d03f9da9..08c7948d 100644 --- a/src/Proxy.cpp +++ b/src/Proxy.cpp @@ -25,16 +25,18 @@ */ #include "Proxy.h" + +#include "sdbus-c++/Error.h" +#include "sdbus-c++/IConnection.h" +#include "sdbus-c++/Message.h" + #include "IConnection.h" #include "MessageUtils.h" -#include "Utils.h" -#include "sdbus-c++/Message.h" -#include "sdbus-c++/IConnection.h" -#include "sdbus-c++/Error.h" #include "ScopeGuard.h" -#include SDBUS_HEADER +#include "Utils.h" + #include -#include +#include SDBUS_HEADER #include namespace sdbus::internal { diff --git a/src/Proxy.h b/src/Proxy.h index 01c917c3..90dec123 100644 --- a/src/Proxy.h +++ b/src/Proxy.h @@ -27,14 +27,16 @@ #ifndef SDBUS_CXX_INTERNAL_PROXY_H_ #define SDBUS_CXX_INTERNAL_PROXY_H_ -#include +#include "sdbus-c++/IProxy.h" + #include "IConnection.h" -#include SDBUS_HEADER -#include -#include + #include -#include +#include #include +#include +#include SDBUS_HEADER +#include namespace sdbus::internal { diff --git a/src/ScopeGuard.h b/src/ScopeGuard.h index 07263172..d64a5edd 100644 --- a/src/ScopeGuard.h +++ b/src/ScopeGuard.h @@ -29,7 +29,6 @@ #include #include -#include // Straightforward, modern, easy-to-use RAII utility to perform work on scope exit in an exception-safe manner. // diff --git a/src/Types.cpp b/src/Types.cpp index cbb6ec67..81d27e8d 100644 --- a/src/Types.cpp +++ b/src/Types.cpp @@ -24,13 +24,15 @@ * along with sdbus-c++. If not, see . */ -#include -#include +#include "sdbus-c++/Types.h" + +#include "sdbus-c++/Error.h" + #include "MessageUtils.h" -#include SDBUS_HEADER -#include + #include #include +#include SDBUS_HEADER #include namespace sdbus { @@ -67,7 +69,7 @@ bool Variant::isEmpty() const return msg_.isEmpty(); } -void UnixFd::close() +void UnixFd::close() // NOLINT(readability-make-member-function-const) { if (fd_ >= 0) { @@ -82,7 +84,7 @@ int UnixFd::checkedDup(int fd) return fd; } - int ret = ::dup(fd); + int ret = ::dup(fd); // NOLINT(android-cloexec-dup) // TODO: verify if (ret < 0) { throw std::system_error(errno, std::generic_category(), "dup failed"); @@ -90,4 +92,4 @@ int UnixFd::checkedDup(int fd) return ret; } -} +} // namespace sdbus diff --git a/src/VTableUtils.h b/src/VTableUtils.h index 1cd4ada7..fd9356cd 100644 --- a/src/VTableUtils.h +++ b/src/VTableUtils.h @@ -27,8 +27,8 @@ #ifndef SDBUS_CXX_INTERNAL_VTABLEUTILS_H_ #define SDBUS_CXX_INTERNAL_VTABLEUTILS_H_ -#include SDBUS_HEADER #include +#include SDBUS_HEADER #ifdef __cplusplus extern "C" { From 7d68b4b7cb8dd8b42516a077702e47993beaafa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Wed, 10 Jan 2024 15:05:09 +0100 Subject: [PATCH 24/52] refactor: add nodiscard attribute for more functions --- include/sdbus-c++/Error.h | 6 +++--- include/sdbus-c++/Flags.h | 14 +++++++------- include/sdbus-c++/IConnection.h | 2 +- include/sdbus-c++/IProxy.h | 2 +- include/sdbus-c++/Types.h | 4 ++-- src/Connection.h | 28 ++++++++++++++-------------- src/IConnection.h | 20 ++++++++++---------- src/Object.h | 8 ++++---- src/Proxy.h | 6 +++--- 9 files changed, 45 insertions(+), 45 deletions(-) diff --git a/include/sdbus-c++/Error.h b/include/sdbus-c++/Error.h index c9d2d849..1bebd91d 100644 --- a/include/sdbus-c++/Error.h +++ b/include/sdbus-c++/Error.h @@ -55,17 +55,17 @@ namespace sdbus { { } - const std::string& getName() const + [[nodiscard]] const std::string& getName() const { return name_; } - const std::string& getMessage() const + [[nodiscard]] const std::string& getMessage() const { return message_; } - bool isValid() const + [[nodiscard]] bool isValid() const { return !getName().empty(); } diff --git a/include/sdbus-c++/Flags.h b/include/sdbus-c++/Flags.h index 8bad518c..d85a4aa5 100644 --- a/include/sdbus-c++/Flags.h +++ b/include/sdbus-c++/Flags.h @@ -74,21 +74,21 @@ namespace sdbus { flags_.set(flag, value); } - bool test(GeneralFlags flag) const + [[nodiscard]] bool test(GeneralFlags flag) const { return flags_.test(flag); } - bool test(PropertyUpdateBehaviorFlags flag) const + [[nodiscard]] bool test(PropertyUpdateBehaviorFlags flag) const { return flags_.test(flag); } - uint64_t toSdBusInterfaceFlags() const; - uint64_t toSdBusMethodFlags() const; - uint64_t toSdBusSignalFlags() const; - uint64_t toSdBusPropertyFlags() const; - uint64_t toSdBusWritablePropertyFlags() const; + [[nodiscard]] uint64_t toSdBusInterfaceFlags() const; + [[nodiscard]] uint64_t toSdBusMethodFlags() const; + [[nodiscard]] uint64_t toSdBusSignalFlags() const; + [[nodiscard]] uint64_t toSdBusPropertyFlags() const; + [[nodiscard]] uint64_t toSdBusWritablePropertyFlags() const; private: std::bitset flags_; diff --git a/include/sdbus-c++/IConnection.h b/include/sdbus-c++/IConnection.h index fb82aad6..5f579d35 100644 --- a/include/sdbus-c++/IConnection.h +++ b/include/sdbus-c++/IConnection.h @@ -381,7 +381,7 @@ namespace sdbus { * * @deprecated This function has been replaced by getEventLoopPollData() */ - [[deprecated("This function has been replaced by getEventLoopPollData()")]] PollData getProcessLoopPollData() const; + [[nodiscard]] [[deprecated("This function has been replaced by getEventLoopPollData()")]] PollData getProcessLoopPollData() const; /*! * @struct PollData diff --git a/include/sdbus-c++/IProxy.h b/include/sdbus-c++/IProxy.h index 8f91cf42..4998719a 100644 --- a/include/sdbus-c++/IProxy.h +++ b/include/sdbus-c++/IProxy.h @@ -488,7 +488,7 @@ namespace sdbus { * Pending call in this context means a call whose results have not arrived, or * have arrived and are currently being processed by the callback handler. */ - bool isPending() const; + [[nodiscard]] bool isPending() const; private: friend internal::Proxy; diff --git a/include/sdbus-c++/Types.h b/include/sdbus-c++/Types.h index e36d44c7..aeb15516 100644 --- a/include/sdbus-c++/Types.h +++ b/include/sdbus-c++/Types.h @@ -273,7 +273,7 @@ namespace sdbus { close(); } - int get() const + [[nodiscard]] int get() const { return fd_; } @@ -293,7 +293,7 @@ namespace sdbus { return std::exchange(fd_, -1); } - bool isValid() const + [[nodiscard]] bool isValid() const { return fd_ >= 0; } diff --git a/src/Connection.h b/src/Connection.h index 778dcd23..c9fd724b 100644 --- a/src/Connection.h +++ b/src/Connection.h @@ -83,11 +83,11 @@ namespace sdbus::internal { void requestName(const std::string& name) override; void releaseName(const std::string& name) override; - std::string getUniqueName() const override; + [[nodiscard]] std::string getUniqueName() const override; void enterEventLoop() override; void enterEventLoopAsync() override; void leaveEventLoop() override; - PollData getEventLoopPollData() const override; + [[nodiscard]] PollData getEventLoopPollData() const override; bool processPendingEvent() override; Message getCurrentlyProcessedMessage() const override; @@ -96,7 +96,7 @@ namespace sdbus::internal { Slot addObjectManager(const std::string& objectPath, return_slot_t) override; void setMethodCallTimeout(uint64_t timeout) override; - uint64_t getMethodCallTimeout() const override; + [[nodiscard]] uint64_t getMethodCallTimeout() const override; [[nodiscard]] Slot addMatch(const std::string& match, message_handler callback) override; void addMatch(const std::string& match, message_handler callback, floating_slot_t) override; @@ -107,22 +107,22 @@ namespace sdbus::internal { void detachSdEventLoop() override; sd_event *getSdEventLoop() override; - const ISdBus& getSdBusInterface() const override; - ISdBus& getSdBusInterface() override; + [[nodiscard]] const ISdBus& getSdBusInterface() const override; + [[nodiscard]] ISdBus& getSdBusInterface() override; Slot addObjectVTable( const std::string& objectPath , const std::string& interfaceName , const sd_bus_vtable* vtable , void* userData ) override; - PlainMessage createPlainMessage() 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; + [[nodiscard]] PlainMessage createPlainMessage() const override; + [[nodiscard]] MethodCall createMethodCall( const std::string& destination + , const std::string& objectPath + , const std::string& interfaceName + , const std::string& methodName ) const override; + [[nodiscard]] Signal createSignal( const std::string& objectPath + , const std::string& interfaceName + , const std::string& signalName ) const override; MethodReply callMethod(const MethodCall& message, uint64_t timeout) override; void callMethod(const MethodCall& message, void* callback, void* userData, uint64_t timeout, floating_slot_t) override; @@ -155,7 +155,7 @@ namespace sdbus::internal { void finishHandshake(sd_bus* bus); bool waitForNextEvent(); - bool arePendingMessagesInReadQueue() const; + [[nodiscard]] bool arePendingMessagesInReadQueue() const; void notifyEventLoopToExit(); void notifyEventLoopToWakeUpFromPoll(); diff --git a/src/IConnection.h b/src/IConnection.h index 380fd8a5..7730b73a 100644 --- a/src/IConnection.h +++ b/src/IConnection.h @@ -56,22 +56,22 @@ namespace sdbus::internal { public: ~IConnection() override = default; - virtual const ISdBus& getSdBusInterface() const = 0; - virtual ISdBus& getSdBusInterface() = 0; + [[nodiscard]] virtual const ISdBus& getSdBusInterface() const = 0; + [[nodiscard]] virtual ISdBus& getSdBusInterface() = 0; [[nodiscard]] virtual Slot addObjectVTable( const std::string& objectPath , const std::string& interfaceName , const sd_bus_vtable* vtable , void* userData ) = 0; - virtual PlainMessage createPlainMessage() const = 0; - virtual MethodCall createMethodCall( const std::string& destination - , const std::string& objectPath - , const std::string& interfaceName - , const std::string& methodName ) const = 0; - virtual Signal createSignal( const std::string& objectPath - , const std::string& interfaceName - , const std::string& signalName ) const = 0; + [[nodiscard]] virtual PlainMessage createPlainMessage() const = 0; + [[nodiscard]] virtual MethodCall createMethodCall( const std::string& destination + , const std::string& objectPath + , const std::string& interfaceName + , const std::string& methodName ) const = 0; + [[nodiscard]] virtual Signal createSignal( const std::string& objectPath + , const std::string& interfaceName + , const std::string& signalName ) const = 0; virtual MethodReply callMethod(const MethodCall& message, uint64_t timeout) = 0; virtual void callMethod(const MethodCall& message, void* callback, void* userData, uint64_t timeout, floating_slot_t) = 0; diff --git a/src/Object.h b/src/Object.h index f36fed7a..07dfd16b 100644 --- a/src/Object.h +++ b/src/Object.h @@ -62,11 +62,11 @@ namespace sdbus::internal { void addObjectManager() override; void removeObjectManager() override; - bool hasObjectManager() const override; + [[nodiscard]] bool hasObjectManager() const override; - sdbus::IConnection& getConnection() const override; - const std::string& getObjectPath() const override; - Message getCurrentlyProcessedMessage() const override; + [[nodiscard]] sdbus::IConnection& getConnection() const override; + [[nodiscard]] const std::string& getObjectPath() const override; + [[nodiscard]] Message getCurrentlyProcessedMessage() const override; private: // A vtable record comprising methods, signals, properties, flags. diff --git a/src/Proxy.h b/src/Proxy.h index 90dec123..714ef758 100644 --- a/src/Proxy.h +++ b/src/Proxy.h @@ -70,9 +70,9 @@ namespace sdbus::internal { , return_slot_t ) override; void unregister() override; - sdbus::IConnection& getConnection() const override; - const std::string& getObjectPath() const override; - Message getCurrentlyProcessedMessage() const override; + [[nodiscard]] sdbus::IConnection& getConnection() const override; + [[nodiscard]] const std::string& getObjectPath() const override; + [[nodiscard]] Message getCurrentlyProcessedMessage() const override; private: static int sdbus_signal_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError); From 2bd25e0942a06854814687b14b56ddbc56ba9a0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Wed, 10 Jan 2024 15:43:37 +0100 Subject: [PATCH 25/52] refactor: use optional for passing potential call errors (#396) This switches from a raw pointer to std::optional type to pass prospective call errors to the client (using std::optional was not possible years back when sdbus-c++ was based on C++14). This makes the API a little clearer, safer, idiomatically more expressive, and removes potential confusion associated with raw pointers (like ownership, lifetime questions, etc.). --- docs/using-sdbus-c++.md | 40 +++++++++---------- include/sdbus-c++/ConvenienceApiClasses.inl | 30 +++++++------- include/sdbus-c++/IProxy.h | 4 +- include/sdbus-c++/TypeTraits.h | 21 +++++----- src/Proxy.cpp | 10 ++--- .../DBusAsyncMethodsTests.cpp | 26 ++++++------ .../DBusStandardInterfacesTests.cpp | 18 ++++----- tests/integrationtests/TestProxy.cpp | 16 ++++---- tests/integrationtests/TestProxy.h | 8 ++-- tests/stresstests/concatenator-proxy.h | 4 +- tests/stresstests/sdbus-c++-stress-tests.cpp | 4 +- tools/xml2cpp-codegen/ProxyGenerator.cpp | 18 ++++----- 12 files changed, 101 insertions(+), 98 deletions(-) diff --git a/docs/using-sdbus-c++.md b/docs/using-sdbus-c++.md index 975907d7..a950b667 100644 --- a/docs/using-sdbus-c++.md +++ b/docs/using-sdbus-c++.md @@ -167,10 +167,10 @@ All public types and functions of sdbus-c++ reside in the `sdbus` namespace. Error signalling and propagation -------------------------------- -`sdbus::Error` exception is used to signal errors in sdbus-c++. There are two types of errors: +`sdbus::Error` type is used as an exception to signal errors in sdbus-c++. There are two types of errors: * 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. + * user-defined errors, i.e. errors signalled and propagated from remote methods back to the caller. So these are issued by sdbus-c++ clients. `sdbus::Error` is a carrier for both types of errors, carrying the error name and error message with it. @@ -605,15 +605,15 @@ When registering methods, calling methods or emitting signals, multiple lines of We recommend that sdbus-c++ users prefer the convenience API to the lower level, basic API. When feasible, using generated adaptor and proxy C++ bindings is even better as it provides yet slightly higher abstraction built on top of the convenience API, where remote calls look simply like local, native calls of object methods. They are described in the following section. -> **_Note_:** By default, signal callback handlers are not invoked (i.e., the signal is silently dropped) if there is a signal signature mismatch. If you want to be informed of such situations, they can prepend `const sdbus::Error*` parameter to their signal callback handler's parameter list. This argument will be `nullptr` in normal cases, and will provide access to the corresponding `sdbus::Error` object in case of deserialization failures. An example of a handler with the signature (`int`) different from the real signal contents (`string`): +> **_Note_:** By default, signal callback handlers are not invoked (i.e., the signal is silently dropped) if there is a signal signature mismatch. If you want to be informed of such situations, you can add `std::optional` parameter to the beginning of your signal callback handler's parameter list. When sdbus-c++ invokes the handler, it will set this argument either to be empty (in normal cases), or to carry a corresponding `sdbus::Error` object (in case of deserialization failures, like type mismatches). An example of a handler with the signature (`int`) different from the real signal contents (`string`): > ```c++ -> void onConcatenated(const sdbus::Error* e, int wrongParameter) +> void onConcatenated(std::optional e, int wrongParameter) > { -> assert(e); +> assert(e.has_value()); > assert(e->getMessage() == "Failed to deserialize a int32 value"); > } > ``` -> Signature mismatch in signal handlers is probably the most common reason why signals are not received in the client, while we can see them on the bus with `dbus-monitor`. Use `const sdbus::Error*`-based callback variant and inspect the error to check if that's the cause of such problems. +> Signature mismatch in signal handlers is probably the most common reason why signals are not received in the client, while we can see them on the bus with `dbus-monitor`. Use `std::optional`-based callback variant and inspect the error to check if that's the cause of your problems. > **_Tip_:** When registering a D-Bus object, we can additionally provide names of input and output parameters of its methods and names of parameters of its signals. When the object is introspected, these names are listed in the resulting introspection XML, which improves the description of object's interfaces: > ```c++ @@ -1127,15 +1127,15 @@ int main(int argc, char *argv[]) { /* ... */ - auto callback = [](MethodReply reply, const sdbus::Error* error) + auto callback = [](MethodReply reply, std::optional error) { - if (error == nullptr) // No error + if (!error) // No error { std::string result; reply >> result; std::cout << "Got concatenate result: " << result << std::endl; } - else // We got a D-Bus error... + else // We've got a D-Bus error... { std::cerr << "Got concatenate error " << error->getName() << " with message " << error->getMessage() << std::endl; } @@ -1163,7 +1163,7 @@ int main(int argc, char *argv[]) } ``` -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. +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. Empty `error` optional argument means that no D-Bus error occurred while making the call, and the reply message contains a valid reply. A non-empty `error` argument means that an error occurred during the call, and we can access the error name and message from the `Error` value inside the argument. There is also an overload of this `IProxy::callMethod()` function taking method call timeout argument. @@ -1192,16 +1192,16 @@ Another option is to use `std::future`-based overload of the `IProxy::callMethod ### Convenience API -On the convenience API level, the call statement starts with `callMethodAsync()`, and one option is to finish the statement 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: +On the convenience API level, the call statement starts with `callMethodAsync()`, and one option is to finish the statement with `uponReplyInvoke()` that takes a callback handler. The callback is a void-returning function that takes at least one argument: `std::optional`. 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) + auto callback = [](std::optional error, const std::string& concatenatedString) { - if (error == nullptr) // No error + if (!error) // 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; @@ -1225,7 +1225,7 @@ int main(int argc, char *argv[]) } ``` -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. +Empty `error` parameter means that no D-Bus error occurred while making the call, and subsequent arguments are valid D-Bus method return values. However, `error` parameter containing a value means that an error occurred during the call (and subsequent arguments are simply default-constructed), and the underlying `Error` instance provides us with the error name and message. Another option is to finish the async call statement with `getResultAsFuture()`, which is a template function which takes the list of types returned by the D-Bus method (empty list in case of `void`-returning method) which returns a `std::future` object, which will later, when the reply arrives, be set to contain the return value(s). Or if the call returns an error, `sdbus::Error` will be thrown by `std::future::get()`. @@ -1274,7 +1274,7 @@ An asynchronous method can be generated as a callback-based method or `std::futu For each client-side async method, a corresponding `onReply` pure virtual function, where `` is the 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 tool 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`. +So in the specific example above, the tool 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(std::optional error, const std::string& concatenatedString);` method, which we shall override in the derived `ConcatenatorProxy`. #### Generating std:future-based async methods @@ -1324,7 +1324,7 @@ Getting a property in asynchronous manner is also possible, in both callback-bas ```c++ // Callback-based method: -auto callback = [](const sdbus::Error* err, sdbus::Variant value) +auto callback = [](std::optional /*error*/, sdbus::Variant value) { std::cout << "Got property value: " << value.get() << std::endl; }; @@ -1335,7 +1335,7 @@ std::future statusFuture = object.getPropertyAsync("status").onI std::cout << "Got property value: " << statusFuture.get().get() << std::endl; ``` -More information on `error` callback handler parameter, on behavior of `future` in erroneous situations, can be found in section [Asynchronous client-side methods](#asynchronous-client-side-methods). +More information on an `error` callback handler parameter, on behavior of `future` in erroneous situations, can be found in section [Asynchronous client-side methods](#asynchronous-client-side-methods). #### Writing a property @@ -1350,7 +1350,7 @@ Setting a property in asynchronous manner is also possible, in both callback-bas ```c++ // Callback-based method: -auto callback = [](const sdbus::Error* err) { /*... Error handling in case err is non-null...*/ }; +auto callback = [](std::optional error { /*... Error handling in case error contains a value...*/ }; uint32_t status = proxy->setPropertyAsync("status").onInterface("org.sdbuscpp.Concatenator").toValue(status).uponReplyInvoke(std::move(callback)); // Future-based method: std::future statusFuture = object.setPropertyAsync("status").onInterface("org.sdbuscpp.Concatenator").getResultAsFuture(); @@ -1473,13 +1473,13 @@ class PropertyProvider_proxy { /*...*/ - virtual void onStatusPropertyGetReply(const uint32_t& value, const sdbus::Error* error) = 0; + virtual void onStatusPropertyGetReply(const uint32_t& value, std::optional error) = 0; public: // getting the property value sdbus::PendingAsyncCall status() { - return object_->getPropertyAsync("status").onInterface(INTERFACE_NAME).uponReplyInvoke([this](const sdbus::Error* error, const sdbus::Variant& value){ this->onActionPropertyGetReply(value.get(), error); }); + return object_->getPropertyAsync("status").onInterface(INTERFACE_NAME).uponReplyInvoke([this](std::optional error, const sdbus::Variant& value){ this->onStatusPropertyGetReply(value.get(), std::move(error)); }); } // setting the property value diff --git a/include/sdbus-c++/ConvenienceApiClasses.inl b/include/sdbus-c++/ConvenienceApiClasses.inl index 46287b33..fbc21094 100644 --- a/include/sdbus-c++/ConvenienceApiClasses.inl +++ b/include/sdbus-c++/ConvenienceApiClasses.inl @@ -232,14 +232,14 @@ namespace sdbus { { assert(method_.isValid()); // onInterface() must be placed/called prior to this function - auto asyncReplyHandler = [callback = std::forward<_Function>(callback)](MethodReply reply, const Error* error) + auto asyncReplyHandler = [callback = std::forward<_Function>(callback)](MethodReply reply, std::optional 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) + if (!error) { try { @@ -249,13 +249,13 @@ namespace sdbus { { // Pass message deserialization exceptions to the client via callback error parameter, // instead of propagating them up the message loop call stack. - sdbus::apply(callback, &e, args); + sdbus::apply(callback, e, args); return; } } // Invoke callback with input arguments from the tuple. - sdbus::apply(callback, error, args); + sdbus::apply(callback, std::move(error), args); }; return proxy_.callMethodAsync(method_, std::move(asyncReplyHandler), timeout_); @@ -267,15 +267,15 @@ namespace sdbus { auto promise = std::make_shared>>(); auto future = promise->get_future(); - uponReplyInvoke([promise = std::move(promise)](const Error* error, _Args... args) + uponReplyInvoke([promise = std::move(promise)](std::optional error, _Args... args) { - if (error == nullptr) + if (!error) if constexpr (!std::is_void_v>) promise->set_value({std::move(args)...}); else promise->set_value(); else - promise->set_exception(std::make_exception_ptr(*error)); + promise->set_exception(std::make_exception_ptr(*std::move(error))); }); // Will be std::future for no D-Bus method return value @@ -331,10 +331,10 @@ namespace sdbus { // as a storage for the argument values deserialized from the signal message. tuple_of_function_input_arg_types_t<_Function> signalArgs; - // The signal handler can take pure signal parameters only, or an additional `const Error*` as its first + // The signal handler can take pure signal parameters only, or an additional `std::optional` as its first // parameter. In the former case, if the deserialization fails (e.g. due to signature mismatch), // the failure is ignored (and signal simply dropped). In the latter case, the deserialization failure - // will be communicated as a non-zero Error pointer to the client's signal handler. + // will be communicated to the client's signal handler as a valid Error object inside the std::optional parameter. if constexpr (has_error_param_v<_Function>) { // Deserialize input arguments from the signal message into the tuple @@ -346,12 +346,12 @@ namespace sdbus { { // Pass message deserialization exceptions to the client via callback error parameter, // instead of propagating them up the message loop call stack. - sdbus::apply(callback, &e, signalArgs); + sdbus::apply(callback, e, signalArgs); return; } // Invoke callback with no error and input arguments from the tuple. - sdbus::apply(callback, nullptr, signalArgs); + sdbus::apply(callback, {}, signalArgs); } else { @@ -404,7 +404,7 @@ namespace sdbus { template PendingAsyncCall AsyncPropertyGetter::uponReplyInvoke(_Function&& callback) { - static_assert(std::is_invocable_r_v, "Property get callback function must accept Error* and property value as Variant"); + static_assert(std::is_invocable_r_v, Variant>, "Property get callback function must accept std::optional and property value as Variant"); assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function @@ -505,7 +505,7 @@ namespace sdbus { template PendingAsyncCall AsyncPropertySetter::uponReplyInvoke(_Function&& callback) { - static_assert(std::is_invocable_r_v, "Property set callback function must accept Error* only"); + static_assert(std::is_invocable_r_v>, "Property set callback function must accept std::optional only"); assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function @@ -563,8 +563,8 @@ namespace sdbus { template PendingAsyncCall AsyncAllPropertiesGetter::uponReplyInvoke(_Function&& callback) { - static_assert( std::is_invocable_r_v> - , "All properties get callback function must accept Error* and a map of property names to their values" ); + static_assert( std::is_invocable_r_v, std::map> + , "All properties get callback function must accept std::optional state = object.getPropertyAsync("state").onInterface("com.kistler.foo").getResultAsFuture(); - * auto callback = [](const sdbus::Error* err, const sdbus::Variant& value){ ... }; + * auto callback = [](std::optional err, const sdbus::Variant& value){ ... }; * object.getPropertyAsync("state").onInterface("com.kistler.foo").uponReplyInvoke(std::move(callback)); * @endcode * @@ -420,7 +420,7 @@ namespace sdbus { * * Example of use: * @code - * auto callback = [](const sdbus::Error* err, const std::map>& properties){ ... }; + * auto callback = [](std::optional err, const std::map>& properties){ ... }; * auto props = object.getAllPropertiesAsync().onInterface("com.kistler.foo").uponReplyInvoke(std::move(callback)); * @endcode * diff --git a/include/sdbus-c++/TypeTraits.h b/include/sdbus-c++/TypeTraits.h index fd6faab2..e7bc59a3 100644 --- a/include/sdbus-c++/TypeTraits.h +++ b/include/sdbus-c++/TypeTraits.h @@ -27,11 +27,14 @@ #ifndef SDBUS_CXX_TYPETRAITS_H_ #define SDBUS_CXX_TYPETRAITS_H_ +#include + #include #include #include #include #include +#include #if __cplusplus >= 202002L #include #endif @@ -63,7 +66,7 @@ namespace sdbus { // Callbacks from sdbus-c++ using method_callback = std::function; - using async_reply_handler = std::function; + using async_reply_handler = std::function error)>; using signal_handler = std::function; using message_handler = std::function; using property_set_callback = std::function; @@ -502,7 +505,7 @@ namespace sdbus { }; template - struct function_traits + struct function_traits, _Args...)> : public function_traits_base { static constexpr bool has_error_param = true; @@ -677,12 +680,12 @@ namespace sdbus { } template - constexpr decltype(auto) apply_impl( _Function&& f - , const Error* e - , _Tuple&& t - , std::index_sequence<_I...> ) + decltype(auto) apply_impl( _Function&& f + , std::optional e + , _Tuple&& t + , std::index_sequence<_I...> ) { - return std::forward<_Function>(f)(e, std::get<_I>(std::forward<_Tuple>(t))...); + return std::forward<_Function>(f)(std::move(e), std::get<_I>(std::forward<_Tuple>(t))...); } // For non-void returning functions, apply_impl simply returns function return value (a tuple of values). @@ -723,10 +726,10 @@ namespace sdbus { // Convert tuple `t' of values into a list of arguments // and invoke function `f' with those arguments. template - constexpr decltype(auto) apply(_Function&& f, const Error* e, _Tuple&& t) + decltype(auto) apply(_Function&& f, std::optional e, _Tuple&& t) { return detail::apply_impl( std::forward<_Function>(f) - , e + , std::move(e) , std::forward<_Tuple>(t) , std::make_index_sequence>::value>{} ); } diff --git a/src/Proxy.cpp b/src/Proxy.cpp index 08c7948d..d343608c 100644 --- a/src/Proxy.cpp +++ b/src/Proxy.cpp @@ -121,12 +121,12 @@ std::future Proxy::callMethodAsync(const MethodCall& message, uint6 auto promise = std::make_shared>(); auto future = promise->get_future(); - async_reply_handler asyncReplyCallback = [promise = std::move(promise)](MethodReply reply, const Error* error) noexcept + async_reply_handler asyncReplyCallback = [promise = std::move(promise)](MethodReply reply, std::optional error) noexcept { - if (error == nullptr) + if (!error) promise->set_value(std::move(reply)); else - promise->set_exception(std::make_exception_ptr(*error)); + promise->set_exception(std::make_exception_ptr(*std::move(error))); }; (void)Proxy::callMethodAsync(message, std::move(asyncReplyCallback), timeout); @@ -207,12 +207,12 @@ int Proxy::sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userDat const auto* error = sd_bus_message_get_error(sdbusMessage); if (error == nullptr) { - asyncCallData->callback(std::move(message), nullptr); + asyncCallData->callback(std::move(message), {}); } else { Error exception(error->name, error->message); - asyncCallData->callback(std::move(message), &exception); + asyncCallData->callback(std::move(message), std::move(exception)); } }, retError); diff --git a/tests/integrationtests/DBusAsyncMethodsTests.cpp b/tests/integrationtests/DBusAsyncMethodsTests.cpp index 20401357..c13bfcc7 100644 --- a/tests/integrationtests/DBusAsyncMethodsTests.cpp +++ b/tests/integrationtests/DBusAsyncMethodsTests.cpp @@ -60,12 +60,12 @@ TYPED_TEST(AsyncSdbusTestObject, ThrowsTimeoutErrorWhenClientSideAsyncMethodTime { std::promise promise; auto future = promise.get_future(); - this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t res, const sdbus::Error* err) + this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t res, std::optional err) { - if (err == nullptr) + if (!err) promise.set_value(res); else - promise.set_exception(std::make_exception_ptr(*err)); + promise.set_exception(std::make_exception_ptr(*std::move(err))); }); start = std::chrono::steady_clock::now(); @@ -146,12 +146,12 @@ TYPED_TEST(AsyncSdbusTestObject, InvokesMethodAsynchronouslyOnClientSide) { std::promise promise; auto future = promise.get_future(); - this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t res, const sdbus::Error* err) + this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t res, std::optional err) { - if (err == nullptr) + if (!err) promise.set_value(res); else - promise.set_exception(std::make_exception_ptr(*err)); + promise.set_exception(std::make_exception_ptr(std::move(err))); }); this->m_proxy->doOperationClientSideAsync(100); @@ -179,7 +179,7 @@ TYPED_TEST(AsyncSdbusTestObject, InvokesMethodAsynchronouslyOnClientSideWithFutu TYPED_TEST(AsyncSdbusTestObject, AnswersThatAsyncCallIsPendingIfItIsInProgress) { - this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t /*res*/, const sdbus::Error* /*err*/){}); + this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t /*res*/, std::optional /*err*/){}); auto call = this->m_proxy->doOperationClientSideAsync(100); @@ -190,7 +190,7 @@ TYPED_TEST(AsyncSdbusTestObject, CancelsPendingAsyncCallOnClientSide) { std::promise promise; auto future = promise.get_future(); - this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t /*res*/, const sdbus::Error* /*err*/){ promise.set_value(1); }); + this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t /*res*/, std::optional /*err*/){ promise.set_value(1); }); auto call = this->m_proxy->doOperationClientSideAsync(100); call.cancel(); @@ -202,7 +202,7 @@ TYPED_TEST(AsyncSdbusTestObject, AnswersThatAsyncCallIsNotPendingAfterItHasBeenC { std::promise promise; auto future = promise.get_future(); - this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t /*res*/, const sdbus::Error* /*err*/){ promise.set_value(1); }); + this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t /*res*/, std::optional /*err*/){ promise.set_value(1); }); auto call = this->m_proxy->doOperationClientSideAsync(100); call.cancel(); @@ -214,7 +214,7 @@ TYPED_TEST(AsyncSdbusTestObject, AnswersThatAsyncCallIsNotPendingAfterItHasBeenC { std::promise promise; auto future = promise.get_future(); - this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t /*res*/, const sdbus::Error* /*err*/){ promise.set_value(1); }); + this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t /*res*/, std::optional /*err*/){ promise.set_value(1); }); auto call = this->m_proxy->doOperationClientSideAsync(0); (void) future.get(); // Wait for the call to finish @@ -242,12 +242,12 @@ TYPED_TEST(AsyncSdbusTestObject, ReturnsNonnullErrorWhenAsynchronousMethodCallFa { std::promise promise; auto future = promise.get_future(); - this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t res, const sdbus::Error* err) + this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t res, std::optional err) { - if (err == nullptr) + if (!err) promise.set_value(res); else - promise.set_exception(std::make_exception_ptr(*err)); + promise.set_exception(std::make_exception_ptr(*std::move(err))); }); this->m_proxy->doErroneousOperationClientSideAsync(); diff --git a/tests/integrationtests/DBusStandardInterfacesTests.cpp b/tests/integrationtests/DBusStandardInterfacesTests.cpp index f9eaabc6..a745f7d5 100644 --- a/tests/integrationtests/DBusStandardInterfacesTests.cpp +++ b/tests/integrationtests/DBusStandardInterfacesTests.cpp @@ -82,12 +82,12 @@ TYPED_TEST(SdbusTestObject, GetsPropertyAsynchronouslyViaPropertiesInterface) std::promise promise; auto future = promise.get_future(); - this->m_proxy->GetAsync(INTERFACE_NAME, "state", [&](const sdbus::Error* err, sdbus::Variant value) + this->m_proxy->GetAsync(INTERFACE_NAME, "state", [&](std::optional err, sdbus::Variant value) { - if (err == nullptr) + if (!err) promise.set_value(value.get()); else - promise.set_exception(std::make_exception_ptr(*err)); + promise.set_exception(std::make_exception_ptr(*std::move(err))); }); ASSERT_THAT(future.get(), Eq(DEFAULT_STATE_VALUE)); @@ -115,12 +115,12 @@ TYPED_TEST(SdbusTestObject, SetsPropertyAsynchronouslyViaPropertiesInterface) std::promise promise; auto future = promise.get_future(); - this->m_proxy->SetAsync(INTERFACE_NAME, "action", sdbus::Variant{newActionValue}, [&](const sdbus::Error* err) + this->m_proxy->SetAsync(INTERFACE_NAME, "action", sdbus::Variant{newActionValue}, [&](std::optional err) { - if (err == nullptr) + if (!err) promise.set_value(); else - promise.set_exception(std::make_exception_ptr(*err)); + promise.set_exception(std::make_exception_ptr(*std::move(err))); }); ASSERT_NO_THROW(future.get()); @@ -152,12 +152,12 @@ TYPED_TEST(SdbusTestObject, GetsAllPropertiesAsynchronouslyViaPropertiesInterfac std::promise> promise; auto future = promise.get_future(); - this->m_proxy->GetAllAsync(INTERFACE_NAME, [&](const sdbus::Error* err, std::map value) + this->m_proxy->GetAllAsync(INTERFACE_NAME, [&](std::optional err, std::map value) { - if (err == nullptr) + if (!err) promise.set_value(std::move(value)); else - promise.set_exception(std::make_exception_ptr(*err)); + promise.set_exception(std::make_exception_ptr(*std::move(err))); }); const auto properties = future.get(); diff --git a/tests/integrationtests/TestProxy.cpp b/tests/integrationtests/TestProxy.cpp index 44cab9da..5089bdd8 100644 --- a/tests/integrationtests/TestProxy.cpp +++ b/tests/integrationtests/TestProxy.cpp @@ -85,7 +85,7 @@ void TestProxy::onSignalWithoutRegistration(const sdbus::Struct error) { if (m_DoOperationClientSideAsyncReplyHandler) m_DoOperationClientSideAsyncReplyHandler(returnValue, error); @@ -99,7 +99,7 @@ void TestProxy::onPropertiesChanged( const std::string& interfaceName m_onPropertiesChangedHandler(interfaceName, changedProperties, invalidatedProperties); } -void TestProxy::installDoOperationClientSideAsyncReplyHandler(std::function handler) +void TestProxy::installDoOperationClientSideAsyncReplyHandler(std::function err)> handler) { m_DoOperationClientSideAsyncReplyHandler = std::move(handler); } @@ -117,9 +117,9 @@ sdbus::PendingAsyncCall TestProxy::doOperationClientSideAsync(uint32_t param) return getProxy().callMethodAsync("doOperation") .onInterface(sdbus::test::INTERFACE_NAME) .withArguments(param) - .uponReplyInvoke([this](const sdbus::Error* error, uint32_t returnValue) + .uponReplyInvoke([this](std::optional error, uint32_t returnValue) { - this->onDoOperationReply(returnValue, error); + this->onDoOperationReply(returnValue, std::move(error)); }); } @@ -143,9 +143,9 @@ void TestProxy::doErroneousOperationClientSideAsync() { getProxy().callMethodAsync("throwError") .onInterface(sdbus::test::INTERFACE_NAME) - .uponReplyInvoke([this](const sdbus::Error* error) + .uponReplyInvoke([this](std::optional error) { - this->onDoOperationReply(0, error); + this->onDoOperationReply(0, std::move(error)); }); } @@ -163,9 +163,9 @@ void TestProxy::doOperationClientSideAsyncWithTimeout(const std::chrono::microse .onInterface(sdbus::test::INTERFACE_NAME) .withTimeout(timeout) .withArguments(param) - .uponReplyInvoke([this](const sdbus::Error* error, uint32_t returnValue) + .uponReplyInvoke([this](std::optional error, uint32_t returnValue) { - this->onDoOperationReply(returnValue, error); + this->onDoOperationReply(returnValue, std::move(error)); }); } diff --git a/tests/integrationtests/TestProxy.h b/tests/integrationtests/TestProxy.h index cebbce86..cf8a6273 100644 --- a/tests/integrationtests/TestProxy.h +++ b/tests/integrationtests/TestProxy.h @@ -85,7 +85,7 @@ class TestProxy final : public sdbus::ProxyInterfaces< org::sdbuscpp::integratio void onSignalWithVariant(const sdbus::Variant& aVariant) override; void onSignalWithoutRegistration(const sdbus::Struct>& s); - void onDoOperationReply(uint32_t returnValue, const sdbus::Error* error); + void onDoOperationReply(uint32_t returnValue, std::optional error); // Signals of standard D-Bus interfaces void onPropertiesChanged( const std::string& interfaceName @@ -93,7 +93,7 @@ class TestProxy final : public sdbus::ProxyInterfaces< org::sdbuscpp::integratio , const std::vector& invalidatedProperties ) override; public: - void installDoOperationClientSideAsyncReplyHandler(std::function handler); + void installDoOperationClientSideAsyncReplyHandler(std::function err)> handler); uint32_t doOperationWithTimeout(const std::chrono::microseconds &timeout, uint32_t param); sdbus::PendingAsyncCall doOperationClientSideAsync(uint32_t param); std::future doOperationClientSideAsync(uint32_t param, with_future_t); @@ -116,7 +116,7 @@ class TestProxy final : public sdbus::ProxyInterfaces< org::sdbuscpp::integratio std::atomic m_gotSignalWithSignature{false}; std::map m_signatureFromSignal; - std::function m_DoOperationClientSideAsyncReplyHandler; + std::function err)> m_DoOperationClientSideAsyncReplyHandler; std::function&, const std::vector&)> m_onPropertiesChangedHandler; std::unique_ptr m_signalMsg; @@ -140,7 +140,7 @@ class DummyTestProxy final : public sdbus::ProxyInterfaces< org::sdbuscpp::integ void onSignalWithVariant(const sdbus::Variant&) override {} void onSignalWithoutRegistration(const sdbus::Struct>&) {} - void onDoOperationReply(uint32_t, const sdbus::Error*) {} + void onDoOperationReply(uint32_t, std::optional) {} // Signals of standard D-Bus interfaces void onPropertiesChanged( const std::string&, const std::map&, const std::vector& ) override {} diff --git a/tests/stresstests/concatenator-proxy.h b/tests/stresstests/concatenator-proxy.h index 8aa14beb..2383dc73 100644 --- a/tests/stresstests/concatenator-proxy.h +++ b/tests/stresstests/concatenator-proxy.h @@ -39,12 +39,12 @@ class concatenator_proxy virtual void onConcatenatedSignal(const std::string& concatenatedString) = 0; - virtual void onConcatenateReply(const std::string& result, const sdbus::Error* error) = 0; + virtual void onConcatenateReply(const std::string& result, std::optional error) = 0; public: sdbus::PendingAsyncCall concatenate(const std::map& params) { - return proxy_->callMethodAsync("concatenate").onInterface(INTERFACE_NAME).withArguments(params).uponReplyInvoke([this](const sdbus::Error* error, const std::string& result){ this->onConcatenateReply(result, error); }); + return proxy_->callMethodAsync("concatenate").onInterface(INTERFACE_NAME).withArguments(params).uponReplyInvoke([this](std::optional error, const std::string& result){ this->onConcatenateReply(result, std::move(error)); }); } private: diff --git a/tests/stresstests/sdbus-c++-stress-tests.cpp b/tests/stresstests/sdbus-c++-stress-tests.cpp index 6742223e..e379cce8 100644 --- a/tests/stresstests/sdbus-c++-stress-tests.cpp +++ b/tests/stresstests/sdbus-c++-stress-tests.cpp @@ -310,9 +310,9 @@ class ConcatenatorProxy final : public sdbus::ProxyInterfaces error) override { - assert(error == nullptr); + assert(error == std::nullopt); std::stringstream str(result); std::string aString; diff --git a/tools/xml2cpp-codegen/ProxyGenerator.cpp b/tools/xml2cpp-codegen/ProxyGenerator.cpp index fec70d74..99225a7c 100644 --- a/tools/xml2cpp-codegen/ProxyGenerator.cpp +++ b/tools/xml2cpp-codegen/ProxyGenerator.cpp @@ -251,11 +251,11 @@ std::tuple ProxyGenerator::processMethods(const Nodes& } else // Async methods implemented through callbacks { - definitionSS << ".uponReplyInvoke([this](const sdbus::Error* error" << (outArgTypeStr.empty() ? "" : ", ") << outArgTypeStr << ")" - "{ this->on" << nameBigFirst << "Reply(" << outArgStr << (outArgStr.empty() ? "" : ", ") << "error); })"; + definitionSS << ".uponReplyInvoke([this](std::optional error" << (outArgTypeStr.empty() ? "" : ", ") << outArgTypeStr << ")" + "{ this->on" << nameBigFirst << "Reply(" << outArgStr << (outArgStr.empty() ? "" : ", ") << "std::move(error)); })"; asyncDeclarationSS << tab << "virtual void on" << nameBigFirst << "Reply(" - << outArgTypeStr << (outArgTypeStr.empty() ? "" : ", ") << "const sdbus::Error* error) = 0;" << endl; + << outArgTypeStr << (outArgTypeStr.empty() ? "" : ", ") << "std::optional error) = 0;" << endl; } } else if (outArgs.size() > 0) @@ -359,11 +359,11 @@ std::tuple ProxyGenerator::processProperties(const Nod } else // Async methods implemented through callbacks { - propertySS << ".uponReplyInvoke([this](const sdbus::Error* error, const sdbus::Variant& value)" - "{ this->on" << nameBigFirst << "PropertyGetReply(value.get<" << propertyType << ">(), error); })"; + propertySS << ".uponReplyInvoke([this](std::optional error, const sdbus::Variant& value)" + "{ this->on" << nameBigFirst << "PropertyGetReply(value.get<" << propertyType << ">(), std::move(error)); })"; asyncDeclarationSS << tab << "virtual void on" << nameBigFirst << "PropertyGetReply(" - << "const " << propertyType << "& value, const sdbus::Error* error) = 0;" << endl; + << "const " << propertyType << "& value, std::optional error) = 0;" << endl; } } propertySS << ";" << endl << tab << "}" << endl << endl; @@ -391,11 +391,11 @@ std::tuple ProxyGenerator::processProperties(const Nod } else // Async methods implemented through callbacks { - propertySS << ".uponReplyInvoke([this](const sdbus::Error* error)" - "{ this->on" << nameBigFirst << "PropertySetReply(error); })"; + propertySS << ".uponReplyInvoke([this](std::optional error)" + "{ this->on" << nameBigFirst << "PropertySetReply(std::move(error)); })"; asyncDeclarationSS << tab << "virtual void on" << nameBigFirst << "PropertySetReply(" - << "const sdbus::Error* error) = 0;" << endl; + << "std::optional error) = 0;" << endl; } } From 4c50f55fb00ed591f6ff7b54486cee12c8931f90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Sat, 10 Feb 2024 20:20:42 +0100 Subject: [PATCH 26/52] refactor: rename and re-organize CMake options (#401) This improves usability of sdbus-c++ in downstream CMake projects. CMake options now have `SDBUSCPP_` prefix so potential conflicts with downstream options/variables are minimized. Also, all configurable options and variables are placed in a single place in the root CMake file. --- .github/workflows/ci.yml | 8 +- CMakeLists.txt | 164 +++++++++++++++++--------- README.md | 28 ++--- cmake/LibsystemdExternalProject.cmake | 15 +-- docs/CMakeLists.txt | 18 +-- docs/using-sdbus-c++.md | 4 +- examples/CMakeLists.txt | 8 +- tests/CMakeLists.txt | 51 ++++---- tools/CMakeLists.txt | 41 ++++--- 9 files changed, 194 insertions(+), 143 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f325e10e..2fb3a1c4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,19 +63,19 @@ jobs: run: | mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_CXX_FLAGS="-O0 -g -W -Wextra -Wall -Wnon-virtual-dtor -Werror" -DCMAKE_VERBOSE_MAKEFILE=ON -DBUILD_TESTS=ON -DINSTALL_TESTS=ON -DENABLE_PERF_TESTS=ON -DENABLE_STRESS_TESTS=ON -DBUILD_CODE_GEN=ON .. + cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_CXX_FLAGS="-O0 -g -W -Wextra -Wall -Wnon-virtual-dtor -Werror" -DCMAKE_VERBOSE_MAKEFILE=ON -DSDBUSCPP_INSTALL=ON -DSDBUSCPP_BUILD_TESTS=ON -DSDBUSCPP_BUILD_PERF_TESTS=ON -DSDBUSCPP_BUILD_STRESS_TESTS=ON -DSDBUSCPP_BUILD_CODEGEN=ON .. - name: configure-release if: matrix.build == 'shared-libsystemd' && matrix.os == 'ubuntu-22.04' run: | mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_CXX_FLAGS="-O3 -DNDEBUG -W -Wextra -Wall -Wnon-virtual-dtor -Werror" -DCMAKE_VERBOSE_MAKEFILE=ON -DBUILD_TESTS=ON -DINSTALL_TESTS=ON -DENABLE_PERF_TESTS=ON -DENABLE_STRESS_TESTS=ON -DBUILD_CODE_GEN=ON .. + cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_CXX_FLAGS="-O3 -DNDEBUG -W -Wextra -Wall -Wnon-virtual-dtor -Werror" -DCMAKE_VERBOSE_MAKEFILE=ON -DSDBUSCPP_INSTALL=ON -DSDBUSCPP_BUILD_TESTS=ON -DSDBUSCPP_BUILD_PERF_TESTS=ON -DSDBUSCPP_BUILD_STRESS_TESTS=ON -DSDBUSCPP_BUILD_CODEGEN=ON .. - name: configure-with-embedded-libsystemd if: matrix.build == 'embedded-static-libsystemd' run: | mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_VERBOSE_MAKEFILE=ON -DBUILD_TESTS=ON -DINSTALL_TESTS=ON -DENABLE_PERF_TESTS=ON -DENABLE_STRESS_TESTS=ON -DBUILD_CODE_GEN=ON -DBUILD_LIBSYSTEMD=ON -DLIBSYSTEMD_VERSION=252 .. + cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_VERBOSE_MAKEFILE=ON -DSDBUSCPP_INSTALL=ON -DSDBUSCPP_BUILD_TESTS=ON -DSDBUSCPP_BUILD_PERF_TESTS=ON -DSDBUSCPP_BUILD_STRESS_TESTS=ON -DSDBUSCPP_BUILD_CODEGEN=ON -DSDBUSCPP_BUILD_LIBSYSTEMD=ON -DSDBUSCPP_LIBSYSTEMD_VERSION=252 .. - name: make run: | cd build @@ -112,7 +112,7 @@ jobs: prepare: | pkg install -y cmake ninja pkgconf basu expat googletest run: | - cmake -B _build -G Ninja -DBUILD_CODE_GEN=ON -DBUILD_TESTS=ON -DINSTALL_TESTS=ON -DENABLE_PERF_TESTS=ON -DENABLE_STRESS_TESTS=ON + cmake -B _build -G Ninja -DSDBUSCPP_INSTALL=ON -DSDBUSCPP_BUILD_CODEGEN=ON -DSDBUSCPP_BUILD_TESTS=ON -DSDBUSCPP_BUILD_PERF_TESTS=ON -DSDBUSCPP_BUILD_STRESS_TESTS=ON cmake --build _build cmake --install _build pkg install -y dbus diff --git a/CMakeLists.txt b/CMakeLists.txt index 05cf65bf..db2ea2f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,67 @@ project(sdbus-c++ VERSION 2.0.0 LANGUAGES CXX C) include(GNUInstallDirs) # Installation directories for `install` command and pkgconfig file +# ------------------------------- +# CONFIGURATION OPTIONS +# ------------------------------- + +option(SDBUSCPP_BUILD_LIBSYSTEMD "Fetch & build libsystemd static library and make it part of libsdbus-c++, instead of searching for libsystemd in the system" OFF) +if(SDBUSCPP_BUILD_LIBSYSTEMD) + set(SDBUSCPP_LIBSYSTEMD_VERSION "242" CACHE STRING "libsystemd version (>=239) to build and incorporate into libsdbus-c++") + set(SDBUSCPP_LIBSYSTEMD_EXTRA_CONFIG_OPTS "" CACHE STRING "Additional configuration options to be passed as-is to libsystemd build system") +endif() +option(SDBUSCPP_INSTALL "Enable installation of sdbus-c++ (downstream projects embedding sdbus-c++ may want to turn this OFF)" ON) #TODO: new, honor +# TODO: Continue here with search&replace old names with new names +option(SDBUSCPP_BUILD_TESTS "Build tests" OFF) +if (SDBUSCPP_BUILD_TESTS) + option(SDBUSCPP_BUILD_PERF_TESTS "Build also sdbus-c++ performance tests" OFF) # tranferred from tests/cmake + option(SDBUSCPP_BUILD_STRESS_TESTS "Build also sdbus-c++ stress tests" OFF) # tranferred from tests/cmake + set(SDBUSCPP_TESTS_INSTALL_PATH "tests/${PROJECT_NAME}" CACHE STRING "Specifies where the test binaries will be installed") # tranferred from tests/cmake + set(SDBUSCPP_GOOGLETEST_VERSION 1.10.0 CACHE STRING "Version of gmock library to use") # tranferred from tests/cmake + set(SDBUSCPP_GOOGLETEST_GIT_REPO "https://github.com/google/googletest.git" CACHE STRING "A git repo to clone and build googletest from if gmock is not found in the system") # tranferred from tests/cmake +endif() +# TODO: add more embedded options from sub=-cmakefiles +option(SDBUSCPP_BUILD_CODEGEN "Build generator tool for C++ native bindings" OFF) +option(SDBUSCPP_BUILD_EXAMPLES "Build example programs" OFF) +option(SDBUSCPP_BUILD_DOCS "Build documentation for sdbus-c++" ON) # TODO: renamed +if(SDBUSCPP_BUILD_DOCS) + option(SDBUSCPP_BUILD_DOXYGEN_DOCS "Build doxygen documentation for sdbus-c++ API" OFF) +endif() +#option(SDBUSCPP_CLANG_TIDY "Co-compile with clang-tidy static analyzer" OFF) +#option(SDBUSCPP_COVERAGE "Build sdbus-c++ with code coverage instrumentation" OFF) +# We promote the BUILD_SHARED_LIBS flag to a (global) option only if we are the main project +if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) + option(BUILD_SHARED_LIBS "Build shared libraries (.so) instead of static ones (.a)" ON) +endif() + +message(STATUS "") +message(STATUS " *******************************************") +message(STATUS " * SDBUS-C++ CONFIGURATION *") +message(STATUS " *******************************************") +message(STATUS "") +message(STATUS " SDBUSCPP_BUILD_LIBSYSTEMD: ${SDBUSCPP_BUILD_LIBSYSTEMD}") +if(SDBUSCPP_BUILD_LIBSYSTEMD) + message(STATUS " SDBUSCPP_LIBSYSTEMD_VERSION: ${SDBUSCPP_LIBSYSTEMD_VERSION}") + message(STATUS " SDBUSCPP_LIBSYSTEMD_EXTRA_CONFIG_OPTS: ${SDBUSCPP_LIBSYSTEMD_EXTRA_CONFIG_OPTS}") +endif() +message(STATUS " SDBUSCPP_INSTALL: ${SDBUSCPP_INSTALL}") +message(STATUS " SDBUSCPP_BUILD_TESTS: ${SDBUSCPP_BUILD_TESTS}") +if(SDBUSCPP_BUILD_TESTS) + message(STATUS " SDBUSCPP_BUILD_PERF_TESTS: ${SDBUSCPP_BUILD_PERF_TESTS}") + message(STATUS " SDBUSCPP_BUILD_STRESS_TESTS: ${SDBUSCPP_BUILD_STRESS_TESTS}") + message(STATUS " SDBUSCPP_TESTS_INSTALL_PATH: ${SDBUSCPP_TESTS_INSTALL_PATH}") + message(STATUS " SDBUSCPP_GOOGLETEST_VERSION: ${SDBUSCPP_GOOGLETEST_VERSION}") + message(STATUS " SDBUSCPP_GOOGLETEST_GIT_REPO: ${SDBUSCPP_GOOGLETEST_GIT_REPO}") +endif() +message(STATUS " SDBUSCPP_BUILD_CODEGEN: ${SDBUSCPP_BUILD_CODEGEN}") +message(STATUS " SDBUSCPP_BUILD_EXAMPLES: ${SDBUSCPP_BUILD_EXAMPLES}") +message(STATUS " SDBUSCPP_BUILD_DOCS: ${SDBUSCPP_BUILD_DOCS}") +if(SDBUSCPP_BUILD_DOCS) + message(STATUS " SDBUSCPP_BUILD_DOXYGEN_DOCS: ${SDBUSCPP_BUILD_DOXYGEN_DOCS}") +endif() +message(STATUS " BUILD_SHARED_LIBS: ${BUILD_SHARED_LIBS}") +message(STATUS "") + #------------------------------- # PERFORMING CHECKS & PREPARING THE DEPENDENCIES #------------------------------- @@ -15,9 +76,7 @@ include(GNUInstallDirs) # Installation directories for `install` command and pkg set(LIBSYSTEMD_IMPL "systemd") set(LIBSYSTEMD_LIB "libsystemd") -option(BUILD_LIBSYSTEMD "Build libsystemd static library and incorporate it into libsdbus-c++" OFF) - -if(NOT BUILD_LIBSYSTEMD) +if(NOT SDBUSCPP_BUILD_LIBSYSTEMD) find_package(PkgConfig REQUIRED) pkg_check_modules(Systemd IMPORTED_TARGET GLOBAL libsystemd>=238) if(NOT TARGET PkgConfig::Systemd) @@ -41,13 +100,13 @@ if(NOT BUILD_LIBSYSTEMD) message(FATAL_ERROR "libsystemd of version at least 238 is required, but was not found " "(if you have systemd in your OS, you may want to install package containing pkgconfig " " files for libsystemd library. On Ubuntu, that is libsystemd-dev. " - " Alternatively, you may turn BUILD_LIBSYSTEMD on for sdbus-c++ to download, build " - "and incorporate libsystemd as embedded library within sdbus-c++)") + " Alternatively, you may turn SDBUSCPP_BUILD_LIBSYSTEMD on for sdbus-c++ to download, " + " build and incorporate libsystemd as embedded library within sdbus-c++)") endif() add_library(Systemd::Libsystemd ALIAS PkgConfig::Systemd) string(REGEX MATCHALL "([0-9]+)" SYSTEMD_VERSION_LIST "${Systemd_VERSION}") - list(GET SYSTEMD_VERSION_LIST 0 LIBSYSTEMD_VERSION) - message(STATUS "Building with libsystemd v${LIBSYSTEMD_VERSION}") + list(GET SYSTEMD_VERSION_LIST 0 SDBUSCPP_LIBSYSTEMD_VERSION) + message(STATUS "Building with libsystemd v${SDBUSCPP_LIBSYSTEMD_VERSION}") else() # Build static libsystemd library as an external project include(cmake/LibsystemdExternalProject.cmake) @@ -120,16 +179,11 @@ set(CMAKE_CXX_STANDARD 17) set(SDBUSCPP_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}") set(SDBUSCPP_VERSION "${PROJECT_VERSION}") -# We promote the BUILD_SHARED_LIBS flag to a (global) option only if we are the main project -if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) - option(BUILD_SHARED_LIBS "Build shared libraries (.so) instead of static ones (.a)" ON) -endif() - # Having an object target allows unit tests to reuse already built sources without re-building add_library(sdbus-c++-objlib OBJECT ${SDBUSCPP_SRCS}) target_compile_definitions(sdbus-c++-objlib PRIVATE BUILD_LIB=1 - LIBSYSTEMD_VERSION=${LIBSYSTEMD_VERSION} + LIBSYSTEMD_VERSION=${SDBUSCPP_LIBSYSTEMD_VERSION} SDBUS_${LIBSYSTEMD_IMPL} SDBUS_HEADER=<${LIBSYSTEMD_IMPL}/sd-bus.h>) target_include_directories(sdbus-c++-objlib PUBLIC $ @@ -137,7 +191,7 @@ target_include_directories(sdbus-c++-objlib PUBLIC $= 236 is needed. (In case you have a non-systemd environment, don't worry, see [Solving libsystemd dependency](docs/using-sdbus-c++.md#solving-libsystemd-dependency) for more information.) * `googletest` - google unit testing framework, only necessary when building tests, will be downloaded and built automatically. * `pkgconfig` - required for sdbus-c++ to be able to find some dependency packages. -* `expat` - necessary when building xml2cpp code generator (`BUILD_CODE_GEN` option is ON). +* `expat` - necessary when building the xml2cpp binding code generator (`SDBUSCPP_BUILD_CODEGEN` option is ON). Licensing --------- diff --git a/cmake/LibsystemdExternalProject.cmake b/cmake/LibsystemdExternalProject.cmake index 40911c27..4e3eb902 100644 --- a/cmake/LibsystemdExternalProject.cmake +++ b/cmake/LibsystemdExternalProject.cmake @@ -18,9 +18,6 @@ if (NOT CAP_FOUND) find_library(CAP_LIBRARIES cap) # Compat with Ubuntu 14.04 which ships libcap w/o .pc file endif() -set(LIBSYSTEMD_VERSION "242" CACHE STRING "libsystemd version (>=239) to build and incorporate into libsdbus-c++") -set(LIBSYSTEMD_EXTRA_CONFIG_OPTS "" CACHE STRING "Additional configuration options to be passed as-is to libsystemd build system") - if(NOT CMAKE_BUILD_TYPE) set(LIBSYSTEMD_BUILD_TYPE "plain") elseif(CMAKE_BUILD_TYPE STREQUAL "Debug") @@ -29,24 +26,24 @@ else() set(LIBSYSTEMD_BUILD_TYPE "release") endif() -if(LIBSYSTEMD_VERSION LESS "239") +if(SDBUSCPP_LIBSYSTEMD_VERSION LESS "239") message(FATAL_ERROR "Only libsystemd version >=239 can be built as static part of sdbus-c++") endif() -if(LIBSYSTEMD_VERSION GREATER "240") +if(SDBUSCPP_LIBSYSTEMD_VERSION GREATER "240") set(BUILD_VERSION_H ${NINJA} -C version.h) endif() -message(STATUS "Building with embedded libsystemd v${LIBSYSTEMD_VERSION}") +message(STATUS "Building with embedded libsystemd v${SDBUSCPP_LIBSYSTEMD_VERSION}") include(ExternalProject) ExternalProject_Add(LibsystemdBuildProject - PREFIX libsystemd-v${LIBSYSTEMD_VERSION} + PREFIX libsystemd-v${SDBUSCPP_LIBSYSTEMD_VERSION} GIT_REPOSITORY https://github.com/systemd/systemd-stable.git - GIT_TAG v${LIBSYSTEMD_VERSION}-stable + GIT_TAG v${SDBUSCPP_LIBSYSTEMD_VERSION}-stable GIT_SHALLOW 1 UPDATE_COMMAND "" CONFIGURE_COMMAND ${CMAKE_COMMAND} -E remove /* - COMMAND ${MESON} --prefix= --buildtype=${LIBSYSTEMD_BUILD_TYPE} -Drootprefix= -Dstatic-libsystemd=pic -Dselinux=false ${LIBSYSTEMD_EXTRA_CONFIG_OPTS} + COMMAND ${MESON} --prefix= --buildtype=${LIBSYSTEMD_BUILD_TYPE} -Drootprefix= -Dstatic-libsystemd=pic -Dselinux=false ${SDBUSCPP_LIBSYSTEMD_EXTRA_CONFIG_OPTS} BUILD_COMMAND ${BUILD_VERSION_H} COMMAND ${NINJA} -C libsystemd.a BUILD_ALWAYS 0 diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index dae91b88..899c02b1 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -15,15 +15,19 @@ if(BUILD_DOXYGEN_DOC) # workaround bug https://github.com/doxygen/doxygen/pull/6787 add_custom_command(TARGET doc POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_LIST_DIR}/sdbus-c++-class-diagram.png ${CMAKE_CURRENT_BINARY_DIR}/html/.) - - install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html DESTINATION ${CMAKE_INSTALL_DOCDIR} OPTIONAL COMPONENT doc) else() message(WARNING "Documentation enabled, but Doxygen cannot be found") endif() endif() -install(FILES sdbus-c++-class-diagram.png - sdbus-c++-class-diagram.uml - systemd-dbus-config.md - using-sdbus-c++.md - DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT doc) +if(SDBUSCPP_INSTALL) + install(FILES sdbus-c++-class-diagram.png + sdbus-c++-class-diagram.uml + systemd-dbus-config.md + using-sdbus-c++.md + DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT doc) + + if (BUILD_DOXYGEN_DOC AND DOXYGEN_FOUND) + install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html DESTINATION ${CMAKE_INSTALL_DOCDIR} OPTIONAL COMPONENT doc) + endif() +endif() diff --git a/docs/using-sdbus-c++.md b/docs/using-sdbus-c++.md index a950b667..45103e86 100644 --- a/docs/using-sdbus-c++.md +++ b/docs/using-sdbus-c++.md @@ -108,9 +108,9 @@ $ ninja libsystemd.so.0.26.0 # or another version number depending which system ### Building and distributing libsystemd as part of sdbus-c++ -sdbus-c++ provides `BUILD_LIBSYSTEMD` configuration option. When turned on, sdbus-c++ will automatically download and build libsystemd as a static library and make it an opaque part of sdbus-c++ shared library for you. This is the most convenient and effective approach to build, distribute and use sdbus-c++ as a self-contained, systemd-independent library in non-systemd environments. Just make sure your build machine has all dependencies needed by libsystemd build process. That includes, among others, `meson`, `ninja`, `git`, `gperf`, and -- primarily -- libraries and library headers for `libmount`, `libcap` and `librt` (part of glibc). Also, when distributing, make sure these dependency libraries are installed on the production machine. +sdbus-c++ provides `SDBUSCPP_BUILD_LIBSYSTEMD` configuration option. When turned on, sdbus-c++ will automatically download and build libsystemd as a static library and make it an opaque part of sdbus-c++ shared library for you. This is the most convenient and effective approach to build, distribute and use sdbus-c++ as a self-contained, systemd-independent library in non-systemd environments. Just make sure your build machine has all dependencies needed by libsystemd build process. That includes, among others, `meson`, `ninja`, `git`, `gperf`, and -- primarily -- libraries and library headers for `libmount`, `libcap` and `librt` (part of glibc). Also, when distributing, make sure these dependency libraries are installed on the production machine. -You may additionally set the `LIBSYSTEMD_VERSION` configuration flag to fine-tune the version of systemd to be taken in. (The default value is 242). +You may additionally set the `SDBUSCPP_LIBSYSTEMD_VERSION` configuration flag to fine-tune the version of systemd to be taken in. (The default value is 242). Distributing sdbus-c++ ---------------------- diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 5befa9a6..0b2d7503 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,6 +1,12 @@ +# Building examples add_executable(obj-manager-server org.freedesktop.DBus.ObjectManager/obj-manager-server.cpp) target_link_libraries(obj-manager-server sdbus-c++) add_executable(obj-manager-client org.freedesktop.DBus.ObjectManager/obj-manager-client.cpp) -target_link_libraries(obj-manager-client sdbus-c++) \ No newline at end of file +target_link_libraries(obj-manager-client sdbus-c++) + +if(SDBUSCPP_INSTALL) + install(TARGETS obj-manager-server DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT examples) + install(TARGETS obj-manager-client DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT examples) +endif() diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 99aa27a3..d11139d7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -2,9 +2,6 @@ # DOWNLOAD AND BUILD OF GOOGLETEST #------------------------------- -set(GOOGLETEST_VERSION 1.10.0 CACHE STRING "Version of gmock to use") -set(GOOGLETEST_GIT_REPO "https://github.com/google/googletest.git" CACHE STRING "A git repo to clone and build googletest from if gmock is not found in the system") - find_package(GTest ${GOOGLETEST_VERSION} CONFIG) if (NOT TARGET GTest::gmock) # Try pkg-config if GTest was not found through CMake config @@ -106,14 +103,14 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}) add_executable(sdbus-c++-unit-tests ${UNITTESTS_SRCS}) target_compile_definitions(sdbus-c++-unit-tests PRIVATE - LIBSYSTEMD_VERSION=${LIBSYSTEMD_VERSION} + LIBSYSTEMD_VERSION=${SDBUSCPP_LIBSYSTEMD_VERSION} SDBUS_${LIBSYSTEMD_IMPL} SDBUS_HEADER=<${LIBSYSTEMD_IMPL}/sd-bus.h>) target_link_libraries(sdbus-c++-unit-tests sdbus-c++-objlib GTest::gmock) add_executable(sdbus-c++-integration-tests ${INTEGRATIONTESTS_SRCS}) target_compile_definitions(sdbus-c++-integration-tests PRIVATE - LIBSYSTEMD_VERSION=${LIBSYSTEMD_VERSION} + LIBSYSTEMD_VERSION=${SDBUSCPP_LIBSYSTEMD_VERSION} SDBUS_${LIBSYSTEMD}) if(NOT LIBSYSTEMD STREQUAL "basu") # Systemd::Libsystemd is included because integration tests use sd-event. Otherwise sdbus-c++ encapsulates and hides libsystemd. @@ -123,15 +120,11 @@ else() target_link_libraries(sdbus-c++-integration-tests sdbus-c++ GTest::gmock) endif() -# 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) +if(SDBUSCPP_BUILD_PERF_TESTS OR SDBUSCPP_BUILD_STRESS_TESTS) set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) - if(ENABLE_PERF_TESTS) + if(SDBUSCPP_BUILD_PERF_TESTS) message(STATUS "Building with performance tests") add_executable(sdbus-c++-perf-tests-client ${STRESSTESTS_CLIENT_SRCS}) target_link_libraries(sdbus-c++-perf-tests-client sdbus-c++ Threads::Threads) @@ -139,7 +132,7 @@ if(ENABLE_PERF_TESTS OR ENABLE_STRESS_TESTS) target_link_libraries(sdbus-c++-perf-tests-server sdbus-c++ Threads::Threads) endif() - if(ENABLE_STRESS_TESTS) + if(SDBUSCPP_BUILD_STRESS_TESTS) message(STATUS "Building with stress tests") add_executable(sdbus-c++-stress-tests ${STRESSTESTS_SRCS}) target_link_libraries(sdbus-c++-stress-tests sdbus-c++ Threads::Threads) @@ -150,25 +143,25 @@ endif() # INSTALLATION #---------------------------------- -option(INSTALL_TESTS "Install tests (default OFF)" OFF) - -if(INSTALL_TESTS) +if(SDBUSCPP_INSTALL) include(GNUInstallDirs) - set(TESTS_INSTALL_PATH "tests/${PROJECT_NAME}" CACHE STRING "Specifies where the test binaries will be installed") - - install(TARGETS sdbus-c++-unit-tests DESTINATION ${TESTS_INSTALL_PATH} COMPONENT test) - install(TARGETS sdbus-c++-integration-tests DESTINATION ${TESTS_INSTALL_PATH} COMPONENT test) - install(FILES ${INTEGRATIONTESTS_SOURCE_DIR}/files/org.sdbuscpp.integrationtests.conf DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/dbus-1/system.d COMPONENT test) - - if(ENABLE_PERF_TESTS) - install(TARGETS sdbus-c++-perf-tests-client DESTINATION ${TESTS_INSTALL_PATH} COMPONENT test) - install(TARGETS sdbus-c++-perf-tests-server DESTINATION ${TESTS_INSTALL_PATH} COMPONENT test) - install(FILES ${PERFTESTS_SOURCE_DIR}/files/org.sdbuscpp.perftests.conf DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/dbus-1/system.d COMPONENT test) + install(TARGETS sdbus-c++-unit-tests DESTINATION ${SDBUSCPP_TESTS_INSTALL_PATH} COMPONENT test) + install(TARGETS sdbus-c++-integration-tests DESTINATION ${SDBUSCPP_TESTS_INSTALL_PATH} COMPONENT test) + install(FILES ${INTEGRATIONTESTS_SOURCE_DIR}/files/org.sdbuscpp.integrationtests.conf + DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/dbus-1/system.d + COMPONENT test) + if(SDBUSCPP_BUILD_PERF_TESTS) + install(TARGETS sdbus-c++-perf-tests-client DESTINATION ${SDBUSCPP_TESTS_INSTALL_PATH} COMPONENT test) + install(TARGETS sdbus-c++-perf-tests-server DESTINATION ${SDBUSCPP_TESTS_INSTALL_PATH} COMPONENT test) + install(FILES ${PERFTESTS_SOURCE_DIR}/files/org.sdbuscpp.perftests.conf + DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/dbus-1/system.d + COMPONENT test) endif() - - if(ENABLE_STRESS_TESTS) - install(TARGETS sdbus-c++-stress-tests DESTINATION ${TESTS_INSTALL_PATH} COMPONENT test) - install(FILES ${STRESSTESTS_SOURCE_DIR}/files/org.sdbuscpp.stresstests.conf DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/dbus-1/system.d COMPONENT test) + if(SDBUSCPP_BUILD_STRESS_TESTS) + install(TARGETS sdbus-c++-stress-tests DESTINATION ${SDBUSCPP_TESTS_INSTALL_PATH} COMPONENT test) + install(FILES ${STRESSTESTS_SOURCE_DIR}/files/org.sdbuscpp.stresstests.conf + DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/dbus-1/system.d + COMPONENT test) endif() endif() diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 4e9bec03..ab71d4f1 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -45,31 +45,38 @@ add_executable(sdbus-c++-xml2cpp ${SDBUSCPP_XML2CPP_SRCS}) target_link_libraries (sdbus-c++-xml2cpp ${EXPAT_LIBRARIES}) target_include_directories(sdbus-c++-xml2cpp PRIVATE ${EXPAT_INCLUDE_DIRS}) -#---------------------------------- -# INSTALLATION -#---------------------------------- - -install(TARGETS sdbus-c++-xml2cpp EXPORT sdbus-c++-tools-targets DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT dev) - #---------------------------------- # CMAKE CONFIG & PACKAGE CONFIG #---------------------------------- include(CMakePackageConfigHelpers) -install(EXPORT sdbus-c++-tools-targets - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/sdbus-c++-tools - NAMESPACE SDBusCpp:: - COMPONENT dev) - configure_package_config_file(cmake/sdbus-c++-tools-config.cmake.in cmake/sdbus-c++-tools-config.cmake INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/sdbus-c++) write_basic_package_version_file(cmake/sdbus-c++-tools-config-version.cmake COMPATIBILITY SameMajorVersion) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/cmake/sdbus-c++-tools-config.cmake - ${CMAKE_CURRENT_BINARY_DIR}/cmake/sdbus-c++-tools-config-version.cmake - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/sdbus-c++-tools - COMPONENT dev) configure_file(pkgconfig/sdbus-c++-tools.pc.in pkgconfig/sdbus-c++-tools.pc @ONLY) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/pkgconfig/sdbus-c++-tools.pc - DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig COMPONENT dev) + +#---------------------------------- +# INSTALLATION +#---------------------------------- + +if(NOT DEFINED SDBUSCPP_INSTALL) + set(SDBUSCPP_INSTALL ON) +endif() + +if (SDBUSCPP_INSTALL) + install(TARGETS sdbus-c++-xml2cpp + EXPORT sdbus-c++-tools-targets + DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT dev) + install(EXPORT sdbus-c++-tools-targets + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/sdbus-c++-tools + NAMESPACE SDBusCpp:: + COMPONENT dev) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/cmake/sdbus-c++-tools-config.cmake + ${CMAKE_CURRENT_BINARY_DIR}/cmake/sdbus-c++-tools-config-version.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/sdbus-c++-tools + COMPONENT dev) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/pkgconfig/sdbus-c++-tools.pc + DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig COMPONENT dev) +endif() From 954e6cd09529c39047c44a6a4ce72913b6a63aa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Sat, 10 Feb 2024 20:27:14 +0100 Subject: [PATCH 27/52] refactor: remove deprecated API stuff (#403) --- include/sdbus-c++/IConnection.h | 65 --------------------------------- src/Connection.cpp | 5 --- src/Connection.h | 1 - 3 files changed, 71 deletions(-) diff --git a/include/sdbus-c++/IConnection.h b/include/sdbus-c++/IConnection.h index 5f579d35..5652be2d 100644 --- a/include/sdbus-c++/IConnection.h +++ b/include/sdbus-c++/IConnection.h @@ -138,23 +138,6 @@ namespace sdbus { */ virtual sd_event *getSdEventLoop() = 0; - /*! - * @brief Adds an ObjectManager at the specified D-Bus object path - * - * Creates an ObjectManager interface at the specified object path on - * the connection. This is a convenient way to interrogate a connection - * to see what objects it has. - * - * This call creates a floating registration. The ObjectManager will - * be there for the object path until the connection is destroyed. - * - * Another, recommended way to add object managers is directly through - * IObject API. - * - * @throws sdbus::Error in case of failure - */ - [[deprecated("Use one of other addObjectManager overloads")]] virtual void addObjectManager(const std::string& objectPath) = 0; - /*! * @brief Returns fd's, I/O events and timeout data to be used in an external event loop * @@ -355,34 +338,6 @@ namespace sdbus { */ virtual void addMatchAsync(const std::string& match, message_handler callback, message_handler installCallback, floating_slot_t) = 0; - /*! - * @copydoc IConnection::enterEventLoop() - * - * @deprecated This function has been replaced by enterEventLoop() - */ - [[deprecated("This function has been replaced by enterEventLoop()")]] void enterProcessingLoop(); - - /*! - * @copydoc IConnection::enterEventLoopAsync() - * - * @deprecated This function has been replaced by enterEventLoopAsync() - */ - [[deprecated("This function has been replaced by enterEventLoopAsync()")]] void enterProcessingLoopAsync(); - - /*! - * @copydoc IConnection::leaveEventLoop() - * - * @deprecated This function has been replaced by leaveEventLoop() - */ - [[deprecated("This function has been replaced by leaveEventLoop()")]] void leaveProcessingLoop(); - - /*! - * @copydoc IConnection::getEventLoopPollData() - * - * @deprecated This function has been replaced by getEventLoopPollData() - */ - [[nodiscard]] [[deprecated("This function has been replaced by getEventLoopPollData()")]] PollData getProcessLoopPollData() const; - /*! * @struct PollData * @@ -440,26 +395,6 @@ namespace sdbus { return setMethodCallTimeout(microsecs.count()); } - inline void IConnection::enterProcessingLoop() - { - enterEventLoop(); - } - - inline void IConnection::enterProcessingLoopAsync() - { - enterEventLoopAsync(); - } - - inline void IConnection::leaveProcessingLoop() - { - leaveEventLoop(); - } - - inline IConnection::PollData IConnection::getProcessLoopPollData() const - { - return getEventLoopPollData(); - } - /*! * @brief Creates/opens D-Bus system bus connection * diff --git a/src/Connection.cpp b/src/Connection.cpp index bf8b85dd..c7f9f777 100644 --- a/src/Connection.cpp +++ b/src/Connection.cpp @@ -188,11 +188,6 @@ ISdBus& Connection::getSdBusInterface() return *sdbus_.get(); } -void Connection::addObjectManager(const std::string& objectPath) -{ - Connection::addObjectManager(objectPath, floating_slot); -} - void Connection::addObjectManager(const std::string& objectPath, floating_slot_t) { auto r = sdbus_->sd_bus_add_object_manager(bus_.get(), nullptr, objectPath.c_str()); diff --git a/src/Connection.h b/src/Connection.h index c9fd724b..269f74f2 100644 --- a/src/Connection.h +++ b/src/Connection.h @@ -91,7 +91,6 @@ namespace sdbus::internal { bool processPendingEvent() override; Message getCurrentlyProcessedMessage() const override; - void addObjectManager(const std::string& objectPath) override; void addObjectManager(const std::string& objectPath, floating_slot_t) override; Slot addObjectManager(const std::string& objectPath, return_slot_t) override; From 0437d934b04243807463d9d47bd6d618028f5cf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Sat, 10 Feb 2024 20:30:22 +0100 Subject: [PATCH 28/52] chore: rename COMPONENTs in CMake (#402) --- CMakeLists.txt | 16 ++++++++-------- docs/CMakeLists.txt | 4 ++-- examples/CMakeLists.txt | 4 ++-- tests/CMakeLists.txt | 16 ++++++++-------- tools/CMakeLists.txt | 8 ++++---- 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index db2ea2f2..6db0fea6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -275,22 +275,22 @@ if(SDBUSCPP_INSTALL) endif() install(TARGETS ${EXPORT_SET} EXPORT sdbus-c++-targets - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT runtime NAMELINK_COMPONENT dev - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT dev - PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${SDBUSCPP_INCLUDE_SUBDIR} COMPONENT dev) + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT sdbus-c++-runtime + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT sdbus-c++-runtime NAMELINK_COMPONENT sdbus-c++-dev + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT sdbus-c++-dev + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${SDBUSCPP_INCLUDE_SUBDIR} COMPONENT sdbus-c++-dev) install(EXPORT sdbus-c++-targets DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/sdbus-c++ NAMESPACE SDBusCpp:: - COMPONENT dev) + COMPONENT sdbus-c++-dev) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/cmake/sdbus-c++-config.cmake ${CMAKE_CURRENT_BINARY_DIR}/cmake/sdbus-c++-config-version.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/sdbus-c++ - COMPONENT dev) + COMPONENT sdbus-c++-dev) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/pkgconfig/sdbus-c++.pc - DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig COMPONENT dev) + DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig COMPONENT sdbus-c++-dev) if(SDBUSCPP_BUILD_DOCS) - install(FILES README README.md NEWS COPYING ChangeLog AUTHORS DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT doc) + install(FILES README README.md NEWS COPYING ChangeLog AUTHORS DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT sdbus-c++-doc) endif() endif() diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index 899c02b1..862503f8 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -25,9 +25,9 @@ if(SDBUSCPP_INSTALL) sdbus-c++-class-diagram.uml systemd-dbus-config.md using-sdbus-c++.md - DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT doc) + DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT sdbus-c++-doc) if (BUILD_DOXYGEN_DOC AND DOXYGEN_FOUND) - install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html DESTINATION ${CMAKE_INSTALL_DOCDIR} OPTIONAL COMPONENT doc) + install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html DESTINATION ${CMAKE_INSTALL_DOCDIR} OPTIONAL COMPONENT sdbus-c++-doc) endif() endif() diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 0b2d7503..423259a8 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -7,6 +7,6 @@ add_executable(obj-manager-client org.freedesktop.DBus.ObjectManager/obj-manager target_link_libraries(obj-manager-client sdbus-c++) if(SDBUSCPP_INSTALL) - install(TARGETS obj-manager-server DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT examples) - install(TARGETS obj-manager-client DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT examples) + install(TARGETS obj-manager-server DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT sdbus-c++-examples) + install(TARGETS obj-manager-client DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT sdbus-c++-examples) endif() diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d11139d7..4681b97d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -145,23 +145,23 @@ endif() if(SDBUSCPP_INSTALL) include(GNUInstallDirs) - install(TARGETS sdbus-c++-unit-tests DESTINATION ${SDBUSCPP_TESTS_INSTALL_PATH} COMPONENT test) - install(TARGETS sdbus-c++-integration-tests DESTINATION ${SDBUSCPP_TESTS_INSTALL_PATH} COMPONENT test) + install(TARGETS sdbus-c++-unit-tests DESTINATION ${SDBUSCPP_TESTS_INSTALL_PATH} COMPONENT sdbus-c++-test) + install(TARGETS sdbus-c++-integration-tests DESTINATION ${SDBUSCPP_TESTS_INSTALL_PATH} COMPONENT sdbus-c++-test) install(FILES ${INTEGRATIONTESTS_SOURCE_DIR}/files/org.sdbuscpp.integrationtests.conf DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/dbus-1/system.d - COMPONENT test) + COMPONENT sdbus-c++-test) if(SDBUSCPP_BUILD_PERF_TESTS) - install(TARGETS sdbus-c++-perf-tests-client DESTINATION ${SDBUSCPP_TESTS_INSTALL_PATH} COMPONENT test) - install(TARGETS sdbus-c++-perf-tests-server DESTINATION ${SDBUSCPP_TESTS_INSTALL_PATH} COMPONENT test) + install(TARGETS sdbus-c++-perf-tests-client DESTINATION ${SDBUSCPP_TESTS_INSTALL_PATH} COMPONENT sdbus-c++-test) + install(TARGETS sdbus-c++-perf-tests-server DESTINATION ${SDBUSCPP_TESTS_INSTALL_PATH} COMPONENT sdbus-c++-test) install(FILES ${PERFTESTS_SOURCE_DIR}/files/org.sdbuscpp.perftests.conf DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/dbus-1/system.d - COMPONENT test) + COMPONENT sdbus-c++-test) endif() if(SDBUSCPP_BUILD_STRESS_TESTS) - install(TARGETS sdbus-c++-stress-tests DESTINATION ${SDBUSCPP_TESTS_INSTALL_PATH} COMPONENT test) + install(TARGETS sdbus-c++-stress-tests DESTINATION ${SDBUSCPP_TESTS_INSTALL_PATH} COMPONENT sdbus-c++-test) install(FILES ${STRESSTESTS_SOURCE_DIR}/files/org.sdbuscpp.stresstests.conf DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/dbus-1/system.d - COMPONENT test) + COMPONENT sdbus-c++-test) endif() endif() diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index ab71d4f1..c025e36a 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -68,15 +68,15 @@ endif() if (SDBUSCPP_INSTALL) install(TARGETS sdbus-c++-xml2cpp EXPORT sdbus-c++-tools-targets - DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT dev) + DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT sdbus-c++-dev) install(EXPORT sdbus-c++-tools-targets DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/sdbus-c++-tools NAMESPACE SDBusCpp:: - COMPONENT dev) + COMPONENT sdbus-c++-dev) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/cmake/sdbus-c++-tools-config.cmake ${CMAKE_CURRENT_BINARY_DIR}/cmake/sdbus-c++-tools-config-version.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/sdbus-c++-tools - COMPONENT dev) + COMPONENT sdbus-c++-dev) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/pkgconfig/sdbus-c++-tools.pc - DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig COMPONENT dev) + DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig COMPONENT sdbus-c++-dev) endif() From 4ea36d2ca64fa51c9782cdf07e584d13154bfbea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Fri, 16 Feb 2024 09:27:45 +0100 Subject: [PATCH 29/52] chore: reorder some API functions (#404) In sdbus-c++ v1, new virtual functions (e.g. overloads of existing virtual functions) were always placed at the end of the class to keep backwards ABI compatibility. Now, with v2, these functions are reordered and functions forming a logical group are together. --- CMakeLists.txt | 6 +-- include/sdbus-c++/IObject.h | 42 ++++++++--------- include/sdbus-c++/IProxy.h | 94 ++++++++++++++++++------------------- 3 files changed, 70 insertions(+), 72 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6db0fea6..604d64ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,8 +17,7 @@ if(SDBUSCPP_BUILD_LIBSYSTEMD) set(SDBUSCPP_LIBSYSTEMD_VERSION "242" CACHE STRING "libsystemd version (>=239) to build and incorporate into libsdbus-c++") set(SDBUSCPP_LIBSYSTEMD_EXTRA_CONFIG_OPTS "" CACHE STRING "Additional configuration options to be passed as-is to libsystemd build system") endif() -option(SDBUSCPP_INSTALL "Enable installation of sdbus-c++ (downstream projects embedding sdbus-c++ may want to turn this OFF)" ON) #TODO: new, honor -# TODO: Continue here with search&replace old names with new names +option(SDBUSCPP_INSTALL "Enable installation of sdbus-c++ (downstream projects embedding sdbus-c++ may want to turn this OFF)" ON) option(SDBUSCPP_BUILD_TESTS "Build tests" OFF) if (SDBUSCPP_BUILD_TESTS) option(SDBUSCPP_BUILD_PERF_TESTS "Build also sdbus-c++ performance tests" OFF) # tranferred from tests/cmake @@ -27,10 +26,9 @@ if (SDBUSCPP_BUILD_TESTS) set(SDBUSCPP_GOOGLETEST_VERSION 1.10.0 CACHE STRING "Version of gmock library to use") # tranferred from tests/cmake set(SDBUSCPP_GOOGLETEST_GIT_REPO "https://github.com/google/googletest.git" CACHE STRING "A git repo to clone and build googletest from if gmock is not found in the system") # tranferred from tests/cmake endif() -# TODO: add more embedded options from sub=-cmakefiles option(SDBUSCPP_BUILD_CODEGEN "Build generator tool for C++ native bindings" OFF) option(SDBUSCPP_BUILD_EXAMPLES "Build example programs" OFF) -option(SDBUSCPP_BUILD_DOCS "Build documentation for sdbus-c++" ON) # TODO: renamed +option(SDBUSCPP_BUILD_DOCS "Build documentation for sdbus-c++" ON) if(SDBUSCPP_BUILD_DOCS) option(SDBUSCPP_BUILD_DOXYGEN_DOCS "Build doxygen documentation for sdbus-c++ API" OFF) endif() diff --git a/include/sdbus-c++/IObject.h b/include/sdbus-c++/IObject.h index 6d19fdb4..b3c118e8 100644 --- a/include/sdbus-c++/IObject.h +++ b/include/sdbus-c++/IObject.h @@ -201,6 +201,27 @@ namespace sdbus { */ virtual void emitSignal(const sdbus::Signal& message) = 0; + /*! + * @brief Emits signal on D-Bus + * + * @param[in] signalName Name of the signal + * @return A helper object for convenient emission of signals + * + * This is a high-level, convenience way of emitting D-Bus signals that abstracts + * from the D-Bus message concept. Signal arguments are automatically serialized + * in a message and D-Bus signatures automatically deduced from the provided native arguments. + * + * Example of use: + * @code + * int arg1 = ...; + * double arg2 = ...; + * object_.emitSignal("fooSignal").onInterface("com.kistler.foo").withArguments(arg1, arg2); + * @endcode + * + * @throws sdbus::Error in case of failure + */ + [[nodiscard]] SignalEmitter emitSignal(const std::string& signalName); + /*! * @brief Emits PropertyChanged signal for specified properties under a given interface of this object path * @@ -298,27 +319,6 @@ namespace sdbus { */ [[nodiscard]] virtual sdbus::IConnection& getConnection() const = 0; - /*! - * @brief Emits signal on D-Bus - * - * @param[in] signalName Name of the signal - * @return A helper object for convenient emission of signals - * - * This is a high-level, convenience way of emitting D-Bus signals that abstracts - * from the D-Bus message concept. Signal arguments are automatically serialized - * in a message and D-Bus signatures automatically deduced from the provided native arguments. - * - * Example of use: - * @code - * int arg1 = ...; - * double arg2 = ...; - * object_.emitSignal("fooSignal").onInterface("com.kistler.foo").withArguments(arg1, arg2); - * @endcode - * - * @throws sdbus::Error in case of failure - */ - [[nodiscard]] SignalEmitter emitSignal(const std::string& signalName); - /*! * @brief Returns object path of the underlying DBus object */ diff --git a/include/sdbus-c++/IProxy.h b/include/sdbus-c++/IProxy.h index d28c234e..b555cc65 100644 --- a/include/sdbus-c++/IProxy.h +++ b/include/sdbus-c++/IProxy.h @@ -197,53 +197,6 @@ namespace sdbus { , const std::chrono::duration<_Rep, _Period>& timeout , with_future_t ); - /*! - * @brief Registers a handler for the desired signal emitted by the D-Bus object - * - * @param[in] interfaceName Name of an interface that the signal belongs to - * @param[in] signalName Name of the signal - * @param[in] signalHandler Callback that implements the body of the signal handler - * - * A signal can be subscribed to and unsubscribed from at any time during proxy - * lifetime. The subscription is active immediately after the call. - * - * @throws sdbus::Error in case of failure - */ - virtual void registerSignalHandler( const std::string& interfaceName - , const std::string& signalName - , signal_handler signalHandler ) = 0; - - /*! - * @brief Registers a handler for the desired signal emitted by the D-Bus object - * - * @param[in] interfaceName Name of an interface that the signal belongs to - * @param[in] signalName Name of the signal - * @param[in] signalHandler Callback that implements the body of the signal handler - * - * @return RAII-style slot handle representing the ownership of the subscription - * - * A signal can be subscribed to and unsubscribed from at any time during proxy - * lifetime. The subscription is active immediately after the call. The subscription - * is unregistered when the client destroys the returned slot object. - * - * @throws sdbus::Error in case of failure - */ - [[nodiscard]] virtual Slot registerSignalHandler( const std::string& interfaceName - , const std::string& signalName - , signal_handler signalHandler - , return_slot_t ) = 0; - - /*! - * @brief Unregisters proxy's signal handlers and stops receiving replies to pending async calls - * - * Unregistration is done automatically also in proxy's destructor. This method makes - * sense if, in the process of proxy removal, we need to make sure that callbacks - * are unregistered explicitly before the final destruction of the proxy instance. - * - * @throws sdbus::Error in case of failure - */ - virtual void unregister() = 0; - /*! * @brief Calls method on the D-Bus object * @@ -289,6 +242,42 @@ namespace sdbus { */ [[nodiscard]] AsyncMethodInvoker callMethodAsync(const std::string& methodName); + /*! + * @brief Registers a handler for the desired signal emitted by the D-Bus object + * + * @param[in] interfaceName Name of an interface that the signal belongs to + * @param[in] signalName Name of the signal + * @param[in] signalHandler Callback that implements the body of the signal handler + * + * A signal can be subscribed to and unsubscribed from at any time during proxy + * lifetime. The subscription is active immediately after the call. + * + * @throws sdbus::Error in case of failure + */ + virtual void registerSignalHandler( const std::string& interfaceName + , const std::string& signalName + , signal_handler signalHandler ) = 0; + + /*! + * @brief Registers a handler for the desired signal emitted by the D-Bus object + * + * @param[in] interfaceName Name of an interface that the signal belongs to + * @param[in] signalName Name of the signal + * @param[in] signalHandler Callback that implements the body of the signal handler + * + * @return RAII-style slot handle representing the ownership of the subscription + * + * A signal can be subscribed to and unsubscribed from at any time during proxy + * lifetime. The subscription is active immediately after the call. The subscription + * is unregistered when the client destroys the returned slot object. + * + * @throws sdbus::Error in case of failure + */ + [[nodiscard]] virtual Slot registerSignalHandler( const std::string& interfaceName + , const std::string& signalName + , signal_handler signalHandler + , return_slot_t ) = 0; + /*! * @brief Registers signal handler for a given signal of the D-Bus object * @@ -312,6 +301,17 @@ namespace sdbus { */ [[nodiscard]] SignalSubscriber uponSignal(const std::string& signalName); + /*! + * @brief Unregisters proxy's signal handlers and stops receiving replies to pending async calls + * + * Unregistration is done automatically also in proxy's destructor. This method makes + * sense if, in the process of proxy removal, we need to make sure that callbacks + * are unregistered explicitly before the final destruction of the proxy instance. + * + * @throws sdbus::Error in case of failure + */ + virtual void unregister() = 0; + /*! * @brief Gets value of a property of the D-Bus object * From c82920850ff698e2980f23b309a4d8b96c0426c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Fri, 16 Feb 2024 09:28:16 +0100 Subject: [PATCH 30/52] chore: update ChangeLog for v2.0.0 (#405) --- ChangeLog | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 255133d1..d2c674e7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -268,8 +268,22 @@ v2.0.0 - `PollData::getRelativeTimeout()` return type was changed to `std::chrono::microseconds`. - `IConnection::processPendingRequest()` was renamed to `IConnection::processPendingEvent()`. - `Variant` constructor is now explicit. - - Object D-Bus API registration is now done through `IObject::addVTable()` method. The registration is immediate; no `finishRegistration()` call is needed anymore. + - `IProxy::getCurrentlyProcessedMessage()` now returns `Message` by value instead of a raw pointer to it. The caller assumes ownership of the message. + - Object D-Bus API registration is now done through `IObject::addVTable()` method. The registration holds immediately; no `finishRegistration()` call is needed anymore. + - Subscription to signals has been simplified. The subscription is active right after the `registerSignalHandler`/`uponSignal()` call. No need for the final + call to `finishRegistration()`. `IProxy::muteSignal()` has been removed in favor of the RAII-based slot object returned by the slot-returning variant of the + registration method. Destroying the slot object means unsubscribing from the signal. + - `request_slot` tag was renamed to `return_slot`. + - `[[nodiscard]]` attribute has been added to relevant API methods. + - `ProxyInterfaces::getObjectPath()` was removed, it can be replaced with `ProxyInterfaces::getProxy().getObjectPath()`. + - `AdaptorInterfaces::getObjectPath()` was removed, it can be replaced with `AdaptorInterfaces::getObject().getObjectPath()`. + - Callbacks taking `const sdbus::Error* error` were changed to take `std::optional`, which better expresses the intent and meaning. + - Types and methods marked deprecated in sdbus-c++ v1 were removed completely. + - CMake options got `SDBUSCPP_` prefix for better usability and minimal risk of conflicts in downstream CMake projects. + - CMake components got `sdbus-c++-` prefix. - Systemd of at least v238 is required to compile sdbus-c++ - A proper fix for timeout handling - Fix for external event loops in which the event loop thread ID was not correctly initialized (now fixed and simplified by not needing the thread ID anymore) +- Introduce native integration for sd-event +- Add method to get currently processed message also to `IConnection` - Other simplifications, improvements and fixes springing out from the above refactoring From b71b69de7a73a49f641b8766a2ec5c6d3d436886 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Fri, 16 Feb 2024 18:57:51 +0100 Subject: [PATCH 31/52] refactor: rename connection creation methods (#406) This PR makes things around connection factories a little more consistent and more intuitive: * createConnection() has been removed. One shall call more expressive createSystemConnection() instead to get a connection to the system bus. * createDefaultBusConnection() has been renamed to createBusConnection(), so as not to be confused with libsystemd's default_bus, which is a different thing (a reusable thread-local bus). Proxies still by default call createBusConnection() to get a connection when the connection is not provided explicitly by the caller, but now createBusConnection() does a different thing, so now the proxies connect to either session bus or system bus depending on the context (as opposed to always to system bus like before). The integration tests were modified to use createBusConnection(). --- ChangeLog | 19 +++++++-------- docs/using-sdbus-c++.md | 12 ++++------ include/sdbus-c++/IConnection.h | 23 ++---------------- src/Connection.cpp | 24 +++---------------- src/IConnection.h | 1 - src/Proxy.cpp | 4 ++-- .../integrationtests/DBusConnectionTests.cpp | 16 ++++++------- tests/integrationtests/DBusGeneralTests.cpp | 4 ++-- tests/integrationtests/DBusSignalsTests.cpp | 2 +- tests/integrationtests/TestFixture.cpp | 4 ++-- 10 files changed, 33 insertions(+), 76 deletions(-) diff --git a/ChangeLog b/ChangeLog index d2c674e7..66c66a95 100644 --- a/ChangeLog +++ b/ChangeLog @@ -257,26 +257,22 @@ v1.6.0 v2.0.0 - Breaking changes in API/ABI/behavior: - - In *synchronous* D-Bus calls, the proxy now **always** blocks the connection for concurrent use until the call finishes (with either a received reply, - an error, or time out). If this creates a connection contention issue in your multi-threaded design, use short-lived, light-weight proxies, or call - the method in an asynchronous way. - - Signatures of callbacks `async_reply_handler`, `signal_handler`, `message_handler` and `property_set_callback` were modified to take input message objects - by value, as opposed to non-const ref. Callee assumes ownership of the message. This API is more idiomatic, cleaner and safer. + - In *synchronous* D-Bus calls, the proxy now **always** blocks the connection for concurrent use until the call finishes (with either a received reply, an error, or time out). If this creates a connection contention issue in your multi-threaded design, use short-lived, light-weight proxies, or call the method in an asynchronous way. + - Signatures of callbacks `async_reply_handler`, `signal_handler`, `message_handler` and `property_set_callback` were modified to take input message objects by value, as opposed to non-const ref. Callee assumes ownership of the message. This API is more idiomatic, cleaner and safer. - The `PollData` struct has been extended with a new data member: `eventFd`. All hooks with external event loops shall be modified to poll on this fd as well. - - `PollData::timeout_usec` was renamed to `PollData::timeout` and its type has been changed to `std::chrono::microseconds`. This member now holds directly - what before had to be obtained through `PollData::getAbsoluteTimeout()` call. + - `PollData::timeout_usec` was renamed to `PollData::timeout` and its type has been changed to `std::chrono::microseconds`. This member now holds directly what before had to be obtained through `PollData::getAbsoluteTimeout()` call. - `PollData::getRelativeTimeout()` return type was changed to `std::chrono::microseconds`. - `IConnection::processPendingRequest()` was renamed to `IConnection::processPendingEvent()`. - `Variant` constructor is now explicit. - `IProxy::getCurrentlyProcessedMessage()` now returns `Message` by value instead of a raw pointer to it. The caller assumes ownership of the message. - Object D-Bus API registration is now done through `IObject::addVTable()` method. The registration holds immediately; no `finishRegistration()` call is needed anymore. - - Subscription to signals has been simplified. The subscription is active right after the `registerSignalHandler`/`uponSignal()` call. No need for the final - call to `finishRegistration()`. `IProxy::muteSignal()` has been removed in favor of the RAII-based slot object returned by the slot-returning variant of the - registration method. Destroying the slot object means unsubscribing from the signal. + - Subscription to signals has been simplified. The subscription is active right after the `registerSignalHandler`/`uponSignal()` call. No need for the final call to `finishRegistration()`. `IProxy::muteSignal()` has been removed in favor of the RAII-based slot object returned by the slot-returning variant of the registration method. Destroying the slot object means unsubscribing from the signal. - `request_slot` tag was renamed to `return_slot`. - - `[[nodiscard]]` attribute has been added to relevant API methods. - `ProxyInterfaces::getObjectPath()` was removed, it can be replaced with `ProxyInterfaces::getProxy().getObjectPath()`. - `AdaptorInterfaces::getObjectPath()` was removed, it can be replaced with `AdaptorInterfaces::getObject().getObjectPath()`. + - `createConnection()` has been removed, to create a connection to the system bus use `createSystemConnection()` instead. + - `createDefaultBusConnection()` has been renamed to `createBusConnection()`. + - `Proxy`s now by default call `createBusConnection()` to get a connection when the connection is not provided explicitly by the caller, so they connect to either the session bus or the system bus depending on the context (as opposed to always to the system bus like before). - Callbacks taking `const sdbus::Error* error` were changed to take `std::optional`, which better expresses the intent and meaning. - Types and methods marked deprecated in sdbus-c++ v1 were removed completely. - CMake options got `SDBUSCPP_` prefix for better usability and minimal risk of conflicts in downstream CMake projects. @@ -286,4 +282,5 @@ v2.0.0 - Fix for external event loops in which the event loop thread ID was not correctly initialized (now fixed and simplified by not needing the thread ID anymore) - Introduce native integration for sd-event - Add method to get currently processed message also to `IConnection` +- `[[nodiscard]]` attribute has been added to relevant API methods. - Other simplifications, improvements and fixes springing out from the above refactoring diff --git a/docs/using-sdbus-c++.md b/docs/using-sdbus-c++.md index 45103e86..4820da38 100644 --- a/docs/using-sdbus-c++.md +++ b/docs/using-sdbus-c++.md @@ -390,7 +390,7 @@ int main(int argc, char *argv[]) } ``` -In simple cases, we don't need to create D-Bus connection explicitly for our proxies. Unless a connection is provided to a proxy object explicitly via factory parameter, the proxy will create a connection of his own (unless it is a light-weight, short-lived proxy created with `dont_run_event_loop_thread_t`), 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 handlers for signals we are interested in (if any). +In simple cases, we don't need to create D-Bus connection explicitly for our proxies. We either pass a connection object to the proxy upon creation, or otherwise the proxy will create a connection of his own (to either the session bus or the system bus, depending on the context, see `man sd_bus_open`). 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 handlers for signals we are interested in (if any). 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. @@ -404,10 +404,8 @@ Please note that we can create and destroy D-Bus object proxies dynamically, at There are several factory methods to create a bus connection object in sdbus-c++: -* `createConnection()` - opens a connection to the system bus -* `createConnection(const std::string& name)` - opens a connection with the given name to the system bus -* `createDefaultBusConnection()` - opens a connection to the session bus when in a user context, and a connection to the system bus, otherwise -* `createDefaultBusConnection(const std::string& name)` - opens a connection with the given name to the session bus when in a user context, and a connection with the given name to the system bus, otherwise +* `createBusConnection()` - opens a connection to the session bus when in a user context, and a connection to the system bus, otherwise +* `createBusConnection(const std::string& name)` - opens a connection with the given name to the session bus when in a user context, and a connection with the given name to the system bus, otherwise * `createSystemBusConnection()` - opens a connection to the system bus * `createSystemBusConnection(const std::string& name)` - opens a connection with the given name to the system bus * `createSessionBusConnection()` - opens a connection to the session bus @@ -467,7 +465,7 @@ On the **client** side we likewise need a connection -- just that unlike on the * when created **without** `dont_run_event_loop_thread_t` tag, the proxy **will start** a dedicated event loop thread on that connection; * or, when created **with** `dont_run_event_loop_thread_t` tag, the proxy will start **no** event loop thread on that connection. - * Or we don't care about connnections at all (proxy factory overloads with no connection parameter). Under the hood, the proxy creates its own *system bus* connection. Additionally: + * Or we don't care about connnections at all (proxy factory overloads with no connection parameter). Under the hood, the proxy creates its own connection, to either the session bus (when in a user context) or the system bus otherwise. Additionally: * when created **without** `dont_run_event_loop_thread_t` tag, the proxy **will start** a dedicated event loop thread on that connection; * or, when created **with** `dont_run_event_loop_thread_t` tag, the proxy will start **no** event loop thread on that connection. @@ -926,7 +924,7 @@ protected: > **_Tip_:** By inheriting from `sdbus::ProxyInterfaces`, we get access to the protected `getProxy()` method. We can call this method inside our proxy implementation class to access the underlying `IProxy` object. -In the above example, a proxy is created that creates and maintains its own system bus connection. However, there are `ProxyInterfaces` class template constructor overloads that also take the connection from the user as the first parameter, and pass that connection over to the underlying proxy. The connection instance is used by all interfaces listed in the `ProxyInterfaces` template parameter list. +In the above example, a proxy is created that creates and maintains its own bus connection (the bus is either session bus or system bus depending on the context, see `man sd_bus_open`). However, there are `ProxyInterfaces` class template constructor overloads that also take the connection from the user as the first parameter, and pass that connection over to the underlying proxy. The connection instance is used by all interfaces listed in the `ProxyInterfaces` template parameter list. Note however that there are multiple `ProxyInterfaces` constructor overloads, and they differ in how the proxy behaves towards the D-Bus connection. These overloads precisely map the `sdbus::createProxy` overloads, as they are actually implemented on top of them. See [Proxy and D-Bus connection](#Proxy-and-D-Bus-connection) for more info. We can even create a `IProxy` instance on our own, and inject it into our proxy class -- there is a constructor overload for it in `ProxyInterfaces`. This can help if we need to provide mocked implementations in our unit tests. diff --git a/include/sdbus-c++/IConnection.h b/include/sdbus-c++/IConnection.h index 5652be2d..11f4c568 100644 --- a/include/sdbus-c++/IConnection.h +++ b/include/sdbus-c++/IConnection.h @@ -395,25 +395,6 @@ namespace sdbus { return setMethodCallTimeout(microsecs.count()); } - /*! - * @brief Creates/opens D-Bus system bus connection - * - * @return Connection instance - * - * @throws sdbus::Error in case of failure - */ - [[nodiscard]] std::unique_ptr createConnection(); - - /*! - * @brief Creates/opens D-Bus system bus connection with a name - * - * @param[in] name Name to request on the connection after its opening - * @return Connection instance - * - * @throws sdbus::Error in case of failure - */ - [[nodiscard]] std::unique_ptr createConnection(const std::string& name); - /*! * @brief Creates/opens D-Bus session bus connection when in a user context, and a system bus connection, otherwise. * @@ -421,7 +402,7 @@ namespace sdbus { * * @throws sdbus::Error in case of failure */ - [[nodiscard]] std::unique_ptr createDefaultBusConnection(); + [[nodiscard]] std::unique_ptr createBusConnection(); /*! * @brief Creates/opens D-Bus session bus connection with a name when in a user context, and a system bus connection with a name, otherwise. @@ -431,7 +412,7 @@ namespace sdbus { * * @throws sdbus::Error in case of failure */ - [[nodiscard]] std::unique_ptr createDefaultBusConnection(const std::string& name); + [[nodiscard]] std::unique_ptr createBusConnection(const std::string& name); /*! * @brief Creates/opens D-Bus system bus connection diff --git a/src/Connection.cpp b/src/Connection.cpp index c7f9f777..7187d8d3 100644 --- a/src/Connection.cpp +++ b/src/Connection.cpp @@ -872,14 +872,6 @@ int IConnection::PollData::getPollTimeout() const namespace sdbus::internal { -std::unique_ptr createConnection() -{ - auto connection = sdbus::createConnection(); - SCOPE_EXIT{ connection.release(); }; - auto connectionInternal = dynamic_cast(connection.get()); - return std::unique_ptr(connectionInternal); -} - std::unique_ptr createPseudoConnection() { auto interface = std::make_unique(); @@ -892,25 +884,15 @@ namespace sdbus { using internal::Connection; -std::unique_ptr createConnection() -{ - return createSystemBusConnection(); -} - -std::unique_ptr createConnection(const std::string& name) -{ - return createSystemBusConnection(name); -} - -std::unique_ptr createDefaultBusConnection() +std::unique_ptr createBusConnection() { auto interface = std::make_unique(); return std::make_unique(std::move(interface), Connection::default_bus); } -std::unique_ptr createDefaultBusConnection(const std::string& name) +std::unique_ptr createBusConnection(const std::string& name) { - auto conn = createDefaultBusConnection(); + auto conn = createBusConnection(); conn->requestName(name); return conn; } diff --git a/src/IConnection.h b/src/IConnection.h index 7730b73a..d1524481 100644 --- a/src/IConnection.h +++ b/src/IConnection.h @@ -98,7 +98,6 @@ namespace sdbus::internal { , void* userData ) = 0; }; - [[nodiscard]] std::unique_ptr createConnection(); [[nodiscard]] std::unique_ptr createPseudoConnection(); } diff --git a/src/Proxy.cpp b/src/Proxy.cpp index d343608c..f087447f 100644 --- a/src/Proxy.cpp +++ b/src/Proxy.cpp @@ -310,7 +310,7 @@ std::unique_ptr createProxy( std::unique_ptr&& conne std::unique_ptr createProxy( std::string destination , std::string objectPath ) { - auto connection = sdbus::createConnection(); + auto connection = sdbus::createBusConnection(); auto sdbusConnection = std::unique_ptr(dynamic_cast(connection.release())); assert(sdbusConnection != nullptr); @@ -324,7 +324,7 @@ std::unique_ptr createProxy( std::string destination , std::string objectPath , dont_run_event_loop_thread_t ) { - auto connection = sdbus::createConnection(); + auto connection = sdbus::createBusConnection(); auto sdbusConnection = std::unique_ptr(dynamic_cast(connection.release())); assert(sdbusConnection != nullptr); diff --git a/tests/integrationtests/DBusConnectionTests.cpp b/tests/integrationtests/DBusConnectionTests.cpp index e75db26b..855ab5e2 100644 --- a/tests/integrationtests/DBusConnectionTests.cpp +++ b/tests/integrationtests/DBusConnectionTests.cpp @@ -48,26 +48,26 @@ using namespace sdbus::test; TEST(Connection, CanBeDefaultConstructed) { - ASSERT_NO_THROW(auto con = sdbus::createConnection()); + ASSERT_NO_THROW(auto con = sdbus::createBusConnection()); } -TEST(Connection, CanRequestRegisteredDbusName) +TEST(SystemBusConnection, CanRequestRegisteredDbusName) { - auto connection = sdbus::createConnection(); + auto connection = sdbus::createSystemBusConnection(); ASSERT_NO_THROW(connection->requestName(BUS_NAME)) << "Perhaps you've forgotten to copy `org.sdbuscpp.integrationtests.conf` file to `/etc/dbus-1/system.d` directory before running the tests?"; } -TEST(Connection, CannotRequestNonregisteredDbusName) +TEST(SystemBusConnection, CannotRequestNonregisteredDbusName) { - auto connection = sdbus::createConnection(); + auto connection = sdbus::createSystemBusConnection(); ASSERT_THROW(connection->requestName("some.random.not.supported.dbus.name"), sdbus::Error); } TEST(Connection, CanReleasedRequestedName) { - auto connection = sdbus::createConnection(); + auto connection = sdbus::createBusConnection(); connection->requestName(BUS_NAME); ASSERT_NO_THROW(connection->releaseName(BUS_NAME)); @@ -75,13 +75,13 @@ TEST(Connection, CanReleasedRequestedName) TEST(Connection, CannotReleaseNonrequestedName) { - auto connection = sdbus::createConnection(); + auto connection = sdbus::createBusConnection(); ASSERT_THROW(connection->releaseName("some.random.nonrequested.name"), sdbus::Error); } TEST(Connection, CanEnterAndLeaveInternalEventLoop) { - auto connection = sdbus::createConnection(); + auto connection = sdbus::createBusConnection(); connection->requestName(BUS_NAME); std::thread t([&](){ connection->enterEventLoop(); }); diff --git a/tests/integrationtests/DBusGeneralTests.cpp b/tests/integrationtests/DBusGeneralTests.cpp index d5f225d9..0e5b356c 100644 --- a/tests/integrationtests/DBusGeneralTests.cpp +++ b/tests/integrationtests/DBusGeneralTests.cpp @@ -53,7 +53,7 @@ using ADirectConnection = TestFixtureWithDirectConnection; TEST(AdaptorAndProxy, CanBeConstructedSuccesfully) { - auto connection = sdbus::createConnection(); + auto connection = sdbus::createBusConnection(); connection->requestName(BUS_NAME); ASSERT_NO_THROW(TestAdaptor adaptor(*connection, OBJECT_PATH)); @@ -130,7 +130,7 @@ TYPED_TEST(AConnection, CanAddFloatingMatchRule) { auto matchRule = "sender='" + BUS_NAME + "',path='" + OBJECT_PATH + "'"; std::atomic matchingMessageReceived{false}; - auto con = sdbus::createSystemBusConnection(); + auto con = sdbus::createBusConnection(); con->enterEventLoopAsync(); auto callback = [&](sdbus::Message msg) { diff --git a/tests/integrationtests/DBusSignalsTests.cpp b/tests/integrationtests/DBusSignalsTests.cpp index 779c2c7f..4b12756d 100644 --- a/tests/integrationtests/DBusSignalsTests.cpp +++ b/tests/integrationtests/DBusSignalsTests.cpp @@ -70,7 +70,7 @@ TYPED_TEST(SdbusTestObject, EmitsSimpleSignalToMultipleProxiesSuccesfully) TYPED_TEST(SdbusTestObject, ProxyDoesNotReceiveSignalFromOtherBusName) { auto otherBusName = BUS_NAME + "2"; - auto connection2 = sdbus::createConnection(otherBusName); + auto connection2 = sdbus::createBusConnection(otherBusName); auto adaptor2 = std::make_unique(*connection2, OBJECT_PATH); adaptor2->emitSimpleSignal(); diff --git a/tests/integrationtests/TestFixture.cpp b/tests/integrationtests/TestFixture.cpp index 7769e978..0ddd2207 100644 --- a/tests/integrationtests/TestFixture.cpp +++ b/tests/integrationtests/TestFixture.cpp @@ -28,8 +28,8 @@ namespace sdbus { namespace test { -std::unique_ptr BaseTestFixture::s_adaptorConnection = sdbus::createSystemBusConnection(); -std::unique_ptr BaseTestFixture::s_proxyConnection = sdbus::createSystemBusConnection(); +std::unique_ptr BaseTestFixture::s_adaptorConnection = sdbus::createBusConnection(); +std::unique_ptr BaseTestFixture::s_proxyConnection = sdbus::createBusConnection(); #ifndef SDBUS_basu // sd_event integration is not supported in basu-based sdbus-c++ From c07b19b10cf96c317a5b5284bf9f98651d1802e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Sun, 18 Feb 2024 18:28:24 +0100 Subject: [PATCH 32/52] docs: add v2 migration notes to the tutorial (#407) --- ChangeLog | 22 +--------------------- docs/using-sdbus-c++.md | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/ChangeLog b/ChangeLog index 66c66a95..b83dd774 100644 --- a/ChangeLog +++ b/ChangeLog @@ -256,27 +256,7 @@ v1.6.0 - Fix missing includes v2.0.0 -- Breaking changes in API/ABI/behavior: - - In *synchronous* D-Bus calls, the proxy now **always** blocks the connection for concurrent use until the call finishes (with either a received reply, an error, or time out). If this creates a connection contention issue in your multi-threaded design, use short-lived, light-weight proxies, or call the method in an asynchronous way. - - Signatures of callbacks `async_reply_handler`, `signal_handler`, `message_handler` and `property_set_callback` were modified to take input message objects by value, as opposed to non-const ref. Callee assumes ownership of the message. This API is more idiomatic, cleaner and safer. - - The `PollData` struct has been extended with a new data member: `eventFd`. All hooks with external event loops shall be modified to poll on this fd as well. - - `PollData::timeout_usec` was renamed to `PollData::timeout` and its type has been changed to `std::chrono::microseconds`. This member now holds directly what before had to be obtained through `PollData::getAbsoluteTimeout()` call. - - `PollData::getRelativeTimeout()` return type was changed to `std::chrono::microseconds`. - - `IConnection::processPendingRequest()` was renamed to `IConnection::processPendingEvent()`. - - `Variant` constructor is now explicit. - - `IProxy::getCurrentlyProcessedMessage()` now returns `Message` by value instead of a raw pointer to it. The caller assumes ownership of the message. - - Object D-Bus API registration is now done through `IObject::addVTable()` method. The registration holds immediately; no `finishRegistration()` call is needed anymore. - - Subscription to signals has been simplified. The subscription is active right after the `registerSignalHandler`/`uponSignal()` call. No need for the final call to `finishRegistration()`. `IProxy::muteSignal()` has been removed in favor of the RAII-based slot object returned by the slot-returning variant of the registration method. Destroying the slot object means unsubscribing from the signal. - - `request_slot` tag was renamed to `return_slot`. - - `ProxyInterfaces::getObjectPath()` was removed, it can be replaced with `ProxyInterfaces::getProxy().getObjectPath()`. - - `AdaptorInterfaces::getObjectPath()` was removed, it can be replaced with `AdaptorInterfaces::getObject().getObjectPath()`. - - `createConnection()` has been removed, to create a connection to the system bus use `createSystemConnection()` instead. - - `createDefaultBusConnection()` has been renamed to `createBusConnection()`. - - `Proxy`s now by default call `createBusConnection()` to get a connection when the connection is not provided explicitly by the caller, so they connect to either the session bus or the system bus depending on the context (as opposed to always to the system bus like before). - - Callbacks taking `const sdbus::Error* error` were changed to take `std::optional`, which better expresses the intent and meaning. - - Types and methods marked deprecated in sdbus-c++ v1 were removed completely. - - CMake options got `SDBUSCPP_` prefix for better usability and minimal risk of conflicts in downstream CMake projects. - - CMake components got `sdbus-c++-` prefix. +- A number of breaking changes in API/ABI/behavior and in the generated bindings. Consult the sdbus-c++ tutorial, section "Migrating to sdbus-c++ v2", for detailed info on changes and migration. - Systemd of at least v238 is required to compile sdbus-c++ - A proper fix for timeout handling - Fix for external event loops in which the event loop thread ID was not correctly initialized (now fixed and simplified by not needing the thread ID anymore) diff --git a/docs/using-sdbus-c++.md b/docs/using-sdbus-c++.md index 4820da38..011a7947 100644 --- a/docs/using-sdbus-c++.md +++ b/docs/using-sdbus-c++.md @@ -23,7 +23,8 @@ Using sdbus-c++ library 18. [Support for match rules](#support-for-match-rules) 19. [Using direct (peer-to-peer) D-Bus connections](#using-direct-peer-to-peer-d-bus-connections) 20. [Using sdbus-c++ in external event loops](#using-sdbus-c-in-external-event-loops) -21. [Conclusion](#conclusion) +21. [Migrating to sdbus-c++ v2](#migrating-to-sdbus-c-v2) +22. [Conclusion](#conclusion) Introduction ------------ @@ -1739,6 +1740,38 @@ sdbus-c++ provides built-in integration of sd-event, which makes it very conveni See documentation of `IConnection::attachSdEventLoop()`, `IConnection::detachSdEventLoop()`, and `IConnection::getSdEventLoop()` methods, or sdbus-c++ integration tests for an example of use. These methods are sdbus-c++ counterparts to and mimic the behavior of these underlying sd-bus functions: `sd_bus_attach_event()`, `sd_bus_detach_event()`, and `sd_bus_get_event()`. Their manual pages provide much more details about their behavior. +Migrating to sdbus-c++ v2 +------------------------- + +sdbus-c++ v2 is a major release that comes with a number of breaking API/ABI/behavior changes compared to v1. The following list describes the changes: + +* Change in behavior: In *synchronous* D-Bus calls, the proxy object now keeps the connection instance blocked for the entire duration of the method call. Incoming messages like signals will be queued and processed after the call. Access to the connection from other threads is blocked. To avoid this (in case this hurts you): + * either use short-lived, light-weight proxies for such synchronous calls, + * or call the method in an asynchronous way. +* Signatures of callbacks `async_reply_handler`, `signal_handler`, `message_handler` and `property_set_callback` were modified to take input message objects by value instead of non-const ref to a message. The callback handler assumes ownership of the message. This API is cleaner and more self-explaining. +* The `PollData` struct has been extended with a new data member: `eventFd`. All hooks with external event loops shall be modified to poll on this `eventFd` in addition to the `fd`. +* `PollData::timeout_usec` was renamed to `PollData::timeout` and its type has been changed to `std::chrono::microseconds`. This member now holds directly what before had to be obtained through `PollData::getAbsoluteTimeout()` call. +* `PollData::getRelativeTimeout()` return type was changed to `std::chrono::microseconds`. +* `IConnection::processPendingRequest()` was renamed to `IConnection::processPendingEvent()`. +* `Variant` constructor is now explicit. +* `IProxy::getCurrentlyProcessedMessage()` now returns `Message` by value instead of a raw pointer to it. The caller assumes ownership of the message. +* Object D-Bus API registration is now done through `IObject::addVTable()` method. The vtable gets active immediately. No `finishRegistration()` call is needed anymore. vtables can be added and removed dynamically at run time. In addition to API simplification this brings consistency with sd-bus API and increases flexibility. +* Subscription to signals has been simplified. The subscription is active right after the `registerSignalHandler`/`uponSignal()` call. No need for the final call to `finishRegistration()`. +* `IProxy::muteSignal()` and `IProxy::unregisterSignal()` have been removed. When subscribing to a signal, we can ask sdbus-c++ to give us a RAII-based slot object. As long as we keep the slot, the subscription is active. Destroying the slot object implies unsubscribing from the signal. +* `request_slot` tag was renamed to `return_slot`. +* Deprecated `dont_request_slot` was removed. It shall be replaced with `floating_slot`. +* `ProxyInterfaces::getObjectPath()` was removed. It shall be replaced with `ProxyInterfaces::getProxy().getObjectPath()`. +* `AdaptorInterfaces::getObjectPath()` was removed. It can be replaced with `AdaptorInterfaces::getObject().getObjectPath()`. +* `createConnection()` has been removed. To create a connection to the system bus use `createSystemConnection()` instead. +* `createDefaultBusConnection()` has been renamed to `createBusConnection()`. +* Change in behavior: `Proxy`s now by default call `createBusConnection()` to get a connection when the connection is not provided explicitly by the caller, so they connect to either the session bus or the system bus depending on the context (as opposed to always to the system bus like before). +* Callbacks taking `const sdbus::Error* error` were changed to take `std::optional`, which better expresses the intent and meaning. +* Types and methods marked deprecated in sdbus-c++ v1 were removed completely. +* CMake options got `SDBUSCPP_` prefix for better usability and minimal risk of conflicts in downstream CMake projects. `SDBUSCPP_INSTALL` CMake option was added. +* CMake components got `sdbus-c++-` prefix. + +Some of these changes required correspoding adaptations in the sdbus-c++ codegen. Hence, your **C++ bindings (if any) must be re-generated** with the new sdbus-c++-xml2cpp v2 in order to use them with sdbus-c++ v2 API. + Conclusion ---------- From 3a8a3a8c1cd00aa5b208034d469ed23e732756da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Sun, 18 Feb 2024 18:44:15 +0100 Subject: [PATCH 33/52] fix: pass correctly underlying lib to intg tests --- tests/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4681b97d..ffb6d36b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -111,8 +111,8 @@ target_link_libraries(sdbus-c++-unit-tests sdbus-c++-objlib GTest::gmock) add_executable(sdbus-c++-integration-tests ${INTEGRATIONTESTS_SRCS}) target_compile_definitions(sdbus-c++-integration-tests PRIVATE LIBSYSTEMD_VERSION=${SDBUSCPP_LIBSYSTEMD_VERSION} - SDBUS_${LIBSYSTEMD}) -if(NOT LIBSYSTEMD STREQUAL "basu") + SDBUS_${LIBSYSTEMD_IMPL}) +if(NOT LIBSYSTEMD_IMPL STREQUAL "basu") # Systemd::Libsystemd is included because integration tests use sd-event. Otherwise sdbus-c++ encapsulates and hides libsystemd. target_link_libraries(sdbus-c++-integration-tests sdbus-c++ Systemd::Libsystemd GTest::gmock) else() From b2453044f599b991130d209c8634f4f01065c0ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Mon, 19 Feb 2024 19:49:31 +0100 Subject: [PATCH 34/52] fix: conditional use and tests of span (#411) --- include/sdbus-c++/Message.h | 14 ++++++++------ include/sdbus-c++/TypeTraits.h | 8 +++++--- tests/unittests/Message_test.cpp | 4 ++-- tests/unittests/TypeTraits_test.cpp | 8 ++++---- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/include/sdbus-c++/Message.h b/include/sdbus-c++/Message.h index 743604c5..db5f6585 100644 --- a/include/sdbus-c++/Message.h +++ b/include/sdbus-c++/Message.h @@ -36,8 +36,10 @@ #include #include #include -#if __cplusplus >= 202002L -#include +#ifdef __has_include +# if __has_include() +# include +# endif #endif #include #include @@ -104,7 +106,7 @@ namespace sdbus { Message& operator<<(const std::vector<_Element, _Allocator>& items); template Message& operator<<(const std::array<_Element, _Size>& items); -#if __cplusplus >= 202002L +#ifdef __cpp_lib_span template Message& operator<<(const std::span<_Element, _Extent>& items); #endif @@ -140,7 +142,7 @@ namespace sdbus { Message& operator>>(std::vector<_Element, _Allocator>& items); template Message& operator>>(std::array<_Element, _Size>& items); -#if __cplusplus >= 202002L +#ifdef __cpp_lib_span template Message& operator>>(std::span<_Element, _Extent>& items); #endif @@ -339,7 +341,7 @@ namespace sdbus { return *this; } -#if __cplusplus >= 202002L +#ifdef __cpp_lib_span template inline Message& Message::operator<<(const std::span<_Element, _Extent>& items) { @@ -498,7 +500,7 @@ namespace sdbus { return *this; } -#if __cplusplus >= 202002L +#ifdef __cpp_lib_span template inline Message& Message::operator>>(std::span<_Element, _Extent>& items) { diff --git a/include/sdbus-c++/TypeTraits.h b/include/sdbus-c++/TypeTraits.h index e7bc59a3..4fd03435 100644 --- a/include/sdbus-c++/TypeTraits.h +++ b/include/sdbus-c++/TypeTraits.h @@ -35,8 +35,10 @@ #include #include #include -#if __cplusplus >= 202002L -#include +#ifdef __has_include +# if __has_include() +# include +# endif #endif #include #include @@ -399,7 +401,7 @@ namespace sdbus { } }; -#if __cplusplus >= 202002L +#ifdef __cpp_lib_span template struct signature_of> { diff --git a/tests/unittests/Message_test.cpp b/tests/unittests/Message_test.cpp index 73e792f2..c91f230c 100644 --- a/tests/unittests/Message_test.cpp +++ b/tests/unittests/Message_test.cpp @@ -311,7 +311,7 @@ TEST(AMessage, CanCarryDBusArrayOfNontrivialTypesGivenAsStdArray) ASSERT_THAT(dataRead, Eq(dataWritten)); } -#if __cplusplus >= 202002L +#ifdef __cpp_lib_span TEST(AMessage, CanCarryDBusArrayOfTrivialTypesGivenAsStdSpan) { auto msg = sdbus::createPlainMessage(); @@ -378,7 +378,7 @@ TEST(AMessage, ThrowsWhenDestinationStdArrayIsTooSmallDuringDeserialization) ASSERT_THROW(msg >> dataRead, sdbus::Error); } -#if __cplusplus >= 202002L +#ifdef __cpp_lib_span TEST(AMessage, ThrowsWhenDestinationStdSpanIsTooSmallDuringDeserialization) { auto msg = sdbus::createPlainMessage(); diff --git a/tests/unittests/TypeTraits_test.cpp b/tests/unittests/TypeTraits_test.cpp index 9f698950..3e7e092e 100644 --- a/tests/unittests/TypeTraits_test.cpp +++ b/tests/unittests/TypeTraits_test.cpp @@ -88,13 +88,13 @@ namespace TYPE(sdbus::ObjectPath)HAS_DBUS_TYPE_SIGNATURE("o") TYPE(sdbus::Signature)HAS_DBUS_TYPE_SIGNATURE("g") TYPE(sdbus::Variant)HAS_DBUS_TYPE_SIGNATURE("v") + TYPE(std::variant)HAS_DBUS_TYPE_SIGNATURE("v") TYPE(sdbus::UnixFd)HAS_DBUS_TYPE_SIGNATURE("h") TYPE(sdbus::Struct)HAS_DBUS_TYPE_SIGNATURE("(b)") TYPE(sdbus::Struct)HAS_DBUS_TYPE_SIGNATURE("(qdsv)") TYPE(std::vector)HAS_DBUS_TYPE_SIGNATURE("an") TYPE(std::array)HAS_DBUS_TYPE_SIGNATURE("an") - TYPE(std::variant)HAS_DBUS_TYPE_SIGNATURE("v") -#if __cplusplus >= 202002L +#ifdef __cpp_lib_span TYPE(std::span)HAS_DBUS_TYPE_SIGNATURE("an") #endif TYPE(SomeEnumClass)HAS_DBUS_TYPE_SIGNATURE("y") @@ -138,13 +138,13 @@ namespace , sdbus::ObjectPath , sdbus::Signature , sdbus::Variant + , std::variant , sdbus::UnixFd , sdbus::Struct , sdbus::Struct , std::vector , std::array - , std::variant -#if __cplusplus >= 202002L +#ifdef __cpp_lib_span , std::span #endif , SomeEnumClass From 30ee5a38d83932de14195a849d60e38c76f03a81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Mon, 19 Feb 2024 19:53:01 +0100 Subject: [PATCH 35/52] chore(cmake): implement more flexible searching for sd-bus libs (#409) A new CMake configuration variable SDBUSCPP_SDBUS_LIB has been introduced that enables clients to specify which sd-bus implementation library shall be used by sdbus-c++. --- CMakeLists.txt | 90 ++++++++++++++++++++++++----------------- ChangeLog | 1 + README.md | 16 ++++++-- docs/using-sdbus-c++.md | 16 ++++---- tests/CMakeLists.txt | 8 ++-- 5 files changed, 80 insertions(+), 51 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 604d64ce..d9597dfe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,19 +12,22 @@ include(GNUInstallDirs) # Installation directories for `install` command and pkg # CONFIGURATION OPTIONS # ------------------------------- -option(SDBUSCPP_BUILD_LIBSYSTEMD "Fetch & build libsystemd static library and make it part of libsdbus-c++, instead of searching for libsystemd in the system" OFF) -if(SDBUSCPP_BUILD_LIBSYSTEMD) +option(SDBUSCPP_BUILD_LIBSYSTEMD "Fetch & build libsystemd static library and make it part of libsdbus-c++, instead of searching for it in the system" OFF) +if(NOT SDBUSCPP_BUILD_LIBSYSTEMD) + set(SDBUSCPP_SDBUS_LIB "default" CACHE STRING "sd-bus implementation library to search for and use (default, systemd, elogind, or basu)") + set_property(CACHE SDBUSCPP_SDBUS_LIB PROPERTY STRINGS default systemd elogind basu) +else() set(SDBUSCPP_LIBSYSTEMD_VERSION "242" CACHE STRING "libsystemd version (>=239) to build and incorporate into libsdbus-c++") set(SDBUSCPP_LIBSYSTEMD_EXTRA_CONFIG_OPTS "" CACHE STRING "Additional configuration options to be passed as-is to libsystemd build system") endif() option(SDBUSCPP_INSTALL "Enable installation of sdbus-c++ (downstream projects embedding sdbus-c++ may want to turn this OFF)" ON) option(SDBUSCPP_BUILD_TESTS "Build tests" OFF) if (SDBUSCPP_BUILD_TESTS) - option(SDBUSCPP_BUILD_PERF_TESTS "Build also sdbus-c++ performance tests" OFF) # tranferred from tests/cmake - option(SDBUSCPP_BUILD_STRESS_TESTS "Build also sdbus-c++ stress tests" OFF) # tranferred from tests/cmake - set(SDBUSCPP_TESTS_INSTALL_PATH "tests/${PROJECT_NAME}" CACHE STRING "Specifies where the test binaries will be installed") # tranferred from tests/cmake - set(SDBUSCPP_GOOGLETEST_VERSION 1.10.0 CACHE STRING "Version of gmock library to use") # tranferred from tests/cmake - set(SDBUSCPP_GOOGLETEST_GIT_REPO "https://github.com/google/googletest.git" CACHE STRING "A git repo to clone and build googletest from if gmock is not found in the system") # tranferred from tests/cmake + option(SDBUSCPP_BUILD_PERF_TESTS "Build also sdbus-c++ performance tests" OFF) + option(SDBUSCPP_BUILD_STRESS_TESTS "Build also sdbus-c++ stress tests" OFF) + set(SDBUSCPP_TESTS_INSTALL_PATH "tests/${PROJECT_NAME}" CACHE STRING "Specifies where the test binaries will be installed") + set(SDBUSCPP_GOOGLETEST_VERSION 1.10.0 CACHE STRING "Version of gmock library to use") + set(SDBUSCPP_GOOGLETEST_GIT_REPO "https://github.com/google/googletest.git" CACHE STRING "A git repo to clone and build googletest from if gmock is not found in the system") endif() option(SDBUSCPP_BUILD_CODEGEN "Build generator tool for C++ native bindings" OFF) option(SDBUSCPP_BUILD_EXAMPLES "Build example programs" OFF) @@ -71,43 +74,56 @@ message(STATUS "") # PERFORMING CHECKS & PREPARING THE DEPENDENCIES #------------------------------- -set(LIBSYSTEMD_IMPL "systemd") -set(LIBSYSTEMD_LIB "libsystemd") +if(SDBUSCPP_BUILD_LIBSYSTEMD) + # Build static libsystemd library as an external project + include(cmake/LibsystemdExternalProject.cmake) + set(SDBUS_IMPL "systemd") + set(SDBUS_LIB "libsystemd") +else() + # Search for sd-bus implementations in the system as per user's configuration + set(SDBUS_LIBS ${SDBUSCPP_SDBUS_LIB}) + if(SDBUSCPP_SDBUS_LIB STREQUAL "default") + set(SDBUS_LIBS systemd elogind basu) # This is the default search order + endif() -if(NOT SDBUSCPP_BUILD_LIBSYSTEMD) find_package(PkgConfig REQUIRED) - pkg_check_modules(Systemd IMPORTED_TARGET GLOBAL libsystemd>=238) - if(NOT TARGET PkgConfig::Systemd) - message(WARNING "libsystemd not found, checking for libelogind instead") - pkg_check_modules(Systemd IMPORTED_TARGET GLOBAL libelogind>=238) - if(TARGET PkgConfig::Systemd) - set(LIBSYSTEMD_IMPL "elogind") - set(LIBSYSTEMD_LIB "libelogind") - string(REPLACE "." ";" VERSION_LIST ${Systemd_VERSION}) - list(GET VERSION_LIST 0 Systemd_VERSION) - else() - message(WARNING "libelogind not found, checking for basu instead") + foreach(LIB ${SDBUS_LIBS}) + if(LIB STREQUAL "systemd") + pkg_check_modules(Systemd IMPORTED_TARGET GLOBAL libsystemd>=238) + if(TARGET PkgConfig::Systemd) + set(SDBUS_IMPL "systemd") + set(SDBUS_LIB "libsystemd") + break() + endif() + elseif(LIB STREQUAL "elogind") + pkg_check_modules(Systemd IMPORTED_TARGET GLOBAL libelogind>=238) + if(TARGET PkgConfig::Systemd) + set(SDBUS_IMPL "elogind") + set(SDBUS_LIB "libelogind") + string(REPLACE "." ";" VERSION_LIST ${Systemd_VERSION}) + list(GET VERSION_LIST 0 Systemd_VERSION) + break() + endif() + elseif(LIB STREQUAL "basu") pkg_check_modules(Systemd IMPORTED_TARGET GLOBAL basu) - set(LIBSYSTEMD_IMPL "basu") - set(LIBSYSTEMD_LIB "basu") - # https://git.sr.ht/~emersion/basu/commit/d4d185d29a26 - set(Systemd_VERSION "240") + if(TARGET PkgConfig::Systemd) + set(SDBUS_IMPL "basu") + set(SDBUS_LIB "basu") + set(Systemd_VERSION "240") # https://git.sr.ht/~emersion/basu/commit/d4d185d29a26 + break() + endif() + else() + message(FATAL_ERROR "Unrecognized sd-bus implementation library ${LIB}") endif() - endif() + endforeach() + if(NOT TARGET PkgConfig::Systemd) - message(FATAL_ERROR "libsystemd of version at least 238 is required, but was not found " - "(if you have systemd in your OS, you may want to install package containing pkgconfig " - " files for libsystemd library. On Ubuntu, that is libsystemd-dev. " - " Alternatively, you may turn SDBUSCPP_BUILD_LIBSYSTEMD on for sdbus-c++ to download, " - " build and incorporate libsystemd as embedded library within sdbus-c++)") + message(FATAL_ERROR "None of ${SDBUS_LIBS} libraries implementing sd-bus was found (Are their dev packages installed?)") endif() add_library(Systemd::Libsystemd ALIAS PkgConfig::Systemd) string(REGEX MATCHALL "([0-9]+)" SYSTEMD_VERSION_LIST "${Systemd_VERSION}") list(GET SYSTEMD_VERSION_LIST 0 SDBUSCPP_LIBSYSTEMD_VERSION) message(STATUS "Building with libsystemd v${SDBUSCPP_LIBSYSTEMD_VERSION}") -else() - # Build static libsystemd library as an external project - include(cmake/LibsystemdExternalProject.cmake) endif() find_package(Threads REQUIRED) @@ -182,8 +198,8 @@ add_library(sdbus-c++-objlib OBJECT ${SDBUSCPP_SRCS}) target_compile_definitions(sdbus-c++-objlib PRIVATE BUILD_LIB=1 LIBSYSTEMD_VERSION=${SDBUSCPP_LIBSYSTEMD_VERSION} - SDBUS_${LIBSYSTEMD_IMPL} - SDBUS_HEADER=<${LIBSYSTEMD_IMPL}/sd-bus.h>) + SDBUS_${SDBUS_IMPL} + SDBUS_HEADER=<${SDBUS_IMPL}/sd-bus.h>) target_include_directories(sdbus-c++-objlib PUBLIC $ $) if(BUILD_SHARED_LIBS) @@ -259,7 +275,7 @@ if(BUILD_SHARED_LIBS AND (SDBUSCPP_BUILD_LIBSYSTEMD OR Systemd_LINK_LIBRARIES MA else() set(PKGCONFIG_REQS "") endif() -set(PKGCONFIG_DEPS ${LIBSYSTEMD_LIB}) +set(PKGCONFIG_DEPS ${SDBUS_LIB}) configure_file(pkgconfig/sdbus-c++.pc.in pkgconfig/sdbus-c++.pc @ONLY) #---------------------------------- diff --git a/ChangeLog b/ChangeLog index b83dd774..e79b6cff 100644 --- a/ChangeLog +++ b/ChangeLog @@ -263,4 +263,5 @@ v2.0.0 - Introduce native integration for sd-event - Add method to get currently processed message also to `IConnection` - `[[nodiscard]]` attribute has been added to relevant API methods. +- Add new `SDBUSCPP_SDBUS_LIB` CMake configuration variable determining which sd-bus library shall be picked - Other simplifications, improvements and fixes springing out from the above refactoring diff --git a/README.md b/README.md index 894322f6..cb432fa3 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,17 @@ $ sudo cmake --build . --target install * `SDBUSCPP_BUILD_LIBSYSTEMD` [boolean] - Option for building libsystemd dependency library automatically when sdbus-c++ is built, and making libsystemd an integral part of sdbus-c++ library. Default value: `OFF`. Might be very helpful in non-systemd environments where libsystemd shared library is unavailable (see [Solving libsystemd dependency](docs/using-sdbus-c++.md#solving-libsystemd-dependency) for more information). With this option turned on, you may also provide the following configuration flag: + Option for building libsystemd as a sd-bus implementation when sdbus-c++ is built, and making libsystemd an integral part of sdbus-c++ library. Default value: `OFF`, which means that the sd-bus implementation library (`libsystemd`, `libelogind`, or `basu`) will be searched via `pkg-config` in the system. + + This option may be very helpful in environments where sd-bus implementation library is unavailable (see [Solving sd-bus dependency](docs/using-sdbus-c++.md#solving-sd-bus-dependency) for more information). + + With this option turned off, you may provide the following additional configuration flag: + + * `SDBUSCPP_SDBUS_LIB` [string] + + Defines which sd-bus implementation library to search for and use. Allowed values: `default`, `systemd`, `elogind`, `basu`. Default value: `default`, which means that sdbus-c++ will try to find any of `systemd`, `elogind`, `basu` in the order as listed here. + + With this option turned on, you may provide the following additional configuration flag: * `SDBUSCPP_LIBSYSTEMD_VERSION` [string] @@ -82,10 +92,10 @@ Dependencies ------------ * `C++17` - the library uses C++17 features. -* `libsystemd`/`libelogind`/`basu` - libraries containing sd-bus implementation that sdbus-c++ is written around. In case of `libsystemd` and `libelogind`, version >= 236 is needed. (In case you have a non-systemd environment, don't worry, see [Solving libsystemd dependency](docs/using-sdbus-c++.md#solving-libsystemd-dependency) for more information.) +* `libsystemd`/`libelogind`/`basu` - libraries containing sd-bus implementation that sdbus-c++ is written around. In case of `libsystemd` and `libelogind`, version >= 236 is needed. (In case you have you're missing any of those sd-bus implementations, don't worry, see [Solving sd-bus dependency](docs/using-sdbus-c++.md#solving-sd-bus-dependency) for more information.) * `googletest` - google unit testing framework, only necessary when building tests, will be downloaded and built automatically. * `pkgconfig` - required for sdbus-c++ to be able to find some dependency packages. -* `expat` - necessary when building the xml2cpp binding code generator (`SDBUSCPP_BUILD_CODEGEN` option is ON). +* `expat` - necessary when building the xml2cpp binding code generator (`SDBUSCPP_BUILD_CODEGEN` option is `ON`). Licensing --------- diff --git a/docs/using-sdbus-c++.md b/docs/using-sdbus-c++.md index 011a7947..0e537dc9 100644 --- a/docs/using-sdbus-c++.md +++ b/docs/using-sdbus-c++.md @@ -5,7 +5,7 @@ Using sdbus-c++ library 1. [Introduction](#introduction) 2. [Integrating sdbus-c++ into your project](#integrating-sdbus-c-into-your-project) -3. [Solving libsystemd dependency](#solving-libsystemd-dependency) +3. [Solving sd-bus dependency](#solving-sd-bus-dependency) 4. [Distributing sdbus-c++](#distributing-sdbus-c) 5. [Header files and namespaces](#header-files-and-namespaces) 6. [Error signalling and propagation](#error-signalling-and-propagation) @@ -74,14 +74,14 @@ add_custom_command( ) ``` -Solving libsystemd dependency ------------------------------ +Solving sd-bus dependency +------------------------- -sdbus-c++ depends on sd-bus API, which is implemented in libsystemd, a C library that is part of [systemd](https://github.com/systemd/systemd). +sdbus-c++ is a wrapper around sd-bus, a C library that has been written as part of [systemd](https://github.com/systemd/systemd) project. -Minimum required libsystemd shared library version is 0.20.0 (which corresponds to minimum systemd version 236). +Within systemd, sd-bus is implemented as part of `libsystemd` shared library. At least version 0.22.0 (which corresponds to the minimum systemd version 238) of `libsystemd` is needed. -If your target Linux distribution is already based on systemd ecosystem of version 236 and higher, then there is no additional effort, just make sure you have corresponding systemd header files available (provided by `libsystemd-dev` package on Debian/Ubuntu, for example), and you may go on building sdbus-c++ seamlessly. +If your target Linux distribution is already based on systemd ecosystem of version 238 and higher, then there is no additional effort, just make sure you have corresponding systemd header files available (provided by `libsystemd-dev` package on Debian/Ubuntu, for example), and you may go on building sdbus-c++ seamlessly. However, sdbus-c++ can perfectly be used in non-systemd environments as well. If `libsystemd` is not found in the system when configuring sdbus-c++, then @@ -89,7 +89,9 @@ However, sdbus-c++ can perfectly be used in non-systemd environments as well. If 2. sdbus-c++ will try to find `basu`, which is just sd-bus implementation extracted from systemd. -On systems where neither of these is available, we can build sd-bus as a shared lib manually or we can (conveniently) instruct sdbus-c++ to build and integrate sd-bus into itself for us. +Alternatively to this fallback search sequence, you may explicitly instruct sdbus-c++ to use a specified sd-bus implementation through the `SDBUSCPP_SDBUS_LIB` CMake configuration option. + +On systems where neither of those libraries is available, we can build sd-bus manually, or we can (conveniently) instruct sdbus-c++ to build and integrate sd-bus into itself for us. ### Building and distributing libsystemd as a shared library yourself diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ffb6d36b..507fd5bb 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -104,15 +104,15 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}) add_executable(sdbus-c++-unit-tests ${UNITTESTS_SRCS}) target_compile_definitions(sdbus-c++-unit-tests PRIVATE LIBSYSTEMD_VERSION=${SDBUSCPP_LIBSYSTEMD_VERSION} - SDBUS_${LIBSYSTEMD_IMPL} - SDBUS_HEADER=<${LIBSYSTEMD_IMPL}/sd-bus.h>) + SDBUS_${SDBUS_IMPL} + SDBUS_HEADER=<${SDBUS_IMPL}/sd-bus.h>) target_link_libraries(sdbus-c++-unit-tests sdbus-c++-objlib GTest::gmock) add_executable(sdbus-c++-integration-tests ${INTEGRATIONTESTS_SRCS}) target_compile_definitions(sdbus-c++-integration-tests PRIVATE LIBSYSTEMD_VERSION=${SDBUSCPP_LIBSYSTEMD_VERSION} - SDBUS_${LIBSYSTEMD_IMPL}) -if(NOT LIBSYSTEMD_IMPL STREQUAL "basu") + SDBUS_${SDBUS_IMPL}) +if(NOT SDBUS_IMPL STREQUAL "basu") # Systemd::Libsystemd is included because integration tests use sd-event. Otherwise sdbus-c++ encapsulates and hides libsystemd. target_link_libraries(sdbus-c++-integration-tests sdbus-c++ Systemd::Libsystemd GTest::gmock) else() From 79abb4e19c2dd3b442433ee43afb7f7d69b1ea28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Tue, 20 Feb 2024 16:02:26 +0100 Subject: [PATCH 36/52] fix(cmake): rename forgotten CMake variables --- tests/CMakeLists.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 507fd5bb..27e05e7b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -2,12 +2,12 @@ # DOWNLOAD AND BUILD OF GOOGLETEST #------------------------------- -find_package(GTest ${GOOGLETEST_VERSION} CONFIG) +find_package(GTest ${SDBUSCPP_GOOGLETEST_VERSION} CONFIG) if (NOT TARGET GTest::gmock) # Try pkg-config if GTest was not found through CMake config find_package(PkgConfig) if (PkgConfig_FOUND) - pkg_check_modules(GMock IMPORTED_TARGET GLOBAL gmock>=${GOOGLETEST_VERSION}) + pkg_check_modules(GMock IMPORTED_TARGET GLOBAL gmock>=${SDBUSCPP_GOOGLETEST_VERSION}) if(TARGET PkgConfig::GMock) add_library(GTest::gmock ALIAS PkgConfig::GMock) endif() @@ -16,15 +16,15 @@ if (NOT TARGET GTest::gmock) if (NOT TARGET GTest::gmock) include(FetchContent) - if (GOOGLETEST_VERSION VERSION_GREATER_EQUAL 1.13.0) - set(GOOGLETEST_TAG "v${GOOGLETEST_VERSION}") + if (SDBUSCPP_GOOGLETEST_VERSION VERSION_GREATER_EQUAL 1.13.0) + set(GOOGLETEST_TAG "v${SDBUSCPP_GOOGLETEST_VERSION}") else() - set(GOOGLETEST_TAG "release-${GOOGLETEST_VERSION}") + set(GOOGLETEST_TAG "release-${SDBUSCPP_GOOGLETEST_VERSION}") endif() - message("Manually fetching & building googletest v${GOOGLETEST_VERSION}...") + message("Manually fetching & building googletest ${GOOGLETEST_TAG}...") FetchContent_Declare(googletest - GIT_REPOSITORY ${GOOGLETEST_GIT_REPO} + GIT_REPOSITORY ${SDBUSCPP_GOOGLETEST_GIT_REPO} GIT_TAG ${GOOGLETEST_TAG} GIT_SHALLOW 1 UPDATE_COMMAND "") From e17720723b3825edbf1eaa4a3bac1cb25d4a5808 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Tue, 20 Feb 2024 16:27:42 +0100 Subject: [PATCH 37/52] chore: use C++20 standard (#410) As of now sdbus-c++ supports C++20 but does not require it, and the used C++20 features are conditionally compiled depending on whether they are available or not. --- .github/workflows/ci.yml | 37 ++++++++++++++++++++----------------- CMakeLists.txt | 2 +- ChangeLog | 1 + src/Message.cpp | 6 +++++- 4 files changed, 27 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2fb3a1c4..f79e3df1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,38 +44,41 @@ jobs: sudo update-alternatives --install /usr/bin/cc cc /usr/bin/clang 10 sudo update-alternatives --remove-all c++ sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++ 10 - - name: install-googletest - if: matrix.os == 'ubuntu-22.04' - run: | - sudo apt-get install -y libgmock-dev - - name: install-googletest - if: matrix.os == 'ubuntu-20.04' # On older ubuntus the libgmock-dev package is either unavailable or has faulty pkg-config file, so we build & install manually - run: | - git clone https://github.com/google/googletest.git - cd googletest - mkdir build - cd build - cmake .. - cmake --build . -j4 - sudo cmake --build . --target install + # We need to use libc++ with Clang because there is a bug in libstdc++ chrono headers that Clang has issues with + echo "SDBUSCPP_EXTRA_CXX_FLAGS=-stdlib=libc++" >> $GITHUB_ENV +# We don't install googletest but we let it be built within sdbus-c++ builds below, since it needs to be built against libc++ for Clang jobs to pass +# - name: install-googletest +# if: matrix.os == 'ubuntu-22.04' +# run: | +# sudo apt-get install -y libgmock-dev +# - name: install-googletest +# if: matrix.os == 'ubuntu-20.04' # On older ubuntus the libgmock-dev package is either unavailable or has faulty pkg-config file, so we build & install manually +# run: | +# git clone https://github.com/google/googletest.git +# cd googletest +# mkdir build +# cd build +# cmake .. -DCMAKE_CXX_FLAGS="$SDBUSCPP_EXTRA_CXX_FLAGS" +# cmake --build . -j4 +# sudo cmake --build . --target install - name: configure-debug if: matrix.build == 'shared-libsystemd' && matrix.os == 'ubuntu-20.04' run: | mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_CXX_FLAGS="-O0 -g -W -Wextra -Wall -Wnon-virtual-dtor -Werror" -DCMAKE_VERBOSE_MAKEFILE=ON -DSDBUSCPP_INSTALL=ON -DSDBUSCPP_BUILD_TESTS=ON -DSDBUSCPP_BUILD_PERF_TESTS=ON -DSDBUSCPP_BUILD_STRESS_TESTS=ON -DSDBUSCPP_BUILD_CODEGEN=ON .. + cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_CXX_FLAGS="-O0 -g -W -Wextra -Wall -Wnon-virtual-dtor -Werror $SDBUSCPP_EXTRA_CXX_FLAGS" -DCMAKE_VERBOSE_MAKEFILE=ON -DSDBUSCPP_INSTALL=ON -DSDBUSCPP_BUILD_TESTS=ON -DSDBUSCPP_BUILD_PERF_TESTS=ON -DSDBUSCPP_BUILD_STRESS_TESTS=ON -DSDBUSCPP_BUILD_CODEGEN=ON .. - name: configure-release if: matrix.build == 'shared-libsystemd' && matrix.os == 'ubuntu-22.04' run: | mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_CXX_FLAGS="-O3 -DNDEBUG -W -Wextra -Wall -Wnon-virtual-dtor -Werror" -DCMAKE_VERBOSE_MAKEFILE=ON -DSDBUSCPP_INSTALL=ON -DSDBUSCPP_BUILD_TESTS=ON -DSDBUSCPP_BUILD_PERF_TESTS=ON -DSDBUSCPP_BUILD_STRESS_TESTS=ON -DSDBUSCPP_BUILD_CODEGEN=ON .. + cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_CXX_FLAGS="-O3 -DNDEBUG -W -Wextra -Wall -Wnon-virtual-dtor -Werror $SDBUSCPP_EXTRA_CXX_FLAGS" -DCMAKE_VERBOSE_MAKEFILE=ON -DSDBUSCPP_INSTALL=ON -DSDBUSCPP_BUILD_TESTS=ON -DSDBUSCPP_BUILD_PERF_TESTS=ON -DSDBUSCPP_BUILD_STRESS_TESTS=ON -DSDBUSCPP_BUILD_CODEGEN=ON -DSDBUSCPP_GOOGLETEST_VERSION=1.14.0 .. - name: configure-with-embedded-libsystemd if: matrix.build == 'embedded-static-libsystemd' run: | mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_VERBOSE_MAKEFILE=ON -DSDBUSCPP_INSTALL=ON -DSDBUSCPP_BUILD_TESTS=ON -DSDBUSCPP_BUILD_PERF_TESTS=ON -DSDBUSCPP_BUILD_STRESS_TESTS=ON -DSDBUSCPP_BUILD_CODEGEN=ON -DSDBUSCPP_BUILD_LIBSYSTEMD=ON -DSDBUSCPP_LIBSYSTEMD_VERSION=252 .. + cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_CXX_FLAGS="$SDBUSCPP_EXTRA_CXX_FLAGS" -DCMAKE_VERBOSE_MAKEFILE=ON -DSDBUSCPP_INSTALL=ON -DSDBUSCPP_BUILD_TESTS=ON -DSDBUSCPP_BUILD_PERF_TESTS=ON -DSDBUSCPP_BUILD_STRESS_TESTS=ON -DSDBUSCPP_BUILD_CODEGEN=ON -DSDBUSCPP_BUILD_LIBSYSTEMD=ON -DSDBUSCPP_LIBSYSTEMD_VERSION=252 -DSDBUSCPP_GOOGLETEST_VERSION=1.14.0 .. - name: make run: | cd build diff --git a/CMakeLists.txt b/CMakeLists.txt index d9597dfe..7bbd8911 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -184,7 +184,7 @@ set(SDBUSCPP_SRCS ${SDBUSCPP_CPP_SRCS} ${SDBUSCPP_HDR_SRCS} ${SDBUSCPP_PUBLIC_HD # GENERAL COMPILER CONFIGURATION #------------------------------- -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) #---------------------------------- # LIBRARY BUILD INFORMATION diff --git a/ChangeLog b/ChangeLog index e79b6cff..fbc8b7df 100644 --- a/ChangeLog +++ b/ChangeLog @@ -264,4 +264,5 @@ v2.0.0 - Add method to get currently processed message also to `IConnection` - `[[nodiscard]]` attribute has been added to relevant API methods. - Add new `SDBUSCPP_SDBUS_LIB` CMake configuration variable determining which sd-bus library shall be picked +- Switch to C++20 standard (but C++20 is not required, and the used C++20 features are conditionally compiled) - Other simplifications, improvements and fixes springing out from the above refactoring diff --git a/src/Message.cpp b/src/Message.cpp index 3ac89ae3..22227266 100644 --- a/src/Message.cpp +++ b/src/Message.cpp @@ -884,7 +884,11 @@ namespace { // Please note that the solution is NOT thread-safe. // Another common solution is global sdbus-c++ startup/shutdown functions, but that would be an intrusive change. -/*constinit (C++20 keyword) */ static bool pseudoConnectionDestroyed{}; +#ifdef __cpp_constinit +constinit static bool pseudoConnectionDestroyed{}; +#else +static bool pseudoConnectionDestroyed{}; +#endif std::unique_ptr createPseudoConnection() { From 877497fd0396b16bc8b53c813e9f6d649670ede4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Tue, 20 Feb 2024 21:57:26 +0100 Subject: [PATCH 38/52] fix(tests): fix intermittently failing integration tests (#412) --- tests/integrationtests/DBusGeneralTests.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/integrationtests/DBusGeneralTests.cpp b/tests/integrationtests/DBusGeneralTests.cpp index 0e5b356c..1164f6cd 100644 --- a/tests/integrationtests/DBusGeneralTests.cpp +++ b/tests/integrationtests/DBusGeneralTests.cpp @@ -58,6 +58,8 @@ TEST(AdaptorAndProxy, CanBeConstructedSuccesfully) ASSERT_NO_THROW(TestAdaptor adaptor(*connection, OBJECT_PATH)); ASSERT_NO_THROW(TestProxy proxy(BUS_NAME, OBJECT_PATH)); + + connection->releaseName(BUS_NAME); } TEST(AProxy, SupportsMoveSemantics) From 7127e8ed6e4126aa411a4df5c91e92ba74d1b330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Fri, 29 Mar 2024 13:23:44 +0100 Subject: [PATCH 39/52] refactor: add strong types to public API (#414) This introduces strong types for `std::string`-based D-Bus types. This facilitates safer, less error-prone and more expressive API. What previously was `auto proxy = createProxy("org.sdbuscpp.concatenator", "/org/sdbuscpp/concatenator");` is now written like `auto proxy = createProxy(ServiceName{"org.sdbuscpp.concatenator"}, ObjectPath{"/org/sdbuscpp/concatenator"});`. These types are: * `ObjectPath` type for the object path (the type has been around already but now is also used consistently in sdbus-c++ API for object path strings), * `InterfaceName` type for D-Bus interface names, * `BusName` (and its aliases `ServiceName` and `ConnectionName`) type for bus/service/connection names, * `MemberName` (and its aliases `MethodName`, `SignalName` and `PropertyName`) type for D-Bus method, signal and property names, * `Signature` type for the D-Bus signature (the type has been around already but now is also used consistently in sdbus-c++ API for signature strings), * `Error::Name` type for D-Bus error names. --- docs/using-sdbus-c++.md | 123 ++++++++------- .../examplemanager-planet1-client-glue.h | 2 +- .../examplemanager-planet1-server-glue.h | 2 +- .../obj-manager-client.cpp | 29 ++-- .../obj-manager-server.cpp | 19 ++- include/sdbus-c++/AdaptorInterfaces.h | 2 +- include/sdbus-c++/ConvenienceApiClasses.h | 76 ++++++--- include/sdbus-c++/ConvenienceApiClasses.inl | 146 +++++++++++++---- include/sdbus-c++/Error.h | 33 ++-- include/sdbus-c++/IConnection.h | 24 +-- include/sdbus-c++/IObject.h | 54 +++++-- include/sdbus-c++/IProxy.h | 147 ++++++++++++++---- include/sdbus-c++/Message.h | 16 +- include/sdbus-c++/ProxyInterfaces.h | 10 +- include/sdbus-c++/StandardInterfaces.h | 102 ++++++++---- include/sdbus-c++/TypeTraits.h | 18 +++ include/sdbus-c++/Types.h | 101 +++++++++--- include/sdbus-c++/VTableItems.h | 18 ++- include/sdbus-c++/VTableItems.inl | 21 ++- src/Connection.cpp | 72 ++++----- src/Connection.h | 70 +++++---- src/Error.cpp | 12 +- src/IConnection.h | 56 ++++--- src/Message.cpp | 33 ++-- src/Object.cpp | 36 +++-- src/Object.h | 44 +++--- src/Proxy.cpp | 44 +++--- src/Proxy.h | 29 ++-- src/Utils.h | 6 +- .../DBusAsyncMethodsTests.cpp | 4 +- .../integrationtests/DBusConnectionTests.cpp | 23 +-- tests/integrationtests/DBusGeneralTests.cpp | 14 +- tests/integrationtests/DBusMethodsTests.cpp | 24 +-- tests/integrationtests/DBusSignalsTests.cpp | 16 +- .../DBusStandardInterfacesTests.cpp | 72 ++++----- tests/integrationtests/Defs.h | 19 ++- tests/integrationtests/TestAdaptor.cpp | 12 +- tests/integrationtests/TestAdaptor.h | 10 +- tests/integrationtests/TestFixture.h | 8 +- tests/integrationtests/TestProxy.cpp | 20 +-- tests/integrationtests/TestProxy.h | 32 ++-- .../integrationtests-adaptor.h | 2 +- .../integrationtests/integrationtests-proxy.h | 2 +- tests/perftests/client.cpp | 8 +- tests/perftests/perftests-adaptor.h | 2 +- tests/perftests/perftests-proxy.h | 2 +- tests/perftests/server.cpp | 8 +- .../stresstests/celsius-thermometer-adaptor.h | 2 +- tests/stresstests/celsius-thermometer-proxy.h | 2 +- tests/stresstests/concatenator-adaptor.h | 2 +- tests/stresstests/concatenator-proxy.h | 2 +- .../fahrenheit-thermometer-adaptor.h | 4 +- .../fahrenheit-thermometer-proxy.h | 4 +- tests/stresstests/sdbus-c++-stress-tests.cpp | 29 ++-- tests/unittests/Connection_test.cpp | 9 +- tests/unittests/Message_test.cpp | 8 +- tests/unittests/TypeTraits_test.cpp | 6 + tests/unittests/Types_test.cpp | 13 +- tools/xml2cpp-codegen/AdaptorGenerator.cpp | 2 +- tools/xml2cpp-codegen/ProxyGenerator.cpp | 2 +- 60 files changed, 1086 insertions(+), 622 deletions(-) diff --git a/docs/using-sdbus-c++.md b/docs/using-sdbus-c++.md index 0e537dc9..a6c127a5 100644 --- a/docs/using-sdbus-c++.md +++ b/docs/using-sdbus-c++.md @@ -184,7 +184,7 @@ The following diagram illustrates the major entities in sdbus-c++. ![class](sdbus-c++-class-diagram.png) -`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. An I/O event loop should be run on the bus 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 well-known service names to those connections. An I/O event loop should be run on the bus connection. `IObject` represents the concept of an object that exposes its methods, signals and properties. Its responsibilities are: @@ -238,7 +238,7 @@ Let's have an object `/org/sdbuscpp/concatenator` that implements the `org.sdbus In the following sections, we will elaborate on the ways of implementing such an object on both the server and the client side. -> **Before running Concatenator example in your system:** In order for your service to be allowed to provide a D-Bus API on system bus, a D-Bus security policy file has to be put in place for that service. Otherwise the service will fail to start (you'll get `[org.freedesktop.DBus.Error.AccessDenied] Failed to request bus name (Permission denied)`, for example). To make the Concatenator example work in your system, [look in this section of systemd configuration](systemd-dbus-config.md#dbus-configuration) for how to name the file, where to place it, how to populate it. For further information, consult [dbus-daemon documentation](https://dbus.freedesktop.org/doc/dbus-daemon.1.html), sections *INTEGRATING SYSTEM SERVICES* and *CONFIGURATION FILE*. As an example used for sdbus-c++ integration tests, you may look at the [policy file for sdbus-c++ integration tests](/tests/integrationtests/files/org.sdbuscpp.integrationtests.conf). +> **Before running Concatenator example in your system:** In order for your service to be allowed to provide a D-Bus API on ** the system bus**, a D-Bus security policy file has to be put in place for that service. Otherwise the service will fail to start (you'll get `[org.freedesktop.DBus.Error.AccessDenied] Failed to request bus name (Permission denied)`, for example). To make the Concatenator example work in your system, [look in this section of systemd configuration](systemd-dbus-config.md#dbus-configuration) for how to name the file, where to place it, how to populate it. For further information, consult [dbus-daemon documentation](https://dbus.freedesktop.org/doc/dbus-daemon.1.html), sections *INTEGRATING SYSTEM SERVICES* and *CONFIGURATION FILE*. As an example used for sdbus-c++ integration tests, you may look at the [policy file for sdbus-c++ integration tests](/tests/integrationtests/files/org.sdbuscpp.integrationtests.conf). Implementing the Concatenator example using basic sdbus-c++ API layer --------------------------------------------------------------------- @@ -289,28 +289,29 @@ void concatenate(sdbus::MethodCall call) reply.send(); // Emit 'concatenated' signal - const char* interfaceName = "org.sdbuscpp.Concatenator"; - auto signal = g_concatenator->createSignal(interfaceName, "concatenated"); + sdbus::InterfaceName interfaceName{"org.sdbuscpp.Concatenator"}; + sdbus::SignalName signalName{"concatenated"}; + auto signal = g_concatenator->createSignal(interfaceName, signalName); signal << result; g_concatenator->emitSignal(signal); } int main(int argc, char *argv[]) { - // Create D-Bus connection to the system bus and requests name on it. - const char* serviceName = "org.sdbuscpp.concatenator"; - auto connection = sdbus::createSystemBusConnection(serviceName); + // Create D-Bus connection to (either the session or system) bus and requests a well-known name on it. + sdbus::ServiceName serviceName{"org.sdbuscpp.concatenator"}; + auto connection = sdbus::createBusConnection(serviceName); // Create concatenator D-Bus object. - const char* objectPath = "/org/sdbuscpp/concatenator"; - auto concatenator = sdbus::createObject(*connection, objectPath); + sdbus::ObjectPath objectPath{"/org/sdbuscpp/concatenator"}; + auto concatenator = sdbus::createObject(*connection, std::move(objectPath)); g_concatenator = concatenator.get(); // Register D-Bus methods and signals on the concatenator object, and exports the object. - const char* interfaceName = "org.sdbuscpp.Concatenator"; - concatenator->addVTable( sdbus::MethodVTableItem{"concatenate", "ais", {}, "s", {}, &concatenate, {}} - , sdbus::SignalVTableItem{"concatenated", "s", {}, {}} ) + sdbus::InterfaceName interfaceName{"org.sdbuscpp.Concatenator"}; + concatenator->addVTable( sdbus::MethodVTableItem{"concatenate", sdbus::Signature{"ais"}, {}, sdbus::Signature{"s"}, {}, &concatenate, {}} + , sdbus::SignalVTableItem{"concatenated", sdbus::Signature{"s"}, {}, {}} ) .forInterface(interfaceName); // Run the I/O event loop on the bus connection. @@ -349,21 +350,24 @@ int main(int argc, char *argv[]) { // Create proxy object for the concatenator object on the server side. Since here // we are creating the proxy instance without passing connection to it, the proxy - // will create its own connection automatically, and it will be system bus connection. - const char* destinationName = "org.sdbuscpp.concatenator"; - const char* objectPath = "/org/sdbuscpp/concatenator"; - auto concatenatorProxy = sdbus::createProxy(destinationName, objectPath); + // will create its own connection automatically (to either system bus or session bus, + // depending on the context). + sdbus::ServiceName destination{"org.sdbuscpp.concatenator"}; + sdbus::ObjectPath objectPath{"/org/sdbuscpp/concatenator"}; + auto concatenatorProxy = sdbus::createProxy(std::move(destination), std::move(objectPath)); // Let's subscribe for the 'concatenated' signals - const char* interfaceName = "org.sdbuscpp.Concatenator"; - concatenatorProxy->registerSignalHandler(interfaceName, "concatenated", &onConcatenated); + sdbus::InterfaceName interfaceName{"org.sdbuscpp.Concatenator"}; + sdbus::SignalName signalName{"concatenated"}; + concatenatorProxy->registerSignalHandler(interfaceName, signalName, &onConcatenated); std::vector numbers = {1, 2, 3}; std::string separator = ":"; + MethodName concatenate{"concatenate"}; // Invoke concatenate on given interface of the object { - auto method = concatenatorProxy->createMethodCall(interfaceName, "concatenate"); + auto method = concatenatorProxy->createMethodCall(interfaceName, concatenate); method << numbers << separator; auto reply = concatenatorProxy->callMethod(method); std::string result; @@ -373,7 +377,7 @@ int main(int argc, char *argv[]) / // Invoke concatenate again, this time with no numbers and we shall get an error { - auto method = concatenatorProxy->createMethodCall(interfaceName, "concatenate"); + auto method = concatenatorProxy->createMethodCall(interfaceName, concatenate); method << std::vector() << separator; try { @@ -408,11 +412,11 @@ Please note that we can create and destroy D-Bus object proxies dynamically, at There are several factory methods to create a bus connection object in sdbus-c++: * `createBusConnection()` - opens a connection to the session bus when in a user context, and a connection to the system bus, otherwise -* `createBusConnection(const std::string& name)` - opens a connection with the given name to the session bus when in a user context, and a connection with the given name to the system bus, otherwise +* `createBusConnection(const sdbus::ServiceName& name)` - opens a connection to the session bus when in a user context, and a connection with the given name to the system bus, otherwise, and requests the given well-known service name on the bus * `createSystemBusConnection()` - opens a connection to the system bus -* `createSystemBusConnection(const std::string& name)` - opens a connection with the given name to the system bus +* `createSystemBusConnection(const sdbus::ServiceName& name)` - opens a connection to the system bus, and requests the given well-known service name on the bus * `createSessionBusConnection()` - opens a connection to the session bus -* `createSessionBusConnection(const std::string& name)` - opens a connection with the given name to the session bus +* `createSessionBusConnection(const sdbus::ServiceName& name)` - opens a connection to the session bus, and requests the given well-known service name on the bus. * `createSessionBusConnectionWithAddress(const std::string& address)` - opens a connection to the session bus at a custom address * `createRemoteSystemBusConnection(const std::string& host)` - opens a connection to the system bus on a remote host using ssh * `createDirectBusConnection(const std::string& address)` - opens direct D-Bus connection at a custom address (see [Using direct (peer-to-peer) D-Bus connections](#using-direct-peer-to-peer-d-bus-connections)) @@ -503,13 +507,13 @@ The code written using this layer expresses in a declarative way *what* it does, int main(int argc, char *argv[]) { - // Create D-Bus connection to the system bus and requests name on it. - const char* serviceName = "org.sdbuscpp.concatenator"; - auto connection = sdbus::createSystemBusConnection(serviceName); + // Create D-Bus connection to the (either system or session) bus and requests a well-known name on it. + sdbus::ServiceName serviceName{"org.sdbuscpp.concatenator"}; + auto connection = sdbus::createBusConnection(serviceName); // Create concatenator D-Bus object. - const char* objectPath = "/org/sdbuscpp/concatenator"; - auto concatenator = sdbus::createObject(*connection, objectPath); + sdbus::ObjectPath objectPath{"/org/sdbuscpp/concatenator"}; + auto concatenator = sdbus::createObject(*connection, std::move(objectPath)); auto concatenate = [&concatenator](const std::vector numbers, const std::string& separator) { @@ -524,17 +528,15 @@ int main(int argc, char *argv[]) } // Emit 'concatenated' signal - const char* interfaceName = "org.sdbuscpp.Concatenator"; - concatenator->emitSignal("concatenated").onInterface(interfaceName).withArguments(result); + concatenator->emitSignal("concatenated").onInterface("org.sdbuscpp.Concatenator").withArguments(result); return result; }; // Register D-Bus methods and signals on the concatenator object, and exports the object. - const char* interfaceName = "org.sdbuscpp.Concatenator"; concatenator->addVTable( sdbus::registerMethod("concatenate").implementedAs(std::move(concatenate)) , sdbus::registerSignal{"concatenated").withParameters() ) - .forInterface(interfaceName); + .forInterface("org.sdbuscpp.Concatenator"); // Run the loop on the connection. connection->enterEventLoop(); @@ -562,12 +564,12 @@ void onConcatenated(const std::string& concatenatedString) int main(int argc, char *argv[]) { // Create proxy object for the concatenator object on the server side - const char* destinationName = "org.sdbuscpp.concatenator"; - const char* objectPath = "/org/sdbuscpp/concatenator"; - auto concatenatorProxy = sdbus::createProxy(destinationName, objectPath); + sdbus::ServiceName destination{"org.sdbuscpp.concatenator"}; + sdbus::ObjectPath objectPath{"/org/sdbuscpp/concatenator"}; + auto concatenatorProxy = sdbus::createProxy(std::move(destination), std::move(objectPath)); // Let's subscribe for the 'concatenated' signals - const char* interfaceName = "org.sdbuscpp.Concatenator"; + sdbus::InterfaceName interfaceName{"org.sdbuscpp.Concatenator"}; concatenatorProxy->uponSignal("concatenated").onInterface(interfaceName).call([](const std::string& str){ onConcatenated(str); }); std::vector numbers = {1, 2, 3}; @@ -712,7 +714,7 @@ namespace sdbuscpp { class Concatenator_adaptor { public: - static constexpr const char* INTERFACE_NAME = "org.sdbuscpp.Concatenator"; + static inline const sdbus::InterfaceName INTERFACE_NAME{"org.sdbuscpp.Concatenator"}; protected: Concatenator_adaptor(sdbus::IObject& object) @@ -771,7 +773,7 @@ namespace sdbuscpp { class Concatenator_proxy { public: - static constexpr const char* INTERFACE_NAME = "org.sdbuscpp.Concatenator"; + static inline const sdbus::InterfaceName INTERFACE_NAME{"org.sdbuscpp.Concatenator"}; protected: Concatenator_proxy(sdbus::IProxy& proxy) @@ -827,7 +829,7 @@ Calling `registerAdaptor()` and `unregisterAdaptor()` was not necessary in previ class Concatenator : public sdbus::AdaptorInterfaces { public: - Concatenator(sdbus::IConnection& connection, std::string objectPath) + Concatenator(sdbus::IConnection& connection, sdbus::ObjectPath objectPath) : AdaptorInterfaces(connection, std::move(objectPath)) { registerAdaptor(); @@ -870,13 +872,13 @@ That's it. We now have an implementation of a D-Bus object implementing `org.sdb int main(int argc, char *argv[]) { - // Create D-Bus connection to the system bus and requests name on it. - const char* serviceName = "org.sdbuscpp.concatenator"; - auto connection = sdbus::createSystemBusConnection(serviceName); + // Create D-Bus connection to (either the system or session) bus and request a well-known name on it. + sdbus::ServiceName serviceName{"org.sdbuscpp.concatenator"}; + auto connection = sdbus::createBusConnection(serviceName); // Create concatenator D-Bus object. - const char* objectPath = "/org/sdbuscpp/concatenator"; - Concatenator concatenator(*connection, objectPath); + sdbus::ObjectPath objectPath{"/org/sdbuscpp/concatenator"}; + Concatenator concatenator(*connection, std::move(objectPath)); // Run the loop on the connection. connection->enterEventLoop(); @@ -906,7 +908,7 @@ Calling `registerProxy()` and `unregisterProxy()` was not necessary in previous class ConcatenatorProxy : public sdbus::ProxyInterfaces { public: - ConcatenatorProxy(std::string destination, std::string objectPath) + ConcatenatorProxy(sdbus::ServiceName destination, sdbus::ObjectPath objectPath) : ProxyInterfaces(std::move(destination), std::move(objectPath)) { registerProxy(); @@ -940,9 +942,9 @@ Now let's use this proxy to make remote calls and listen to signals in a real ap int main(int argc, char *argv[]) { // Create proxy object for the concatenator object on the server side - const char* destinationName = "org.sdbuscpp.concatenator"; - const char* objectPath = "/org/sdbuscpp/concatenator"; - ConcatenatorProxy concatenatorProxy(destinationName, objectPath); + sdbus::ServiceName destination{"org.sdbuscpp.concatenator"}; + sdbus::ObjectPath objectPath{"/org/sdbuscpp/concatenator"}; + ConcatenatorProxy concatenatorProxy(std::move(destination), std::move(objectPath)); std::vector numbers = {1, 2, 3}; std::string separator = ":"; @@ -1034,8 +1036,9 @@ void concatenate(sdbus::MethodCall call) reply.send(); // Emit 'concatenated' signal (creating and emitting signals is thread-safe) - const char* interfaceName = "org.sdbuscpp.Concatenator"; - auto signal = g_concatenator->createSignal(interfaceName, "concatenated"); + sdbus::InterfaceName interfaceName{"org.sdbuscpp.Concatenator"}; + sdbus::SignalName signalName{"concatenated"}; + auto signal = g_concatenator->createSignal(interfaceName, signalName); signal << result; g_concatenator->emitSignal(signal); }).detach(); @@ -1144,7 +1147,7 @@ int main(int argc, char *argv[]) // Invoke concatenate on given interface of the object { - auto method = concatenatorProxy->createMethodCall(interfaceName, "concatenate"); + 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 @@ -1152,7 +1155,7 @@ int main(int argc, char *argv[]) // Invoke concatenate again, this time with no numbers and we shall get an error { - auto method = concatenatorProxy->createMethodCall(interfaceName, "concatenate"); + auto method = concatenatorProxy->createMethodCall(interfaceName, concatenate); method << std::vector() << separator; concatenatorProxy->callMethod(method, callback); // When the reply comes, we shall get concatenation error message on the standard error output @@ -1174,7 +1177,7 @@ Another option is to use `std::future`-based overload of the `IProxy::callMethod ... // Invoke concatenate on given interface of the object { - auto method = concatenatorProxy->createMethodCall(interfaceName, "concatenate"); + auto method = concatenatorProxy->createMethodCall(interfaceName, concatenate); method << numbers << separator; auto future = concatenatorProxy->callMethod(method, sdbus::with_future); try @@ -1702,10 +1705,9 @@ int main(int argc, char *argv[]) // We can now use connection objects in a familiar way, e.g. create adaptor and proxy objects on them, and exchange messages. // Here, using Concatenator IDL-generated bindings example from chapters above: - const char* objectPath = "/org/sdbuscpp/concatenator"; - Concatenator concatenator(*serverConnection, objectPath); - const char* emptyDestinationName = ""; // Destination may be empty in case of direct connections - ConcatenatorProxy concatenatorProxy(*clientConnection, emptyDestinationName, objectPath); + Concatenator concatenator(*serverConnection, sdbus::ObjectPath{"/org/sdbuscpp/concatenator"}); + sdbus::ServiceName emptyDestination; // Destination may be empty in case of direct connections + ConcatenatorProxy concatenatorProxy(*clientConnection, std::move(emptyDestination), sdbus::ObjectPath{"/org/sdbuscpp/concatenator"}); // Perform call of concatenate D-Bus method std::vector numbers = {1, 2, 3}; @@ -1750,6 +1752,13 @@ sdbus-c++ v2 is a major release that comes with a number of breaking API/ABI/beh * Change in behavior: In *synchronous* D-Bus calls, the proxy object now keeps the connection instance blocked for the entire duration of the method call. Incoming messages like signals will be queued and processed after the call. Access to the connection from other threads is blocked. To avoid this (in case this hurts you): * either use short-lived, light-weight proxies for such synchronous calls, * or call the method in an asynchronous way. +* Strong types were introduced for safer, less error-prone and more expressive API. What previously was `auto proxy = createProxy("org.sdbuscpp.concatenator", "/org/sdbuscpp/concatenator");` is now written like `auto proxy = createProxy(ServiceName{"org.sdbuscpp.concatenator"}, ObjectPath{"/org/sdbuscpp/concatenator"});`. These types are: + * `ObjectPath` type for the object path (the type has been around already but now is also used consistently in sdbus-c++ API for object path strings) + * `InterfaceName` type for D-Bus interface names + * `BusName` (and its aliases `ServiceName` and `ConnectionName`) type for bus/service/connection names + * `MemberName` (and its aliases `MethodName`, `SignalName` and `PropertyName`) type for D-Bus method, signal and property names + * `Signature` type for the D-Bus signature (the type has been around already but now is also used consistently in sdbus-c++ API for signature strings) + * `Error::Name` type for D-Bus error names * Signatures of callbacks `async_reply_handler`, `signal_handler`, `message_handler` and `property_set_callback` were modified to take input message objects by value instead of non-const ref to a message. The callback handler assumes ownership of the message. This API is cleaner and more self-explaining. * The `PollData` struct has been extended with a new data member: `eventFd`. All hooks with external event loops shall be modified to poll on this `eventFd` in addition to the `fd`. * `PollData::timeout_usec` was renamed to `PollData::timeout` and its type has been changed to `std::chrono::microseconds`. This member now holds directly what before had to be obtained through `PollData::getAbsoluteTimeout()` call. diff --git a/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-client-glue.h b/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-client-glue.h index e51fd78f..913ad82d 100644 --- a/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-client-glue.h +++ b/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-client-glue.h @@ -17,7 +17,7 @@ namespace ExampleManager { class Planet1_proxy { public: - static constexpr const char* INTERFACE_NAME = "org.sdbuscpp.ExampleManager.Planet1"; + static inline const sdbus::InterfaceName INTERFACE_NAME{"org.sdbuscpp.ExampleManager.Planet1"}; protected: Planet1_proxy(sdbus::IProxy& proxy) diff --git a/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-server-glue.h b/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-server-glue.h index b2ca89ce..7acfac01 100644 --- a/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-server-glue.h +++ b/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-server-glue.h @@ -17,7 +17,7 @@ namespace ExampleManager { class Planet1_adaptor { public: - static constexpr const char* INTERFACE_NAME = "org.sdbuscpp.ExampleManager.Planet1"; + static inline const sdbus::InterfaceName INTERFACE_NAME{"org.sdbuscpp.ExampleManager.Planet1"}; protected: Planet1_adaptor(sdbus::IObject& object) diff --git a/examples/org.freedesktop.DBus.ObjectManager/obj-manager-client.cpp b/examples/org.freedesktop.DBus.ObjectManager/obj-manager-client.cpp index 25184f11..be6f1b2c 100644 --- a/examples/org.freedesktop.DBus.ObjectManager/obj-manager-client.cpp +++ b/examples/org.freedesktop.DBus.ObjectManager/obj-manager-client.cpp @@ -17,7 +17,7 @@ class PlanetProxy final : public sdbus::ProxyInterfaces< org::sdbuscpp::ExampleManager::Planet1_proxy > { public: - PlanetProxy(sdbus::IConnection& connection, std::string destination, std::string path) + PlanetProxy(sdbus::IConnection& connection, sdbus::ServiceName destination, sdbus::ObjectPath path) : ProxyInterfaces(connection, std::move(destination), std::move(path)) { registerProxy(); @@ -29,13 +29,13 @@ class PlanetProxy final : public sdbus::ProxyInterfaces< org::sdbuscpp::ExampleM } }; -class ManagerProxy final : public sdbus::ProxyInterfaces< sdbus::ObjectManager_proxy > +class ManagerProxy final : public sdbus::ProxyInterfaces { public: - ManagerProxy(sdbus::IConnection& connection, const std::string& destination, std::string path) - : ProxyInterfaces(connection, destination, std::move(path)) - , m_connection(connection) - , m_destination(destination) + ManagerProxy(sdbus::IConnection& connection, sdbus::ServiceName destination, sdbus::ObjectPath path) + : ProxyInterfaces(connection, destination, std::move(path)) + , m_connection(connection) + , m_destination(destination) { registerProxy(); } @@ -47,8 +47,7 @@ class ManagerProxy final : public sdbus::ProxyInterfaces< sdbus::ObjectManager_p void handleExistingObjects() { - std::map>> objectsInterfacesAndProperties; - objectsInterfacesAndProperties = GetManagedObjects(); + auto objectsInterfacesAndProperties = GetManagedObjects(); for (const auto& [object, interfacesAndProperties] : objectsInterfacesAndProperties) { onInterfacesAdded(object, interfacesAndProperties); } @@ -56,7 +55,7 @@ class ManagerProxy final : public sdbus::ProxyInterfaces< sdbus::ObjectManager_p private: void onInterfacesAdded( const sdbus::ObjectPath& objectPath - , const std::map>& interfacesAndProperties) override + , const std::map>& interfacesAndProperties) override { std::cout << objectPath << " added:\t"; for (const auto& [interface, _] : interfacesAndProperties) { @@ -71,14 +70,14 @@ class ManagerProxy final : public sdbus::ProxyInterfaces< sdbus::ObjectManager_p } const auto& properties = planetInterface->second; // get a property which was passed as part of the signal. - const auto& name = properties.at("Name").get(); + const auto& name = properties.at(sdbus::PropertyName{"Name"}).get(); // or create a proxy instance to the newly added object. PlanetProxy planet(m_connection, m_destination, objectPath); std::cout << name << " has a population of " << planet.GetPopulation() << ".\n" << std::endl; } void onInterfacesRemoved( const sdbus::ObjectPath& objectPath - , const std::vector& interfaces) override + , const std::vector& interfaces) override { std::cout << objectPath << " removed:\t"; for (const auto& interface : interfaces) { @@ -88,14 +87,16 @@ class ManagerProxy final : public sdbus::ProxyInterfaces< sdbus::ObjectManager_p } sdbus::IConnection& m_connection; - std::string m_destination; + sdbus::ServiceName m_destination; }; int main() { auto connection = sdbus::createSessionBusConnection(); - auto managerProxy = std::make_unique(*connection, "org.sdbuscpp.examplemanager", "/org/sdbuscpp/examplemanager"); + sdbus::ServiceName destination{"org.sdbuscpp.examplemanager"}; + sdbus::ObjectPath objectPath{"/org/sdbuscpp/examplemanager"}; + auto managerProxy = std::make_unique(*connection, std::move(destination), std::move(objectPath)); try { managerProxy->handleExistingObjects(); } @@ -107,4 +108,4 @@ int main() connection->enterEventLoop(); return 0; -} \ No newline at end of file +} diff --git a/examples/org.freedesktop.DBus.ObjectManager/obj-manager-server.cpp b/examples/org.freedesktop.DBus.ObjectManager/obj-manager-server.cpp index 4631ed5f..9d4ef7d4 100644 --- a/examples/org.freedesktop.DBus.ObjectManager/obj-manager-server.cpp +++ b/examples/org.freedesktop.DBus.ObjectManager/obj-manager-server.cpp @@ -19,10 +19,12 @@ #include #include +using sdbus::ObjectPath; + class ManagerAdaptor : public sdbus::AdaptorInterfaces { public: - ManagerAdaptor(sdbus::IConnection& connection, std::string path) + ManagerAdaptor(sdbus::IConnection& connection, sdbus::ObjectPath path) : AdaptorInterfaces(connection, std::move(path)) { registerAdaptor(); @@ -39,7 +41,7 @@ class PlanetAdaptor final : public sdbus::AdaptorInterfaces< org::sdbuscpp::Exam , sdbus::Properties_adaptor > { public: - PlanetAdaptor(sdbus::IConnection& connection, std::string path, std::string name, uint64_t population) + PlanetAdaptor(sdbus::IConnection& connection, sdbus::ObjectPath path, std::string name, uint64_t population) : AdaptorInterfaces(connection, std::move(path)) , m_name(std::move(name)) , m_population(population) @@ -82,18 +84,19 @@ void printCountDown(const std::string& message, int seconds) int main() { auto connection = sdbus::createSessionBusConnection(); - connection->requestName("org.sdbuscpp.examplemanager"); + sdbus::ServiceName serviceName{"org.sdbuscpp.examplemanager"}; + connection->requestName(serviceName); connection->enterEventLoopAsync(); - auto manager = std::make_unique(*connection, "/org/sdbuscpp/examplemanager"); + auto manager = std::make_unique(*connection, ObjectPath{"/org/sdbuscpp/examplemanager"}); while (true) { printCountDown("Creating PlanetAdaptor in ", 5); - auto earth = std::make_unique(*connection, "/org/sdbuscpp/examplemanager/Planet1/Earth", "Earth", 7'874'965'825); + auto earth = std::make_unique(*connection, ObjectPath{"/org/sdbuscpp/examplemanager/Planet1/Earth"}, "Earth", 7'874'965'825); printCountDown("Creating PlanetAdaptor in ", 5); - auto trantor = std::make_unique(*connection, "/org/sdbuscpp/examplemanager/Planet1/Trantor", "Trantor", 40'000'000'000); + auto trantor = std::make_unique(*connection, ObjectPath{"/org/sdbuscpp/examplemanager/Planet1/Trantor"}, "Trantor", 40'000'000'000); printCountDown("Creating PlanetAdaptor in ", 5); - auto laconia = std::make_unique(*connection, "/org/sdbuscpp/examplemanager/Planet1/Laconia", "Laconia", 231'721); + auto laconia = std::make_unique(*connection, ObjectPath{"/org/sdbuscpp/examplemanager/Planet1/Laconia"}, "Laconia", 231'721); printCountDown("Removing PlanetAdaptor in ", 5); earth.reset(); printCountDown("Removing PlanetAdaptor in ", 5); @@ -102,7 +105,7 @@ int main() laconia.reset(); } - connection->releaseName("org.sdbuscpp.examplemanager"); + connection->releaseName(serviceName); connection->leaveEventLoop(); return 0; } diff --git a/include/sdbus-c++/AdaptorInterfaces.h b/include/sdbus-c++/AdaptorInterfaces.h index 13400329..b0898134 100644 --- a/include/sdbus-c++/AdaptorInterfaces.h +++ b/include/sdbus-c++/AdaptorInterfaces.h @@ -102,7 +102,7 @@ namespace sdbus { * * For more information, consult @ref createObject(sdbus::IConnection&,std::string) */ - AdaptorInterfaces(IConnection& connection, std::string objectPath) + AdaptorInterfaces(IConnection& connection, ObjectPath objectPath) : ObjectHolder(createObject(connection, std::move(objectPath))) , _Interfaces(getObject())... { diff --git a/include/sdbus-c++/ConvenienceApiClasses.h b/include/sdbus-c++/ConvenienceApiClasses.h index ee4cbe75..fbad100b 100644 --- a/include/sdbus-c++/ConvenienceApiClasses.h +++ b/include/sdbus-c++/ConvenienceApiClasses.h @@ -53,7 +53,9 @@ namespace sdbus { { public: VTableAdder(IObject& object, std::vector vtable); + void forInterface(InterfaceName interfaceName); void forInterface(std::string interfaceName); + [[nodiscard]] Slot forInterface(InterfaceName interfaceName, return_slot_t); [[nodiscard]] Slot forInterface(std::string interfaceName, return_slot_t); private: @@ -64,15 +66,16 @@ namespace sdbus { class SignalEmitter { public: - SignalEmitter(IObject& object, const std::string& signalName); + SignalEmitter(IObject& object, const SignalName& signalName); SignalEmitter(SignalEmitter&& other) = default; ~SignalEmitter() noexcept(false); + SignalEmitter& onInterface(const InterfaceName& interfaceName); SignalEmitter& onInterface(const std::string& interfaceName); template void withArguments(_Args&&... args); private: IObject& object_; - const std::string& signalName_; + const SignalName& signalName_; Signal signal_; int exceptions_{}; // Number of active exceptions when SignalEmitter is constructed }; @@ -80,10 +83,11 @@ namespace sdbus { class MethodInvoker { public: - MethodInvoker(IProxy& proxy, const std::string& methodName); + MethodInvoker(IProxy& proxy, const MethodName& methodName); MethodInvoker(MethodInvoker&& other) = default; ~MethodInvoker() noexcept(false); + MethodInvoker& onInterface(const InterfaceName& interfaceName); MethodInvoker& onInterface(const std::string& interfaceName); MethodInvoker& withTimeout(uint64_t usec); template @@ -95,7 +99,7 @@ namespace sdbus { private: IProxy& proxy_; - const std::string& methodName_; + const MethodName& methodName_; uint64_t timeout_{}; MethodCall method_; int exceptions_{}; // Number of active exceptions when MethodInvoker is constructed @@ -105,7 +109,8 @@ namespace sdbus { class AsyncMethodInvoker { public: - AsyncMethodInvoker(IProxy& proxy, const std::string& methodName); + AsyncMethodInvoker(IProxy& proxy, const MethodName& methodName); + AsyncMethodInvoker& onInterface(const InterfaceName& interfaceName); AsyncMethodInvoker& onInterface(const std::string& interfaceName); AsyncMethodInvoker& withTimeout(uint64_t usec); template @@ -119,7 +124,7 @@ namespace sdbus { private: IProxy& proxy_; - const std::string& methodName_; + const MethodName& methodName_; uint64_t timeout_{}; MethodCall method_; }; @@ -127,7 +132,8 @@ namespace sdbus { class SignalSubscriber { public: - SignalSubscriber(IProxy& proxy, const std::string& signalName); + SignalSubscriber(IProxy& proxy, const SignalName& signalName); + SignalSubscriber& onInterface(InterfaceName interfaceName); SignalSubscriber& onInterface(std::string interfaceName); template void call(_Function&& callback); template [[nodiscard]] Slot call(_Function&& callback, return_slot_t); @@ -137,65 +143,81 @@ namespace sdbus { private: IProxy& proxy_; - const std::string& signalName_; - std::string interfaceName_; + const SignalName& signalName_; + InterfaceName interfaceName_; }; class PropertyGetter { public: - PropertyGetter(IProxy& proxy, const std::string& propertyName); + PropertyGetter(IProxy& proxy, const PropertyName& propertyName); + Variant onInterface(const InterfaceName& interfaceName); Variant onInterface(const std::string& interfaceName); + private: + static inline const InterfaceName DBUS_PROPERTIES_INTERFACE_NAME{"org.freedesktop.DBus.Properties"}; + private: IProxy& proxy_; - const std::string& propertyName_; + const PropertyName& propertyName_; }; class AsyncPropertyGetter { public: - AsyncPropertyGetter(IProxy& proxy, const std::string& propertyName); + AsyncPropertyGetter(IProxy& proxy, const PropertyName& propertyName); + AsyncPropertyGetter& onInterface(const InterfaceName& interfaceName); AsyncPropertyGetter& onInterface(const std::string& interfaceName); template PendingAsyncCall uponReplyInvoke(_Function&& callback); std::future getResultAsFuture(); + private: + static inline const InterfaceName DBUS_PROPERTIES_INTERFACE_NAME{"org.freedesktop.DBus.Properties"}; + private: IProxy& proxy_; - const std::string& propertyName_; - const std::string* interfaceName_{}; + const PropertyName& propertyName_; + const InterfaceName* interfaceName_{}; }; class PropertySetter { public: - PropertySetter(IProxy& proxy, const std::string& propertyName); + PropertySetter(IProxy& proxy, const PropertyName& propertyName); + PropertySetter& onInterface(const InterfaceName& interfaceName); PropertySetter& onInterface(const std::string& interfaceName); template void toValue(const _Value& value); template void toValue(const _Value& value, dont_expect_reply_t); void toValue(const Variant& value); void toValue(const Variant& value, dont_expect_reply_t); + private: + static inline const InterfaceName DBUS_PROPERTIES_INTERFACE_NAME{"org.freedesktop.DBus.Properties"}; + private: IProxy& proxy_; - const std::string& propertyName_; - const std::string* interfaceName_{}; + const PropertyName& propertyName_; + const InterfaceName* interfaceName_{}; }; class AsyncPropertySetter { public: - AsyncPropertySetter(IProxy& proxy, const std::string& propertyName); + AsyncPropertySetter(IProxy& proxy, const PropertyName& propertyName); + AsyncPropertySetter& onInterface(const InterfaceName& interfaceName); AsyncPropertySetter& onInterface(const std::string& interfaceName); template AsyncPropertySetter& toValue(_Value&& value); AsyncPropertySetter& toValue(Variant value); template PendingAsyncCall uponReplyInvoke(_Function&& callback); std::future getResultAsFuture(); + private: + static inline const InterfaceName DBUS_PROPERTIES_INTERFACE_NAME{"org.freedesktop.DBus.Properties"}; + private: IProxy& proxy_; - const std::string& propertyName_; - const std::string* interfaceName_{}; + const PropertyName& propertyName_; + const InterfaceName* interfaceName_{}; Variant value_; }; @@ -203,7 +225,11 @@ namespace sdbus { { public: AllPropertiesGetter(IProxy& proxy); - std::map onInterface(const std::string& interfaceName); + std::map onInterface(const InterfaceName& interfaceName); + std::map onInterface(const std::string& interfaceName); + + private: + static inline const InterfaceName DBUS_PROPERTIES_INTERFACE_NAME{"org.freedesktop.DBus.Properties"}; private: IProxy& proxy_; @@ -213,13 +239,17 @@ namespace sdbus { { public: AsyncAllPropertiesGetter(IProxy& proxy); + AsyncAllPropertiesGetter& onInterface(const InterfaceName& interfaceName); AsyncAllPropertiesGetter& onInterface(const std::string& interfaceName); template PendingAsyncCall uponReplyInvoke(_Function&& callback); - std::future> getResultAsFuture(); + std::future> getResultAsFuture(); + + private: + static inline const InterfaceName DBUS_PROPERTIES_INTERFACE_NAME{"org.freedesktop.DBus.Properties"}; private: IProxy& proxy_; - const std::string* interfaceName_{}; + const InterfaceName* interfaceName_{}; }; } // namespace sdbus diff --git a/include/sdbus-c++/ConvenienceApiClasses.inl b/include/sdbus-c++/ConvenienceApiClasses.inl index fbc21094..a6111da1 100644 --- a/include/sdbus-c++/ConvenienceApiClasses.inl +++ b/include/sdbus-c++/ConvenienceApiClasses.inl @@ -53,21 +53,31 @@ namespace sdbus { { } - inline void VTableAdder::forInterface(std::string interfaceName) + inline void VTableAdder::forInterface(InterfaceName interfaceName) { object_.addVTable(std::move(interfaceName), std::move(vtable_)); } - [[nodiscard]] inline Slot VTableAdder::forInterface(std::string interfaceName, return_slot_t) + inline void VTableAdder::forInterface(std::string interfaceName) + { + forInterface(InterfaceName{std::move(interfaceName)}); + } + + [[nodiscard]] inline Slot VTableAdder::forInterface(InterfaceName interfaceName, return_slot_t) { return object_.addVTable(std::move(interfaceName), std::move(vtable_), return_slot); } + [[nodiscard]] inline Slot VTableAdder::forInterface(std::string interfaceName, return_slot_t) + { + return forInterface(InterfaceName{std::move(interfaceName)}, return_slot); + } + /*** ------------- ***/ /*** SignalEmitter ***/ /*** ------------- ***/ - inline SignalEmitter::SignalEmitter(IObject& object, const std::string& signalName) + inline SignalEmitter::SignalEmitter(IObject& object, const SignalName& signalName) : object_(object) , signalName_(signalName) , exceptions_(std::uncaught_exceptions()) @@ -92,13 +102,20 @@ namespace sdbus { object_.emitSignal(signal_); } - inline SignalEmitter& SignalEmitter::onInterface(const std::string& interfaceName) + inline SignalEmitter& SignalEmitter::onInterface(const InterfaceName& interfaceName) { signal_ = object_.createSignal(interfaceName, signalName_); return *this; } + inline SignalEmitter& SignalEmitter::onInterface(const std::string& interfaceName) + { + // Down-cast through static cast for performance reasons (no extra copy and object construction needed) + static_assert(sizeof(interfaceName) == sizeof(InterfaceName)); + return onInterface(static_cast(interfaceName)); + } + template inline void SignalEmitter::withArguments(_Args&&... args) { @@ -111,7 +128,7 @@ namespace sdbus { /*** MethodInvoker ***/ /*** ------------- ***/ - inline MethodInvoker::MethodInvoker(IProxy& proxy, const std::string& methodName) + inline MethodInvoker::MethodInvoker(IProxy& proxy, const MethodName& methodName) : proxy_(proxy) , methodName_(methodName) , exceptions_(std::uncaught_exceptions()) @@ -137,13 +154,20 @@ namespace sdbus { proxy_.callMethod(method_, timeout_); } - inline MethodInvoker& MethodInvoker::onInterface(const std::string& interfaceName) + inline MethodInvoker& MethodInvoker::onInterface(const InterfaceName& interfaceName) { method_ = proxy_.createMethodCall(interfaceName, methodName_); return *this; } + inline MethodInvoker& MethodInvoker::onInterface(const std::string& interfaceName) + { + // Down-cast through static cast for performance reasons (no extra copy and object construction needed) + static_assert(sizeof(interfaceName) == sizeof(InterfaceName)); + return onInterface(static_cast(interfaceName)); + } + inline MethodInvoker& MethodInvoker::withTimeout(uint64_t usec) { timeout_ = usec; @@ -190,19 +214,26 @@ namespace sdbus { /*** AsyncMethodInvoker ***/ /*** ------------------ ***/ - inline AsyncMethodInvoker::AsyncMethodInvoker(IProxy& proxy, const std::string& methodName) + inline AsyncMethodInvoker::AsyncMethodInvoker(IProxy& proxy, const MethodName& methodName) : proxy_(proxy) , methodName_(methodName) { } - inline AsyncMethodInvoker& AsyncMethodInvoker::onInterface(const std::string& interfaceName) + inline AsyncMethodInvoker& AsyncMethodInvoker::onInterface(const InterfaceName& interfaceName) { method_ = proxy_.createMethodCall(interfaceName, methodName_); return *this; } + inline AsyncMethodInvoker& AsyncMethodInvoker::onInterface(const std::string& interfaceName) + { + // Down-cast through static cast for performance reasons (no extra copy and object construction needed) + static_assert(sizeof(interfaceName) == sizeof(InterfaceName)); + return onInterface(static_cast(interfaceName)); + } + inline AsyncMethodInvoker& AsyncMethodInvoker::withTimeout(uint64_t usec) { timeout_ = usec; @@ -288,13 +319,18 @@ namespace sdbus { /*** SignalSubscriber ***/ /*** ---------------- ***/ - inline SignalSubscriber::SignalSubscriber(IProxy& proxy, const std::string& signalName) + inline SignalSubscriber::SignalSubscriber(IProxy& proxy, const SignalName& signalName) : proxy_(proxy) , signalName_(signalName) { } inline SignalSubscriber& SignalSubscriber::onInterface(std::string interfaceName) + { + return onInterface(InterfaceName{std::move(interfaceName)}); + } + + inline SignalSubscriber& SignalSubscriber::onInterface(InterfaceName interfaceName) { interfaceName_ = std::move(interfaceName); @@ -368,39 +404,53 @@ namespace sdbus { /*** PropertyGetter ***/ /*** -------------- ***/ - inline PropertyGetter::PropertyGetter(IProxy& proxy, const std::string& propertyName) + inline PropertyGetter::PropertyGetter(IProxy& proxy, const PropertyName& propertyName) : proxy_(proxy) , propertyName_(propertyName) { } - inline Variant PropertyGetter::onInterface(const std::string& interfaceName) + inline Variant PropertyGetter::onInterface(const InterfaceName& interfaceName) { Variant var; proxy_.callMethod("Get") - .onInterface("org.freedesktop.DBus.Properties") + .onInterface(DBUS_PROPERTIES_INTERFACE_NAME) .withArguments(interfaceName, propertyName_) .storeResultsTo(var); return var; } + inline Variant PropertyGetter::onInterface(const std::string& interfaceName) + { + // Down-cast through static cast for performance reasons (no extra copy and object construction needed) + static_assert(sizeof(interfaceName) == sizeof(InterfaceName)); + return onInterface(static_cast(interfaceName)); + } + /*** ------------------- ***/ /*** AsyncPropertyGetter ***/ /*** ------------------- ***/ - inline AsyncPropertyGetter::AsyncPropertyGetter(IProxy& proxy, const std::string& propertyName) + inline AsyncPropertyGetter::AsyncPropertyGetter(IProxy& proxy, const PropertyName& propertyName) : proxy_(proxy) , propertyName_(propertyName) { } - inline AsyncPropertyGetter& AsyncPropertyGetter::onInterface(const std::string& interfaceName) + inline AsyncPropertyGetter& AsyncPropertyGetter::onInterface(const InterfaceName& interfaceName) { interfaceName_ = &interfaceName; return *this; } + inline AsyncPropertyGetter& AsyncPropertyGetter::onInterface(const std::string& interfaceName) + { + // Down-cast through static cast for performance reasons (no extra copy and object construction needed) + static_assert(sizeof(interfaceName) == sizeof(InterfaceName)); + return onInterface(static_cast(interfaceName)); + } + template PendingAsyncCall AsyncPropertyGetter::uponReplyInvoke(_Function&& callback) { @@ -409,7 +459,7 @@ namespace sdbus { assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function return proxy_.callMethodAsync("Get") - .onInterface("org.freedesktop.DBus.Properties") + .onInterface(DBUS_PROPERTIES_INTERFACE_NAME) .withArguments(*interfaceName_, propertyName_) .uponReplyInvoke(std::forward<_Function>(callback)); } @@ -419,7 +469,7 @@ namespace sdbus { assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function return proxy_.callMethodAsync("Get") - .onInterface("org.freedesktop.DBus.Properties") + .onInterface(DBUS_PROPERTIES_INTERFACE_NAME) .withArguments(*interfaceName_, propertyName_) .getResultAsFuture(); } @@ -428,19 +478,26 @@ namespace sdbus { /*** PropertySetter ***/ /*** -------------- ***/ - inline PropertySetter::PropertySetter(IProxy& proxy, const std::string& propertyName) + inline PropertySetter::PropertySetter(IProxy& proxy, const PropertyName& propertyName) : proxy_(proxy) , propertyName_(propertyName) { } - inline PropertySetter& PropertySetter::onInterface(const std::string& interfaceName) + inline PropertySetter& PropertySetter::onInterface(const InterfaceName& interfaceName) { interfaceName_ = &interfaceName; return *this; } + inline PropertySetter& PropertySetter::onInterface(const std::string& interfaceName) + { + // Down-cast through static cast for performance reasons (no extra copy and object construction needed) + static_assert(sizeof(interfaceName) == sizeof(InterfaceName)); + return onInterface(static_cast(interfaceName)); + } + template inline void PropertySetter::toValue(const _Value& value) { @@ -458,7 +515,7 @@ namespace sdbus { assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function proxy_.callMethod("Set") - .onInterface("org.freedesktop.DBus.Properties") + .onInterface(DBUS_PROPERTIES_INTERFACE_NAME) .withArguments(*interfaceName_, propertyName_, value); } @@ -467,7 +524,7 @@ namespace sdbus { assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function proxy_.callMethod("Set") - .onInterface("org.freedesktop.DBus.Properties") + .onInterface(DBUS_PROPERTIES_INTERFACE_NAME) .withArguments(*interfaceName_, propertyName_, value) .dontExpectReply(); } @@ -476,19 +533,26 @@ namespace sdbus { /*** AsyncPropertySetter ***/ /*** ------------------- ***/ - inline AsyncPropertySetter::AsyncPropertySetter(IProxy& proxy, const std::string& propertyName) + inline AsyncPropertySetter::AsyncPropertySetter(IProxy& proxy, const PropertyName& propertyName) : proxy_(proxy) , propertyName_(propertyName) { } - inline AsyncPropertySetter& AsyncPropertySetter::onInterface(const std::string& interfaceName) + inline AsyncPropertySetter& AsyncPropertySetter::onInterface(const InterfaceName& interfaceName) { interfaceName_ = &interfaceName; return *this; } + inline AsyncPropertySetter& AsyncPropertySetter::onInterface(const std::string& interfaceName) + { + // Down-cast through static cast for performance reasons (no extra copy and object construction needed) + static_assert(sizeof(interfaceName) == sizeof(InterfaceName)); + return onInterface(static_cast(interfaceName)); + } + template inline AsyncPropertySetter& AsyncPropertySetter::toValue(_Value&& value) { @@ -510,7 +574,7 @@ namespace sdbus { assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function return proxy_.callMethodAsync("Set") - .onInterface("org.freedesktop.DBus.Properties") + .onInterface(DBUS_PROPERTIES_INTERFACE_NAME) .withArguments(*interfaceName_, propertyName_, std::move(value_)) .uponReplyInvoke(std::forward<_Function>(callback)); } @@ -520,7 +584,7 @@ namespace sdbus { assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function return proxy_.callMethodAsync("Set") - .onInterface("org.freedesktop.DBus.Properties") + .onInterface(DBUS_PROPERTIES_INTERFACE_NAME) .withArguments(*interfaceName_, propertyName_, std::move(value_)) .getResultAsFuture<>(); } @@ -534,16 +598,23 @@ namespace sdbus { { } - inline std::map AllPropertiesGetter::onInterface(const std::string& interfaceName) + inline std::map AllPropertiesGetter::onInterface(const InterfaceName& interfaceName) { - std::map props; + std::map props; proxy_.callMethod("GetAll") - .onInterface("org.freedesktop.DBus.Properties") + .onInterface(DBUS_PROPERTIES_INTERFACE_NAME) .withArguments(interfaceName) .storeResultsTo(props); return props; } + inline std::map AllPropertiesGetter::onInterface(const std::string& interfaceName) + { + // Down-cast through static cast for performance reasons (no extra copy and object construction needed) + static_assert(sizeof(interfaceName) == sizeof(InterfaceName)); + return onInterface(static_cast(interfaceName)); + } + /*** ------------------------ ***/ /*** AsyncAllPropertiesGetter ***/ /*** ------------------------ ***/ @@ -553,35 +624,42 @@ namespace sdbus { { } - inline AsyncAllPropertiesGetter& AsyncAllPropertiesGetter::onInterface(const std::string& interfaceName) + inline AsyncAllPropertiesGetter& AsyncAllPropertiesGetter::onInterface(const InterfaceName& interfaceName) { interfaceName_ = &interfaceName; return *this; } + inline AsyncAllPropertiesGetter& AsyncAllPropertiesGetter::onInterface(const std::string& interfaceName) + { + // Down-cast through static cast for performance reasons (no extra copy and object construction needed) + static_assert(sizeof(interfaceName) == sizeof(InterfaceName)); + return onInterface(static_cast(interfaceName)); + } + template PendingAsyncCall AsyncAllPropertiesGetter::uponReplyInvoke(_Function&& callback) { - static_assert( std::is_invocable_r_v, std::map> - , "All properties get callback function must accept std::optional, std::map> + , "All properties get callback function must accept std::optional and a map of property names to their values" ); assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function return proxy_.callMethodAsync("GetAll") - .onInterface("org.freedesktop.DBus.Properties") + .onInterface(DBUS_PROPERTIES_INTERFACE_NAME) .withArguments(*interfaceName_) .uponReplyInvoke(std::forward<_Function>(callback)); } - inline std::future> AsyncAllPropertiesGetter::getResultAsFuture() + inline std::future> AsyncAllPropertiesGetter::getResultAsFuture() { assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function return proxy_.callMethodAsync("GetAll") - .onInterface("org.freedesktop.DBus.Properties") + .onInterface(DBUS_PROPERTIES_INTERFACE_NAME) .withArguments(*interfaceName_) - .getResultAsFuture>(); + .getResultAsFuture>(); } } // namespace sdbus diff --git a/include/sdbus-c++/Error.h b/include/sdbus-c++/Error.h index 1bebd91d..c52dbc18 100644 --- a/include/sdbus-c++/Error.h +++ b/include/sdbus-c++/Error.h @@ -43,19 +43,34 @@ namespace sdbus { : public std::runtime_error { public: - explicit Error(const std::string& name, const char* message = nullptr) - : Error(name, std::string(message ? message : "")) + // Strong type representing the D-Bus error name + class Name : public std::string + { + public: + Name() = default; + explicit Name(std::string value) + : std::string(std::move(value)) + {} + explicit Name(const char* value) + : std::string(value) + {} + + using std::string::operator=; + }; + + explicit Error(Name name, const char* message = nullptr) + : Error(std::move(name), std::string(message ? message : "")) { } - Error(const std::string& name, const std::string& message) + Error(Name name, std::string message) : std::runtime_error("[" + name + "] " + message) - , name_(name) - , message_(message) + , name_(std::move(name)) + , message_(std::move(message)) { } - [[nodiscard]] const std::string& getName() const + [[nodiscard]] const Name& getName() const { return name_; } @@ -71,13 +86,13 @@ namespace sdbus { } private: - std::string name_; + Name name_; std::string message_; }; - sdbus::Error createError(int errNo, const std::string& customMsg); + Error createError(int errNo, std::string customMsg); - inline const char* SDBUSCPP_ERROR_NAME = "org.sdbuscpp.Error"; + inline const Error::Name SDBUSCPP_ERROR_NAME{"org.sdbuscpp.Error"}; } #define SDBUS_THROW_ERROR(_MSG, _ERRNO) \ diff --git a/include/sdbus-c++/IConnection.h b/include/sdbus-c++/IConnection.h index 11f4c568..a95ee20a 100644 --- a/include/sdbus-c++/IConnection.h +++ b/include/sdbus-c++/IConnection.h @@ -35,10 +35,14 @@ #include #include +// Forward declarations struct sd_bus; struct sd_event; namespace sdbus { - class Message; + class Message; + class ObjectPath; + class BusName; + using ServiceName = BusName; } namespace sdbus { @@ -61,29 +65,29 @@ namespace sdbus { virtual ~IConnection() = default; /*! - * @brief Requests D-Bus name on the connection + * @brief Requests a well-known D-Bus service name on a bus * * @param[in] name Name to request * * @throws sdbus::Error in case of failure */ - virtual void requestName(const std::string& name) = 0; + virtual void requestName(const ServiceName& name) = 0; /*! - * @brief Releases D-Bus name on the connection + * @brief Releases an acquired well-known D-Bus service name on a bus * * @param[in] name Name to release * * @throws sdbus::Error in case of failure */ - virtual void releaseName(const std::string& name) = 0; + virtual void releaseName(const ServiceName& name) = 0; /*! * @brief Retrieves the unique name of a connection. E.g. ":1.xx" * * @throws sdbus::Error in case of failure */ - [[nodiscard]] virtual std::string getUniqueName() const = 0; + [[nodiscard]] virtual BusName getUniqueName() const = 0; /*! * @brief Enters I/O event loop on this bus connection @@ -257,7 +261,7 @@ namespace sdbus { * * @throws sdbus::Error in case of failure */ - virtual void addObjectManager(const std::string& objectPath, floating_slot_t) = 0; + virtual void addObjectManager(const ObjectPath& objectPath, floating_slot_t) = 0; /*! * @brief Installs a match rule for messages received on this bus connection @@ -412,7 +416,7 @@ namespace sdbus { * * @throws sdbus::Error in case of failure */ - [[nodiscard]] std::unique_ptr createBusConnection(const std::string& name); + [[nodiscard]] std::unique_ptr createBusConnection(const ServiceName& name); /*! * @brief Creates/opens D-Bus system bus connection @@ -431,7 +435,7 @@ namespace sdbus { * * @throws sdbus::Error in case of failure */ - [[nodiscard]] std::unique_ptr createSystemBusConnection(const std::string& name); + [[nodiscard]] std::unique_ptr createSystemBusConnection(const ServiceName& name); /*! * @brief Creates/opens D-Bus session bus connection @@ -450,7 +454,7 @@ namespace sdbus { * * @throws sdbus::Error in case of failure */ - [[nodiscard]] std::unique_ptr createSessionBusConnection(const std::string& name); + [[nodiscard]] std::unique_ptr createSessionBusConnection(const ServiceName& name); /*! * @brief Creates/opens D-Bus session bus connection at a custom address diff --git a/include/sdbus-c++/IObject.h b/include/sdbus-c++/IObject.h index b3c118e8..97b31475 100644 --- a/include/sdbus-c++/IObject.h +++ b/include/sdbus-c++/IObject.h @@ -41,6 +41,7 @@ namespace sdbus { class Signal; class IConnection; + class ObjectPath; } namespace sdbus { @@ -89,7 +90,7 @@ namespace sdbus { */ template < typename... VTableItems , typename = std::enable_if_t<(is_one_of_variants_types> && ...)> > - void addVTable(std::string interfaceName, VTableItems&&... items); + void addVTable(InterfaceName interfaceName, VTableItems&&... items); /*! * @brief Adds a declaration of methods, properties and signals of the object at a given interface @@ -114,7 +115,7 @@ namespace sdbus { * * @throws sdbus::Error in case of failure */ - virtual void addVTable(std::string interfaceName, std::vector vtable) = 0; + virtual void addVTable(InterfaceName interfaceName, std::vector vtable) = 0; /*! * @brief Adds a declaration of methods, properties and signals of the object at a given interface @@ -141,7 +142,7 @@ namespace sdbus { * * @throws sdbus::Error in case of failure */ - [[nodiscard]] virtual Slot addVTable(std::string interfaceName, std::vector vtable, return_slot_t) = 0; + [[nodiscard]] virtual Slot addVTable(InterfaceName interfaceName, std::vector vtable, return_slot_t) = 0; /*! * @brief A little more convenient overload of addVTable() above @@ -188,7 +189,7 @@ namespace sdbus { * * @throws sdbus::Error in case of failure */ - [[nodiscard]] virtual Signal createSignal(const std::string& interfaceName, const std::string& signalName) = 0; + [[nodiscard]] virtual Signal createSignal(const InterfaceName& interfaceName, const SignalName& signalName) = 0; /*! * @brief Emits signal for this object path @@ -201,6 +202,28 @@ namespace sdbus { */ virtual void emitSignal(const sdbus::Signal& message) = 0; + /*! + * @brief Emits signal on D-Bus + * + * @param[in] signalName Name of the signal + * @return A helper object for convenient emission of signals + * + * This is a high-level, convenience way of emitting D-Bus signals that abstracts + * from the D-Bus message concept. Signal arguments are automatically serialized + * in a message and D-Bus signatures automatically deduced from the provided native arguments. + * + * Example of use: + * @code + * int arg1 = ...; + * double arg2 = ...; + * SignalName fooSignal{"fooSignal"}; + * object_.emitSignal(fooSignal).onInterface("com.kistler.foo").withArguments(arg1, arg2); + * @endcode + * + * @throws sdbus::Error in case of failure + */ + [[nodiscard]] SignalEmitter emitSignal(const SignalName& signalName); + /*! * @brief Emits signal on D-Bus * @@ -230,7 +253,7 @@ namespace sdbus { * * @throws sdbus::Error in case of failure */ - virtual void emitPropertiesChangedSignal(const std::string& interfaceName, const std::vector& propNames) = 0; + virtual void emitPropertiesChangedSignal(const InterfaceName& interfaceName, const std::vector& propNames) = 0; /*! * @brief Emits PropertyChanged signal for all properties on a given interface of this object path @@ -239,7 +262,7 @@ namespace sdbus { * * @throws sdbus::Error in case of failure */ - virtual void emitPropertiesChangedSignal(const std::string& interfaceName) = 0; + virtual void emitPropertiesChangedSignal(const InterfaceName& interfaceName) = 0; /*! * @brief Emits InterfacesAdded signal on this object path @@ -264,7 +287,7 @@ namespace sdbus { * * @throws sdbus::Error in case of failure */ - virtual void emitInterfacesAddedSignal(const std::vector& interfaces) = 0; + virtual void emitInterfacesAddedSignal(const std::vector& interfaces) = 0; /*! * @brief Emits InterfacesRemoved signal on this object path @@ -286,7 +309,7 @@ namespace sdbus { * * @throws sdbus::Error in case of failure */ - virtual void emitInterfacesRemovedSignal(const std::vector& interfaces) = 0; + virtual void emitInterfacesRemovedSignal(const std::vector& interfaces) = 0; /*! * @brief Adds an ObjectManager interface at the path of this D-Bus object @@ -322,7 +345,7 @@ namespace sdbus { /*! * @brief Returns object path of the underlying DBus object */ - [[nodiscard]] virtual const std::string& getObjectPath() const = 0; + [[nodiscard]] virtual const ObjectPath& getObjectPath() const = 0; /*! * @brief Provides access to the currently processed D-Bus message @@ -342,13 +365,20 @@ namespace sdbus { // Out-of-line member definitions - inline SignalEmitter IObject::emitSignal(const std::string& signalName) + inline SignalEmitter IObject::emitSignal(const SignalName& signalName) { return SignalEmitter(*this, signalName); } + inline SignalEmitter IObject::emitSignal(const std::string& signalName) + { + // Down-cast through static cast for performance reasons (no extra copy and object construction needed) + static_assert(sizeof(signalName) == sizeof(SignalName)); + return emitSignal(static_cast(signalName)); + } + template - void IObject::addVTable(std::string interfaceName, VTableItems&&... items) + void IObject::addVTable(InterfaceName interfaceName, VTableItems&&... items) { addVTable(std::move(interfaceName), {std::forward(items)...}); } @@ -382,7 +412,7 @@ namespace sdbus { * auto proxy = sdbus::createObject(connection, "/com/kistler/foo"); * @endcode */ - [[nodiscard]] std::unique_ptr createObject(sdbus::IConnection& connection, std::string objectPath); + [[nodiscard]] std::unique_ptr createObject(sdbus::IConnection& connection, ObjectPath objectPath); } diff --git a/include/sdbus-c++/IProxy.h b/include/sdbus-c++/IProxy.h index b555cc65..88eb1b82 100644 --- a/include/sdbus-c++/IProxy.h +++ b/include/sdbus-c++/IProxy.h @@ -41,6 +41,7 @@ namespace sdbus { class MethodCall; class MethodReply; class IConnection; + class ObjectPath; class PendingAsyncCall; namespace internal { class Proxy; @@ -81,7 +82,7 @@ namespace sdbus { * * @throws sdbus::Error in case of failure */ - [[nodiscard]] virtual MethodCall createMethodCall(const std::string& interfaceName, const std::string& methodName) = 0; + [[nodiscard]] virtual MethodCall createMethodCall(const InterfaceName& interfaceName, const MethodName& methodName) = 0; /*! * @brief Calls method on the remote D-Bus object @@ -211,11 +212,17 @@ namespace sdbus { * Example of use: * @code * int result, a = ..., b = ...; - * object_.callMethod("multiply").onInterface(INTERFACE_NAME).withArguments(a, b).storeResultsTo(result); + * MethodName multiply{"multiply"}; + * object_.callMethod(multiply).onInterface(INTERFACE_NAME).withArguments(a, b).storeResultsTo(result); * @endcode * * @throws sdbus::Error in case of failure */ + [[nodiscard]] MethodInvoker callMethod(const MethodName& methodName); + + /*! + * @copydoc IProxy::callMethod(const MethodName&) + */ [[nodiscard]] MethodInvoker callMethod(const std::string& methodName); /*! @@ -232,7 +239,8 @@ namespace sdbus { * Example of use: * @code * int a = ..., b = ...; - * object_.callMethodAsync("multiply").onInterface(INTERFACE_NAME).withArguments(a, b).uponReplyInvoke([](int result) + * MethodName multiply{"multiply"}; + * object_.callMethodAsync(multiply).onInterface(INTERFACE_NAME).withArguments(a, b).uponReplyInvoke([](int result) * { * std::cout << "Got result of multiplying " << a << " and " << b << ": " << result << std::endl; * }); @@ -240,6 +248,11 @@ namespace sdbus { * * @throws sdbus::Error in case of failure */ + [[nodiscard]] AsyncMethodInvoker callMethodAsync(const MethodName& methodName); + + /*! + * @copydoc IProxy::callMethodAsync(const MethodName&) + */ [[nodiscard]] AsyncMethodInvoker callMethodAsync(const std::string& methodName); /*! @@ -254,8 +267,8 @@ namespace sdbus { * * @throws sdbus::Error in case of failure */ - virtual void registerSignalHandler( const std::string& interfaceName - , const std::string& signalName + virtual void registerSignalHandler( const InterfaceName& interfaceName + , const SignalName& signalName , signal_handler signalHandler ) = 0; /*! @@ -273,8 +286,8 @@ namespace sdbus { * * @throws sdbus::Error in case of failure */ - [[nodiscard]] virtual Slot registerSignalHandler( const std::string& interfaceName - , const std::string& signalName + [[nodiscard]] virtual Slot registerSignalHandler( const InterfaceName& interfaceName + , const SignalName& signalName , signal_handler signalHandler , return_slot_t ) = 0; @@ -294,11 +307,19 @@ namespace sdbus { * * Example of use: * @code - * object_.uponSignal("fooSignal").onInterface("com.kistler.foo").call([this](int arg1, double arg2){ this->onFooSignal(arg1, arg2); }); + * object_.uponSignal("stateChanged").onInterface("com.kistler.foo").call([this](int arg1, double arg2){ this->onStateChanged(arg1, arg2); }); + * sdbus::InterfaceName foo{"com.kistler.foo"}; + * sdbus::SignalName levelChanged{"levelChanged"}; + * object_.uponSignal(levelChanged).onInterface(foo).call([this](uint16_t level){ this->onLevelChanged(level); }); * @endcode * * @throws sdbus::Error in case of failure */ + [[nodiscard]] SignalSubscriber uponSignal(const SignalName& signalName); + + /*! + * @copydoc IProxy::uponSignal(const SignalName&) + */ [[nodiscard]] SignalSubscriber uponSignal(const std::string& signalName); /*! @@ -325,10 +346,18 @@ namespace sdbus { * Example of use: * @code * int state = object.getProperty("state").onInterface("com.kistler.foo"); + * sdbus::InterfaceName foo{"com.kistler.foo"}; + * sdbus::PropertyName level{"level"}; + * int level = object.getProperty(level).onInterface(foo); * @endcode * * @throws sdbus::Error in case of failure */ + [[nodiscard]] PropertyGetter getProperty(const PropertyName& propertyName); + + /*! + * @copydoc IProxy::getProperty(const PropertyName&) + */ [[nodiscard]] PropertyGetter getProperty(const std::string& propertyName); /*! @@ -349,6 +378,11 @@ namespace sdbus { * * @throws sdbus::Error in case of failure */ + [[nodiscard]] AsyncPropertyGetter getPropertyAsync(const PropertyName& propertyName); + + /*! + * @copydoc IProxy::getPropertyAsync(const PropertyName&) + */ [[nodiscard]] AsyncPropertyGetter getPropertyAsync(const std::string& propertyName); /*! @@ -371,6 +405,11 @@ namespace sdbus { * * @throws sdbus::Error in case of failure */ + [[nodiscard]] PropertySetter setProperty(const PropertyName& propertyName); + + /*! + * @copydoc IProxy::setProperty(const PropertyName&) + */ [[nodiscard]] PropertySetter setProperty(const std::string& propertyName); /*! @@ -391,6 +430,11 @@ namespace sdbus { * * @throws sdbus::Error in case of failure */ + [[nodiscard]] AsyncPropertySetter setPropertyAsync(const PropertyName& propertyName); + + /*! + * @copydoc IProxy::setPropertyAsync(const PropertyName&) + */ [[nodiscard]] AsyncPropertySetter setPropertyAsync(const std::string& propertyName); /*! @@ -420,7 +464,7 @@ namespace sdbus { * * Example of use: * @code - * auto callback = [](std::optional err, const std::map>& properties){ ... }; + * auto callback = [](std::optional err, const std::map>& properties){ ... }; * auto props = object.getAllPropertiesAsync().onInterface("com.kistler.foo").uponReplyInvoke(std::move(callback)); * @endcode * @@ -438,7 +482,7 @@ namespace sdbus { /*! * @brief Returns object path of the underlying DBus object */ - [[nodiscard]] virtual const std::string& getObjectPath() const = 0; + [[nodiscard]] virtual const ObjectPath& getObjectPath() const = 0; /*! * @brief Provides access to the currently processed D-Bus message @@ -525,41 +569,90 @@ namespace sdbus { return callMethodAsync(message, microsecs.count(), with_future); } - inline MethodInvoker IProxy::callMethod(const std::string& methodName) + inline MethodInvoker IProxy::callMethod(const MethodName& methodName) { return MethodInvoker(*this, methodName); } - inline AsyncMethodInvoker IProxy::callMethodAsync(const std::string& methodName) + inline MethodInvoker IProxy::callMethod(const std::string& methodName) + { + // Down-cast through static cast for performance reasons (no extra copy and object construction needed) + static_assert(sizeof(methodName) == sizeof(MethodName)); + return callMethod(static_cast(methodName)); + } + + inline AsyncMethodInvoker IProxy::callMethodAsync(const MethodName& methodName) { return AsyncMethodInvoker(*this, methodName); } - inline SignalSubscriber IProxy::uponSignal(const std::string& signalName) + inline AsyncMethodInvoker IProxy::callMethodAsync(const std::string& methodName) + { + // Down-cast through static cast for performance reasons (no extra copy and object construction needed) + static_assert(sizeof(methodName) == sizeof(MethodName)); + return callMethodAsync(static_cast(methodName)); + } + + inline SignalSubscriber IProxy::uponSignal(const SignalName& signalName) { return SignalSubscriber(*this, signalName); } - inline PropertyGetter IProxy::getProperty(const std::string& propertyName) + inline SignalSubscriber IProxy::uponSignal(const std::string& signalName) + { + // Down-cast through static cast for performance reasons (no extra copy and object construction needed) + static_assert(sizeof(signalName) == sizeof(SignalName)); + return uponSignal(static_cast(signalName)); + } + + inline PropertyGetter IProxy::getProperty(const PropertyName& propertyName) { return PropertyGetter(*this, propertyName); } - inline AsyncPropertyGetter IProxy::getPropertyAsync(const std::string& propertyName) + inline PropertyGetter IProxy::getProperty(const std::string& propertyName) + { + // Down-cast through static cast for performance reasons (no extra copy and object construction needed) + static_assert(sizeof(propertyName) == sizeof(PropertyName)); + return getProperty(static_cast(propertyName)); + } + + inline AsyncPropertyGetter IProxy::getPropertyAsync(const PropertyName& propertyName) { return AsyncPropertyGetter(*this, propertyName); } - inline PropertySetter IProxy::setProperty(const std::string& propertyName) + inline AsyncPropertyGetter IProxy::getPropertyAsync(const std::string& propertyName) + { + // Down-cast through static cast for performance reasons (no extra copy and object construction needed) + static_assert(sizeof(propertyName) == sizeof(PropertyName)); + return getPropertyAsync(static_cast(propertyName)); + } + + inline PropertySetter IProxy::setProperty(const PropertyName& propertyName) { return PropertySetter(*this, propertyName); } - inline AsyncPropertySetter IProxy::setPropertyAsync(const std::string& propertyName) + inline PropertySetter IProxy::setProperty(const std::string& propertyName) + { + // Down-cast through static cast for performance reasons (no extra copy and object construction needed) + static_assert(sizeof(propertyName) == sizeof(PropertyName)); + return setProperty(static_cast(propertyName)); + } + + inline AsyncPropertySetter IProxy::setPropertyAsync(const PropertyName& propertyName) { return AsyncPropertySetter(*this, propertyName); } + inline AsyncPropertySetter IProxy::setPropertyAsync(const std::string& propertyName) + { + // Down-cast through static cast for performance reasons (no extra copy and object construction needed) + static_assert(sizeof(propertyName) == sizeof(PropertyName)); + return setPropertyAsync(static_cast(propertyName)); + } + inline AllPropertiesGetter IProxy::getAllProperties() { return AllPropertiesGetter(*this); @@ -593,8 +686,8 @@ namespace sdbus { * @endcode */ [[nodiscard]] std::unique_ptr createProxy( sdbus::IConnection& connection - , std::string destination - , std::string objectPath ); + , ServiceName destination + , ObjectPath objectPath ); /*! * @brief Creates a proxy object for a specific remote D-Bus object @@ -619,8 +712,8 @@ namespace sdbus { * @endcode */ [[nodiscard]] std::unique_ptr createProxy( std::unique_ptr&& connection - , std::string destination - , std::string objectPath ); + , ServiceName destination + , ObjectPath objectPath ); /*! * @brief Creates a proxy object for a specific remote D-Bus object @@ -646,8 +739,8 @@ namespace sdbus { * @endcode */ [[nodiscard]] std::unique_ptr createProxy( std::unique_ptr&& connection - , std::string destination - , std::string objectPath + , ServiceName destination + , ObjectPath objectPath , dont_run_event_loop_thread_t ); /*! @@ -667,8 +760,8 @@ namespace sdbus { * auto proxy = sdbus::createProxy("com.kistler.foo", "/com/kistler/foo"); * @endcode */ - [[nodiscard]] std::unique_ptr createProxy( std::string destination - , std::string objectPath ); + [[nodiscard]] std::unique_ptr createProxy( ServiceName destination + , ObjectPath objectPath ); /*! * @brief Creates a proxy object for a specific remote D-Bus object @@ -688,8 +781,8 @@ namespace sdbus { * auto proxy = sdbus::createProxy("com.kistler.foo", "/com/kistler/foo", sdbus::dont_run_event_loop_thread ); * @endcode */ - [[nodiscard]] std::unique_ptr createProxy( std::string destination - , std::string objectPath + [[nodiscard]] std::unique_ptr createProxy( ServiceName destination + , ObjectPath objectPath , dont_run_event_loop_thread_t ); } diff --git a/include/sdbus-c++/Message.h b/include/sdbus-c++/Message.h index db5f6585..811720ec 100644 --- a/include/sdbus-c++/Message.h +++ b/include/sdbus-c++/Message.h @@ -52,10 +52,14 @@ namespace sdbus { class Variant; class ObjectPath; + class InterfaceName; + class MemberName; class Signature; template class Struct; class UnixFd; class MethodReply; + class BusName; + using ConnectionName = BusName; namespace internal { class ISdBus; } @@ -181,11 +185,11 @@ namespace sdbus { explicit operator bool() const; void clearFlags(); - std::string getInterfaceName() const; - std::string getMemberName() const; - std::string getSender() const; - std::string getPath() const; - std::string getDestination() const; + InterfaceName getInterfaceName() const; + MemberName getMemberName() const; + ConnectionName getSender() const; + ObjectPath getPath() const; + ConnectionName getDestination() const; void peekType(std::string& type, std::string& contents) const; bool isValid() const; bool isEmpty() const; @@ -281,7 +285,7 @@ namespace sdbus { public: Signal() = default; - void setDestination(const std::string& destination); + void setDestination(const ConnectionName& destination); void send() const; }; diff --git a/include/sdbus-c++/ProxyInterfaces.h b/include/sdbus-c++/ProxyInterfaces.h index d587523b..6cbb804c 100644 --- a/include/sdbus-c++/ProxyInterfaces.h +++ b/include/sdbus-c++/ProxyInterfaces.h @@ -104,7 +104,7 @@ namespace sdbus { * This constructor overload creates a proxy that manages its own D-Bus connection(s). * For more information on its behavior, consult @ref createProxy(std::string,std::string) */ - ProxyInterfaces(std::string destination, std::string objectPath) + ProxyInterfaces(ServiceName destination, ObjectPath objectPath) : ProxyObjectHolder(createProxy(std::move(destination), std::move(objectPath))) , _Interfaces(getProxy())... { @@ -119,7 +119,7 @@ namespace sdbus { * This constructor overload creates a proxy that manages its own D-Bus connection(s). * For more information on its behavior, consult @ref createProxy(std::string,std::string,sdbus::dont_run_event_loop_thread_t) */ - ProxyInterfaces(std::string destination, std::string objectPath, dont_run_event_loop_thread_t) + ProxyInterfaces(ServiceName destination, ObjectPath objectPath, dont_run_event_loop_thread_t) : ProxyObjectHolder(createProxy(std::move(destination), std::move(objectPath), dont_run_event_loop_thread)) , _Interfaces(getProxy())... { @@ -135,7 +135,7 @@ namespace sdbus { * The proxy created this way just references a D-Bus connection owned and managed by the user. * For more information on its behavior, consult @ref createProxy(IConnection&,std::string,std::string) */ - ProxyInterfaces(IConnection& connection, std::string destination, std::string objectPath) + ProxyInterfaces(IConnection& connection, ServiceName destination, ObjectPath objectPath) : ProxyObjectHolder(createProxy(connection, std::move(destination), std::move(objectPath))) , _Interfaces(getProxy())... { @@ -151,7 +151,7 @@ namespace sdbus { * The proxy created this way becomes an owner of the connection. * For more information on its behavior, consult @ref createProxy(std::unique_ptr&&,std::string,std::string) */ - ProxyInterfaces(std::unique_ptr&& connection, std::string destination, std::string objectPath) + ProxyInterfaces(std::unique_ptr&& connection, ServiceName destination, ObjectPath objectPath) : ProxyObjectHolder(createProxy(std::move(connection), std::move(destination), std::move(objectPath))) , _Interfaces(getProxy())... { @@ -167,7 +167,7 @@ namespace sdbus { * The proxy created this way becomes an owner of the connection. * For more information on its behavior, consult @ref createProxy(std::unique_ptr&&,std::string,std::string,sdbus::dont_run_event_loop_thread_t) */ - ProxyInterfaces(std::unique_ptr&& connection, std::string destination, std::string objectPath, dont_run_event_loop_thread_t) + ProxyInterfaces(std::unique_ptr&& connection, ServiceName destination, ObjectPath objectPath, dont_run_event_loop_thread_t) : ProxyObjectHolder(createProxy(std::move(connection), std::move(destination), std::move(objectPath), dont_run_event_loop_thread)) , _Interfaces(getProxy())... { diff --git a/include/sdbus-c++/StandardInterfaces.h b/include/sdbus-c++/StandardInterfaces.h index b9dee98e..c1ffa504 100644 --- a/include/sdbus-c++/StandardInterfaces.h +++ b/include/sdbus-c++/StandardInterfaces.h @@ -39,7 +39,7 @@ namespace sdbus { // Proxy for peer class Peer_proxy { - static constexpr const char* INTERFACE_NAME = "org.freedesktop.DBus.Peer"; + static inline const InterfaceName INTERFACE_NAME{"org.freedesktop.DBus.Peer"}; protected: Peer_proxy(sdbus::IProxy& proxy) @@ -78,7 +78,7 @@ namespace sdbus { // Proxy for introspection class Introspectable_proxy { - static constexpr const char* INTERFACE_NAME = "org.freedesktop.DBus.Introspectable"; + static inline const InterfaceName INTERFACE_NAME{"org.freedesktop.DBus.Introspectable"}; protected: Introspectable_proxy(sdbus::IProxy& proxy) @@ -112,7 +112,7 @@ namespace sdbus { // Proxy for properties class Properties_proxy { - static constexpr const char* INTERFACE_NAME = "org.freedesktop.DBus.Properties"; + static inline const InterfaceName INTERFACE_NAME{"org.freedesktop.DBus.Properties"}; protected: Properties_proxy(sdbus::IProxy& proxy) @@ -132,68 +132,106 @@ namespace sdbus { proxy_ ->uponSignal("PropertiesChanged") .onInterface(INTERFACE_NAME) - .call([this]( const std::string& interfaceName - , const std::map& changedProperties - , const std::vector& invalidatedProperties ) + .call([this]( const InterfaceName& interfaceName + , const std::map& changedProperties + , const std::vector& invalidatedProperties ) { this->onPropertiesChanged(interfaceName, changedProperties, invalidatedProperties); }); } - virtual void onPropertiesChanged( const std::string& interfaceName - , const std::map& changedProperties - , const std::vector& invalidatedProperties ) = 0; + virtual void onPropertiesChanged( const InterfaceName& interfaceName + , const std::map& changedProperties + , const std::vector& invalidatedProperties ) = 0; public: - sdbus::Variant Get(const std::string& interfaceName, const std::string& propertyName) + sdbus::Variant Get(const InterfaceName& interfaceName, const PropertyName& propertyName) + { + return proxy_->getProperty(propertyName).onInterface(interfaceName); + } + + // TODO: Refactor from std::string to std::string_view before release/v2.0 !!! + sdbus::Variant Get(const InterfaceName& interfaceName, const std::string& propertyName) { return proxy_->getProperty(propertyName).onInterface(interfaceName); } template - PendingAsyncCall GetAsync(const std::string& interfaceName, const std::string& propertyName, _Function&& callback) + PendingAsyncCall GetAsync(const InterfaceName& interfaceName, const PropertyName& propertyName, _Function&& callback) { return proxy_->getPropertyAsync(propertyName).onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback)); } - std::future GetAsync(const std::string& interfaceName, const std::string& propertyName, with_future_t) + template + PendingAsyncCall GetAsync(const InterfaceName& interfaceName, const std::string& propertyName, _Function&& callback) + { + return proxy_->getPropertyAsync(propertyName).onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback)); + } + + std::future GetAsync(const InterfaceName& interfaceName, const PropertyName& propertyName, with_future_t) { return proxy_->getPropertyAsync(propertyName).onInterface(interfaceName).getResultAsFuture(); } - void Set(const std::string& interfaceName, const std::string& propertyName, const sdbus::Variant& value) + std::future GetAsync(const InterfaceName& interfaceName, const std::string& propertyName, with_future_t) + { + return proxy_->getPropertyAsync(propertyName).onInterface(interfaceName).getResultAsFuture(); + } + + void Set(const InterfaceName& interfaceName, const PropertyName& propertyName, const sdbus::Variant& value) + { + proxy_->setProperty(propertyName).onInterface(interfaceName).toValue(value); + } + + void Set(const InterfaceName& interfaceName, const std::string& propertyName, const sdbus::Variant& value) { proxy_->setProperty(propertyName).onInterface(interfaceName).toValue(value); } - void Set(const std::string& interfaceName, const std::string& propertyName, const sdbus::Variant& value, dont_expect_reply_t) + void Set(const InterfaceName& interfaceName, const PropertyName& propertyName, const sdbus::Variant& value, dont_expect_reply_t) + { + proxy_->setProperty(propertyName).onInterface(interfaceName).toValue(value, dont_expect_reply); + } + + void Set(const InterfaceName& interfaceName, const std::string& propertyName, const sdbus::Variant& value, dont_expect_reply_t) { proxy_->setProperty(propertyName).onInterface(interfaceName).toValue(value, dont_expect_reply); } template - PendingAsyncCall SetAsync(const std::string& interfaceName, const std::string& propertyName, const sdbus::Variant& value, _Function&& callback) + PendingAsyncCall SetAsync(const InterfaceName& interfaceName, const PropertyName& propertyName, const sdbus::Variant& value, _Function&& callback) { return proxy_->setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).uponReplyInvoke(std::forward<_Function>(callback)); } - std::future SetAsync(const std::string& interfaceName, const std::string& propertyName, const sdbus::Variant& value, with_future_t) + template + PendingAsyncCall SetAsync(const InterfaceName& interfaceName, const std::string& propertyName, const sdbus::Variant& value, _Function&& callback) + { + return proxy_->setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).uponReplyInvoke(std::forward<_Function>(callback)); + } + + std::future SetAsync(const InterfaceName& interfaceName, const PropertyName& propertyName, const sdbus::Variant& value, with_future_t) + { + return proxy_->setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).getResultAsFuture(); + } + + std::future SetAsync(const InterfaceName& interfaceName, const std::string& propertyName, const sdbus::Variant& value, with_future_t) { return proxy_->setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).getResultAsFuture(); } - std::map GetAll(const std::string& interfaceName) + std::map GetAll(const InterfaceName& interfaceName) { return proxy_->getAllProperties().onInterface(interfaceName); } template - PendingAsyncCall GetAllAsync(const std::string& interfaceName, _Function&& callback) + PendingAsyncCall GetAllAsync(const InterfaceName& interfaceName, _Function&& callback) { return proxy_->getAllPropertiesAsync().onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback)); } - std::future> GetAllAsync(const std::string& interfaceName, with_future_t) + std::future> GetAllAsync(const InterfaceName& interfaceName, with_future_t) { return proxy_->getAllPropertiesAsync().onInterface(interfaceName).getResultAsFuture(); } @@ -205,7 +243,7 @@ namespace sdbus { // Proxy for object manager class ObjectManager_proxy { - static constexpr const char* INTERFACE_NAME = "org.freedesktop.DBus.ObjectManager"; + static inline const InterfaceName INTERFACE_NAME{"org.freedesktop.DBus.ObjectManager"}; protected: ObjectManager_proxy(sdbus::IProxy& proxy) @@ -226,7 +264,7 @@ namespace sdbus { ->uponSignal("InterfacesAdded") .onInterface(INTERFACE_NAME) .call([this]( const sdbus::ObjectPath& objectPath - , const std::map>& interfacesAndProperties ) + , const std::map>& interfacesAndProperties ) { this->onInterfacesAdded(objectPath, interfacesAndProperties); }); @@ -234,21 +272,21 @@ namespace sdbus { proxy_->uponSignal("InterfacesRemoved") .onInterface(INTERFACE_NAME) .call([this]( const sdbus::ObjectPath& objectPath - , const std::vector& interfaces ) + , const std::vector& interfaces ) { this->onInterfacesRemoved(objectPath, interfaces); }); } virtual void onInterfacesAdded( const sdbus::ObjectPath& objectPath - , const std::map>& interfacesAndProperties) = 0; + , const std::map>& interfacesAndProperties) = 0; virtual void onInterfacesRemoved( const sdbus::ObjectPath& objectPath - , const std::vector& interfaces) = 0; + , const std::vector& interfaces) = 0; public: - std::map>> GetManagedObjects() + std::map>> GetManagedObjects() { - std::map>> objectsInterfacesAndProperties; + std::map>> objectsInterfacesAndProperties; proxy_->callMethod("GetManagedObjects").onInterface(INTERFACE_NAME).storeResultsTo(objectsInterfacesAndProperties); return objectsInterfacesAndProperties; } @@ -264,7 +302,7 @@ namespace sdbus { // Adaptor for properties class Properties_adaptor { - static constexpr const char* INTERFACE_NAME = "org.freedesktop.DBus.Properties"; + static inline const InterfaceName INTERFACE_NAME{"org.freedesktop.DBus.Properties"}; protected: Properties_adaptor(sdbus::IObject& object) : object_(&object) @@ -283,12 +321,12 @@ namespace sdbus { } public: - void emitPropertiesChangedSignal(const std::string& interfaceName, const std::vector& properties) + void emitPropertiesChangedSignal(const InterfaceName& interfaceName, const std::vector& properties) { object_->emitPropertiesChangedSignal(interfaceName, properties); } - void emitPropertiesChangedSignal(const std::string& interfaceName) + void emitPropertiesChangedSignal(const InterfaceName& interfaceName) { object_->emitPropertiesChangedSignal(interfaceName); } @@ -309,7 +347,7 @@ namespace sdbus { */ class ObjectManager_adaptor { - static constexpr const char* INTERFACE_NAME = "org.freedesktop.DBus.ObjectManager"; + static inline const InterfaceName INTERFACE_NAME{"org.freedesktop.DBus.ObjectManager"}; protected: explicit ObjectManager_adaptor(sdbus::IObject& object) : object_(&object) @@ -378,7 +416,7 @@ namespace sdbus { * * See IObject::emitInterfacesAddedSignal(). */ - void emitInterfacesAddedSignal(const std::vector& interfaces) + void emitInterfacesAddedSignal(const std::vector& interfaces) { object_->emitInterfacesAddedSignal(interfaces); } @@ -398,7 +436,7 @@ namespace sdbus { * * See IObject::emitInterfacesRemovedSignal(). */ - void emitInterfacesRemovedSignal(const std::vector& interfaces) + void emitInterfacesRemovedSignal(const std::vector& interfaces) { object_->emitInterfacesRemovedSignal(interfaces); } diff --git a/include/sdbus-c++/TypeTraits.h b/include/sdbus-c++/TypeTraits.h index 4fd03435..cec5f933 100644 --- a/include/sdbus-c++/TypeTraits.h +++ b/include/sdbus-c++/TypeTraits.h @@ -54,6 +54,9 @@ namespace sdbus { class ObjectPath; class Signature; class UnixFd; + class BusName; + class InterfaceName; + class MemberName; class MethodCall; class MethodReply; class Signal; @@ -309,6 +312,21 @@ namespace sdbus { } }; + template <> + struct signature_of : signature_of + { + }; + + template <> + struct signature_of : signature_of + { + }; + + template <> + struct signature_of : signature_of + { + }; + template struct signature_of> { diff --git a/include/sdbus-c++/Types.h b/include/sdbus-c++/Types.h index aeb15516..0323e343 100644 --- a/include/sdbus-c++/Types.h +++ b/include/sdbus-c++/Types.h @@ -171,42 +171,107 @@ namespace sdbus { /********************************************//** * @class ObjectPath * - * Representation of object path D-Bus type + * Strong type representing the D-Bus object path * ***********************************************/ class ObjectPath : public std::string { public: - using std::string::string; - ObjectPath() = default; // Fixes gcc 6.3 error (default c-tor is not imported in above using declaration) - ObjectPath(const ObjectPath&) = default; // Fixes gcc 8.3 error (deleted copy constructor) - ObjectPath(ObjectPath&&) = default; // Enable move - user-declared copy ctor prevents implicit creation - ObjectPath& operator = (const ObjectPath&) = default; // Fixes gcc 8.3 error (deleted copy assignment) - ObjectPath& operator = (ObjectPath&&) = default; // Enable move - user-declared copy assign prevents implicit creation - ObjectPath(std::string path) - : std::string(std::move(path)) + ObjectPath() = default; + explicit ObjectPath(std::string value) + : std::string(std::move(value)) {} + explicit ObjectPath(const char* value) + : std::string(value) + {} + + using std::string::operator=; + }; + + /********************************************//** + * @class BusName + * + * Strong type representing the D-Bus bus/service/connection name + * + ***********************************************/ + class BusName : public std::string + { + public: + BusName() = default; + explicit BusName(std::string value) + : std::string(std::move(value)) + {} + explicit BusName(const char* value) + : std::string(value) + {} + + using std::string::operator=; + }; + + using ServiceName = BusName; + using ConnectionName = BusName; + + /********************************************//** + * @class InterfaceName + * + * Strong type representing the D-Bus interface name + * + ***********************************************/ + class InterfaceName : public std::string + { + public: + InterfaceName() = default; + explicit InterfaceName(std::string value) + : std::string(std::move(value)) + {} + explicit InterfaceName(const char* value) + : std::string(value) + {} + using std::string::operator=; }; + /********************************************//** + * @class MemberName + * + * Strong type representing the D-Bus member name + * + ***********************************************/ + class MemberName : public std::string + { + public: + MemberName() = default; + explicit MemberName(std::string value) + : std::string(std::move(value)) + {} + explicit MemberName(const char* value) + : std::string(value) + {} + + using std::string::operator=; + }; + + using MethodName = MemberName; + using SignalName = MemberName; + using PropertyName = MemberName; + /********************************************//** * @class Signature * - * Representation of Signature D-Bus type + * Strong type representing the D-Bus object path * ***********************************************/ class Signature : public std::string { public: - using std::string::string; - Signature() = default; // Fixes gcc 6.3 error (default c-tor is not imported in above using declaration) - Signature(const Signature&) = default; // Fixes gcc 8.3 error (deleted copy constructor) - Signature(Signature&&) = default; // Enable move - user-declared copy ctor prevents implicit creation - Signature& operator = (const Signature&) = default; // Fixes gcc 8.3 error (deleted copy assignment) - Signature& operator = (Signature&&) = default; // Enable move - user-declared copy assign prevents implicit creation - Signature(std::string path) - : std::string(std::move(path)) + Signature() = default; + explicit Signature(std::string value) + : std::string(std::move(value)) {} + explicit Signature(const char* value) + : std::string(value) + {} + using std::string::operator=; }; diff --git a/include/sdbus-c++/VTableItems.h b/include/sdbus-c++/VTableItems.h index 73a95e7d..b0aa3f08 100644 --- a/include/sdbus-c++/VTableItems.h +++ b/include/sdbus-c++/VTableItems.h @@ -27,6 +27,7 @@ #define SDBUS_CXX_VTABLEITEMS_H_ #include +#include #include #include @@ -46,15 +47,16 @@ namespace sdbus { MethodVTableItem& markAsPrivileged(); MethodVTableItem& withNoReply(); - std::string name; - std::string inputSignature; + MethodName name; + Signature inputSignature; std::vector inputParamNames; - std::string outputSignature; + Signature outputSignature; std::vector outputParamNames; method_callback callbackHandler; Flags flags; }; + MethodVTableItem registerMethod(MethodName methodName); MethodVTableItem registerMethod(std::string methodName); struct SignalVTableItem @@ -64,12 +66,13 @@ namespace sdbus { template SignalVTableItem& withParameters(_String... names); SignalVTableItem& markAsDeprecated(); - std::string name; - std::string signature; + SignalName name; + Signature signature; std::vector paramNames; Flags flags; }; + SignalVTableItem registerSignal(SignalName signalName); SignalVTableItem registerSignal(std::string signalName); struct PropertyVTableItem @@ -80,13 +83,14 @@ namespace sdbus { PropertyVTableItem& markAsPrivileged(); PropertyVTableItem& withUpdateBehavior(Flags::PropertyUpdateBehaviorFlags behavior); - std::string name; - std::string signature; + PropertyName name; + Signature signature; property_get_callback getter; property_set_callback setter; Flags flags; }; + PropertyVTableItem registerProperty(PropertyName propertyName); PropertyVTableItem registerProperty(std::string propertyName); struct InterfaceFlagsVTableItem diff --git a/include/sdbus-c++/VTableItems.inl b/include/sdbus-c++/VTableItems.inl index 0d2f3ac5..b3f60441 100644 --- a/include/sdbus-c++/VTableItems.inl +++ b/include/sdbus-c++/VTableItems.inl @@ -125,11 +125,16 @@ namespace sdbus { return *this; } - inline MethodVTableItem registerMethod(std::string methodName) + inline MethodVTableItem registerMethod(MethodName methodName) { return {std::move(methodName), {}, {}, {}, {}, {}, {}}; } + inline MethodVTableItem registerMethod(std::string methodName) + { + return registerMethod(MethodName{std::move(methodName)}); + } + /*** -------------------- ***/ /*** Signal VTable Item ***/ /*** -------------------- ***/ @@ -166,11 +171,16 @@ namespace sdbus { return *this; } - inline SignalVTableItem registerSignal(std::string signalName) + inline SignalVTableItem registerSignal(SignalName signalName) { return {std::move(signalName), {}, {}, {}}; } + inline SignalVTableItem registerSignal(std::string signalName) + { + return registerSignal(SignalName{std::move(signalName)}); + } + /*** -------------------- ***/ /*** Property VTable Item ***/ /*** -------------------- ***/ @@ -239,11 +249,16 @@ namespace sdbus { return *this; } - inline PropertyVTableItem registerProperty(std::string propertyName) + inline PropertyVTableItem registerProperty(PropertyName propertyName) { return {std::move(propertyName), {}, {}, {}, {}}; } + inline PropertyVTableItem registerProperty(std::string propertyName) + { + return registerProperty(PropertyName{std::move(propertyName)}); + } + /*** --------------------------- ***/ /*** Interface Flags VTable Item ***/ /*** --------------------------- ***/ diff --git a/src/Connection.cpp b/src/Connection.cpp index 7187d8d3..1353fbf4 100644 --- a/src/Connection.cpp +++ b/src/Connection.cpp @@ -28,6 +28,7 @@ #include "sdbus-c++/Error.h" #include "sdbus-c++/Message.h" +#include "sdbus-c++/Types.h" #include "MessageUtils.h" #include "ScopeGuard.h" @@ -108,7 +109,7 @@ Connection::~Connection() Connection::leaveEventLoop(); } -void Connection::requestName(const std::string& name) +void Connection::requestName(const ServiceName& name) { SDBUS_CHECK_SERVICE_NAME(name); @@ -120,7 +121,7 @@ void Connection::requestName(const std::string& name) wakeUpEventLoopIfMessagesInQueue(); } -void Connection::releaseName(const std::string& name) +void Connection::releaseName(const ServiceName& name) { auto r = sdbus_->sd_bus_release_name(bus_.get(), name.c_str()); SDBUS_THROW_ERROR_IF(r < 0, "Failed to release bus name", -r); @@ -130,12 +131,12 @@ void Connection::releaseName(const std::string& name) wakeUpEventLoopIfMessagesInQueue(); } -std::string Connection::getUniqueName() const +BusName Connection::getUniqueName() const { - const char* unique = nullptr; - auto r = sdbus_->sd_bus_get_unique_name(bus_.get(), &unique); - SDBUS_THROW_ERROR_IF(r < 0 || unique == nullptr, "Failed to get unique bus name", -r); - return unique; + const char* name{}; + auto r = sdbus_->sd_bus_get_unique_name(bus_.get(), &name); + SDBUS_THROW_ERROR_IF(r < 0 || name == nullptr, "Failed to get unique bus name", -r); + return BusName{name}; } void Connection::enterEventLoop() @@ -188,14 +189,14 @@ ISdBus& Connection::getSdBusInterface() return *sdbus_.get(); } -void Connection::addObjectManager(const std::string& objectPath, floating_slot_t) +void Connection::addObjectManager(const ObjectPath& objectPath, floating_slot_t) { auto r = sdbus_->sd_bus_add_object_manager(bus_.get(), nullptr, objectPath.c_str()); SDBUS_THROW_ERROR_IF(r < 0, "Failed to add object manager", -r); } -Slot Connection::addObjectManager(const std::string& objectPath, return_slot_t) +Slot Connection::addObjectManager(const ObjectPath& objectPath, return_slot_t) { sd_bus_slot *slot{}; @@ -456,8 +457,8 @@ void Connection::deleteSdEventSource(sd_event_source *s) #endif // SDBUS_basu -Slot Connection::addObjectVTable( const std::string& objectPath - , const std::string& interfaceName +Slot Connection::addObjectVTable( const ObjectPath& objectPath + , const InterfaceName& interfaceName , const sd_bus_vtable* vtable , void* userData ) { @@ -486,10 +487,10 @@ PlainMessage Connection::createPlainMessage() const return Message::Factory::create(sdbusMsg, sdbus_.get(), adopt_message); } -MethodCall Connection::createMethodCall( const std::string& destination - , const std::string& objectPath - , const std::string& interfaceName - , const std::string& methodName ) const +MethodCall Connection::createMethodCall( const ServiceName& destination + , const ObjectPath& objectPath + , const InterfaceName& interfaceName + , const MethodName& methodName ) const { sd_bus_message *sdbusMsg{}; @@ -505,9 +506,9 @@ MethodCall Connection::createMethodCall( const std::string& destination return Message::Factory::create(sdbusMsg, sdbus_.get(), adopt_message); } -Signal Connection::createSignal( const std::string& objectPath - , const std::string& interfaceName - , const std::string& signalName ) const +Signal Connection::createSignal( const ObjectPath& objectPath + , const InterfaceName& interfaceName + , const SignalName& signalName ) const { sd_bus_message *sdbusMsg{}; @@ -562,9 +563,9 @@ Slot Connection::callMethod(const MethodCall& message, void* callback, void* use return slot; } -void Connection::emitPropertiesChangedSignal( const std::string& objectPath - , const std::string& interfaceName - , const std::vector& propNames ) +void Connection::emitPropertiesChangedSignal( const ObjectPath& objectPath + , const InterfaceName& interfaceName + , const std::vector& propNames ) { auto names = to_strv(propNames); @@ -576,15 +577,15 @@ void Connection::emitPropertiesChangedSignal( const std::string& objectPath SDBUS_THROW_ERROR_IF(r < 0, "Failed to emit PropertiesChanged signal", -r); } -void Connection::emitInterfacesAddedSignal(const std::string& objectPath) +void Connection::emitInterfacesAddedSignal(const ObjectPath& objectPath) { auto r = sdbus_->sd_bus_emit_object_added(bus_.get(), objectPath.c_str()); SDBUS_THROW_ERROR_IF(r < 0, "Failed to emit InterfacesAdded signal for all registered interfaces", -r); } -void Connection::emitInterfacesAddedSignal( const std::string& objectPath - , const std::vector& interfaces ) +void Connection::emitInterfacesAddedSignal( const ObjectPath& objectPath + , const std::vector& interfaces ) { auto names = to_strv(interfaces); @@ -595,15 +596,15 @@ void Connection::emitInterfacesAddedSignal( const std::string& objectPath SDBUS_THROW_ERROR_IF(r < 0, "Failed to emit InterfacesAdded signal", -r); } -void Connection::emitInterfacesRemovedSignal(const std::string& objectPath) +void Connection::emitInterfacesRemovedSignal(const ObjectPath& objectPath) { auto r = sdbus_->sd_bus_emit_object_removed(bus_.get(), objectPath.c_str()); SDBUS_THROW_ERROR_IF(r < 0, "Failed to emit InterfacesRemoved signal for all registered interfaces", -r); } -void Connection::emitInterfacesRemovedSignal( const std::string& objectPath - , const std::vector& interfaces ) +void Connection::emitInterfacesRemovedSignal( const ObjectPath& objectPath + , const std::vector& interfaces ) { auto names = to_strv(interfaces); @@ -614,10 +615,10 @@ void Connection::emitInterfacesRemovedSignal( const std::string& objectPath SDBUS_THROW_ERROR_IF(r < 0, "Failed to emit InterfacesRemoved signal", -r); } -Slot Connection::registerSignalHandler( const std::string& sender - , const std::string& objectPath - , const std::string& interfaceName - , const std::string& signalName +Slot Connection::registerSignalHandler( const ServiceName& sender + , const ObjectPath& objectPath + , const InterfaceName& interfaceName + , const SignalName& signalName , sd_bus_message_handler_t callback , void* userData ) { @@ -777,7 +778,8 @@ Message Connection::getCurrentlyProcessedMessage() const return Message::Factory::create(sdbusMsg, sdbus_.get()); } -std::vector Connection::to_strv(const std::vector& strings) +template +std::vector Connection::to_strv(const std::vector& strings) { std::vector strv; for (auto& str : strings) @@ -890,7 +892,7 @@ std::unique_ptr createBusConnection() return std::make_unique(std::move(interface), Connection::default_bus); } -std::unique_ptr createBusConnection(const std::string& name) +std::unique_ptr createBusConnection(const ServiceName& name) { auto conn = createBusConnection(); conn->requestName(name); @@ -903,7 +905,7 @@ std::unique_ptr createSystemBusConnection() return std::make_unique(std::move(interface), Connection::system_bus); } -std::unique_ptr createSystemBusConnection(const std::string& name) +std::unique_ptr createSystemBusConnection(const ServiceName& name) { auto conn = createSystemBusConnection(); conn->requestName(name); @@ -916,7 +918,7 @@ std::unique_ptr createSessionBusConnection() return std::make_unique(std::move(interface), Connection::session_bus); } -std::unique_ptr createSessionBusConnection(const std::string& name) +std::unique_ptr createSessionBusConnection(const ServiceName& name) { auto conn = createSessionBusConnection(); conn->requestName(name); diff --git a/src/Connection.h b/src/Connection.h index 269f74f2..1f83f31e 100644 --- a/src/Connection.h +++ b/src/Connection.h @@ -41,7 +41,18 @@ #include #include +// Forward declarations struct sd_event_source; +namespace sdbus { + class ObjectPath; + class InterfaceName; + class BusName; + using ServiceName = BusName; + class MemberName; + using MethodName = MemberName; + using SignalName = MemberName; + using PropertyName = MemberName; +} namespace sdbus::internal { @@ -81,9 +92,9 @@ namespace sdbus::internal { Connection(std::unique_ptr&& interface, pseudo_bus_t); ~Connection() override; - void requestName(const std::string& name) override; - void releaseName(const std::string& name) override; - [[nodiscard]] std::string getUniqueName() const override; + void requestName(const ServiceName & name) override; + void releaseName(const ServiceName& name) override; + [[nodiscard]] BusName getUniqueName() const override; void enterEventLoop() override; void enterEventLoopAsync() override; void leaveEventLoop() override; @@ -91,8 +102,8 @@ namespace sdbus::internal { bool processPendingEvent() override; Message getCurrentlyProcessedMessage() const override; - void addObjectManager(const std::string& objectPath, floating_slot_t) override; - Slot addObjectManager(const std::string& objectPath, return_slot_t) override; + void addObjectManager(const ObjectPath& objectPath, floating_slot_t) override; + Slot addObjectManager(const ObjectPath& objectPath, return_slot_t) override; void setMethodCallTimeout(uint64_t timeout) override; [[nodiscard]] uint64_t getMethodCallTimeout() const override; @@ -109,38 +120,38 @@ namespace sdbus::internal { [[nodiscard]] const ISdBus& getSdBusInterface() const override; [[nodiscard]] ISdBus& getSdBusInterface() override; - Slot addObjectVTable( const std::string& objectPath - , const std::string& interfaceName + Slot addObjectVTable( const ObjectPath& objectPath + , const InterfaceName& interfaceName , const sd_bus_vtable* vtable , void* userData ) override; [[nodiscard]] PlainMessage createPlainMessage() const override; - [[nodiscard]] MethodCall createMethodCall( const std::string& destination - , const std::string& objectPath - , const std::string& interfaceName - , const std::string& methodName ) const override; - [[nodiscard]] Signal createSignal( const std::string& objectPath - , const std::string& interfaceName - , const std::string& signalName ) const override; + [[nodiscard]] MethodCall createMethodCall( const ServiceName& destination + , const ObjectPath& objectPath + , const InterfaceName& interfaceName + , const MethodName& methodName ) const override; + [[nodiscard]] Signal createSignal( const ObjectPath& objectPath + , const InterfaceName& interfaceName + , const SignalName& signalName ) const override; MethodReply callMethod(const MethodCall& message, uint64_t timeout) override; void callMethod(const MethodCall& message, void* callback, void* userData, uint64_t timeout, floating_slot_t) override; Slot callMethod(const MethodCall& message, void* callback, void* userData, uint64_t timeout) override; - void emitPropertiesChangedSignal( const std::string& objectPath - , const std::string& interfaceName - , const std::vector& propNames ) override; - void emitInterfacesAddedSignal(const std::string& objectPath) override; - void emitInterfacesAddedSignal( const std::string& objectPath - , const std::vector& interfaces ) override; - void emitInterfacesRemovedSignal(const std::string& objectPath) override; - void emitInterfacesRemovedSignal( const std::string& objectPath - , const std::vector& interfaces ) override; - - Slot registerSignalHandler( const std::string& sender - , const std::string& objectPath - , const std::string& interfaceName - , const std::string& signalName + void emitPropertiesChangedSignal( const ObjectPath& objectPath + , const InterfaceName& interfaceName + , const std::vector& propNames ) override; + void emitInterfacesAddedSignal(const ObjectPath& objectPath) override; + void emitInterfacesAddedSignal( const ObjectPath& objectPath + , const std::vector& interfaces ) override; + void emitInterfacesRemovedSignal(const ObjectPath& objectPath) override; + void emitInterfacesRemovedSignal( const ObjectPath& objectPath + , const std::vector& interfaces ) override; + + Slot registerSignalHandler( const ServiceName& sender + , const ObjectPath& objectPath + , const InterfaceName& interfaceName + , const SignalName& signalName , sd_bus_message_handler_t callback , void* userData ) override; @@ -161,7 +172,8 @@ namespace sdbus::internal { void wakeUpEventLoopIfMessagesInQueue(); void joinWithEventLoop(); - static std::vector to_strv(const std::vector& strings); + template + static std::vector to_strv(const std::vector& strings); static int sdbus_match_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError); static int sdbus_match_install_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError); diff --git a/src/Error.cpp b/src/Error.cpp index d4adfeb9..a93e956f 100644 --- a/src/Error.cpp +++ b/src/Error.cpp @@ -32,14 +32,18 @@ namespace sdbus { - sdbus::Error createError(int errNo, const std::string& customMsg) + sdbus::Error createError(int errNo, std::string customMsg) { sd_bus_error sdbusError = SD_BUS_ERROR_NULL; sd_bus_error_set_errno(&sdbusError, errNo); SCOPE_EXIT{ sd_bus_error_free(&sdbusError); }; - std::string name(sdbusError.name); - std::string message(customMsg + " (" + sdbusError.message + ")"); - return sdbus::Error(name, message); + Error::Name name(sdbusError.name); + std::string message(std::move(customMsg)); + message.append(" ("); + message.append(sdbusError.message); + message.append(")"); + + return Error(std::move(name), std::move(message)); } } // namespace sdbus diff --git a/src/IConnection.h b/src/IConnection.h index d1524481..dc1b1892 100644 --- a/src/IConnection.h +++ b/src/IConnection.h @@ -37,12 +37,20 @@ #include SDBUS_HEADER #include -// Forward declaration +// Forward declarations namespace sdbus { class MethodCall; class MethodReply; class Signal; class PlainMessage; + class ObjectPath; + class InterfaceName; + class BusName; + using ServiceName = BusName; + class MemberName; + using MethodName = MemberName; + using SignalName = MemberName; + using PropertyName = MemberName; namespace internal { class ISdBus; } @@ -59,41 +67,41 @@ namespace sdbus::internal { [[nodiscard]] virtual const ISdBus& getSdBusInterface() const = 0; [[nodiscard]] virtual ISdBus& getSdBusInterface() = 0; - [[nodiscard]] virtual Slot addObjectVTable( const std::string& objectPath - , const std::string& interfaceName + [[nodiscard]] virtual Slot addObjectVTable( const ObjectPath& objectPath + , const InterfaceName& interfaceName , const sd_bus_vtable* vtable , void* userData ) = 0; [[nodiscard]] virtual PlainMessage createPlainMessage() const = 0; - [[nodiscard]] virtual MethodCall createMethodCall( const std::string& destination - , const std::string& objectPath - , const std::string& interfaceName - , const std::string& methodName ) const = 0; - [[nodiscard]] virtual Signal createSignal( const std::string& objectPath - , const std::string& interfaceName - , const std::string& signalName ) const = 0; + [[nodiscard]] virtual MethodCall createMethodCall( const ServiceName& destination + , const ObjectPath& objectPath + , const InterfaceName& interfaceName + , const MethodName& methodName ) const = 0; + [[nodiscard]] virtual Signal createSignal( const ObjectPath& objectPath + , const InterfaceName& interfaceName + , const SignalName& signalName ) const = 0; virtual MethodReply callMethod(const MethodCall& message, uint64_t timeout) = 0; virtual void callMethod(const MethodCall& message, void* callback, void* userData, uint64_t timeout, floating_slot_t) = 0; [[nodiscard]] virtual Slot callMethod(const MethodCall& message, void* callback, void* userData, uint64_t timeout) = 0; - virtual void emitPropertiesChangedSignal( const std::string& objectPath - , const std::string& interfaceName - , const std::vector& propNames ) = 0; - virtual void emitInterfacesAddedSignal(const std::string& objectPath) = 0; - virtual void emitInterfacesAddedSignal( const std::string& objectPath - , const std::vector& interfaces ) = 0; - virtual void emitInterfacesRemovedSignal(const std::string& objectPath) = 0; - virtual void emitInterfacesRemovedSignal( const std::string& objectPath - , const std::vector& interfaces ) = 0; + virtual void emitPropertiesChangedSignal( const ObjectPath& objectPath + , const InterfaceName& interfaceName + , const std::vector& propNames ) = 0; + virtual void emitInterfacesAddedSignal(const ObjectPath& objectPath) = 0; + virtual void emitInterfacesAddedSignal( const ObjectPath& objectPath + , const std::vector& interfaces ) = 0; + virtual void emitInterfacesRemovedSignal(const ObjectPath& objectPath) = 0; + virtual void emitInterfacesRemovedSignal( const ObjectPath& objectPath + , const std::vector& interfaces ) = 0; using sdbus::IConnection::addObjectManager; - [[nodiscard]] virtual Slot addObjectManager(const std::string& objectPath, return_slot_t) = 0; + [[nodiscard]] virtual Slot addObjectManager(const ObjectPath& objectPath, return_slot_t) = 0; - [[nodiscard]] virtual Slot registerSignalHandler( const std::string& sender - , const std::string& objectPath - , const std::string& interfaceName - , const std::string& signalName + [[nodiscard]] virtual Slot registerSignalHandler( const ServiceName& sender + , const ObjectPath& objectPath + , const InterfaceName& interfaceName + , const SignalName& signalName , sd_bus_message_handler_t callback , void* userData ) = 0; }; diff --git a/src/Message.cpp b/src/Message.cpp index 22227266..ae281e2a 100644 --- a/src/Message.cpp +++ b/src/Message.cpp @@ -602,33 +602,34 @@ void Message::rewind(bool complete) SDBUS_THROW_ERROR_IF(r < 0, "Failed to rewind the message", -r); } -std::string Message::getInterfaceName() const +InterfaceName Message::getInterfaceName() const { - auto interface = sd_bus_message_get_interface((sd_bus_message*)msg_); - return interface != nullptr ? interface : ""; + const auto* interface = sd_bus_message_get_interface((sd_bus_message*)msg_); + return interface != nullptr ? InterfaceName{interface} : InterfaceName{}; } -std::string Message::getMemberName() const +MemberName Message::getMemberName() const { - auto member = sd_bus_message_get_member((sd_bus_message*)msg_); - return member != nullptr ? member : ""; + const auto* member = sd_bus_message_get_member((sd_bus_message*)msg_); + return member != nullptr ? MemberName{member} : MemberName{}; } -std::string Message::getSender() const +ConnectionName Message::getSender() const { - return sd_bus_message_get_sender((sd_bus_message*)msg_); + const auto* sender = sd_bus_message_get_sender((sd_bus_message*)msg_); + return ConnectionName{sender}; } -std::string Message::getPath() const +ObjectPath Message::getPath() const { - auto path = sd_bus_message_get_path((sd_bus_message*)msg_); - return path != nullptr ? path : ""; + const auto* path = sd_bus_message_get_path((sd_bus_message*)msg_); + return path != nullptr ? ObjectPath{path} : ObjectPath{}; } -std::string Message::getDestination() const +ConnectionName Message::getDestination() const { - auto destination = sd_bus_message_get_destination((sd_bus_message*)msg_); - return destination != nullptr ? destination : ""; + const auto* destination = sd_bus_message_get_destination((sd_bus_message*)msg_); + return destination != nullptr ? ConnectionName{destination} : ConnectionName{}; } void Message::peekType(std::string& type, std::string& contents) const @@ -801,7 +802,7 @@ MethodReply MethodCall::sendWithReply(uint64_t timeout) const auto r = sdbus_->sd_bus_call(nullptr, (sd_bus_message*)msg_, timeout, &sdbusError, &sdbusReply); if (sd_bus_error_is_set(&sdbusError)) - throw sdbus::Error(sdbusError.name, sdbusError.message); + throw Error(Error::Name{sdbusError.name}, sdbusError.message); SDBUS_THROW_ERROR_IF(r < 0, "Failed to call method", -r); @@ -866,7 +867,7 @@ void Signal::send() const SDBUS_THROW_ERROR_IF(r < 0, "Failed to emit signal", -r); } -void Signal::setDestination(const std::string& destination) +void Signal::setDestination(const ConnectionName& destination) { auto r = sdbus_->sd_bus_message_set_destination((sd_bus_message*)msg_, destination.c_str()); SDBUS_THROW_ERROR_IF(r < 0, "Failed to set signal destination", -r); diff --git a/src/Object.cpp b/src/Object.cpp index 6305f92e..fa90db70 100644 --- a/src/Object.cpp +++ b/src/Object.cpp @@ -43,20 +43,20 @@ namespace sdbus::internal { -Object::Object(sdbus::internal::IConnection& connection, std::string objectPath) +Object::Object(sdbus::internal::IConnection& connection, ObjectPath objectPath) : connection_(connection), objectPath_(std::move(objectPath)) { SDBUS_CHECK_OBJECT_PATH(objectPath_); } -void Object::addVTable(std::string interfaceName, std::vector vtable) +void Object::addVTable(InterfaceName interfaceName, std::vector vtable) { auto slot = Object::addVTable(std::move(interfaceName), std::move(vtable), return_slot); vtables_.push_back(std::move(slot)); } -Slot Object::addVTable(std::string interfaceName, std::vector vtable, return_slot_t) +Slot Object::addVTable(InterfaceName interfaceName, std::vector vtable, return_slot_t) { SDBUS_CHECK_INTERFACE_NAME(interfaceName); @@ -79,7 +79,7 @@ void Object::unregister() removeObjectManager(); } -sdbus::Signal Object::createSignal(const std::string& interfaceName, const std::string& signalName) +sdbus::Signal Object::createSignal(const InterfaceName& interfaceName, const SignalName& signalName) { return connection_.createSignal(objectPath_, interfaceName, signalName); } @@ -91,12 +91,12 @@ void Object::emitSignal(const sdbus::Signal& message) message.send(); } -void Object::emitPropertiesChangedSignal(const std::string& interfaceName, const std::vector& propNames) +void Object::emitPropertiesChangedSignal(const InterfaceName& interfaceName, const std::vector& propNames) { connection_.emitPropertiesChangedSignal(objectPath_, interfaceName, propNames); } -void Object::emitPropertiesChangedSignal(const std::string& interfaceName) +void Object::emitPropertiesChangedSignal(const InterfaceName& interfaceName) { Object::emitPropertiesChangedSignal(interfaceName, {}); } @@ -106,7 +106,7 @@ void Object::emitInterfacesAddedSignal() connection_.emitInterfacesAddedSignal(objectPath_); } -void Object::emitInterfacesAddedSignal(const std::vector& interfaces) +void Object::emitInterfacesAddedSignal(const std::vector& interfaces) { connection_.emitInterfacesAddedSignal(objectPath_, interfaces); } @@ -116,7 +116,7 @@ void Object::emitInterfacesRemovedSignal() connection_.emitInterfacesRemovedSignal(objectPath_); } -void Object::emitInterfacesRemovedSignal(const std::vector& interfaces) +void Object::emitInterfacesRemovedSignal(const std::vector& interfaces) { connection_.emitInterfacesRemovedSignal(objectPath_, interfaces); } @@ -141,7 +141,7 @@ sdbus::IConnection& Object::getConnection() const return connection_; } -const std::string& Object::getObjectPath() const +const ObjectPath& Object::getObjectPath() const { return objectPath_; } @@ -151,7 +151,7 @@ Message Object::getCurrentlyProcessedMessage() const return connection_.getCurrentlyProcessedMessage(); } -Object::VTable Object::createInternalVTable(std::string interfaceName, std::vector vtable) +Object::VTable Object::createInternalVTable(InterfaceName interfaceName, std::vector vtable) { VTable internalVTable; @@ -241,8 +241,8 @@ void Object::startSdBusVTable(const Flags& interfaceFlags, std::vector& vtable) { auto vtableItem = createSdBusVTableMethodItem( method.name.c_str() - , method.inputArgs.c_str() - , method.outputArgs.c_str() + , method.inputSignature.c_str() + , method.outputSignature.c_str() , method.paramNames.c_str() , &Object::sdbus_method_callback , method.flags.toSdBusMethodFlags() ); @@ -278,23 +278,27 @@ void Object::finalizeSdBusVTable(std::vector& vtable) vtable.push_back(createSdBusVTableEndItem()); } -const Object::VTable::MethodItem* Object::findMethod(const VTable& vtable, const std::string& methodName) +const Object::VTable::MethodItem* Object::findMethod(const VTable& vtable, std::string_view methodName) { auto it = std::lower_bound(vtable.methods.begin(), vtable.methods.end(), methodName, [](const auto& methodItem, const auto& methodName) { return methodItem.name < methodName; }); + (void)it; + return it != vtable.methods.end() && it->name == methodName ? &*it : nullptr; } -const Object::VTable::PropertyItem* Object::findProperty(const VTable& vtable, const std::string& propertyName) +const Object::VTable::PropertyItem* Object::findProperty(const VTable& vtable, std::string_view propertyName) { auto it = std::lower_bound(vtable.properties.begin(), vtable.properties.end(), propertyName, [](const auto& propertyItem, const auto& propertyName) { return propertyItem.name < propertyName; }); + (void)it; + return it != vtable.properties.end() && it->name == propertyName ? &*it : nullptr; } @@ -314,7 +318,7 @@ int Object::sdbus_method_callback(sd_bus_message *sdbusMessage, void *userData, auto message = Message::Factory::create(sdbusMessage, &vtable->object->connection_.getSdBusInterface()); - const auto* methodItem = findMethod(*vtable, message.getMemberName()); + const auto* methodItem = findMethod(*vtable, message.getMemberName()); // TODO: optimize the situation around getMemberName() assert(methodItem != nullptr); assert(methodItem->callback); @@ -379,7 +383,7 @@ int Object::sdbus_property_set_callback( sd_bus */*bus*/ namespace sdbus { -std::unique_ptr createObject(sdbus::IConnection& connection, std::string objectPath) +std::unique_ptr createObject(sdbus::IConnection& connection, ObjectPath objectPath) { auto* sdbusConnection = dynamic_cast(&connection); SDBUS_THROW_ERROR_IF(!sdbusConnection, "Connection is not a real sdbus-c++ connection", EINVAL); diff --git a/src/Object.h b/src/Object.h index 07dfd16b..f69fbb4d 100644 --- a/src/Object.h +++ b/src/Object.h @@ -30,12 +30,14 @@ #include "sdbus-c++/IObject.h" #include "IConnection.h" +#include "sdbus-c++/Types.h" #include #include #include #include #include +#include #include SDBUS_HEADER #include @@ -45,27 +47,27 @@ namespace sdbus::internal { : public IObject { public: - Object(sdbus::internal::IConnection& connection, std::string objectPath); + Object(sdbus::internal::IConnection& connection, ObjectPath objectPath); - void addVTable(std::string interfaceName, std::vector vtable) override; - Slot addVTable(std::string interfaceName, std::vector vtable, return_slot_t) override; + void addVTable(InterfaceName interfaceName, std::vector vtable) override; + Slot addVTable(InterfaceName interfaceName, std::vector vtable, return_slot_t) override; void unregister() override; - sdbus::Signal createSignal(const std::string& interfaceName, const std::string& signalName) override; + sdbus::Signal createSignal(const InterfaceName& interfaceName, const SignalName& signalName) override; void emitSignal(const sdbus::Signal& message) override; - void emitPropertiesChangedSignal(const std::string& interfaceName, const std::vector& propNames) override; - void emitPropertiesChangedSignal(const std::string& interfaceName) override; + void emitPropertiesChangedSignal(const InterfaceName& interfaceName, const std::vector& propNames) override; + void emitPropertiesChangedSignal(const InterfaceName& interfaceName) override; void emitInterfacesAddedSignal() override; - void emitInterfacesAddedSignal(const std::vector& interfaces) override; + void emitInterfacesAddedSignal(const std::vector& interfaces) override; void emitInterfacesRemovedSignal() override; - void emitInterfacesRemovedSignal(const std::vector& interfaces) override; + void emitInterfacesRemovedSignal(const std::vector& interfaces) override; void addObjectManager() override; void removeObjectManager() override; [[nodiscard]] bool hasObjectManager() const override; [[nodiscard]] sdbus::IConnection& getConnection() const override; - [[nodiscard]] const std::string& getObjectPath() const override; + [[nodiscard]] const ObjectPath& getObjectPath() const override; [[nodiscard]] Message getCurrentlyProcessedMessage() const override; private: @@ -74,14 +76,14 @@ namespace sdbus::internal { // An interface can have any number of vtables attached to it, not only one. struct VTable { - std::string interfaceName; + InterfaceName interfaceName; Flags interfaceFlags; struct MethodItem { - std::string name; - std::string inputArgs; - std::string outputArgs; + MethodName name; + Signature inputSignature; + Signature outputSignature; std::string paramNames; method_callback callback; Flags flags; @@ -91,8 +93,8 @@ namespace sdbus::internal { struct SignalItem { - std::string name; - std::string signature; + SignalName name; + Signature signature; std::string paramNames; Flags flags; }; @@ -101,8 +103,8 @@ namespace sdbus::internal { struct PropertyItem { - std::string name; - std::string signature; + PropertyName name; + Signature signature; property_get_callback getCallback; property_set_callback setCallback; Flags flags; @@ -121,7 +123,7 @@ namespace sdbus::internal { Slot slot; }; - VTable createInternalVTable(std::string interfaceName, std::vector vtable); + VTable createInternalVTable(InterfaceName interfaceName, std::vector vtable); void writeInterfaceFlagsToVTable(InterfaceFlagsVTableItem flags, VTable& vtable); void writeMethodRecordToVTable(MethodVTableItem method, VTable& vtable); void writeSignalRecordToVTable(SignalVTableItem signal, VTable& vtable); @@ -134,8 +136,8 @@ namespace sdbus::internal { static void writePropertyRecordToSdBusVTable(const VTable::PropertyItem& property, std::vector& vtable); static void finalizeSdBusVTable(std::vector& vtable); - static const VTable::MethodItem* findMethod(const VTable& vtable, const std::string& methodName); - static const VTable::PropertyItem* findProperty(const VTable& vtable, const std::string& propertyName); + static const VTable::MethodItem* findMethod(const VTable& vtable, std::string_view methodName); + static const VTable::PropertyItem* findProperty(const VTable& vtable, std::string_view propertyName); static std::string paramNamesToString(const std::vector& paramNames); @@ -157,7 +159,7 @@ namespace sdbus::internal { private: sdbus::internal::IConnection& connection_; - std::string objectPath_; + ObjectPath objectPath_; std::vector vtables_; Slot objectManagerSlot_; }; diff --git a/src/Proxy.cpp b/src/Proxy.cpp index f087447f..a55c8652 100644 --- a/src/Proxy.cpp +++ b/src/Proxy.cpp @@ -41,7 +41,7 @@ namespace sdbus::internal { -Proxy::Proxy(sdbus::internal::IConnection& connection, std::string destination, std::string objectPath) +Proxy::Proxy(sdbus::internal::IConnection& connection, ServiceName destination, ObjectPath objectPath) : connection_(&connection, [](sdbus::internal::IConnection *){ /* Intentionally left empty */ }) , destination_(std::move(destination)) , objectPath_(std::move(objectPath)) @@ -54,8 +54,8 @@ Proxy::Proxy(sdbus::internal::IConnection& connection, std::string destination, } Proxy::Proxy( std::unique_ptr&& connection - , std::string destination - , std::string objectPath ) + , ServiceName destination + , ObjectPath objectPath ) : connection_(std::move(connection)) , destination_(std::move(destination)) , objectPath_(std::move(objectPath)) @@ -69,8 +69,8 @@ Proxy::Proxy( std::unique_ptr&& connection } Proxy::Proxy( std::unique_ptr&& connection - , std::string destination - , std::string objectPath + , ServiceName destination + , ObjectPath objectPath , dont_run_event_loop_thread_t ) : connection_(std::move(connection)) , destination_(std::move(destination)) @@ -83,7 +83,7 @@ Proxy::Proxy( std::unique_ptr&& connection // This proxy is meant to be created, used for simple synchronous D-Bus call(s) and then dismissed. } -MethodCall Proxy::createMethodCall(const std::string& interfaceName, const std::string& methodName) +MethodCall Proxy::createMethodCall(const InterfaceName& interfaceName, const MethodName& methodName) { return connection_->createMethodCall(destination_, objectPath_, interfaceName, methodName); } @@ -134,8 +134,8 @@ std::future Proxy::callMethodAsync(const MethodCall& message, uint6 return future; } -void Proxy::registerSignalHandler( const std::string& interfaceName - , const std::string& signalName +void Proxy::registerSignalHandler( const InterfaceName& interfaceName + , const SignalName& signalName , signal_handler signalHandler ) { auto slot = Proxy::registerSignalHandler(interfaceName, signalName, std::move(signalHandler), return_slot); @@ -143,8 +143,8 @@ void Proxy::registerSignalHandler( const std::string& interfaceName floatingSignalSlots_.push_back(std::move(slot)); } -Slot Proxy::registerSignalHandler( const std::string& interfaceName - , const std::string& signalName +Slot Proxy::registerSignalHandler( const InterfaceName& interfaceName + , const SignalName& signalName , signal_handler signalHandler , return_slot_t ) { @@ -175,7 +175,7 @@ sdbus::IConnection& Proxy::getConnection() const return *connection_; } -const std::string& Proxy::getObjectPath() const +const ObjectPath& Proxy::getObjectPath() const { return objectPath_; } @@ -211,7 +211,7 @@ int Proxy::sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userDat } else { - Error exception(error->name, error->message); + Error exception(Error::Name{error->name}, error->message); asyncCallData->callback(std::move(message), std::move(exception)); } }, retError); @@ -266,8 +266,8 @@ bool PendingAsyncCall::isPending() const namespace sdbus { std::unique_ptr createProxy( IConnection& connection - , std::string destination - , std::string objectPath ) + , ServiceName destination + , ObjectPath objectPath ) { auto* sdbusConnection = dynamic_cast(&connection); SDBUS_THROW_ERROR_IF(!sdbusConnection, "Connection is not a real sdbus-c++ connection", EINVAL); @@ -278,8 +278,8 @@ std::unique_ptr createProxy( IConnection& connection } std::unique_ptr createProxy( std::unique_ptr&& connection - , std::string destination - , std::string objectPath ) + , ServiceName destination + , ObjectPath objectPath ) { auto* sdbusConnection = dynamic_cast(connection.get()); SDBUS_THROW_ERROR_IF(!sdbusConnection, "Connection is not a real sdbus-c++ connection", EINVAL); @@ -292,8 +292,8 @@ std::unique_ptr createProxy( std::unique_ptr&& conne } std::unique_ptr createProxy( std::unique_ptr&& connection - , std::string destination - , std::string objectPath + , ServiceName destination + , ObjectPath objectPath , dont_run_event_loop_thread_t ) { auto* sdbusConnection = dynamic_cast(connection.get()); @@ -307,8 +307,8 @@ std::unique_ptr createProxy( std::unique_ptr&& conne , dont_run_event_loop_thread ); } -std::unique_ptr createProxy( std::string destination - , std::string objectPath ) +std::unique_ptr createProxy( ServiceName destination + , ObjectPath objectPath ) { auto connection = sdbus::createBusConnection(); @@ -320,8 +320,8 @@ std::unique_ptr createProxy( std::string destination , std::move(objectPath) ); } -std::unique_ptr createProxy( std::string destination - , std::string objectPath +std::unique_ptr createProxy( ServiceName destination + , ObjectPath objectPath , dont_run_event_loop_thread_t ) { auto connection = sdbus::createBusConnection(); diff --git a/src/Proxy.h b/src/Proxy.h index 714ef758..d2d606e4 100644 --- a/src/Proxy.h +++ b/src/Proxy.h @@ -30,6 +30,7 @@ #include "sdbus-c++/IProxy.h" #include "IConnection.h" +#include "sdbus-c++/Types.h" #include #include @@ -45,33 +46,33 @@ namespace sdbus::internal { { public: Proxy( sdbus::internal::IConnection& connection - , std::string destination - , std::string objectPath ); + , ServiceName destination + , ObjectPath objectPath ); Proxy( std::unique_ptr&& connection - , std::string destination - , std::string objectPath ); + , ServiceName destination + , ObjectPath objectPath ); Proxy( std::unique_ptr&& connection - , std::string destination - , std::string objectPath + , ServiceName destination + , ObjectPath objectPath , dont_run_event_loop_thread_t ); - MethodCall createMethodCall(const std::string& interfaceName, const std::string& methodName) override; + MethodCall createMethodCall(const InterfaceName& interfaceName, const MethodName& methodName) override; MethodReply callMethod(const MethodCall& message, uint64_t timeout) override; PendingAsyncCall callMethodAsync(const MethodCall& message, async_reply_handler asyncReplyCallback, uint64_t timeout) override; std::future callMethodAsync(const MethodCall& message, with_future_t) override; std::future callMethodAsync(const MethodCall& message, uint64_t timeout, with_future_t) override; - void registerSignalHandler( const std::string& interfaceName - , const std::string& signalName + void registerSignalHandler( const InterfaceName& interfaceName + , const SignalName& signalName , signal_handler signalHandler ) override; - Slot registerSignalHandler( const std::string& interfaceName - , const std::string& signalName + Slot registerSignalHandler( const InterfaceName& interfaceName + , const SignalName& signalName , signal_handler signalHandler , return_slot_t ) override; void unregister() override; [[nodiscard]] sdbus::IConnection& getConnection() const override; - [[nodiscard]] const std::string& getObjectPath() const override; + [[nodiscard]] const ObjectPath& getObjectPath() const override; [[nodiscard]] Message getCurrentlyProcessedMessage() const override; private: @@ -84,8 +85,8 @@ namespace sdbus::internal { std::unique_ptr< sdbus::internal::IConnection , std::function > connection_; - std::string destination_; - std::string objectPath_; + ServiceName destination_; + ObjectPath objectPath_; std::vector floatingSignalSlots_; diff --git a/src/Utils.h b/src/Utils.h index e50667b0..3c08e0b8 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -41,7 +41,7 @@ SDBUS_THROW_ERROR_IF(!_NAME.empty() && !sd_bus_service_name_is_valid(_NAME.c_str()), "Invalid service name '" + _NAME + "' provided", EINVAL) \ /**/ #define SDBUS_CHECK_MEMBER_NAME(_NAME) \ - SDBUS_THROW_ERROR_IF(!sd_bus_member_name_is_valid(_NAME.c_str()), "Invalid member name '" + _NAME + "' provided", EINVAL) \ + SDBUS_THROW_ERROR_IF(!sd_bus_member_name_is_valid(_NAME.c_str()), std::string("Invalid member name '") + _NAME.c_str() + "' provided", EINVAL) \ /**/ #else #define SDBUS_CHECK_OBJECT_PATH(_PATH) @@ -66,12 +66,12 @@ namespace sdbus::internal { } catch (const std::exception& e) { - sd_bus_error_set(retError, SDBUSCPP_ERROR_NAME, e.what()); + sd_bus_error_set(retError, SDBUSCPP_ERROR_NAME.c_str(), e.what()); return false; } catch (...) { - sd_bus_error_set(retError, SDBUSCPP_ERROR_NAME, "Unknown error occurred"); + sd_bus_error_set(retError, SDBUSCPP_ERROR_NAME.c_str(), "Unknown error occurred"); return false; } diff --git a/tests/integrationtests/DBusAsyncMethodsTests.cpp b/tests/integrationtests/DBusAsyncMethodsTests.cpp index c13bfcc7..c868649e 100644 --- a/tests/integrationtests/DBusAsyncMethodsTests.cpp +++ b/tests/integrationtests/DBusAsyncMethodsTests.cpp @@ -96,7 +96,7 @@ TYPED_TEST(AsyncSdbusTestObject, RunsServerSideAsynchoronousMethodAsynchronously std::atomic startedCount{}; auto call = [&](uint32_t param) { - TestProxy proxy{BUS_NAME, OBJECT_PATH}; + TestProxy proxy{SERVICE_NAME, OBJECT_PATH}; ++startedCount; while (!invoke) ; auto result = proxy.doOperationAsync(param); @@ -119,7 +119,7 @@ TYPED_TEST(AsyncSdbusTestObject, HandlesCorrectlyABulkOfParallelServerSideAsyncM std::atomic startedCount{}; auto call = [&]() { - TestProxy proxy{BUS_NAME, OBJECT_PATH}; + TestProxy proxy{SERVICE_NAME, OBJECT_PATH}; ++startedCount; while (!invoke) ; diff --git a/tests/integrationtests/DBusConnectionTests.cpp b/tests/integrationtests/DBusConnectionTests.cpp index 855ab5e2..0093e708 100644 --- a/tests/integrationtests/DBusConnectionTests.cpp +++ b/tests/integrationtests/DBusConnectionTests.cpp @@ -51,38 +51,43 @@ TEST(Connection, CanBeDefaultConstructed) ASSERT_NO_THROW(auto con = sdbus::createBusConnection()); } -TEST(SystemBusConnection, CanRequestRegisteredDbusName) +TEST(Connection, CanRequestName) { - auto connection = sdbus::createSystemBusConnection(); + auto connection = sdbus::createBusConnection(); - ASSERT_NO_THROW(connection->requestName(BUS_NAME)) + // In case of system bus connection, requesting may throw as we need to allow that first through a config file in /etc/dbus-1/system.d + ASSERT_NO_THROW(connection->requestName(SERVICE_NAME)) << "Perhaps you've forgotten to copy `org.sdbuscpp.integrationtests.conf` file to `/etc/dbus-1/system.d` directory before running the tests?"; } TEST(SystemBusConnection, CannotRequestNonregisteredDbusName) { auto connection = sdbus::createSystemBusConnection(); - ASSERT_THROW(connection->requestName("some.random.not.supported.dbus.name"), sdbus::Error); + sdbus::ServiceName notSupportedBusName{"some.random.not.supported.dbus.name"}; + + ASSERT_THROW(connection->requestName(notSupportedBusName), sdbus::Error); } -TEST(Connection, CanReleasedRequestedName) +TEST(Connection, CanReleaseRequestedName) { auto connection = sdbus::createBusConnection(); + connection->requestName(SERVICE_NAME); - connection->requestName(BUS_NAME); - ASSERT_NO_THROW(connection->releaseName(BUS_NAME)); + ASSERT_NO_THROW(connection->releaseName(SERVICE_NAME)); } TEST(Connection, CannotReleaseNonrequestedName) { auto connection = sdbus::createBusConnection(); - ASSERT_THROW(connection->releaseName("some.random.nonrequested.name"), sdbus::Error); + sdbus::ServiceName notAcquiredBusName{"some.random.unacquired.name"}; + + ASSERT_THROW(connection->releaseName(notAcquiredBusName), sdbus::Error); } TEST(Connection, CanEnterAndLeaveInternalEventLoop) { auto connection = sdbus::createBusConnection(); - connection->requestName(BUS_NAME); + connection->requestName(SERVICE_NAME); std::thread t([&](){ connection->enterEventLoop(); }); connection->leaveEventLoop(); diff --git a/tests/integrationtests/DBusGeneralTests.cpp b/tests/integrationtests/DBusGeneralTests.cpp index 1164f6cd..7f9dae06 100644 --- a/tests/integrationtests/DBusGeneralTests.cpp +++ b/tests/integrationtests/DBusGeneralTests.cpp @@ -54,12 +54,12 @@ using ADirectConnection = TestFixtureWithDirectConnection; TEST(AdaptorAndProxy, CanBeConstructedSuccesfully) { auto connection = sdbus::createBusConnection(); - connection->requestName(BUS_NAME); + connection->requestName(SERVICE_NAME); ASSERT_NO_THROW(TestAdaptor adaptor(*connection, OBJECT_PATH)); - ASSERT_NO_THROW(TestProxy proxy(BUS_NAME, OBJECT_PATH)); + ASSERT_NO_THROW(TestProxy proxy(SERVICE_NAME, OBJECT_PATH)); - connection->releaseName(BUS_NAME); + connection->releaseName(SERVICE_NAME); } TEST(AProxy, SupportsMoveSemantics) @@ -76,7 +76,7 @@ TEST(AnAdaptor, SupportsMoveSemantics) TYPED_TEST(AConnection, WillCallCallbackHandlerForIncomingMessageMatchingMatchRule) { - auto matchRule = "sender='" + BUS_NAME + "',path='" + OBJECT_PATH + "'"; + auto matchRule = "sender='" + SERVICE_NAME + "',path='" + OBJECT_PATH + "'"; std::atomic matchingMessageReceived{false}; auto slot = this->s_proxyConnection->addMatch(matchRule, [&](sdbus::Message msg) { @@ -91,7 +91,7 @@ TYPED_TEST(AConnection, WillCallCallbackHandlerForIncomingMessageMatchingMatchRu TYPED_TEST(AConnection, CanInstallMatchRuleAsynchronously) { - auto matchRule = "sender='" + BUS_NAME + "',path='" + OBJECT_PATH + "'"; + auto matchRule = "sender='" + SERVICE_NAME + "',path='" + OBJECT_PATH + "'"; std::atomic matchingMessageReceived{false}; std::atomic matchRuleInstalled{false}; auto slot = this->s_proxyConnection->addMatchAsync( matchRule @@ -114,7 +114,7 @@ TYPED_TEST(AConnection, CanInstallMatchRuleAsynchronously) TYPED_TEST(AConnection, WillUnsubscribeMatchRuleWhenClientDestroysTheAssociatedSlot) { - auto matchRule = "sender='" + BUS_NAME + "',path='" + OBJECT_PATH + "'"; + auto matchRule = "sender='" + SERVICE_NAME + "',path='" + OBJECT_PATH + "'"; std::atomic matchingMessageReceived{false}; auto slot = this->s_proxyConnection->addMatch(matchRule, [&](sdbus::Message msg) { @@ -130,7 +130,7 @@ TYPED_TEST(AConnection, WillUnsubscribeMatchRuleWhenClientDestroysTheAssociatedS TYPED_TEST(AConnection, CanAddFloatingMatchRule) { - auto matchRule = "sender='" + BUS_NAME + "',path='" + OBJECT_PATH + "'"; + auto matchRule = "sender='" + SERVICE_NAME + "',path='" + OBJECT_PATH + "'"; std::atomic matchingMessageReceived{false}; auto con = sdbus::createBusConnection(); con->enterEventLoopAsync(); diff --git a/tests/integrationtests/DBusMethodsTests.cpp b/tests/integrationtests/DBusMethodsTests.cpp index b18b5f04..e8256f9c 100644 --- a/tests/integrationtests/DBusMethodsTests.cpp +++ b/tests/integrationtests/DBusMethodsTests.cpp @@ -231,13 +231,13 @@ TYPED_TEST(SdbusTestObject, FailsCallingMethodOnNonexistentInterface) TYPED_TEST(SdbusTestObject, FailsCallingMethodOnNonexistentDestination) { - TestProxy proxy("sdbuscpp.destination.that.does.not.exist", OBJECT_PATH); + TestProxy proxy(sdbus::ServiceName{"sdbuscpp.destination.that.does.not.exist"}, OBJECT_PATH); ASSERT_THROW(proxy.getInt(), sdbus::Error); } TYPED_TEST(SdbusTestObject, FailsCallingMethodOnNonexistentObject) { - TestProxy proxy(BUS_NAME, "/sdbuscpp/path/that/does/not/exist"); + TestProxy proxy(SERVICE_NAME, sdbus::ObjectPath{"/sdbuscpp/path/that/does/not/exist"}); ASSERT_THROW(proxy.getInt(), sdbus::Error); } @@ -254,7 +254,7 @@ TYPED_TEST(SdbusTestObject, CanAccessAssociatedMethodCallMessageInMethodCallHand this->m_proxy->doOperation(10); // This will save pointer to method call message on server side ASSERT_THAT(this->m_adaptor->m_methodCallMsg, NotNull()); - ASSERT_THAT(this->m_adaptor->m_methodCallMemberName, Eq("doOperation")); + ASSERT_THAT(this->m_adaptor->m_methodName, Eq("doOperation")); } TYPED_TEST(SdbusTestObject, CanAccessAssociatedMethodCallMessageInAsyncMethodCallHandler) @@ -262,7 +262,7 @@ TYPED_TEST(SdbusTestObject, CanAccessAssociatedMethodCallMessageInAsyncMethodCal this->m_proxy->doOperationAsync(10); // This will save pointer to method call message on server side ASSERT_THAT(this->m_adaptor->m_methodCallMsg, NotNull()); - ASSERT_THAT(this->m_adaptor->m_methodCallMemberName, Eq("doOperationAsync")); + ASSERT_THAT(this->m_adaptor->m_methodName, Eq("doOperationAsync")); } #if LIBSYSTEMD_VERSION>=240 @@ -281,7 +281,7 @@ TYPED_TEST(SdbusTestObject, CannotSetGeneralMethodTimeoutWithLibsystemdVersionLe TYPED_TEST(SdbusTestObject, CanCallMethodSynchronouslyWithoutAnEventLoopThread) { - auto proxy = std::make_unique(BUS_NAME, OBJECT_PATH, sdbus::dont_run_event_loop_thread); + auto proxy = std::make_unique(SERVICE_NAME, OBJECT_PATH, sdbus::dont_run_event_loop_thread); auto multiplyRes = proxy->multiply(INT64_VALUE, DOUBLE_VALUE); @@ -291,15 +291,16 @@ TYPED_TEST(SdbusTestObject, CanCallMethodSynchronouslyWithoutAnEventLoopThread) TYPED_TEST(SdbusTestObject, CanRegisterAdditionalVTableDynamicallyAtAnyTime) { auto& object = this->m_adaptor->getObject(); - auto vtableSlot = object.addVTable( "org.sdbuscpp.integrationtests2" + sdbus::InterfaceName interfaceName{"org.sdbuscpp.integrationtests2"}; + auto vtableSlot = object.addVTable( interfaceName , { sdbus::registerMethod("add").implementedAs([](const int64_t& a, const double& b){ return a + b; }) , sdbus::registerMethod("subtract").implementedAs([](const int& a, const int& b){ return a - b; }) } , sdbus::return_slot ); // The new remote vtable is registered as long as we keep vtableSlot, so remote method calls now should pass - auto proxy = sdbus::createProxy(BUS_NAME, OBJECT_PATH, sdbus::dont_run_event_loop_thread); + auto proxy = sdbus::createProxy(SERVICE_NAME, OBJECT_PATH, sdbus::dont_run_event_loop_thread); int result{}; - proxy->callMethod("subtract").onInterface("org.sdbuscpp.integrationtests2").withArguments(10, 2).storeResultsTo(result); + proxy->callMethod("subtract").onInterface(interfaceName).withArguments(10, 2).storeResultsTo(result); ASSERT_THAT(result, Eq(8)); } @@ -307,14 +308,15 @@ TYPED_TEST(SdbusTestObject, CanRegisterAdditionalVTableDynamicallyAtAnyTime) TYPED_TEST(SdbusTestObject, CanUnregisterAdditionallyRegisteredVTableAtAnyTime) { auto& object = this->m_adaptor->getObject(); + sdbus::InterfaceName interfaceName{"org.sdbuscpp.integrationtests2"}; - auto vtableSlot = object.addVTable( "org.sdbuscpp.integrationtests2" + auto vtableSlot = object.addVTable( interfaceName , { sdbus::registerMethod("add").implementedAs([](const int64_t& a, const double& b){ return a + b; }) , sdbus::registerMethod("subtract").implementedAs([](const int& a, const int& b){ return a - b; }) } , sdbus::return_slot ); vtableSlot.reset(); // Letting the slot go means letting go the associated vtable registration // No such remote D-Bus method under given interface exists anymore... - auto proxy = sdbus::createProxy(BUS_NAME, OBJECT_PATH, sdbus::dont_run_event_loop_thread); - ASSERT_THROW(proxy->callMethod("subtract").onInterface("org.sdbuscpp.integrationtests2").withArguments(10, 2), sdbus::Error); + auto proxy = sdbus::createProxy(SERVICE_NAME, OBJECT_PATH, sdbus::dont_run_event_loop_thread); + ASSERT_THROW(proxy->callMethod("subtract").onInterface(interfaceName).withArguments(10, 2), sdbus::Error); } diff --git a/tests/integrationtests/DBusSignalsTests.cpp b/tests/integrationtests/DBusSignalsTests.cpp index 4b12756d..463bdcd8 100644 --- a/tests/integrationtests/DBusSignalsTests.cpp +++ b/tests/integrationtests/DBusSignalsTests.cpp @@ -57,8 +57,8 @@ TYPED_TEST(SdbusTestObject, EmitsSimpleSignalSuccesfully) TYPED_TEST(SdbusTestObject, EmitsSimpleSignalToMultipleProxiesSuccesfully) { - auto proxy1 = std::make_unique(*this->s_adaptorConnection, BUS_NAME, OBJECT_PATH); - auto proxy2 = std::make_unique(*this->s_adaptorConnection, BUS_NAME, OBJECT_PATH); + auto proxy1 = std::make_unique(*this->s_adaptorConnection, SERVICE_NAME, OBJECT_PATH); + auto proxy2 = std::make_unique(*this->s_adaptorConnection, SERVICE_NAME, OBJECT_PATH); this->m_adaptor->emitSimpleSignal(); @@ -69,7 +69,7 @@ TYPED_TEST(SdbusTestObject, EmitsSimpleSignalToMultipleProxiesSuccesfully) TYPED_TEST(SdbusTestObject, ProxyDoesNotReceiveSignalFromOtherBusName) { - auto otherBusName = BUS_NAME + "2"; + sdbus::ServiceName otherBusName{SERVICE_NAME + "2"}; auto connection2 = sdbus::createBusConnection(otherBusName); auto adaptor2 = std::make_unique(*connection2, OBJECT_PATH); @@ -110,10 +110,10 @@ TYPED_TEST(SdbusTestObject, EmitsSignalWithVariantSuccesfully) TYPED_TEST(SdbusTestObject, EmitsSignalWithoutRegistrationSuccesfully) { - this->m_adaptor->emitSignalWithoutRegistration({"platform", {"av"}}); + this->m_adaptor->emitSignalWithoutRegistration({"platform", sdbus::Signature{"av"}}); ASSERT_TRUE(waitUntil(this->m_proxy->m_gotSignalWithSignature)); - ASSERT_THAT(this->m_proxy->m_signatureFromSignal["platform"], Eq("av")); + ASSERT_THAT(this->m_proxy->m_signatureFromSignal["platform"], Eq(sdbus::Signature{"av"})); } TYPED_TEST(SdbusTestObject, CanAccessAssociatedSignalMessageInSignalHandler) @@ -123,7 +123,7 @@ TYPED_TEST(SdbusTestObject, CanAccessAssociatedSignalMessageInSignalHandler) waitUntil(this->m_proxy->m_gotSimpleSignal); ASSERT_THAT(this->m_proxy->m_signalMsg, NotNull()); - ASSERT_THAT(this->m_proxy->m_signalMemberName, Eq("simpleSignal")); + ASSERT_THAT(this->m_proxy->m_signalName, Eq(std::string("simpleSignal"))); } TYPED_TEST(SdbusTestObject, UnregistersSignalHandler) @@ -137,8 +137,8 @@ TYPED_TEST(SdbusTestObject, UnregistersSignalHandler) TYPED_TEST(SdbusTestObject, UnregistersSignalHandlerForSomeProxies) { - auto proxy1 = std::make_unique(*this->s_adaptorConnection, BUS_NAME, OBJECT_PATH); - auto proxy2 = std::make_unique(*this->s_adaptorConnection, BUS_NAME, OBJECT_PATH); + auto proxy1 = std::make_unique(*this->s_adaptorConnection, SERVICE_NAME, OBJECT_PATH); + auto proxy2 = std::make_unique(*this->s_adaptorConnection, SERVICE_NAME, OBJECT_PATH); ASSERT_NO_THROW(this->m_proxy->unregisterSimpleSignalHandler()); diff --git a/tests/integrationtests/DBusStandardInterfacesTests.cpp b/tests/integrationtests/DBusStandardInterfacesTests.cpp index a745f7d5..578ba5b3 100644 --- a/tests/integrationtests/DBusStandardInterfacesTests.cpp +++ b/tests/integrationtests/DBusStandardInterfacesTests.cpp @@ -142,17 +142,17 @@ TYPED_TEST(SdbusTestObject, GetsAllPropertiesViaPropertiesInterface) const auto properties = this->m_proxy->GetAll(INTERFACE_NAME); ASSERT_THAT(properties, SizeIs(3)); - EXPECT_THAT(properties.at("state").template get(), Eq(DEFAULT_STATE_VALUE)); - EXPECT_THAT(properties.at("action").template get(), Eq(DEFAULT_ACTION_VALUE)); - EXPECT_THAT(properties.at("blocking").template get(), Eq(DEFAULT_BLOCKING_VALUE)); + EXPECT_THAT(properties.at(STATE_PROPERTY).template get(), Eq(DEFAULT_STATE_VALUE)); + EXPECT_THAT(properties.at(ACTION_PROPERTY).template get(), Eq(DEFAULT_ACTION_VALUE)); + EXPECT_THAT(properties.at(BLOCKING_PROPERTY).template get(), Eq(DEFAULT_BLOCKING_VALUE)); } TYPED_TEST(SdbusTestObject, GetsAllPropertiesAsynchronouslyViaPropertiesInterface) { - std::promise> promise; + std::promise> promise; auto future = promise.get_future(); - this->m_proxy->GetAllAsync(INTERFACE_NAME, [&](std::optional err, std::map value) + this->m_proxy->GetAllAsync(INTERFACE_NAME, [&](std::optional err, std::map value) { if (!err) promise.set_value(std::move(value)); @@ -162,9 +162,9 @@ TYPED_TEST(SdbusTestObject, GetsAllPropertiesAsynchronouslyViaPropertiesInterfac const auto properties = future.get(); ASSERT_THAT(properties, SizeIs(3)); - EXPECT_THAT(properties.at("state").get(), Eq(DEFAULT_STATE_VALUE)); - EXPECT_THAT(properties.at("action").get(), Eq(DEFAULT_ACTION_VALUE)); - EXPECT_THAT(properties.at("blocking").get(), Eq(DEFAULT_BLOCKING_VALUE)); + EXPECT_THAT(properties.at(STATE_PROPERTY).get(), Eq(DEFAULT_STATE_VALUE)); + EXPECT_THAT(properties.at(ACTION_PROPERTY).get(), Eq(DEFAULT_ACTION_VALUE)); + EXPECT_THAT(properties.at(BLOCKING_PROPERTY).get(), Eq(DEFAULT_BLOCKING_VALUE)); } TYPED_TEST(SdbusTestObject, GetsAllPropertiesAsynchronouslyViaPropertiesInterfaceWithFuture) @@ -174,27 +174,27 @@ TYPED_TEST(SdbusTestObject, GetsAllPropertiesAsynchronouslyViaPropertiesInterfac auto properties = future.get(); ASSERT_THAT(properties, SizeIs(3)); - EXPECT_THAT(properties.at("state").template get(), Eq(DEFAULT_STATE_VALUE)); - EXPECT_THAT(properties.at("action").template get(), Eq(DEFAULT_ACTION_VALUE)); - EXPECT_THAT(properties.at("blocking").template get(), Eq(DEFAULT_BLOCKING_VALUE)); + EXPECT_THAT(properties.at(STATE_PROPERTY).template get(), Eq(DEFAULT_STATE_VALUE)); + EXPECT_THAT(properties.at(ACTION_PROPERTY).template get(), Eq(DEFAULT_ACTION_VALUE)); + EXPECT_THAT(properties.at(BLOCKING_PROPERTY).template get(), Eq(DEFAULT_BLOCKING_VALUE)); } TYPED_TEST(SdbusTestObject, EmitsPropertyChangedSignalForSelectedProperties) { std::atomic signalReceived{false}; - this->m_proxy->m_onPropertiesChangedHandler = [&signalReceived]( const std::string& interfaceName - , const std::map& changedProperties - , const std::vector& /*invalidatedProperties*/ ) + this->m_proxy->m_onPropertiesChangedHandler = [&signalReceived]( const sdbus::InterfaceName& interfaceName + , const std::map& changedProperties + , const std::vector& /*invalidatedProperties*/ ) { EXPECT_THAT(interfaceName, Eq(INTERFACE_NAME)); EXPECT_THAT(changedProperties, SizeIs(1)); - EXPECT_THAT(changedProperties.at("blocking").get(), Eq(!DEFAULT_BLOCKING_VALUE)); + EXPECT_THAT(changedProperties.at(BLOCKING_PROPERTY).get(), Eq(!DEFAULT_BLOCKING_VALUE)); signalReceived = true; }; this->m_proxy->blocking(!DEFAULT_BLOCKING_VALUE); this->m_proxy->action(DEFAULT_ACTION_VALUE*2); - this->m_adaptor->emitPropertiesChangedSignal(INTERFACE_NAME, {"blocking"}); + this->m_adaptor->emitPropertiesChangedSignal(INTERFACE_NAME, {BLOCKING_PROPERTY}); ASSERT_TRUE(waitUntil(signalReceived)); } @@ -202,13 +202,13 @@ TYPED_TEST(SdbusTestObject, EmitsPropertyChangedSignalForSelectedProperties) TYPED_TEST(SdbusTestObject, EmitsPropertyChangedSignalForAllProperties) { std::atomic signalReceived{false}; - this->m_proxy->m_onPropertiesChangedHandler = [&signalReceived]( const std::string& interfaceName - , const std::map& changedProperties - , const std::vector& invalidatedProperties ) + this->m_proxy->m_onPropertiesChangedHandler = [&signalReceived]( const sdbus::InterfaceName& interfaceName + , const std::map& changedProperties + , const std::vector& invalidatedProperties ) { EXPECT_THAT(interfaceName, Eq(INTERFACE_NAME)); EXPECT_THAT(changedProperties, SizeIs(1)); - EXPECT_THAT(changedProperties.at("blocking").get(), Eq(DEFAULT_BLOCKING_VALUE)); + EXPECT_THAT(changedProperties.at(BLOCKING_PROPERTY).get(), Eq(DEFAULT_BLOCKING_VALUE)); ASSERT_THAT(invalidatedProperties, SizeIs(1)); EXPECT_THAT(invalidatedProperties[0], Eq("action")); signalReceived = true; @@ -235,17 +235,17 @@ TYPED_TEST(SdbusTestObject, GetsManagedObjectsSuccessfully) ASSERT_THAT(objectsInterfacesAndProperties, SizeIs(2)); EXPECT_THAT(objectsInterfacesAndProperties.at(OBJECT_PATH) .at(org::sdbuscpp::integrationtests_adaptor::INTERFACE_NAME) - .at("action").template get(), Eq(DEFAULT_ACTION_VALUE)); + .at(ACTION_PROPERTY).template get(), Eq(DEFAULT_ACTION_VALUE)); EXPECT_THAT(objectsInterfacesAndProperties.at(OBJECT_PATH_2) .at(org::sdbuscpp::integrationtests_adaptor::INTERFACE_NAME) - .at("action").template get(), Eq(DEFAULT_ACTION_VALUE)); + .at(ACTION_PROPERTY).template get(), Eq(DEFAULT_ACTION_VALUE)); } TYPED_TEST(SdbusTestObject, EmitsInterfacesAddedSignalForSelectedObjectInterfaces) { std::atomic signalReceived{false}; this->m_objectManagerProxy->m_onInterfacesAddedHandler = [&signalReceived]( const sdbus::ObjectPath& objectPath - , const std::map>& interfacesAndProperties ) + , const std::map>& interfacesAndProperties ) { EXPECT_THAT(objectPath, Eq(OBJECT_PATH)); EXPECT_THAT(interfacesAndProperties, SizeIs(1)); @@ -253,16 +253,16 @@ TYPED_TEST(SdbusTestObject, EmitsInterfacesAddedSignalForSelectedObjectInterface #if LIBSYSTEMD_VERSION<=244 // Up to sd-bus v244, all properties are added to the list, i.e. `state', `action', and `blocking' in this case. EXPECT_THAT(interfacesAndProperties.at(INTERFACE_NAME), SizeIs(3)); - EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count("state")); - EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count("action")); - EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count("blocking")); + EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count(STATE_PROPERTY)); + EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count(ACTION_PROPERTY)); + EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count(BLOCKING_PROPERTY)); #else // Since v245 sd-bus does not add to the InterfacesAdded signal message the values of properties marked only // for invalidation on change, which makes the behavior consistent with the PropertiesChangedSignal. // So in this specific instance, `action' property is no more added to the list. EXPECT_THAT(interfacesAndProperties.at(INTERFACE_NAME), SizeIs(2)); - EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count("state")); - EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count("blocking")); + EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count(STATE_PROPERTY)); + EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count(BLOCKING_PROPERTY)); #endif signalReceived = true; }; @@ -276,7 +276,7 @@ TYPED_TEST(SdbusTestObject, EmitsInterfacesAddedSignalForAllObjectInterfaces) { std::atomic signalReceived{false}; this->m_objectManagerProxy->m_onInterfacesAddedHandler = [&signalReceived]( const sdbus::ObjectPath& objectPath - , const std::map>& interfacesAndProperties ) + , const std::map>& interfacesAndProperties ) { EXPECT_THAT(objectPath, Eq(OBJECT_PATH)); #if LIBSYSTEMD_VERSION<=250 @@ -289,16 +289,16 @@ TYPED_TEST(SdbusTestObject, EmitsInterfacesAddedSignalForAllObjectInterfaces) #if LIBSYSTEMD_VERSION<=244 // Up to sd-bus v244, all properties are added to the list, i.e. `state', `action', and `blocking' in this case. EXPECT_THAT(interfacesAndProperties.at(INTERFACE_NAME), SizeIs(3)); - EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count("state")); - EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count("action")); - EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count("blocking")); + EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count(STATE_PROPERTY)); + EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count(ACTION_PROPERTY)); + EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count(BLOCKING_PROPERTY)); #else // Since v245 sd-bus does not add to the InterfacesAdded signal message the values of properties marked only // for invalidation on change, which makes the behavior consistent with the PropertiesChangedSignal. // So in this specific instance, `action' property is no more added to the list. EXPECT_THAT(interfacesAndProperties.at(INTERFACE_NAME), SizeIs(2)); - EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count("state")); - EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count("blocking")); + EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count(STATE_PROPERTY)); + EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count(BLOCKING_PROPERTY)); #endif signalReceived = true; }; @@ -312,7 +312,7 @@ TYPED_TEST(SdbusTestObject, EmitsInterfacesRemovedSignalForSelectedObjectInterfa { std::atomic signalReceived{false}; this->m_objectManagerProxy->m_onInterfacesRemovedHandler = [&signalReceived]( const sdbus::ObjectPath& objectPath - , const std::vector& interfaces ) + , const std::vector& interfaces ) { EXPECT_THAT(objectPath, Eq(OBJECT_PATH)); ASSERT_THAT(interfaces, SizeIs(1)); @@ -329,7 +329,7 @@ TYPED_TEST(SdbusTestObject, EmitsInterfacesRemovedSignalForAllObjectInterfaces) { std::atomic signalReceived{false}; this->m_objectManagerProxy->m_onInterfacesRemovedHandler = [&signalReceived]( const sdbus::ObjectPath& objectPath - , const std::vector& interfaces ) + , const std::vector& interfaces ) { EXPECT_THAT(objectPath, Eq(OBJECT_PATH)); #if LIBSYSTEMD_VERSION<=250 diff --git a/tests/integrationtests/Defs.h b/tests/integrationtests/Defs.h index 0b5df551..0bc43f21 100644 --- a/tests/integrationtests/Defs.h +++ b/tests/integrationtests/Defs.h @@ -34,12 +34,15 @@ namespace sdbus { namespace test { -const std::string INTERFACE_NAME{"org.sdbuscpp.integrationtests"}; -const std::string BUS_NAME = INTERFACE_NAME; -const std::string EMPTY_DESTINATION; -const std::string MANAGER_PATH {"/org/sdbuscpp/integrationtests"}; -const std::string OBJECT_PATH {"/org/sdbuscpp/integrationtests/ObjectA1"}; -const std::string OBJECT_PATH_2{"/org/sdbuscpp/integrationtests/ObjectB1"}; +const InterfaceName INTERFACE_NAME{"org.sdbuscpp.integrationtests"}; +const ServiceName SERVICE_NAME{"org.sdbuscpp.integrationtests"}; +const ServiceName EMPTY_DESTINATION; +const ObjectPath MANAGER_PATH {"/org/sdbuscpp/integrationtests"}; +const ObjectPath OBJECT_PATH {"/org/sdbuscpp/integrationtests/ObjectA1"}; +const ObjectPath OBJECT_PATH_2{"/org/sdbuscpp/integrationtests/ObjectB1"}; +const PropertyName STATE_PROPERTY{"state"}; +const PropertyName ACTION_PROPERTY{"action"}; +const PropertyName BLOCKING_PROPERTY{"blocking"}; const std::string DIRECT_CONNECTION_SOCKET_PATH{std::filesystem::temp_directory_path() / "sdbus-cpp-direct-connection-test"}; constexpr const uint8_t UINT8_VALUE{1}; @@ -49,8 +52,8 @@ constexpr const int32_t INT32_VALUE{-42}; constexpr const int32_t INT64_VALUE{-1024}; const std::string STRING_VALUE{"sdbus-c++-testing"}; -const sdbus::Signature SIGNATURE_VALUE{"a{is}"}; -const sdbus::ObjectPath OBJECT_PATH_VALUE{"/"}; +const Signature SIGNATURE_VALUE{"a{is}"}; +const ObjectPath OBJECT_PATH_VALUE{"/"}; const int UNIX_FD_VALUE = 0; const std::string DEFAULT_STATE_VALUE{"default-state-value"}; diff --git a/tests/integrationtests/TestAdaptor.cpp b/tests/integrationtests/TestAdaptor.cpp index 9a13995c..ea66296c 100644 --- a/tests/integrationtests/TestAdaptor.cpp +++ b/tests/integrationtests/TestAdaptor.cpp @@ -31,8 +31,8 @@ namespace sdbus { namespace test { -TestAdaptor::TestAdaptor(sdbus::IConnection& connection, const std::string& path) : - AdaptorInterfaces(connection, path) +TestAdaptor::TestAdaptor(sdbus::IConnection& connection, sdbus::ObjectPath path) : + AdaptorInterfaces(connection, std::move(path)) { registerAdaptor(); } @@ -123,7 +123,7 @@ uint32_t TestAdaptor::doOperation(const uint32_t& param) std::this_thread::sleep_for(std::chrono::milliseconds(param)); m_methodCallMsg = std::make_unique(getObject().getCurrentlyProcessedMessage()); - m_methodCallMemberName = m_methodCallMsg->getMemberName(); + m_methodName = m_methodCallMsg->getMemberName(); return param; } @@ -131,7 +131,7 @@ uint32_t TestAdaptor::doOperation(const uint32_t& param) void TestAdaptor::doOperationAsync(sdbus::Result&& result, uint32_t param) { m_methodCallMsg = std::make_unique(getObject().getCurrentlyProcessedMessage()); - m_methodCallMemberName = m_methodCallMsg->getMemberName(); + m_methodName = m_methodCallMsg->getMemberName(); if (param == 0) { @@ -173,7 +173,7 @@ std::unordered_map { public: - ObjectManagerTestAdaptor(sdbus::IConnection& connection, std::string path) : + ObjectManagerTestAdaptor(sdbus::IConnection& connection, sdbus::ObjectPath path) : AdaptorInterfaces(connection, std::move(path)) { registerAdaptor(); @@ -57,7 +57,7 @@ class TestAdaptor final : public sdbus::AdaptorInterfaces< org::sdbuscpp::integr , sdbus::ManagedObject_adaptor > { public: - TestAdaptor(sdbus::IConnection& connection, const std::string& path); + TestAdaptor(sdbus::IConnection& connection, sdbus::ObjectPath path); ~TestAdaptor(); protected: @@ -105,7 +105,7 @@ class TestAdaptor final : public sdbus::AdaptorInterfaces< org::sdbuscpp::integr mutable std::atomic m_wasThrowErrorCalled{false}; std::unique_ptr m_methodCallMsg; - std::string m_methodCallMemberName; + MethodName m_methodName; std::unique_ptr m_propertySetMsg; std::string m_propertySetSender; }; @@ -115,7 +115,9 @@ class DummyTestAdaptor final : public sdbus::AdaptorInterfaces< org::sdbuscpp::i , sdbus::ManagedObject_adaptor > { public: - DummyTestAdaptor(sdbus::IConnection& connection, const std::string& path) : AdaptorInterfaces(connection, path) {} + DummyTestAdaptor(sdbus::IConnection& connection, sdbus::ObjectPath path) + : AdaptorInterfaces(connection, std::move(path)) + {} protected: void noArgNoReturn() override {} diff --git a/tests/integrationtests/TestFixture.h b/tests/integrationtests/TestFixture.h index ccbe3ddb..4901ea37 100644 --- a/tests/integrationtests/TestFixture.h +++ b/tests/integrationtests/TestFixture.h @@ -55,19 +55,19 @@ class BaseTestFixture : public ::testing::Test public: static void SetUpTestCase() { - s_adaptorConnection->requestName(BUS_NAME); + s_adaptorConnection->requestName(SERVICE_NAME); } static void TearDownTestCase() { - s_adaptorConnection->releaseName(BUS_NAME); + s_adaptorConnection->releaseName(SERVICE_NAME); } private: void SetUp() override { - m_objectManagerProxy = std::make_unique(*s_proxyConnection, BUS_NAME, MANAGER_PATH); - m_proxy = std::make_unique(*s_proxyConnection, BUS_NAME, OBJECT_PATH); + m_objectManagerProxy = std::make_unique(*s_proxyConnection, SERVICE_NAME, MANAGER_PATH); + m_proxy = std::make_unique(*s_proxyConnection, SERVICE_NAME, OBJECT_PATH); m_objectManagerAdaptor = std::make_unique(*s_adaptorConnection, MANAGER_PATH); m_adaptor = std::make_unique(*s_adaptorConnection, OBJECT_PATH); diff --git a/tests/integrationtests/TestProxy.cpp b/tests/integrationtests/TestProxy.cpp index 5089bdd8..2eddffab 100644 --- a/tests/integrationtests/TestProxy.cpp +++ b/tests/integrationtests/TestProxy.cpp @@ -31,7 +31,7 @@ namespace sdbus { namespace test { -TestProxy::TestProxy(std::string destination, std::string objectPath) +TestProxy::TestProxy(ServiceName destination, ObjectPath objectPath) : ProxyInterfaces(std::move(destination), std::move(objectPath)) { getProxy().uponSignal("signalWithoutRegistration").onInterface(sdbus::test::INTERFACE_NAME).call([this](const sdbus::Struct>& s){ this->onSignalWithoutRegistration(s); }); @@ -39,14 +39,14 @@ TestProxy::TestProxy(std::string destination, std::string objectPath) registerProxy(); } -TestProxy::TestProxy(std::string destination, std::string objectPath, dont_run_event_loop_thread_t) +TestProxy::TestProxy(ServiceName destination, ObjectPath objectPath, dont_run_event_loop_thread_t) : ProxyInterfaces(std::move(destination), std::move(objectPath), dont_run_event_loop_thread) { // It doesn't make sense to register any signals here since proxy upon a D-Bus connection with no event loop thread // will not receive any incoming messages except replies to synchronous D-Bus calls. } -TestProxy::TestProxy(sdbus::IConnection& connection, std::string destination, std::string objectPath) +TestProxy::TestProxy(sdbus::IConnection& connection, ServiceName destination, ObjectPath objectPath) : ProxyInterfaces(connection, std::move(destination), std::move(objectPath)) { getProxy().uponSignal("signalWithoutRegistration").onInterface(sdbus::test::INTERFACE_NAME).call([this](const sdbus::Struct>& s){ this->onSignalWithoutRegistration(s); }); @@ -62,7 +62,7 @@ TestProxy::~TestProxy() void TestProxy::onSimpleSignal() { m_signalMsg = std::make_unique(getProxy().getCurrentlyProcessedMessage()); - m_signalMemberName = m_signalMsg->getMemberName(); + m_signalName = m_signalMsg->getMemberName(); m_gotSimpleSignal = true; } @@ -81,6 +81,7 @@ void TestProxy::onSignalWithVariant(const sdbus::Variant& aVariant) void TestProxy::onSignalWithoutRegistration(const sdbus::Struct>& s) { + // Static cast to std::string is a workaround for gcc 11.4 false positive warning (which later gcc versions nor Clang emit) m_signatureFromSignal[std::get<0>(s)] = static_cast(std::get<0>(std::get<1>(s))); m_gotSignalWithSignature = true; } @@ -91,9 +92,9 @@ void TestProxy::onDoOperationReply(uint32_t returnValue, std::optional& changedProperties - , const std::vector& invalidatedProperties ) +void TestProxy::onPropertiesChanged( const sdbus::InterfaceName& interfaceName + , const std::map& changedProperties + , const std::vector& invalidatedProperties ) { if (m_onPropertiesChangedHandler) m_onPropertiesChangedHandler(interfaceName, changedProperties, invalidatedProperties); @@ -133,7 +134,7 @@ std::future TestProxy::doOperationClientSideAsync(uint32_t param, with std::future TestProxy::doOperationClientSideAsyncOnBasicAPILevel(uint32_t param) { - auto methodCall = getProxy().createMethodCall(sdbus::test::INTERFACE_NAME, "doOperation"); + auto methodCall = getProxy().createMethodCall(sdbus::test::INTERFACE_NAME, sdbus::MethodName{"doOperation"}); methodCall << param; return getProxy().callMethodAsync(methodCall, sdbus::with_future); @@ -178,8 +179,9 @@ int32_t TestProxy::callNonexistentMethod() int32_t TestProxy::callMethodOnNonexistentInterface() { + sdbus::InterfaceName nonexistentInterfaceName{"sdbuscpp.interface.that.does.not.exist"}; int32_t result; - getProxy().callMethod("someMethod").onInterface("sdbuscpp.interface.that.does.not.exist").storeResultsTo(result); + getProxy().callMethod("someMethod").onInterface(nonexistentInterfaceName).storeResultsTo(result); return result; } diff --git a/tests/integrationtests/TestProxy.h b/tests/integrationtests/TestProxy.h index cf8a6273..45797fc9 100644 --- a/tests/integrationtests/TestProxy.h +++ b/tests/integrationtests/TestProxy.h @@ -40,7 +40,7 @@ namespace sdbus { namespace test { class ObjectManagerTestProxy final : public sdbus::ProxyInterfaces< sdbus::ObjectManager_proxy > { public: - ObjectManagerTestProxy(sdbus::IConnection& connection, std::string destination, std::string objectPath) + ObjectManagerTestProxy(sdbus::IConnection& connection, ServiceName destination, ObjectPath objectPath) : ProxyInterfaces(connection, std::move(destination), std::move(objectPath)) { registerProxy(); @@ -51,21 +51,21 @@ class ObjectManagerTestProxy final : public sdbus::ProxyInterfaces< sdbus::Objec unregisterProxy(); } protected: - void onInterfacesAdded(const sdbus::ObjectPath& objectPath, const std::map>& interfacesAndProperties) override + void onInterfacesAdded(const sdbus::ObjectPath& objectPath, const std::map>& interfacesAndProperties) override { if (m_onInterfacesAddedHandler) m_onInterfacesAddedHandler(objectPath, interfacesAndProperties); } - void onInterfacesRemoved(const sdbus::ObjectPath& objectPath, const std::vector& interfaces) override + void onInterfacesRemoved(const sdbus::ObjectPath& objectPath, const std::vector& interfaces) override { if (m_onInterfacesRemovedHandler) m_onInterfacesRemovedHandler(objectPath, interfaces); } public: // for tests - std::function>&)> m_onInterfacesAddedHandler; - std::function&)> m_onInterfacesRemovedHandler; + std::function>&)> m_onInterfacesAddedHandler; + std::function&)> m_onInterfacesRemovedHandler; }; class TestProxy final : public sdbus::ProxyInterfaces< org::sdbuscpp::integrationtests_proxy @@ -74,9 +74,9 @@ class TestProxy final : public sdbus::ProxyInterfaces< org::sdbuscpp::integratio , sdbus::Properties_proxy > { public: - TestProxy(std::string destination, std::string objectPath); - TestProxy(std::string destination, std::string objectPath, dont_run_event_loop_thread_t); - TestProxy(sdbus::IConnection& connection, std::string destination, std::string objectPath); + TestProxy(ServiceName destination, ObjectPath objectPath); + TestProxy(ServiceName destination, ObjectPath objectPath, dont_run_event_loop_thread_t); + TestProxy(sdbus::IConnection& connection, ServiceName destination, ObjectPath objectPath); ~TestProxy(); protected: @@ -88,9 +88,9 @@ class TestProxy final : public sdbus::ProxyInterfaces< org::sdbuscpp::integratio void onDoOperationReply(uint32_t returnValue, std::optional error); // Signals of standard D-Bus interfaces - void onPropertiesChanged( const std::string& interfaceName - , const std::map& changedProperties - , const std::vector& invalidatedProperties ) override; + void onPropertiesChanged( const sdbus::InterfaceName& interfaceName + , const std::map& changedProperties + , const std::vector& invalidatedProperties ) override; public: void installDoOperationClientSideAsyncReplyHandler(std::function err)> handler); @@ -114,13 +114,13 @@ class TestProxy final : public sdbus::ProxyInterfaces< org::sdbuscpp::integratio std::atomic m_gotSignalWithVariant{false}; double m_variantFromSignal; std::atomic m_gotSignalWithSignature{false}; - std::map m_signatureFromSignal; + std::map m_signatureFromSignal; std::function err)> m_DoOperationClientSideAsyncReplyHandler; - std::function&, const std::vector&)> m_onPropertiesChangedHandler; + std::function&, const std::vector&)> m_onPropertiesChangedHandler; std::unique_ptr m_signalMsg; - std::string m_signalMemberName; + SignalName m_signalName; }; class DummyTestProxy final : public sdbus::ProxyInterfaces< org::sdbuscpp::integrationtests_proxy @@ -129,7 +129,7 @@ class DummyTestProxy final : public sdbus::ProxyInterfaces< org::sdbuscpp::integ , sdbus::Properties_proxy > { public: - DummyTestProxy(std::string destination, std::string objectPath) + DummyTestProxy(ServiceName destination, ObjectPath objectPath) : ProxyInterfaces(destination, objectPath) { } @@ -143,7 +143,7 @@ class DummyTestProxy final : public sdbus::ProxyInterfaces< org::sdbuscpp::integ void onDoOperationReply(uint32_t, std::optional) {} // Signals of standard D-Bus interfaces - void onPropertiesChanged( const std::string&, const std::map&, const std::vector& ) override {} + void onPropertiesChanged(const InterfaceName&, const std::map&, const std::vector&) override {} }; }} diff --git a/tests/integrationtests/integrationtests-adaptor.h b/tests/integrationtests/integrationtests-adaptor.h index a34146fa..fe651a9f 100644 --- a/tests/integrationtests/integrationtests-adaptor.h +++ b/tests/integrationtests/integrationtests-adaptor.h @@ -16,7 +16,7 @@ namespace sdbuscpp { class integrationtests_adaptor { public: - static constexpr const char* INTERFACE_NAME = "org.sdbuscpp.integrationtests"; + static inline const sdbus::InterfaceName INTERFACE_NAME{"org.sdbuscpp.integrationtests"}; protected: integrationtests_adaptor(sdbus::IObject& object) diff --git a/tests/integrationtests/integrationtests-proxy.h b/tests/integrationtests/integrationtests-proxy.h index 1c86d928..e161183f 100644 --- a/tests/integrationtests/integrationtests-proxy.h +++ b/tests/integrationtests/integrationtests-proxy.h @@ -16,7 +16,7 @@ namespace sdbuscpp { class integrationtests_proxy { public: - static constexpr const char* INTERFACE_NAME = "org.sdbuscpp.integrationtests"; + static inline const sdbus::InterfaceName INTERFACE_NAME{"org.sdbuscpp.integrationtests"}; protected: integrationtests_proxy(sdbus::IProxy& proxy) diff --git a/tests/perftests/client.cpp b/tests/perftests/client.cpp index 04988b32..fcc83d17 100644 --- a/tests/perftests/client.cpp +++ b/tests/perftests/client.cpp @@ -43,7 +43,7 @@ uint64_t totalDuration = 0; class PerftestProxy final : public sdbus::ProxyInterfaces { public: - PerftestProxy(std::string destination, std::string objectPath) + PerftestProxy(sdbus::ServiceName destination, sdbus::ObjectPath objectPath) : ProxyInterfaces(std::move(destination), std::move(objectPath)) { registerProxy(); @@ -101,9 +101,9 @@ std::string createRandomString(size_t length) //----------------------------------------- int main(int /*argc*/, char */*argv*/[]) { - const char* destinationName = "org.sdbuscpp.perftests"; - const char* objectPath = "/org/sdbuscpp/perftests"; - PerftestProxy client(destinationName, objectPath); + sdbus::ServiceName destination{"org.sdbuscpp.perftests"}; + sdbus::ObjectPath objectPath{"/org/sdbuscpp/perftests"}; + PerftestProxy client(std::move(destination), std::move(objectPath)); const unsigned int repetitions{20}; unsigned int msgCount = 1000; diff --git a/tests/perftests/perftests-adaptor.h b/tests/perftests/perftests-adaptor.h index 6aefa1ae..49301176 100644 --- a/tests/perftests/perftests-adaptor.h +++ b/tests/perftests/perftests-adaptor.h @@ -16,7 +16,7 @@ namespace sdbuscpp { class perftests_adaptor { public: - static constexpr const char* INTERFACE_NAME = "org.sdbuscpp.perftests"; + static inline const sdbus::InterfaceName INTERFACE_NAME{"org.sdbuscpp.perftests"}; protected: perftests_adaptor(sdbus::IObject& object) diff --git a/tests/perftests/perftests-proxy.h b/tests/perftests/perftests-proxy.h index dee4bf97..22544ae2 100644 --- a/tests/perftests/perftests-proxy.h +++ b/tests/perftests/perftests-proxy.h @@ -16,7 +16,7 @@ namespace sdbuscpp { class perftests_proxy { public: - static constexpr const char* INTERFACE_NAME = "org.sdbuscpp.perftests"; + static inline const sdbus::InterfaceName INTERFACE_NAME{"org.sdbuscpp.perftests"}; protected: perftests_proxy(sdbus::IProxy& proxy) diff --git a/tests/perftests/server.cpp b/tests/perftests/server.cpp index c7918d15..f88d2b20 100644 --- a/tests/perftests/server.cpp +++ b/tests/perftests/server.cpp @@ -40,7 +40,7 @@ std::string createRandomString(size_t length); class PerftestAdaptor final : public sdbus::AdaptorInterfaces { public: - PerftestAdaptor(sdbus::IConnection& connection, std::string objectPath) + PerftestAdaptor(sdbus::IConnection& connection, sdbus::ObjectPath objectPath) : AdaptorInterfaces(connection, std::move(objectPath)) { registerAdaptor(); @@ -92,11 +92,11 @@ std::string createRandomString(size_t length) //----------------------------------------- int main(int /*argc*/, char */*argv*/[]) { - const char* serviceName = "org.sdbuscpp.perftests"; + sdbus::ServiceName serviceName{"org.sdbuscpp.perftests"}; auto connection = sdbus::createSystemBusConnection(serviceName); - const char* objectPath = "/org/sdbuscpp/perftests"; - PerftestAdaptor server(*connection, objectPath); + sdbus::ObjectPath objectPath{"/org/sdbuscpp/perftests"}; + PerftestAdaptor server(*connection, std::move(objectPath)); connection->enterEventLoop(); } diff --git a/tests/stresstests/celsius-thermometer-adaptor.h b/tests/stresstests/celsius-thermometer-adaptor.h index 36a32bab..6b4b0303 100644 --- a/tests/stresstests/celsius-thermometer-adaptor.h +++ b/tests/stresstests/celsius-thermometer-adaptor.h @@ -18,7 +18,7 @@ namespace celsius { class thermometer_adaptor { public: - static constexpr const char* INTERFACE_NAME = "org.sdbuscpp.stresstests.celsius.thermometer"; + static inline const sdbus::InterfaceName INTERFACE_NAME{"org.sdbuscpp.stresstests.celsius.thermometer"}; protected: thermometer_adaptor(sdbus::IObject& object) diff --git a/tests/stresstests/celsius-thermometer-proxy.h b/tests/stresstests/celsius-thermometer-proxy.h index b4a57158..480f79a1 100644 --- a/tests/stresstests/celsius-thermometer-proxy.h +++ b/tests/stresstests/celsius-thermometer-proxy.h @@ -18,7 +18,7 @@ namespace celsius { class thermometer_proxy { public: - static constexpr const char* INTERFACE_NAME = "org.sdbuscpp.stresstests.celsius.thermometer"; + static inline const sdbus::InterfaceName INTERFACE_NAME{"org.sdbuscpp.stresstests.celsius.thermometer"}; protected: thermometer_proxy(sdbus::IProxy& proxy) diff --git a/tests/stresstests/concatenator-adaptor.h b/tests/stresstests/concatenator-adaptor.h index 0bd47e70..42eae54e 100644 --- a/tests/stresstests/concatenator-adaptor.h +++ b/tests/stresstests/concatenator-adaptor.h @@ -17,7 +17,7 @@ namespace stresstests { class concatenator_adaptor { public: - static constexpr const char* INTERFACE_NAME = "org.sdbuscpp.stresstests.concatenator"; + static inline const sdbus::InterfaceName INTERFACE_NAME{"org.sdbuscpp.stresstests.concatenator"}; protected: concatenator_adaptor(sdbus::IObject& object) diff --git a/tests/stresstests/concatenator-proxy.h b/tests/stresstests/concatenator-proxy.h index 2383dc73..b5fdfefd 100644 --- a/tests/stresstests/concatenator-proxy.h +++ b/tests/stresstests/concatenator-proxy.h @@ -17,7 +17,7 @@ namespace stresstests { class concatenator_proxy { public: - static constexpr const char* INTERFACE_NAME = "org.sdbuscpp.stresstests.concatenator"; + static inline const sdbus::InterfaceName INTERFACE_NAME{"org.sdbuscpp.stresstests.concatenator"}; protected: concatenator_proxy(sdbus::IProxy& proxy) diff --git a/tests/stresstests/fahrenheit-thermometer-adaptor.h b/tests/stresstests/fahrenheit-thermometer-adaptor.h index 0710fbb6..883e5745 100644 --- a/tests/stresstests/fahrenheit-thermometer-adaptor.h +++ b/tests/stresstests/fahrenheit-thermometer-adaptor.h @@ -18,7 +18,7 @@ namespace fahrenheit { class thermometer_adaptor { public: - static constexpr const char* INTERFACE_NAME = "org.sdbuscpp.stresstests.fahrenheit.thermometer"; + static inline const sdbus::InterfaceName INTERFACE_NAME{"org.sdbuscpp.stresstests.fahrenheit.thermometer"}; protected: thermometer_adaptor(sdbus::IObject& object) @@ -56,7 +56,7 @@ namespace thermometer { class factory_adaptor { public: - static constexpr const char* INTERFACE_NAME = "org.sdbuscpp.stresstests.fahrenheit.thermometer.factory"; + static inline const sdbus::InterfaceName INTERFACE_NAME{"org.sdbuscpp.stresstests.fahrenheit.thermometer.factory"}; protected: factory_adaptor(sdbus::IObject& object) diff --git a/tests/stresstests/fahrenheit-thermometer-proxy.h b/tests/stresstests/fahrenheit-thermometer-proxy.h index 3a1fc265..d24a2b57 100644 --- a/tests/stresstests/fahrenheit-thermometer-proxy.h +++ b/tests/stresstests/fahrenheit-thermometer-proxy.h @@ -18,7 +18,7 @@ namespace fahrenheit { class thermometer_proxy { public: - static constexpr const char* INTERFACE_NAME = "org.sdbuscpp.stresstests.fahrenheit.thermometer"; + static inline const sdbus::InterfaceName INTERFACE_NAME{"org.sdbuscpp.stresstests.fahrenheit.thermometer"}; protected: thermometer_proxy(sdbus::IProxy& proxy) @@ -60,7 +60,7 @@ namespace thermometer { class factory_proxy { public: - static constexpr const char* INTERFACE_NAME = "org.sdbuscpp.stresstests.fahrenheit.thermometer.factory"; + static inline const sdbus::InterfaceName INTERFACE_NAME{"org.sdbuscpp.stresstests.fahrenheit.thermometer.factory"}; protected: factory_proxy(sdbus::IProxy& proxy) diff --git a/tests/stresstests/sdbus-c++-stress-tests.cpp b/tests/stresstests/sdbus-c++-stress-tests.cpp index e379cce8..66f82463 100644 --- a/tests/stresstests/sdbus-c++-stress-tests.cpp +++ b/tests/stresstests/sdbus-c++-stress-tests.cpp @@ -46,18 +46,17 @@ #include using namespace std::chrono_literals; -using namespace std::string_literals; -#define SERVICE_1_BUS_NAME "org.sdbuscpp.stresstests.service1"s -#define SERVICE_2_BUS_NAME "org.sdbuscpp.stresstests.service2"s -#define CELSIUS_THERMOMETER_OBJECT_PATH "/org/sdbuscpp/stresstests/celsius/thermometer"s -#define FAHRENHEIT_THERMOMETER_OBJECT_PATH "/org/sdbuscpp/stresstests/fahrenheit/thermometer"s -#define CONCATENATOR_OBJECT_PATH "/org/sdbuscpp/stresstests/concatenator"s +const sdbus::ServiceName SERVICE_1_BUS_NAME{"org.sdbuscpp.stresstests.service1"}; +const sdbus::ServiceName SERVICE_2_BUS_NAME{"org.sdbuscpp.stresstests.service2"}; +const sdbus::ObjectPath CELSIUS_THERMOMETER_OBJECT_PATH{"/org/sdbuscpp/stresstests/celsius/thermometer"}; +const sdbus::ObjectPath FAHRENHEIT_THERMOMETER_OBJECT_PATH{"/org/sdbuscpp/stresstests/fahrenheit/thermometer"}; +const sdbus::ObjectPath CONCATENATOR_OBJECT_PATH{"/org/sdbuscpp/stresstests/concatenator"}; class CelsiusThermometerAdaptor final : public sdbus::AdaptorInterfaces { public: - CelsiusThermometerAdaptor(sdbus::IConnection& connection, std::string objectPath) + CelsiusThermometerAdaptor(sdbus::IConnection& connection, sdbus::ObjectPath objectPath) : AdaptorInterfaces(connection, std::move(objectPath)) { registerAdaptor(); @@ -81,7 +80,7 @@ class CelsiusThermometerAdaptor final : public sdbus::AdaptorInterfaces { public: - CelsiusThermometerProxy(sdbus::IConnection& connection, std::string destination, std::string objectPath) + CelsiusThermometerProxy(sdbus::IConnection& connection, sdbus::ServiceName destination, sdbus::ObjectPath objectPath) : ProxyInterfaces(connection, std::move(destination), std::move(objectPath)) { registerProxy(); @@ -97,7 +96,7 @@ class FahrenheitThermometerAdaptor final : public sdbus::AdaptorInterfaces< org: , org::sdbuscpp::stresstests::fahrenheit::thermometer::factory_adaptor > { public: - FahrenheitThermometerAdaptor(sdbus::IConnection& connection, std::string objectPath, bool isDelegate) + FahrenheitThermometerAdaptor(sdbus::IConnection& connection, sdbus::ObjectPath objectPath, bool isDelegate) : AdaptorInterfaces(connection, std::move(objectPath)) , celsiusProxy_(connection, SERVICE_2_BUS_NAME, CELSIUS_THERMOMETER_OBJECT_PATH) { @@ -128,7 +127,7 @@ class FahrenheitThermometerAdaptor final : public sdbus::AdaptorInterfaces< org: { // Create new delegate object auto& connection = getObject().getConnection(); - sdbus::ObjectPath newObjectPath = FAHRENHEIT_THERMOMETER_OBJECT_PATH + "/" + std::to_string(request.objectNr); + sdbus::ObjectPath newObjectPath{FAHRENHEIT_THERMOMETER_OBJECT_PATH + "/" + std::to_string(request.objectNr)}; // Here we are testing dynamic creation of a D-Bus object in an async way auto adaptor = std::make_unique(connection, newObjectPath, true); @@ -176,7 +175,7 @@ class FahrenheitThermometerAdaptor final : public sdbus::AdaptorInterfaces< org: objectCounter++; std::unique_lock lock(mutex_); - requests_.push(WorkItem{objectCounter, std::string{}, std::move(result)}); + requests_.push(WorkItem{objectCounter, {}, std::move(result)}); lock.unlock(); cond_.notify_one(); } @@ -191,7 +190,7 @@ class FahrenheitThermometerAdaptor final : public sdbus::AdaptorInterfaces< org: private: CelsiusThermometerProxy celsiusProxy_; - std::map> children_; + std::map> children_; std::mutex childrenMutex_; struct WorkItem @@ -211,7 +210,7 @@ class FahrenheitThermometerProxy : public sdbus::ProxyInterfaces< org::sdbuscpp: , org::sdbuscpp::stresstests::fahrenheit::thermometer::factory_proxy > { public: - FahrenheitThermometerProxy(sdbus::IConnection& connection, std::string destination, std::string objectPath) + FahrenheitThermometerProxy(sdbus::IConnection& connection, sdbus::ServiceName destination, sdbus::ObjectPath objectPath) : ProxyInterfaces(connection, std::move(destination), std::move(objectPath)) { registerProxy(); @@ -226,7 +225,7 @@ class FahrenheitThermometerProxy : public sdbus::ProxyInterfaces< org::sdbuscpp: class ConcatenatorAdaptor final : public sdbus::AdaptorInterfaces { public: - ConcatenatorAdaptor(sdbus::IConnection& connection, std::string objectPath) + ConcatenatorAdaptor(sdbus::IConnection& connection, sdbus::ObjectPath objectPath) : AdaptorInterfaces(connection, std::move(objectPath)) { unsigned int workers = std::thread::hardware_concurrency(); @@ -298,7 +297,7 @@ class ConcatenatorAdaptor final : public sdbus::AdaptorInterfaces { public: - ConcatenatorProxy(sdbus::IConnection& connection, std::string destination, std::string objectPath) + ConcatenatorProxy(sdbus::IConnection& connection, sdbus::ServiceName destination, sdbus::ObjectPath objectPath) : ProxyInterfaces(connection, std::move(destination), std::move(objectPath)) { registerProxy(); diff --git a/tests/unittests/Connection_test.cpp b/tests/unittests/Connection_test.cpp index 3ef0abca..88a2662c 100644 --- a/tests/unittests/Connection_test.cpp +++ b/tests/unittests/Connection_test.cpp @@ -26,6 +26,7 @@ */ #include "Connection.h" +#include "sdbus-c++/Types.h" #include "unittests/mocks/SdBusMock.h" #include @@ -207,12 +208,16 @@ TYPED_TEST_SUITE(AConnectionNameRequest, BusTypeTags); TYPED_TEST(AConnectionNameRequest, DoesNotThrowOnSuccess) { EXPECT_CALL(*this->sdBusIntfMock_, sd_bus_request_name(_, _, _)).WillOnce(Return(1)); - this->con_->requestName("org.sdbuscpp.somename"); + sdbus::ConnectionName name{"org.sdbuscpp.somename"}; + + this->con_->requestName(name); } TYPED_TEST(AConnectionNameRequest, ThrowsOnFail) { + sdbus::ConnectionName name{"org.sdbuscpp.somename"}; + EXPECT_CALL(*this->sdBusIntfMock_, sd_bus_request_name(_, _, _)).WillOnce(Return(-1)); - ASSERT_THROW(this->con_->requestName("org.sdbuscpp.somename"), sdbus::Error); + ASSERT_THROW(this->con_->requestName(name), sdbus::Error); } diff --git a/tests/unittests/Message_test.cpp b/tests/unittests/Message_test.cpp index c91f230c..1db7660e 100644 --- a/tests/unittests/Message_test.cpp +++ b/tests/unittests/Message_test.cpp @@ -270,7 +270,7 @@ TEST(AMessage, CanCarryDBusArrayOfNontrivialTypesGivenAsStdVector) { auto msg = sdbus::createPlainMessage(); - const std::vector dataWritten{"s", "u", "b"}; + const std::vector dataWritten{sdbus::Signature{"s"}, sdbus::Signature{"u"}, sdbus::Signature{"b"}}; msg << dataWritten; msg.seal(); @@ -300,7 +300,7 @@ TEST(AMessage, CanCarryDBusArrayOfNontrivialTypesGivenAsStdArray) { auto msg = sdbus::createPlainMessage(); - const std::array dataWritten{"s", "u", "b"}; + const std::array dataWritten{sdbus::Signature{"s"}, sdbus::Signature{"u"}, sdbus::Signature{"b"}}; msg << dataWritten; msg.seal(); @@ -333,7 +333,7 @@ TEST(AMessage, CanCarryDBusArrayOfNontrivialTypesGivenAsStdSpan) { auto msg = sdbus::createPlainMessage(); - const std::array sourceArray{"s", "u", "b"}; + const std::array sourceArray{sdbus::Signature{"s"}, sdbus::Signature{"u"}, sdbus::Signature{"b"}}; const std::span dataWritten{sourceArray}; msg << dataWritten; @@ -433,7 +433,7 @@ TEST(AMessage, CanCarryAComplexType) > >; - ComplexType dataWritten = { {1, {{{5, {{"/some/object", true, 45, {{6, "hello"}, {7, "world"}}}}}}, "av", 3.14}}}; + ComplexType dataWritten = { {1, {{{5, {{sdbus::ObjectPath{"/some/object"}, true, 45, {{6, "hello"}, {7, "world"}}}}}}, sdbus::Signature{"av"}, 3.14}}}; msg << dataWritten; msg.seal(); diff --git a/tests/unittests/TypeTraits_test.cpp b/tests/unittests/TypeTraits_test.cpp index 3e7e092e..0ccc7bdd 100644 --- a/tests/unittests/TypeTraits_test.cpp +++ b/tests/unittests/TypeTraits_test.cpp @@ -85,6 +85,9 @@ namespace TYPE(double)HAS_DBUS_TYPE_SIGNATURE("d") TYPE(const char*)HAS_DBUS_TYPE_SIGNATURE("s") TYPE(std::string)HAS_DBUS_TYPE_SIGNATURE("s") + TYPE(sdbus::BusName)HAS_DBUS_TYPE_SIGNATURE("s") + TYPE(sdbus::InterfaceName)HAS_DBUS_TYPE_SIGNATURE("s") + TYPE(sdbus::MemberName)HAS_DBUS_TYPE_SIGNATURE("s") TYPE(sdbus::ObjectPath)HAS_DBUS_TYPE_SIGNATURE("o") TYPE(sdbus::Signature)HAS_DBUS_TYPE_SIGNATURE("g") TYPE(sdbus::Variant)HAS_DBUS_TYPE_SIGNATURE("v") @@ -135,6 +138,9 @@ namespace , double , const char* , std::string + , sdbus::BusName + , sdbus::InterfaceName + , sdbus::MemberName , sdbus::ObjectPath , sdbus::Signature , sdbus::Variant diff --git a/tests/unittests/Types_test.cpp b/tests/unittests/Types_test.cpp index 70dcd679..647bc0b7 100644 --- a/tests/unittests/Types_test.cpp +++ b/tests/unittests/Types_test.cpp @@ -322,7 +322,6 @@ TEST(ASignature, CanBeMovedLikeAStdString) sdbus::Signature oSignature{aSignature}; ASSERT_THAT(sdbus::Signature{std::move(oSignature)}, Eq(sdbus::Signature(std::move(aSignature)))); - ASSERT_THAT(std::string(oSignature), Eq(aSignature)); } TEST(AUnixFd, DuplicatesAndOwnsFdUponStandardConstruction) @@ -428,17 +427,17 @@ TEST(AUnixFd, TakesOverNewFdAndClosesOriginalFdOnAdoptingReset) TEST(AnError, CanBeConstructedFromANameAndAMessage) { - auto error = sdbus::Error("name", "message"); - EXPECT_THAT(error.getName(), Eq("name")); + auto error = sdbus::Error(sdbus::Error::Name{"org.sdbuscpp.error"}, "message"); + EXPECT_THAT(error.getName(), Eq("org.sdbuscpp.error")); EXPECT_THAT(error.getMessage(), Eq("message")); } TEST(AnError, CanBeConstructedFromANameOnly) { - auto error1 = sdbus::Error("name"); - auto error2 = sdbus::Error("name", nullptr); - EXPECT_THAT(error1.getName(), Eq("name")); - EXPECT_THAT(error2.getName(), Eq("name")); + auto error1 = sdbus::Error(sdbus::Error::Name{"org.sdbuscpp.error"}); + auto error2 = sdbus::Error(sdbus::Error::Name{"org.sdbuscpp.error"}, nullptr); + EXPECT_THAT(error1.getName(), Eq("org.sdbuscpp.error")); + EXPECT_THAT(error2.getName(), Eq("org.sdbuscpp.error")); EXPECT_THAT(error1.getMessage(), Eq("")); EXPECT_THAT(error2.getMessage(), Eq("")); diff --git a/tools/xml2cpp-codegen/AdaptorGenerator.cpp b/tools/xml2cpp-codegen/AdaptorGenerator.cpp index 3c7c24ce..1ee3f0d0 100644 --- a/tools/xml2cpp-codegen/AdaptorGenerator.cpp +++ b/tools/xml2cpp-codegen/AdaptorGenerator.cpp @@ -82,7 +82,7 @@ std::string AdaptorGenerator::processInterface(Node& interface) const body << "class " << className << endl << "{" << endl << "public:" << endl - << tab << "static constexpr const char* INTERFACE_NAME = \"" << ifaceName << "\";" << endl << endl + << tab << "static inline const sdbus::InterfaceName INTERFACE_NAME{\"" << ifaceName << "\"};" << endl << endl << "protected:" << endl << tab << className << "(sdbus::IObject& object)" << endl << tab << tab << ": object_(&object)" << endl diff --git a/tools/xml2cpp-codegen/ProxyGenerator.cpp b/tools/xml2cpp-codegen/ProxyGenerator.cpp index 99225a7c..f95a2fc6 100644 --- a/tools/xml2cpp-codegen/ProxyGenerator.cpp +++ b/tools/xml2cpp-codegen/ProxyGenerator.cpp @@ -81,7 +81,7 @@ std::string ProxyGenerator::processInterface(Node& interface) const body << "class " << className << endl << "{" << endl << "public:" << endl - << tab << "static constexpr const char* INTERFACE_NAME = \"" << ifaceName << "\";" << endl << endl + << tab << "static inline const sdbus::InterfaceName INTERFACE_NAME{\"" << ifaceName << "\"};" << endl << endl << "protected:" << endl << tab << className << "(sdbus::IProxy& proxy)" << endl << tab << tab << ": proxy_(&proxy)" << endl From 8bd44eab9fd8414832b7e879f477d841e8eb66bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Wed, 3 Apr 2024 20:40:28 +0200 Subject: [PATCH 40/52] fix: make Variant conversion operator explicit (#428) Having explicit conversion operator is a good practice according to the C++ core guidelines, as it makes the code safer and better follows the principle of least astonishment. Also, it is consistent with the standard library style, where wrappers like std::variant, std::any, std::optional... also do not provide an implicit conversion to the underlying type. Last but not least, it paves the way for the upcoming std::variant <-> sdbus::Variant implicit conversions without surprising behavior in some edge cases. --- docs/using-sdbus-c++.md | 1 + .../examplemanager-planet1-client-glue.h | 2 +- include/sdbus-c++/Types.h | 2 +- tests/integrationtests/DBusMethodsTests.cpp | 6 +++--- tests/integrationtests/integrationtests-proxy.h | 6 +++--- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/using-sdbus-c++.md b/docs/using-sdbus-c++.md index a6c127a5..cf92c5cc 100644 --- a/docs/using-sdbus-c++.md +++ b/docs/using-sdbus-c++.md @@ -1765,6 +1765,7 @@ sdbus-c++ v2 is a major release that comes with a number of breaking API/ABI/beh * `PollData::getRelativeTimeout()` return type was changed to `std::chrono::microseconds`. * `IConnection::processPendingRequest()` was renamed to `IConnection::processPendingEvent()`. * `Variant` constructor is now explicit. +* `Variant`'s conversion operator to the underlying type is now explicit. * `IProxy::getCurrentlyProcessedMessage()` now returns `Message` by value instead of a raw pointer to it. The caller assumes ownership of the message. * Object D-Bus API registration is now done through `IObject::addVTable()` method. The vtable gets active immediately. No `finishRegistration()` call is needed anymore. vtables can be added and removed dynamically at run time. In addition to API simplification this brings consistency with sd-bus API and increases flexibility. * Subscription to signals has been simplified. The subscription is active right after the `registerSignalHandler`/`uponSignal()` call. No need for the final call to `finishRegistration()`. diff --git a/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-client-glue.h b/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-client-glue.h index 913ad82d..4b6847e6 100644 --- a/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-client-glue.h +++ b/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-client-glue.h @@ -47,7 +47,7 @@ class Planet1_proxy public: std::string Name() { - return proxy_->getProperty("Name").onInterface(INTERFACE_NAME); + return proxy_->getProperty("Name").onInterface(INTERFACE_NAME).get(); } private: diff --git a/include/sdbus-c++/Types.h b/include/sdbus-c++/Types.h index 0323e343..19bfd692 100644 --- a/include/sdbus-c++/Types.h +++ b/include/sdbus-c++/Types.h @@ -87,7 +87,7 @@ namespace sdbus { // Only allow conversion operator for true D-Bus type representations in C++ template ::is_valid>> - operator _ValueType() const + explicit operator _ValueType() const { return get<_ValueType>(); } diff --git a/tests/integrationtests/DBusMethodsTests.cpp b/tests/integrationtests/DBusMethodsTests.cpp index e8256f9c..98ea7688 100644 --- a/tests/integrationtests/DBusMethodsTests.cpp +++ b/tests/integrationtests/DBusMethodsTests.cpp @@ -107,9 +107,9 @@ TYPED_TEST(SdbusTestObject, CallsMethodWithStructVariantsAndGetMapSuccesfully) std::vector x{-2, 0, 2}; sdbus::Struct y{false, true}; std::map mapOfVariants = this->m_proxy->getMapOfVariants(x, y); - decltype(mapOfVariants) res{ {sdbus::Variant{-2}, sdbus::Variant{false}} - , {sdbus::Variant{0}, sdbus::Variant{false}} - , {sdbus::Variant{2}, sdbus::Variant{true}}}; + decltype(mapOfVariants) res{ {-2, sdbus::Variant{false}} + , {0, sdbus::Variant{false}} + , {2, sdbus::Variant{true}}}; ASSERT_THAT(mapOfVariants[-2].get(), Eq(res[-2].get())); ASSERT_THAT(mapOfVariants[0].get(), Eq(res[0].get())); diff --git a/tests/integrationtests/integrationtests-proxy.h b/tests/integrationtests/integrationtests-proxy.h index e161183f..edae7a52 100644 --- a/tests/integrationtests/integrationtests-proxy.h +++ b/tests/integrationtests/integrationtests-proxy.h @@ -198,7 +198,7 @@ class integrationtests_proxy public: uint32_t action() { - return proxy_->getProperty("action").onInterface(INTERFACE_NAME); + return proxy_->getProperty("action").onInterface(INTERFACE_NAME).get(); } void action(const uint32_t& value) @@ -208,7 +208,7 @@ class integrationtests_proxy bool blocking() { - return proxy_->getProperty("blocking").onInterface(INTERFACE_NAME); + return proxy_->getProperty("blocking").onInterface(INTERFACE_NAME).get(); } void blocking(const bool& value) @@ -218,7 +218,7 @@ class integrationtests_proxy std::string state() { - return proxy_->getProperty("state").onInterface(INTERFACE_NAME); + return proxy_->getProperty("state").onInterface(INTERFACE_NAME).get(); } private: From e48d6d46ba598d7ad8e77e3b5f0625b343fa6b86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Tue, 16 Apr 2024 15:40:43 +0200 Subject: [PATCH 41/52] refactor: enable compile-time D-Bus signature generation (#425) Since sdbus-c++ knows types of D-Bus call/signal/property input/output parameters at compile time, why not assemble the D-Bus signature strings at compile time, too, instead of assembling them at run time as std::string with likely cost of (unnecessary) heap allocations... std::array is well supported constexpr type in C++20, so we harness it to build the D-Bus signature string and store it into the binary at compile time. --- .github/workflows/ci.yml | 7 +- docs/using-sdbus-c++.md | 17 +- include/sdbus-c++/Message.h | 148 ++++++++---- include/sdbus-c++/TypeTraits.h | 350 ++++++++++------------------ include/sdbus-c++/Types.h | 24 +- include/sdbus-c++/VTableItems.inl | 10 +- src/Message.cpp | 34 ++- tests/unittests/Message_test.cpp | 4 +- tests/unittests/TypeTraits_test.cpp | 3 +- 9 files changed, 286 insertions(+), 311 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f79e3df1..f364cb40 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,9 +15,12 @@ jobs: fail-fast: false matrix: os: [ubuntu-20.04, ubuntu-22.04] - compiler: [g++, clang] + compiler: [g++] build: [shared-libsystemd] include: + - os: ubuntu-22.04 + compiler: clang + build: shared-libsystemd - os: ubuntu-22.04 compiler: g++ build: embedded-static-libsystemd @@ -94,7 +97,7 @@ jobs: cd build cpack -G DEB - name: 'Upload Artifact' - if: matrix.build == 'shared-libsystemd' && matrix.os == 'ubuntu-20.04' && matrix.compiler == 'g++' + if: matrix.build == 'shared-libsystemd' && matrix.os == 'ubuntu-22.04' && matrix.compiler == 'g++' uses: actions/upload-artifact@v3 with: name: "debian-packages-${{ matrix.os }}-${{ matrix.compiler }}" diff --git a/docs/using-sdbus-c++.md b/docs/using-sdbus-c++.md index cf92c5cc..1cde2cf1 100644 --- a/docs/using-sdbus-c++.md +++ b/docs/using-sdbus-c++.md @@ -56,7 +56,7 @@ PKG_CHECK_MODULES(SDBUSCPP, [sdbus-c++ >= 0.6],, ) ``` -> **_Note_:** sdbus-c++ library uses a number of modern C++17 features. Please make certain you have a recent compiler (gcc >= 7, clang >= 6). +> **_Note_:** sdbus-c++ library uses a number of modern C++17 (and, conditionally, C++20) features. Please make sure you have a recent compiler (best gcc >= 10, clang >= 11). If you intend to use xml-to-c++ generator tool (explained later) in your project to generate interface headers from XML, you can integrate that too with CMake or `pkg-config`: @@ -1568,7 +1568,7 @@ We need two things to do that: * implement `sdbus::Message` insertion (serialization) and extraction (deserialization) operators, so sdbus-c++ knows how to serialize/deserialize our custom type, * specialize `sdbus::signature_of` template for our custom type, so sdbus-c++ knows the mapping to D-Bus type and other necessary information about our type. -Say, we would like to represent D-Bus arrays as `std::list`s in our application. Since sdbus-c++ comes with pre-defined support for `std::vector`s, `std::array`s and `std::span`s as D-Bus array representations, we have to provide an extension. To implement message serialization and deserialization functions for `std::list`, we can simply copy the sdbus-c++ implementation of these functions for `std::vector`, and simply adjust for `std::list`. Then we provide `signature_of` specialization, again written on terms of one specialized for `std::vector`: +Say, we would like to represent D-Bus arrays as `std::list`s in our application. Since sdbus-c++ comes with pre-defined support for `std::vector`s, `std::array`s and `std::span`s as D-Bus array representations, we have to provide an extension. To implement message serialization and deserialization functions for `std::list`, we can simply copy the sdbus-c++ implementation of these functions for `std::vector`, and simply adjust for `std::list`. Then we provide `signature_of` specialization, again written in terms of one specialized for `std::vector`: ```c++ #include @@ -1580,7 +1580,7 @@ namespace sdbus { template sdbus::Message& operator<<(sdbus::Message& msg, const std::list<_ElementType>& items) { - msg.openContainer(sdbus::signature_of<_ElementType>::str()); + msg.openContainer<_ElementType>(); for (const auto& item : items) msg << item; @@ -1594,7 +1594,7 @@ sdbus::Message& operator<<(sdbus::Message& msg, const std::list<_ElementType>& i template sdbus::Message& operator>>(sdbus::Message& msg, std::list<_ElementType>& items) { - if(!msg.enterContainer(sdbus::signature_of<_ElementType>::str())) + if(!msg.enterContainer<_ElementType>()) return msg; while (true) @@ -1615,16 +1615,18 @@ sdbus::Message& operator>>(sdbus::Message& msg, std::list<_ElementType>& items) } // namespace sdbus -// Implementing type traits for std::list, and since we map it to D-Bus array, -// we can re-use std::vector type traits because it's the same stuff. +// Implementing type traits for std::list -- we re-use by inheriting +// from type traits already provided by sdbus-c++ for D-Bus arrays template struct sdbus::signature_of> - : sdbus::signature_of> + : sdbus::signature_of> {}; ``` Then we can simply use `std::list`s, serialize/deserialize them in a D-Bus message, in D-Bus method calls or return values... and they will be simply transmitted as D-Bus arrays. +Similarly, say we have our own `lockfree_map` which we would like to use natively with sdbus-c++ as a C++ type for D-Bus dictionary -- we can copy or build on top of `std::map` specializations. + As another example, say we have our custom type `my::Struct` which we'd like to use as a D-Bus structure representation (sdbus-c++ provides `sdbus::Struct` type for that, but we don't want to use it because using our custom type directly is more convenient). Again, we have to provide type traits and message serialization/deserialization functions for our custom type. We build our functions and specializations on top of `sdbus::Struct`, so we don't have to copy and write a lot of boiler-plate. Serialization/deserialization functions can be placed in the same namespace as our custom type, and will be found thanks to the ADR lookup. The `signature_of` specialization must always be in either `sdbus` namespace or in a global namespace: ```c++ @@ -1778,6 +1780,7 @@ sdbus-c++ v2 is a major release that comes with a number of breaking API/ABI/beh * `createDefaultBusConnection()` has been renamed to `createBusConnection()`. * Change in behavior: `Proxy`s now by default call `createBusConnection()` to get a connection when the connection is not provided explicitly by the caller, so they connect to either the session bus or the system bus depending on the context (as opposed to always to the system bus like before). * Callbacks taking `const sdbus::Error* error` were changed to take `std::optional`, which better expresses the intent and meaning. +* D-Bus signatures when using high-level API are now assembled at compile time. There are breaking changes inside `signature_of` type traits and `Message` serialization/deserialization methods. This only interests you if you extend sdbus-c++ type system with your own types. See the updated tutorial on extending sdbus-c++ type system. * Types and methods marked deprecated in sdbus-c++ v1 were removed completely. * CMake options got `SDBUSCPP_` prefix for better usability and minimal risk of conflicts in downstream CMake projects. `SDBUSCPP_INSTALL` CMake option was added. * CMake components got `sdbus-c++-` prefix. diff --git a/include/sdbus-c++/Message.h b/include/sdbus-c++/Message.h index 811720ec..9dd793e9 100644 --- a/include/sdbus-c++/Message.h +++ b/include/sdbus-c++/Message.h @@ -161,22 +161,38 @@ namespace sdbus { template Message& operator>>(std::tuple<_ValueTypes...>& item); - Message& openContainer(const std::string& signature); + template + Message& openContainer(); + Message& openContainer(const char* signature); Message& closeContainer(); - Message& openDictEntry(const std::string& signature); + template + Message& openDictEntry(); + Message& openDictEntry(const char* signature); Message& closeDictEntry(); - Message& openVariant(const std::string& signature); + template + Message& openVariant(); + Message& openVariant(const char* signature); Message& closeVariant(); - Message& openStruct(const std::string& signature); + template + Message& openStruct(); + Message& openStruct(const char* signature); Message& closeStruct(); - Message& enterContainer(const std::string& signature); + template + Message& enterContainer(); + Message& enterContainer(const char* signature); Message& exitContainer(); - Message& enterDictEntry(const std::string& signature); + template + Message& enterDictEntry(); + Message& enterDictEntry(const char* signature); Message& exitDictEntry(); - Message& enterVariant(const std::string& signature); + template + Message& enterVariant(); + Message& enterVariant(const char* signature); Message& exitVariant(); - Message& enterStruct(const std::string& signature); + template + Message& enterStruct(); + Message& enterStruct(const char* signature); Message& exitStruct(); Message& appendArray(char type, const void *ptr, size_t size); @@ -320,8 +336,9 @@ namespace sdbus { template inline Message& Message::operator<<(const std::variant& value) { - std::visit([this](const auto& inner){ - openVariant(signature_of::str()); + std::visit([this](const auto& inner) + { + openVariant(); *this << inner; closeVariant(); }, value); @@ -370,11 +387,12 @@ namespace sdbus { // otherwise use step-by-step serialization of individual elements. if constexpr (signature_of::is_trivial_dbus_type && !std::is_same_v) { - appendArray(*signature_of::str().c_str(), items.data(), items.size() * sizeof(ElementType)); + constexpr auto signature = as_null_terminated(signature_of_v); + appendArray(*signature.data(), items.data(), items.size() * sizeof(ElementType)); } else { - openContainer(signature_of::str()); + openContainer(); for (const auto& item : items) *this << item; @@ -403,16 +421,13 @@ namespace sdbus { inline void Message::serializeDictionary(const _Dictionary& items) { using KeyType = typename _Dictionary::key_type; - using ValueType = typename _Dictionary::mapped_type; + using MappedType = typename _Dictionary::mapped_type; - const std::string dictEntrySignature = signature_of::str() + signature_of::str(); - const std::string arraySignature = "{" + dictEntrySignature + "}"; - - openContainer(arraySignature); + openContainer>(); for (const auto& item : items) { - openDictEntry(dictEntrySignature); + openDictEntry(); *this << item.first; *this << item.second; closeDictEntry(); @@ -441,12 +456,7 @@ namespace sdbus { template inline Message& Message::operator<<(const Struct<_ValueTypes...>& item) { - auto structSignature = signature_of>::str(); - assert(structSignature.size() > 2); - // Remove opening and closing parenthesis from the struct signature to get contents signature - auto structContentSignature = structSignature.substr(1, structSignature.size() - 2); - - openStruct(structContentSignature); + openStruct<_ValueTypes...>(); detail::serialize_tuple(*this, item, std::index_sequence_for<_ValueTypes...>{}); closeStruct(); @@ -465,11 +475,13 @@ namespace sdbus { template bool deserialize_variant(Message& msg, std::variant<_Elements...>& value, const std::string& signature) { - if (signature != signature_of<_Element>::str()) + constexpr auto elemSignature = sdbus::signature_of_v<_Element>; + // TODO: Try to optimize + if (signature != std::string(elemSignature.begin(), elemSignature.end())) return false; _Element temp; - msg.enterVariant(signature); + msg.enterVariant(signature.c_str()); msg >> temp; msg.exitVariant(); value = std::move(temp); @@ -482,6 +494,7 @@ namespace sdbus { { std::string type; std::string contentType; + // TODO: Refactor ppekType prior to release/v2.0 to return pair of const char* or string_view... peekType(type, contentType); bool result = (detail::deserialize_variant(*this, value, contentType) || ...); SDBUS_THROW_ERROR_IF(!result, "Failed to deserialize variant: signature did not match any of the variant types", EINVAL); @@ -548,7 +561,8 @@ namespace sdbus { size_t arraySize{}; const ElementType* arrayPtr{}; - readArray(*signature_of::str().c_str(), (const void**)&arrayPtr, &arraySize); + constexpr auto signature = as_null_terminated(sdbus::signature_of_v); + readArray(*signature.data(), (const void**)&arrayPtr, &arraySize); size_t elementsInMsg = arraySize / sizeof(ElementType); bool notEnoughSpace = items.size() < elementsInMsg; @@ -563,7 +577,8 @@ namespace sdbus { size_t arraySize{}; const _Element* arrayPtr{}; - readArray(*signature_of<_Element>::str().c_str(), (const void**)&arrayPtr, &arraySize); + constexpr auto signature = as_null_terminated(sdbus::signature_of_v<_Element>); + readArray(*signature.data(), (const void**)&arrayPtr, &arraySize); items.insert(items.end(), arrayPtr, arrayPtr + (arraySize / sizeof(_Element))); } @@ -573,7 +588,7 @@ namespace sdbus { { using ElementType = typename _Array::value_type; - if(!enterContainer(signature_of::str())) + if(!enterContainer()) return; for (auto& elem : items) @@ -590,7 +605,7 @@ namespace sdbus { template void Message::deserializeArraySlow(std::vector<_Element, _Allocator>& items) { - if(!enterContainer(signature_of<_Element>::str())) + if(!enterContainer<_Element>()) return; while (true) @@ -627,21 +642,18 @@ namespace sdbus { inline void Message::deserializeDictionary(_Dictionary& items) { using KeyType = typename _Dictionary::key_type; - using ValueType = typename _Dictionary::mapped_type; + using MappedType = typename _Dictionary::mapped_type; - const std::string dictEntrySignature = signature_of::str() + signature_of::str(); - const std::string arraySignature = "{" + dictEntrySignature + "}"; - - if (!enterContainer(arraySignature)) + if (!enterContainer>()) return; while (true) { - if (!enterDictEntry(dictEntrySignature)) + if (!enterDictEntry()) break; KeyType key; - ValueType value; + MappedType value; *this >> key >> value; items.emplace(std::move(key), std::move(value)); @@ -674,11 +686,7 @@ namespace sdbus { template inline Message& Message::operator>>(Struct<_ValueTypes...>& item) { - auto structSignature = signature_of>::str(); - // Remove opening and closing parenthesis from the struct signature to get contents signature - auto structContentSignature = structSignature.substr(1, structSignature.size()-2); - - if (!enterStruct(structContentSignature)) + if (!enterStruct<_ValueTypes...>()) return *this; detail::deserialize_tuple(*this, item, std::index_sequence_for<_ValueTypes...>{}); @@ -695,6 +703,62 @@ namespace sdbus { return *this; } + template + inline Message& Message::openContainer() + { + constexpr auto signature = as_null_terminated(signature_of_v<_ElementType>); + return openContainer(signature.data()); + } + + template + inline Message& Message::openDictEntry() + { + constexpr auto signature = as_null_terminated(signature_of_v>); + return openDictEntry(signature.data()); + } + + template + inline Message& Message::openVariant() + { + constexpr auto signature = as_null_terminated(signature_of_v<_ValueType>); + return openVariant(signature.data()); + } + + template + inline Message& Message::openStruct() + { + constexpr auto signature = as_null_terminated(signature_of_v>); + return openStruct(signature.data()); + } + + template + inline Message& Message::enterContainer() + { + constexpr auto signature = as_null_terminated(signature_of_v<_ElementType>); + return enterContainer(signature.data()); + } + + template + inline Message& Message::enterDictEntry() + { + constexpr auto signature = as_null_terminated(signature_of_v>); + return enterDictEntry(signature.data()); + } + + template + inline Message& Message::enterVariant() + { + constexpr auto signature = as_null_terminated(signature_of_v<_ValueType>); + return enterVariant(signature.data()); + } + + template + inline Message& Message::enterStruct() + { + constexpr auto signature = as_null_terminated(signature_of_v>); + return enterStruct(signature.data()); + } + } #endif /* SDBUS_CXX_MESSAGE_H_ */ diff --git a/include/sdbus-c++/TypeTraits.h b/include/sdbus-c++/TypeTraits.h index cec5f933..a0e6e2c8 100644 --- a/include/sdbus-c++/TypeTraits.h +++ b/include/sdbus-c++/TypeTraits.h @@ -44,6 +44,7 @@ #include #include #include +#include #include #include @@ -54,6 +55,7 @@ namespace sdbus { class ObjectPath; class Signature; class UnixFd; + template using DictEntry = std::pair<_T1, _T2>; class BusName; class InterfaceName; class MemberName; @@ -65,6 +67,7 @@ namespace sdbus { class PropertyGetReply; template class Result; class Error; + template struct signature_of; } namespace sdbus { @@ -106,253 +109,171 @@ namespace sdbus { // Helper for static assert template constexpr bool always_false = false; + // Helper operator+ for concatenation of `std::array`s + template + constexpr std::array<_T, _N1 + _N2> operator+(std::array<_T, _N1> lhs, std::array<_T, _N2> rhs); + // Template specializations for getting D-Bus signatures from C++ types - template + template + constexpr auto signature_of_v = signature_of<_T>::value; + + template struct signature_of { static constexpr bool is_valid = false; static constexpr bool is_trivial_dbus_type = false; - static const std::string str() + static constexpr void* value = [] { // See using-sdbus-c++.md, section "Extending sdbus-c++ type system", // on how to teach sdbus-c++ about your custom types - static_assert(always_false<_T>, "Unsupported DBus type (template specializations are needed for your custom types)"); - return ""; - } + static_assert(always_false<_T>, "Unsupported D-Bus type (specialize `signature_of` for your custom types)"); + }; }; template - struct signature_of - : public signature_of<_T> + struct signature_of : signature_of<_T> {}; template - struct signature_of<_T&> - : public signature_of<_T> + struct signature_of : signature_of<_T> + {}; + + template + struct signature_of<_T&> : signature_of<_T> {}; template <> struct signature_of { + static constexpr std::array value{}; static constexpr bool is_valid = true; static constexpr bool is_trivial_dbus_type = false; - - static const std::string str() - { - return ""; - } }; template <> struct signature_of { + static constexpr std::array value{'b'}; static constexpr bool is_valid = true; static constexpr bool is_trivial_dbus_type = true; - - static const std::string str() - { - return "b"; - } }; template <> struct signature_of { + static constexpr std::array value{'y'}; static constexpr bool is_valid = true; static constexpr bool is_trivial_dbus_type = true; - - static const std::string str() - { - return "y"; - } }; template <> struct signature_of { + static constexpr std::array value{'n'}; static constexpr bool is_valid = true; static constexpr bool is_trivial_dbus_type = true; - - static const std::string str() - { - return "n"; - } }; template <> struct signature_of { + static constexpr std::array value{'q'}; static constexpr bool is_valid = true; static constexpr bool is_trivial_dbus_type = true; - - static const std::string str() - { - return "q"; - } }; template <> struct signature_of { + static constexpr std::array value{'i'}; static constexpr bool is_valid = true; static constexpr bool is_trivial_dbus_type = true; - - static const std::string str() - { - return "i"; - } }; template <> struct signature_of { + static constexpr std::array value{'u'}; static constexpr bool is_valid = true; static constexpr bool is_trivial_dbus_type = true; - - static const std::string str() - { - return "u"; - } }; template <> struct signature_of { + static constexpr std::array value{'x'}; static constexpr bool is_valid = true; static constexpr bool is_trivial_dbus_type = true; - - static const std::string str() - { - return "x"; - } }; template <> struct signature_of { + static constexpr std::array value{'t'}; static constexpr bool is_valid = true; static constexpr bool is_trivial_dbus_type = true; - - static const std::string str() - { - return "t"; - } }; template <> struct signature_of { + static constexpr std::array value{'d'}; static constexpr bool is_valid = true; static constexpr bool is_trivial_dbus_type = true; - - static const std::string str() - { - return "d"; - } }; template <> - struct signature_of + struct signature_of { + static constexpr std::array value{'s'}; static constexpr bool is_valid = true; static constexpr bool is_trivial_dbus_type = false; - - static const std::string str() - { - return "s"; - } }; template <> - struct signature_of - { - static constexpr bool is_valid = true; - static constexpr bool is_trivial_dbus_type = false; + struct signature_of : signature_of + {}; - static const std::string str() - { - return "s"; - } - }; + template <> + struct signature_of : signature_of + {}; template - struct signature_of - { - static constexpr bool is_valid = true; - static constexpr bool is_trivial_dbus_type = false; - - static const std::string str() - { - return "s"; - } - }; + struct signature_of : signature_of + {}; template - struct signature_of - { - static constexpr bool is_valid = true; - static constexpr bool is_trivial_dbus_type = false; - - static const std::string str() - { - return "s"; - } - }; - - template <> - struct signature_of - { - static constexpr bool is_valid = true; - static constexpr bool is_trivial_dbus_type = false; - - static const std::string str() - { - return "s"; - } - }; + struct signature_of : signature_of + {}; template <> struct signature_of : signature_of - { - }; + {}; template <> struct signature_of : signature_of - { - }; + {}; template <> struct signature_of : signature_of - { - }; + {}; template struct signature_of> { + static constexpr std::array contents = (signature_of_v<_ValueTypes> + ...); + static constexpr std::array value = std::array{'('} + contents + std::array{')'}; static constexpr bool is_valid = true; static constexpr bool is_trivial_dbus_type = false; - - static const std::string str() - { - std::string signature; - signature += "("; - (signature += ... += signature_of<_ValueTypes>::str()); - signature += ")"; - return signature; - } }; template <> struct signature_of { + static constexpr std::array value{'v'}; static constexpr bool is_valid = true; static constexpr bool is_trivial_dbus_type = false; - - static const std::string str() - { - return "v"; - } }; template @@ -362,74 +283,52 @@ namespace sdbus { template <> struct signature_of { + static constexpr std::array value{'o'}; static constexpr bool is_valid = true; static constexpr bool is_trivial_dbus_type = false; - - static const std::string str() - { - return "o"; - } }; template <> struct signature_of { + static constexpr std::array value{'g'}; static constexpr bool is_valid = true; static constexpr bool is_trivial_dbus_type = false; - - static const std::string str() - { - return "g"; - } }; template <> struct signature_of { + static constexpr std::array value{'h'}; static constexpr bool is_valid = true; static constexpr bool is_trivial_dbus_type = false; + }; - static const std::string str() - { - return "h"; - } + template + struct signature_of> + { + static constexpr std::array value = std::array{'{'} + signature_of_v> + std::array{'}'}; + static constexpr bool is_valid = true; + static constexpr bool is_trivial_dbus_type = false; }; template struct signature_of> { + static constexpr std::array value = std::array{'a'} + signature_of_v<_Element>; static constexpr bool is_valid = true; static constexpr bool is_trivial_dbus_type = false; - - static const std::string str() - { - return "a" + signature_of<_Element>::str(); - } }; template - struct signature_of> + struct signature_of> : signature_of> { - static constexpr bool is_valid = true; - static constexpr bool is_trivial_dbus_type = false; - - static const std::string str() - { - return "a" + signature_of<_Element>::str(); - } }; #ifdef __cpp_lib_span template - struct signature_of> + struct signature_of> : signature_of> { - static constexpr bool is_valid = true; - static constexpr bool is_trivial_dbus_type = false; - - static const std::string str() - { - return "a" + signature_of<_Element>::str(); - } }; #endif @@ -438,46 +337,49 @@ namespace sdbus { : public signature_of> {}; - template struct signature_of> { + static constexpr std::array contents = signature_of_v>; + static constexpr std::array dict_entry = std::array{'{'} + contents + std::array{'}'}; + static constexpr std::array value = std::array{'a'} + dict_entry; static constexpr bool is_valid = true; static constexpr bool is_trivial_dbus_type = false; - - static const std::string str() - { - return "a{" + signature_of<_Key>::str() + signature_of<_Value>::str() + "}"; - } }; template struct signature_of> + : signature_of> { - static constexpr bool is_valid = true; - static constexpr bool is_trivial_dbus_type = false; + }; - static const std::string str() - { - return "a{" + signature_of<_Key>::str() + signature_of<_Value>::str() + "}"; - } + template + struct signature_of> // A simple concatenation of signatures of _Types + { + static constexpr std::array value = (std::array{} + ... + signature_of_v<_Types>); + static constexpr bool is_valid = false; + static constexpr bool is_trivial_dbus_type = false; }; + // To simplify conversions of arrays to C strings + template + constexpr auto as_null_terminated(std::array<_T, _N> arr) + { + return arr + std::array<_T, 1>{0}; + } + // Function traits implementation inspired by (c) kennytm, // https://github.com/kennytm/utils/blob/master/traits.hpp template - struct function_traits - : public function_traits + struct function_traits : function_traits {}; template - struct function_traits - : public function_traits<_Type> + struct function_traits : function_traits<_Type> {}; template - struct function_traits<_Type&> - : public function_traits<_Type> + struct function_traits<_Type&> : function_traits<_Type> {}; template @@ -517,72 +419,62 @@ namespace sdbus { }; template - struct function_traits<_ReturnType(_Args...)> - : public function_traits_base<_ReturnType, _Args...> + struct function_traits<_ReturnType(_Args...)> : function_traits_base<_ReturnType, _Args...> { static constexpr bool is_async = false; static constexpr bool has_error_param = false; }; template - struct function_traits, _Args...)> - : public function_traits_base + struct function_traits, _Args...)> : function_traits_base { static constexpr bool has_error_param = true; }; template - struct function_traits, _Args...)> - : public function_traits_base, _Args...> + struct function_traits, _Args...)> : function_traits_base, _Args...> { static constexpr bool is_async = true; using async_result_t = Result<_Results...>; }; template - struct function_traits&&, _Args...)> - : public function_traits_base, _Args...> + struct function_traits&&, _Args...)> : function_traits_base, _Args...> { static constexpr bool is_async = true; using async_result_t = Result<_Results...>; }; template - struct function_traits<_ReturnType(*)(_Args...)> - : public function_traits<_ReturnType(_Args...)> + struct function_traits<_ReturnType(*)(_Args...)> : function_traits<_ReturnType(_Args...)> {}; template - struct function_traits<_ReturnType(_ClassType::*)(_Args...)> - : public function_traits<_ReturnType(_Args...)> + struct function_traits<_ReturnType(_ClassType::*)(_Args...)> : function_traits<_ReturnType(_Args...)> { typedef _ClassType& owner_type; }; template - struct function_traits<_ReturnType(_ClassType::*)(_Args...) const> - : public function_traits<_ReturnType(_Args...)> + struct function_traits<_ReturnType(_ClassType::*)(_Args...) const> : function_traits<_ReturnType(_Args...)> { typedef const _ClassType& owner_type; }; template - struct function_traits<_ReturnType(_ClassType::*)(_Args...) volatile> - : public function_traits<_ReturnType(_Args...)> + struct function_traits<_ReturnType(_ClassType::*)(_Args...) volatile> : function_traits<_ReturnType(_Args...)> { typedef volatile _ClassType& owner_type; }; template - struct function_traits<_ReturnType(_ClassType::*)(_Args...) const volatile> - : public function_traits<_ReturnType(_Args...)> + struct function_traits<_ReturnType(_ClassType::*)(_Args...) const volatile> : function_traits<_ReturnType(_Args...)> { typedef const volatile _ClassType& owner_type; }; template - struct function_traits> - : public function_traits + struct function_traits> : function_traits {}; template @@ -621,45 +513,33 @@ namespace sdbus { template using tuple_of_function_output_arg_types_t = typename tuple_of_function_output_arg_types<_Function>::type; - template - struct aggregate_signature - { - static const std::string str() - { - return signature_of>::str(); - } - }; - - template - struct aggregate_signature> + template + struct signature_of_function_input_arguments : signature_of> { - static const std::string str() + static std::string value_as_string() { - std::string signature; - (void)(signature += ... += signature_of>::str()); - return signature; + constexpr auto signature = as_null_terminated(signature_of_v>); + return signature.data(); } }; template - struct signature_of_function_input_arguments - { - static const std::string str() - { - return aggregate_signature>::str(); - } - }; + inline auto signature_of_function_input_arguments_v = signature_of_function_input_arguments<_Function>::value_as_string(); template - struct signature_of_function_output_arguments + struct signature_of_function_output_arguments : signature_of> { - static const std::string str() + static std::string value_as_string() { - return aggregate_signature>::str(); + constexpr auto signature = as_null_terminated(signature_of_v>); + return signature.data(); } }; + template + inline auto signature_of_function_output_arguments_v = signature_of_function_output_arguments<_Function>::value_as_string(); + // std::future stuff for return values of async calls template struct future_return { typedef std::tuple<_Args...> type; @@ -678,7 +558,6 @@ namespace sdbus { template using future_return_t = typename future_return<_Args...>::type; - // Credit: Piotr Skotnicki (https://stackoverflow.com/a/57639506) template constexpr bool is_one_of_variants_types = false; @@ -687,7 +566,6 @@ namespace sdbus { constexpr bool is_one_of_variants_types, _QueriedType> = (std::is_same_v<_QueriedType, _VariantTypes> || ...); - namespace detail { template @@ -753,6 +631,26 @@ namespace sdbus { , std::forward<_Tuple>(t) , std::make_index_sequence>::value>{} ); } + + // Convenient concatenation of arrays + template + constexpr std::array<_T, _N1 + _N2> operator+(std::array<_T, _N1> lhs, std::array<_T, _N2> rhs) + { + std::array<_T, _N1 + _N2> result{}; + std::size_t index = 0; + + for (auto& el : lhs) { + result[index] = std::move(el); + ++index; + } + for (auto& el : rhs) { + result[index] = std::move(el); + ++index; + } + + return result; + } + } #endif /* SDBUS_CXX_TYPETRAITS_H_ */ diff --git a/include/sdbus-c++/Types.h b/include/sdbus-c++/Types.h index 19bfd692..776bdc84 100644 --- a/include/sdbus-c++/Types.h +++ b/include/sdbus-c++/Types.h @@ -57,10 +57,9 @@ namespace sdbus { Variant(); template - explicit Variant(const _ValueType& value) - : Variant() + explicit Variant(const _ValueType& value) : Variant() { - msg_.openVariant(signature_of<_ValueType>::str()); + msg_.openVariant<_ValueType>(); msg_ << value; msg_.closeVariant(); msg_.seal(); @@ -79,7 +78,8 @@ namespace sdbus { { _ValueType val; msg_.rewind(false); - msg_.enterVariant(signature_of<_ValueType>::str()); + + msg_.enterVariant<_ValueType>(); msg_ >> val; msg_.exitVariant(); return val; @@ -104,7 +104,8 @@ namespace sdbus { template bool containsValueOfType() const { - return signature_of<_Type>::str() == peekValueType(); + constexpr auto signature = as_null_terminated(signature_of_v<_Type>); + return signature.data() == peekValueType(); } bool isEmpty() const; @@ -134,15 +135,12 @@ namespace sdbus { public: using std::tuple<_ValueTypes...>::tuple; - // Disable constructor if an older then 7.1.0 version of GCC is used -#if !((defined(__GNUC__) || defined(__GNUG__)) && !defined(__clang__) && !(__GNUC__ > 7 || (__GNUC__ == 7 && (__GNUC_MINOR__ > 1 || (__GNUC_MINOR__ == 1 && __GNUC_PATCHLEVEL__ > 0))))) Struct() = default; explicit Struct(const std::tuple<_ValueTypes...>& t) : std::tuple<_ValueTypes...>(t) { } -#endif template auto& get() @@ -374,6 +372,16 @@ namespace sdbus { int fd_ = -1; }; + /********************************************//** + * @typedef DictEntry + * + * DictEntry is implemented as std::pair, a standard + * value_type in STL(-like) associative containers. + * + ***********************************************/ + template + using DictEntry = std::pair<_T1, _T2>; + } template diff --git a/include/sdbus-c++/VTableItems.inl b/include/sdbus-c++/VTableItems.inl index b3f60441..b8e5e09c 100644 --- a/include/sdbus-c++/VTableItems.inl +++ b/include/sdbus-c++/VTableItems.inl @@ -42,8 +42,8 @@ namespace sdbus { template MethodVTableItem& MethodVTableItem::implementedAs(_Function&& callback) { - inputSignature = signature_of_function_input_arguments<_Function>::str(); - outputSignature = signature_of_function_output_arguments<_Function>::str(); + inputSignature = signature_of_function_input_arguments_v<_Function>; + outputSignature = signature_of_function_output_arguments_v<_Function>; callbackHandler = [callback = std::forward<_Function>(callback)](MethodCall call) { // Create a tuple of callback input arguments' types, which will be used @@ -142,7 +142,7 @@ namespace sdbus { template inline SignalVTableItem& SignalVTableItem::withParameters() { - signature = signature_of_function_input_arguments::str(); + signature = signature_of_function_input_arguments_v; return *this; } @@ -192,7 +192,7 @@ namespace sdbus { static_assert(!std::is_void>::value, "Property getter function must return property value"); if (signature.empty()) - signature = signature_of_function_output_arguments<_Function>::str(); + signature = signature_of_function_output_arguments_v<_Function>; getter = [callback = std::forward<_Function>(callback)](PropertyGetReply& reply) { @@ -210,7 +210,7 @@ namespace sdbus { static_assert(std::is_void>::value, "Property setter function must not return any value"); if (signature.empty()) - signature = signature_of_function_input_arguments<_Function>::str(); + signature = signature_of_function_input_arguments_v<_Function>; setter = [callback = std::forward<_Function>(callback)](PropertySetCall call) { diff --git a/src/Message.cpp b/src/Message.cpp index ae281e2a..52421c97 100644 --- a/src/Message.cpp +++ b/src/Message.cpp @@ -429,10 +429,9 @@ Message& Message::operator>>(UnixFd &item) return *this; } - -Message& Message::openContainer(const std::string& signature) +Message& Message::openContainer(const char* signature) { - auto r = sd_bus_message_open_container((sd_bus_message*)msg_, SD_BUS_TYPE_ARRAY, signature.c_str()); + auto r = sd_bus_message_open_container((sd_bus_message*)msg_, SD_BUS_TYPE_ARRAY, signature); SDBUS_THROW_ERROR_IF(r < 0, "Failed to open a container", -r); return *this; @@ -446,9 +445,9 @@ Message& Message::closeContainer() return *this; } -Message& Message::openDictEntry(const std::string& signature) +Message& Message::openDictEntry(const char* signature) { - auto r = sd_bus_message_open_container((sd_bus_message*)msg_, SD_BUS_TYPE_DICT_ENTRY, signature.c_str()); + auto r = sd_bus_message_open_container((sd_bus_message*)msg_, SD_BUS_TYPE_DICT_ENTRY, signature); SDBUS_THROW_ERROR_IF(r < 0, "Failed to open a dictionary entry", -r); return *this; @@ -462,9 +461,9 @@ Message& Message::closeDictEntry() return *this; } -Message& Message::openVariant(const std::string& signature) +Message& Message::openVariant(const char* signature) { - auto r = sd_bus_message_open_container((sd_bus_message*)msg_, SD_BUS_TYPE_VARIANT, signature.c_str()); + auto r = sd_bus_message_open_container((sd_bus_message*)msg_, SD_BUS_TYPE_VARIANT, signature); SDBUS_THROW_ERROR_IF(r < 0, "Failed to open a variant", -r); return *this; @@ -478,9 +477,9 @@ Message& Message::closeVariant() return *this; } -Message& Message::openStruct(const std::string& signature) +Message& Message::openStruct(const char* signature) { - auto r = sd_bus_message_open_container((sd_bus_message*)msg_, SD_BUS_TYPE_STRUCT, signature.c_str()); + auto r = sd_bus_message_open_container((sd_bus_message*)msg_, SD_BUS_TYPE_STRUCT, signature); SDBUS_THROW_ERROR_IF(r < 0, "Failed to open a struct", -r); return *this; @@ -494,10 +493,9 @@ Message& Message::closeStruct() return *this; } - -Message& Message::enterContainer(const std::string& signature) +Message& Message::enterContainer(const char* signature) { - auto r = sd_bus_message_enter_container((sd_bus_message*)msg_, SD_BUS_TYPE_ARRAY, signature.c_str()); + auto r = sd_bus_message_enter_container((sd_bus_message*)msg_, SD_BUS_TYPE_ARRAY, signature); if (r == 0) ok_ = false; @@ -514,9 +512,9 @@ Message& Message::exitContainer() return *this; } -Message& Message::enterDictEntry(const std::string& signature) +Message& Message::enterDictEntry(const char* signature) { - auto r = sd_bus_message_enter_container((sd_bus_message*)msg_, SD_BUS_TYPE_DICT_ENTRY, signature.c_str()); + auto r = sd_bus_message_enter_container((sd_bus_message*)msg_, SD_BUS_TYPE_DICT_ENTRY, signature); if (r == 0) ok_ = false; @@ -533,9 +531,9 @@ Message& Message::exitDictEntry() return *this; } -Message& Message::enterVariant(const std::string& signature) +Message& Message::enterVariant(const char* signature) { - auto r = sd_bus_message_enter_container((sd_bus_message*)msg_, SD_BUS_TYPE_VARIANT, signature.c_str()); + auto r = sd_bus_message_enter_container((sd_bus_message*)msg_, SD_BUS_TYPE_VARIANT, signature); if (r == 0) ok_ = false; @@ -552,9 +550,9 @@ Message& Message::exitVariant() return *this; } -Message& Message::enterStruct(const std::string& signature) +Message& Message::enterStruct(const char* signature) { - auto r = sd_bus_message_enter_container((sd_bus_message*)msg_, SD_BUS_TYPE_STRUCT, signature.c_str()); + auto r = sd_bus_message_enter_container((sd_bus_message*)msg_, SD_BUS_TYPE_STRUCT, signature); if (r == 0) ok_ = false; diff --git a/tests/unittests/Message_test.cpp b/tests/unittests/Message_test.cpp index 1db7660e..964b650f 100644 --- a/tests/unittests/Message_test.cpp +++ b/tests/unittests/Message_test.cpp @@ -51,7 +51,7 @@ namespace sdbus { template sdbus::Message& operator<<(sdbus::Message& msg, const std::list<_ElementType>& items) { - msg.openContainer(sdbus::signature_of<_ElementType>::str()); + msg.openContainer<_ElementType>(); for (const auto& item : items) msg << item; @@ -64,7 +64,7 @@ namespace sdbus { template sdbus::Message& operator>>(sdbus::Message& msg, std::list<_ElementType>& items) { - if(!msg.enterContainer(sdbus::signature_of<_ElementType>::str())) + if(!msg.enterContainer<_ElementType>()) return msg; while (true) diff --git a/tests/unittests/TypeTraits_test.cpp b/tests/unittests/TypeTraits_test.cpp index 0ccc7bdd..f06491a8 100644 --- a/tests/unittests/TypeTraits_test.cpp +++ b/tests/unittests/TypeTraits_test.cpp @@ -170,7 +170,8 @@ namespace TYPED_TEST(Type2DBusTypeSignatureConversion, ConvertsTypeToProperDBusSignature) { - ASSERT_THAT(sdbus::signature_of::str(), Eq(this->dbusTypeSignature_)); + constexpr auto signature = sdbus::as_null_terminated(sdbus::signature_of_v); + ASSERT_THAT(signature.data(), Eq(this->dbusTypeSignature_)); } TEST(FreeFunctionTypeTraits, DetectsTraitsOfTrivialSignatureFunction) From 232d223cb0514721ac2334a590ebc3ab9b976db6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Tue, 16 Apr 2024 18:07:47 +0200 Subject: [PATCH 42/52] feat: add support for string_view as D-Bus type --- include/sdbus-c++/Message.h | 1 + include/sdbus-c++/TypeTraits.h | 5 +++++ src/Message.cpp | 12 ++++++++++++ tests/unittests/Message_test.cpp | 17 ++++++++++++++++- tests/unittests/TypeTraits_test.cpp | 7 +++++-- 5 files changed, 39 insertions(+), 3 deletions(-) diff --git a/include/sdbus-c++/Message.h b/include/sdbus-c++/Message.h index 9dd793e9..de735ab2 100644 --- a/include/sdbus-c++/Message.h +++ b/include/sdbus-c++/Message.h @@ -100,6 +100,7 @@ namespace sdbus { Message& operator<<(double item); Message& operator<<(const char *item); Message& operator<<(const std::string &item); + Message& operator<<(std::string_view item); Message& operator<<(const Variant &item); template Message& operator<<(const std::variant& value); diff --git a/include/sdbus-c++/TypeTraits.h b/include/sdbus-c++/TypeTraits.h index a0e6e2c8..7b146c57 100644 --- a/include/sdbus-c++/TypeTraits.h +++ b/include/sdbus-c++/TypeTraits.h @@ -41,6 +41,7 @@ # endif #endif #include +#include #include #include #include @@ -231,6 +232,10 @@ namespace sdbus { static constexpr bool is_trivial_dbus_type = false; }; + template <> + struct signature_of : signature_of + {}; + template <> struct signature_of : signature_of {}; diff --git a/src/Message.cpp b/src/Message.cpp index 52421c97..cda9ccd0 100644 --- a/src/Message.cpp +++ b/src/Message.cpp @@ -35,6 +35,7 @@ #include "ScopeGuard.h" #include +#include #include SDBUS_HEADER namespace sdbus { @@ -197,6 +198,17 @@ Message& Message::operator<<(const std::string& item) return *this; } +Message& Message::operator<<(std::string_view item) +{ + char* destPtr{}; + auto r = sd_bus_message_append_string_space((sd_bus_message*)msg_, item.length(), &destPtr); + SDBUS_THROW_ERROR_IF(r < 0, "Failed to serialize a string_view value", -r); + + std::memcpy(destPtr, item.data(), item.length()); + + return *this; +} + Message& Message::operator<<(const Variant &item) { item.serializeTo(*this); diff --git a/tests/unittests/Message_test.cpp b/tests/unittests/Message_test.cpp index 964b650f..d1d4175d 100644 --- a/tests/unittests/Message_test.cpp +++ b/tests/unittests/Message_test.cpp @@ -119,7 +119,7 @@ namespace my { template <> struct sdbus::signature_of - : sdbus::signature_of>> + : sdbus::signature_of>> {}; /*-------------------------------------*/ @@ -204,6 +204,21 @@ TEST(AMessage, CanCarryASimpleInteger) ASSERT_THAT(dataRead, Eq(dataWritten)); } +TEST(AMessage, CanCarryAStringAsAStringView) +{ + auto msg = sdbus::createPlainMessage(); + + const std::string_view dataWritten = "Hello"; + + msg << dataWritten; + msg.seal(); + + std::string dataRead; + msg >> dataRead; + + ASSERT_THAT(dataRead, Eq(dataWritten)); +} + TEST(AMessage, CanCarryAUnixFd) { auto msg = sdbus::createPlainMessage(); diff --git a/tests/unittests/TypeTraits_test.cpp b/tests/unittests/TypeTraits_test.cpp index f06491a8..be7464e8 100644 --- a/tests/unittests/TypeTraits_test.cpp +++ b/tests/unittests/TypeTraits_test.cpp @@ -85,6 +85,7 @@ namespace TYPE(double)HAS_DBUS_TYPE_SIGNATURE("d") TYPE(const char*)HAS_DBUS_TYPE_SIGNATURE("s") TYPE(std::string)HAS_DBUS_TYPE_SIGNATURE("s") + TYPE(std::string_view)HAS_DBUS_TYPE_SIGNATURE("s") TYPE(sdbus::BusName)HAS_DBUS_TYPE_SIGNATURE("s") TYPE(sdbus::InterfaceName)HAS_DBUS_TYPE_SIGNATURE("s") TYPE(sdbus::MemberName)HAS_DBUS_TYPE_SIGNATURE("s") @@ -122,10 +123,11 @@ namespace >, sdbus::Signature, sdbus::UnixFd, - const char* + const char*, + std::string_view > >; - TYPE(ComplexType)HAS_DBUS_TYPE_SIGNATURE("a{t(a{ya(oanbva{is})}ghs)}") + TYPE(ComplexType)HAS_DBUS_TYPE_SIGNATURE("a{t(a{ya(oanbva{is})}ghss)}") typedef ::testing::Types< bool , uint8_t @@ -138,6 +140,7 @@ namespace , double , const char* , std::string + , std::string_view , sdbus::BusName , sdbus::InterfaceName , sdbus::MemberName From f4bd8216862737bb1ab678d6c792eaa22db80960 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Tue, 16 Apr 2024 18:16:40 +0200 Subject: [PATCH 43/52] refactor: use string_view in Property convenience classes --- include/sdbus-c++/ConvenienceApiClasses.h | 49 ++++---- include/sdbus-c++/ConvenienceApiClasses.inl | 126 +++++++------------- include/sdbus-c++/IProxy.h | 9 +- include/sdbus-c++/StandardInterfaces.h | 3 +- 4 files changed, 69 insertions(+), 118 deletions(-) diff --git a/include/sdbus-c++/ConvenienceApiClasses.h b/include/sdbus-c++/ConvenienceApiClasses.h index fbad100b..2d9dceca 100644 --- a/include/sdbus-c++/ConvenienceApiClasses.h +++ b/include/sdbus-c++/ConvenienceApiClasses.h @@ -37,6 +37,7 @@ #include #include #include +#include #include // Forward declarations @@ -133,8 +134,8 @@ namespace sdbus { { public: SignalSubscriber(IProxy& proxy, const SignalName& signalName); - SignalSubscriber& onInterface(InterfaceName interfaceName); - SignalSubscriber& onInterface(std::string interfaceName); + SignalSubscriber& onInterface(InterfaceName interfaceName); // TODO: This could be const char* + SignalSubscriber& onInterface(std::string interfaceName); // TODO: This could be const char* template void call(_Function&& callback); template [[nodiscard]] Slot call(_Function&& callback, return_slot_t); @@ -150,24 +151,22 @@ namespace sdbus { class PropertyGetter { public: - PropertyGetter(IProxy& proxy, const PropertyName& propertyName); - Variant onInterface(const InterfaceName& interfaceName); - Variant onInterface(const std::string& interfaceName); + PropertyGetter(IProxy& proxy, std::string_view propertyName); + Variant onInterface(std::string_view interfaceName); private: static inline const InterfaceName DBUS_PROPERTIES_INTERFACE_NAME{"org.freedesktop.DBus.Properties"}; private: IProxy& proxy_; - const PropertyName& propertyName_; + std::string_view propertyName_; }; class AsyncPropertyGetter { public: - AsyncPropertyGetter(IProxy& proxy, const PropertyName& propertyName); - AsyncPropertyGetter& onInterface(const InterfaceName& interfaceName); - AsyncPropertyGetter& onInterface(const std::string& interfaceName); + AsyncPropertyGetter(IProxy& proxy, std::string_view propertyName); + AsyncPropertyGetter& onInterface(std::string_view interfaceName); template PendingAsyncCall uponReplyInvoke(_Function&& callback); std::future getResultAsFuture(); @@ -176,16 +175,15 @@ namespace sdbus { private: IProxy& proxy_; - const PropertyName& propertyName_; - const InterfaceName* interfaceName_{}; + std::string_view propertyName_; + std::string_view interfaceName_; }; class PropertySetter { public: - PropertySetter(IProxy& proxy, const PropertyName& propertyName); - PropertySetter& onInterface(const InterfaceName& interfaceName); - PropertySetter& onInterface(const std::string& interfaceName); + PropertySetter(IProxy& proxy, std::string_view propertyName); + PropertySetter& onInterface(std::string_view interfaceName); template void toValue(const _Value& value); template void toValue(const _Value& value, dont_expect_reply_t); void toValue(const Variant& value); @@ -196,16 +194,15 @@ namespace sdbus { private: IProxy& proxy_; - const PropertyName& propertyName_; - const InterfaceName* interfaceName_{}; + std::string_view propertyName_; + std::string_view interfaceName_; }; class AsyncPropertySetter { public: - AsyncPropertySetter(IProxy& proxy, const PropertyName& propertyName); - AsyncPropertySetter& onInterface(const InterfaceName& interfaceName); - AsyncPropertySetter& onInterface(const std::string& interfaceName); + AsyncPropertySetter(IProxy& proxy, std::string_view propertyName); + AsyncPropertySetter& onInterface(std::string_view interfaceName); template AsyncPropertySetter& toValue(_Value&& value); AsyncPropertySetter& toValue(Variant value); template PendingAsyncCall uponReplyInvoke(_Function&& callback); @@ -216,8 +213,8 @@ namespace sdbus { private: IProxy& proxy_; - const PropertyName& propertyName_; - const InterfaceName* interfaceName_{}; + std::string_view propertyName_; + std::string_view interfaceName_; Variant value_; }; @@ -225,8 +222,7 @@ namespace sdbus { { public: AllPropertiesGetter(IProxy& proxy); - std::map onInterface(const InterfaceName& interfaceName); - std::map onInterface(const std::string& interfaceName); + std::map onInterface(std::string_view interfaceName); private: static inline const InterfaceName DBUS_PROPERTIES_INTERFACE_NAME{"org.freedesktop.DBus.Properties"}; @@ -239,17 +235,16 @@ namespace sdbus { { public: AsyncAllPropertiesGetter(IProxy& proxy); - AsyncAllPropertiesGetter& onInterface(const InterfaceName& interfaceName); - AsyncAllPropertiesGetter& onInterface(const std::string& interfaceName); + AsyncAllPropertiesGetter& onInterface(std::string_view interfaceName); template PendingAsyncCall uponReplyInvoke(_Function&& callback); std::future> getResultAsFuture(); private: - static inline const InterfaceName DBUS_PROPERTIES_INTERFACE_NAME{"org.freedesktop.DBus.Properties"}; + static inline const InterfaceName DBUS_PROPERTIES_INTERFACE_NAME{"org.freedesktop.DBus.Properties"}; // TODO: Couldn't this be const char*? private: IProxy& proxy_; - const InterfaceName* interfaceName_{}; + std::string_view interfaceName_; }; } // namespace sdbus diff --git a/include/sdbus-c++/ConvenienceApiClasses.inl b/include/sdbus-c++/ConvenienceApiClasses.inl index a6111da1..fb679733 100644 --- a/include/sdbus-c++/ConvenienceApiClasses.inl +++ b/include/sdbus-c++/ConvenienceApiClasses.inl @@ -404,13 +404,13 @@ namespace sdbus { /*** PropertyGetter ***/ /*** -------------- ***/ - inline PropertyGetter::PropertyGetter(IProxy& proxy, const PropertyName& propertyName) + inline PropertyGetter::PropertyGetter(IProxy& proxy, std::string_view propertyName) : proxy_(proxy) - , propertyName_(propertyName) + , propertyName_(std::move(propertyName)) { } - inline Variant PropertyGetter::onInterface(const InterfaceName& interfaceName) + inline Variant PropertyGetter::onInterface(std::string_view interfaceName) { Variant var; proxy_.callMethod("Get") @@ -420,57 +420,43 @@ namespace sdbus { return var; } - inline Variant PropertyGetter::onInterface(const std::string& interfaceName) - { - // Down-cast through static cast for performance reasons (no extra copy and object construction needed) - static_assert(sizeof(interfaceName) == sizeof(InterfaceName)); - return onInterface(static_cast(interfaceName)); - } - /*** ------------------- ***/ /*** AsyncPropertyGetter ***/ /*** ------------------- ***/ - inline AsyncPropertyGetter::AsyncPropertyGetter(IProxy& proxy, const PropertyName& propertyName) - : proxy_(proxy) - , propertyName_(propertyName) + inline AsyncPropertyGetter::AsyncPropertyGetter(IProxy& proxy, std::string_view propertyName) + : proxy_(proxy) + , propertyName_(std::move(propertyName)) { } - inline AsyncPropertyGetter& AsyncPropertyGetter::onInterface(const InterfaceName& interfaceName) + inline AsyncPropertyGetter& AsyncPropertyGetter::onInterface(std::string_view interfaceName) { - interfaceName_ = &interfaceName; + interfaceName_ = std::move(interfaceName); return *this; } - inline AsyncPropertyGetter& AsyncPropertyGetter::onInterface(const std::string& interfaceName) - { - // Down-cast through static cast for performance reasons (no extra copy and object construction needed) - static_assert(sizeof(interfaceName) == sizeof(InterfaceName)); - return onInterface(static_cast(interfaceName)); - } - template PendingAsyncCall AsyncPropertyGetter::uponReplyInvoke(_Function&& callback) { static_assert(std::is_invocable_r_v, Variant>, "Property get callback function must accept std::optional and property value as Variant"); - assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function + assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function return proxy_.callMethodAsync("Get") .onInterface(DBUS_PROPERTIES_INTERFACE_NAME) - .withArguments(*interfaceName_, propertyName_) + .withArguments(interfaceName_, propertyName_) .uponReplyInvoke(std::forward<_Function>(callback)); } inline std::future AsyncPropertyGetter::getResultAsFuture() { - assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function + assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function return proxy_.callMethodAsync("Get") .onInterface(DBUS_PROPERTIES_INTERFACE_NAME) - .withArguments(*interfaceName_, propertyName_) + .withArguments(interfaceName_, propertyName_) .getResultAsFuture(); } @@ -478,26 +464,19 @@ namespace sdbus { /*** PropertySetter ***/ /*** -------------- ***/ - inline PropertySetter::PropertySetter(IProxy& proxy, const PropertyName& propertyName) + inline PropertySetter::PropertySetter(IProxy& proxy, std::string_view propertyName) : proxy_(proxy) - , propertyName_(propertyName) + , propertyName_(std::move(propertyName)) { } - inline PropertySetter& PropertySetter::onInterface(const InterfaceName& interfaceName) + inline PropertySetter& PropertySetter::onInterface(std::string_view interfaceName) { - interfaceName_ = &interfaceName; + interfaceName_ = std::move(interfaceName); return *this; } - inline PropertySetter& PropertySetter::onInterface(const std::string& interfaceName) - { - // Down-cast through static cast for performance reasons (no extra copy and object construction needed) - static_assert(sizeof(interfaceName) == sizeof(InterfaceName)); - return onInterface(static_cast(interfaceName)); - } - template inline void PropertySetter::toValue(const _Value& value) { @@ -512,47 +491,40 @@ namespace sdbus { inline void PropertySetter::toValue(const Variant& value) { - assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function + assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function proxy_.callMethod("Set") .onInterface(DBUS_PROPERTIES_INTERFACE_NAME) - .withArguments(*interfaceName_, propertyName_, value); + .withArguments(interfaceName_, propertyName_, value); } inline void PropertySetter::toValue(const Variant& value, dont_expect_reply_t) { - assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function + assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function proxy_.callMethod("Set") - .onInterface(DBUS_PROPERTIES_INTERFACE_NAME) - .withArguments(*interfaceName_, propertyName_, value) - .dontExpectReply(); + .onInterface(DBUS_PROPERTIES_INTERFACE_NAME) + .withArguments(interfaceName_, propertyName_, value) + .dontExpectReply(); } /*** ------------------- ***/ /*** AsyncPropertySetter ***/ /*** ------------------- ***/ - inline AsyncPropertySetter::AsyncPropertySetter(IProxy& proxy, const PropertyName& propertyName) - : proxy_(proxy) - , propertyName_(propertyName) + inline AsyncPropertySetter::AsyncPropertySetter(IProxy& proxy, std::string_view propertyName) + : proxy_(proxy) + , propertyName_(propertyName) { } - inline AsyncPropertySetter& AsyncPropertySetter::onInterface(const InterfaceName& interfaceName) + inline AsyncPropertySetter& AsyncPropertySetter::onInterface(std::string_view interfaceName) { - interfaceName_ = &interfaceName; + interfaceName_ = std::move(interfaceName); return *this; } - inline AsyncPropertySetter& AsyncPropertySetter::onInterface(const std::string& interfaceName) - { - // Down-cast through static cast for performance reasons (no extra copy and object construction needed) - static_assert(sizeof(interfaceName) == sizeof(InterfaceName)); - return onInterface(static_cast(interfaceName)); - } - template inline AsyncPropertySetter& AsyncPropertySetter::toValue(_Value&& value) { @@ -571,21 +543,21 @@ namespace sdbus { { static_assert(std::is_invocable_r_v>, "Property set callback function must accept std::optional only"); - assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function + assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function return proxy_.callMethodAsync("Set") .onInterface(DBUS_PROPERTIES_INTERFACE_NAME) - .withArguments(*interfaceName_, propertyName_, std::move(value_)) + .withArguments(interfaceName_, propertyName_, std::move(value_)) .uponReplyInvoke(std::forward<_Function>(callback)); } inline std::future AsyncPropertySetter::getResultAsFuture() { - assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function + assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function return proxy_.callMethodAsync("Set") .onInterface(DBUS_PROPERTIES_INTERFACE_NAME) - .withArguments(*interfaceName_, propertyName_, std::move(value_)) + .withArguments(interfaceName_, propertyName_, std::move(value_)) .getResultAsFuture<>(); } @@ -594,27 +566,20 @@ namespace sdbus { /*** ------------------- ***/ inline AllPropertiesGetter::AllPropertiesGetter(IProxy& proxy) - : proxy_(proxy) + : proxy_(proxy) { } - inline std::map AllPropertiesGetter::onInterface(const InterfaceName& interfaceName) + inline std::map AllPropertiesGetter::onInterface(std::string_view interfaceName) { std::map props; proxy_.callMethod("GetAll") - .onInterface(DBUS_PROPERTIES_INTERFACE_NAME) - .withArguments(interfaceName) - .storeResultsTo(props); + .onInterface(DBUS_PROPERTIES_INTERFACE_NAME) + .withArguments(std::move(interfaceName)) + .storeResultsTo(props); return props; } - inline std::map AllPropertiesGetter::onInterface(const std::string& interfaceName) - { - // Down-cast through static cast for performance reasons (no extra copy and object construction needed) - static_assert(sizeof(interfaceName) == sizeof(InterfaceName)); - return onInterface(static_cast(interfaceName)); - } - /*** ------------------------ ***/ /*** AsyncAllPropertiesGetter ***/ /*** ------------------------ ***/ @@ -624,41 +589,34 @@ namespace sdbus { { } - inline AsyncAllPropertiesGetter& AsyncAllPropertiesGetter::onInterface(const InterfaceName& interfaceName) + inline AsyncAllPropertiesGetter& AsyncAllPropertiesGetter::onInterface(std::string_view interfaceName) { - interfaceName_ = &interfaceName; + interfaceName_ = std::move(interfaceName); return *this; } - inline AsyncAllPropertiesGetter& AsyncAllPropertiesGetter::onInterface(const std::string& interfaceName) - { - // Down-cast through static cast for performance reasons (no extra copy and object construction needed) - static_assert(sizeof(interfaceName) == sizeof(InterfaceName)); - return onInterface(static_cast(interfaceName)); - } - template PendingAsyncCall AsyncAllPropertiesGetter::uponReplyInvoke(_Function&& callback) { static_assert( std::is_invocable_r_v, std::map> , "All properties get callback function must accept std::optional and a map of property names to their values" ); - assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function + assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function return proxy_.callMethodAsync("GetAll") .onInterface(DBUS_PROPERTIES_INTERFACE_NAME) - .withArguments(*interfaceName_) + .withArguments(interfaceName_) .uponReplyInvoke(std::forward<_Function>(callback)); } inline std::future> AsyncAllPropertiesGetter::getResultAsFuture() { - assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function + assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function return proxy_.callMethodAsync("GetAll") .onInterface(DBUS_PROPERTIES_INTERFACE_NAME) - .withArguments(*interfaceName_) + .withArguments(interfaceName_) .getResultAsFuture>(); } diff --git a/include/sdbus-c++/IProxy.h b/include/sdbus-c++/IProxy.h index 88eb1b82..46c7cbe2 100644 --- a/include/sdbus-c++/IProxy.h +++ b/include/sdbus-c++/IProxy.h @@ -35,6 +35,7 @@ #include #include #include +#include // Forward declarations namespace sdbus { @@ -358,7 +359,7 @@ namespace sdbus { /*! * @copydoc IProxy::getProperty(const PropertyName&) */ - [[nodiscard]] PropertyGetter getProperty(const std::string& propertyName); + [[nodiscard]] PropertyGetter getProperty(std::string_view propertyName); /*! * @brief Gets value of a property of the D-Bus object asynchronously @@ -610,11 +611,9 @@ namespace sdbus { return PropertyGetter(*this, propertyName); } - inline PropertyGetter IProxy::getProperty(const std::string& propertyName) + inline PropertyGetter IProxy::getProperty(std::string_view propertyName) { - // Down-cast through static cast for performance reasons (no extra copy and object construction needed) - static_assert(sizeof(propertyName) == sizeof(PropertyName)); - return getProperty(static_cast(propertyName)); + return PropertyGetter(*this, propertyName); } inline AsyncPropertyGetter IProxy::getPropertyAsync(const PropertyName& propertyName) diff --git a/include/sdbus-c++/StandardInterfaces.h b/include/sdbus-c++/StandardInterfaces.h index c1ffa504..a4217d0e 100644 --- a/include/sdbus-c++/StandardInterfaces.h +++ b/include/sdbus-c++/StandardInterfaces.h @@ -150,8 +150,7 @@ namespace sdbus { return proxy_->getProperty(propertyName).onInterface(interfaceName); } - // TODO: Refactor from std::string to std::string_view before release/v2.0 !!! - sdbus::Variant Get(const InterfaceName& interfaceName, const std::string& propertyName) + sdbus::Variant Get(const InterfaceName& interfaceName, std::string_view propertyName) { return proxy_->getProperty(propertyName).onInterface(interfaceName); } From a3c9f97a73689708cb3755cee737a35408c01f4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Tue, 16 Apr 2024 22:28:42 +0200 Subject: [PATCH 44/52] perf: provide also const char* overloads for convenience functions --- docs/using-sdbus-c++.md | 4 +- .../examplemanager-planet1-client-glue.h | 2 +- .../examplemanager-planet1-server-glue.h | 2 +- .../obj-manager-client.cpp | 2 +- .../obj-manager-server.cpp | 4 +- include/sdbus-c++/ConvenienceApiClasses.h | 93 +++++++++++++------ include/sdbus-c++/ConvenienceApiClasses.inl | 80 +++++++++++----- include/sdbus-c++/IObject.h | 47 ++++++---- include/sdbus-c++/IProxy.h | 82 +++++++++++----- include/sdbus-c++/StandardInterfaces.h | 53 ++++++++--- src/Connection.cpp | 58 ++++++++---- src/Connection.h | 18 +++- src/IConnection.h | 18 +++- src/Object.cpp | 25 ++++- src/Object.h | 3 + src/Proxy.cpp | 37 ++++++-- src/Proxy.h | 8 ++ src/Utils.h | 16 ++-- .../DBusStandardInterfacesTests.cpp | 4 +- .../integrationtests-adaptor.h | 2 +- .../integrationtests/integrationtests-proxy.h | 2 +- tests/perftests/perftests-adaptor.h | 2 +- tests/perftests/perftests-proxy.h | 2 +- .../stresstests/celsius-thermometer-adaptor.h | 2 +- tests/stresstests/celsius-thermometer-proxy.h | 2 +- tests/stresstests/concatenator-adaptor.h | 2 +- tests/stresstests/concatenator-proxy.h | 2 +- .../fahrenheit-thermometer-adaptor.h | 4 +- .../fahrenheit-thermometer-proxy.h | 4 +- tools/xml2cpp-codegen/AdaptorGenerator.cpp | 2 +- tools/xml2cpp-codegen/ProxyGenerator.cpp | 2 +- 31 files changed, 401 insertions(+), 183 deletions(-) diff --git a/docs/using-sdbus-c++.md b/docs/using-sdbus-c++.md index 1cde2cf1..0771d73b 100644 --- a/docs/using-sdbus-c++.md +++ b/docs/using-sdbus-c++.md @@ -714,7 +714,7 @@ namespace sdbuscpp { class Concatenator_adaptor { public: - static inline const sdbus::InterfaceName INTERFACE_NAME{"org.sdbuscpp.Concatenator"}; + static constexpr const char* INTERFACE_NAME = "org.sdbuscpp.Concatenator"; protected: Concatenator_adaptor(sdbus::IObject& object) @@ -773,7 +773,7 @@ namespace sdbuscpp { class Concatenator_proxy { public: - static inline const sdbus::InterfaceName INTERFACE_NAME{"org.sdbuscpp.Concatenator"}; + static constexpr const char* INTERFACE_NAME = "org.sdbuscpp.Concatenator"; protected: Concatenator_proxy(sdbus::IProxy& proxy) diff --git a/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-client-glue.h b/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-client-glue.h index 4b6847e6..b312656e 100644 --- a/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-client-glue.h +++ b/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-client-glue.h @@ -17,7 +17,7 @@ namespace ExampleManager { class Planet1_proxy { public: - static inline const sdbus::InterfaceName INTERFACE_NAME{"org.sdbuscpp.ExampleManager.Planet1"}; + static constexpr const char* INTERFACE_NAME = "org.sdbuscpp.ExampleManager.Planet1"; protected: Planet1_proxy(sdbus::IProxy& proxy) diff --git a/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-server-glue.h b/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-server-glue.h index 7acfac01..b2ca89ce 100644 --- a/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-server-glue.h +++ b/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-server-glue.h @@ -17,7 +17,7 @@ namespace ExampleManager { class Planet1_adaptor { public: - static inline const sdbus::InterfaceName INTERFACE_NAME{"org.sdbuscpp.ExampleManager.Planet1"}; + static constexpr const char* INTERFACE_NAME = "org.sdbuscpp.ExampleManager.Planet1"; protected: Planet1_adaptor(sdbus::IObject& object) diff --git a/examples/org.freedesktop.DBus.ObjectManager/obj-manager-client.cpp b/examples/org.freedesktop.DBus.ObjectManager/obj-manager-client.cpp index be6f1b2c..fb2d8a30 100644 --- a/examples/org.freedesktop.DBus.ObjectManager/obj-manager-client.cpp +++ b/examples/org.freedesktop.DBus.ObjectManager/obj-manager-client.cpp @@ -64,7 +64,7 @@ class ManagerProxy final : public sdbus::ProxyInterfaces vtable); void forInterface(InterfaceName interfaceName); void forInterface(std::string interfaceName); [[nodiscard]] Slot forInterface(InterfaceName interfaceName, return_slot_t); [[nodiscard]] Slot forInterface(std::string interfaceName, return_slot_t); + private: + friend IObject; + VTableAdder(IObject& object, std::vector vtable); + private: IObject& object_; std::vector vtable_; @@ -67,16 +70,22 @@ namespace sdbus { class SignalEmitter { public: - SignalEmitter(IObject& object, const SignalName& signalName); - SignalEmitter(SignalEmitter&& other) = default; - ~SignalEmitter() noexcept(false); SignalEmitter& onInterface(const InterfaceName& interfaceName); SignalEmitter& onInterface(const std::string& interfaceName); + SignalEmitter& onInterface(const char* interfaceName); template void withArguments(_Args&&... args); + SignalEmitter(SignalEmitter&& other) = default; + ~SignalEmitter() noexcept(false); + + private: + friend IObject; + SignalEmitter(IObject& object, const SignalName& signalName); + SignalEmitter(IObject& object, const char* signalName); + private: IObject& object_; - const SignalName& signalName_; + const char* signalName_; Signal signal_; int exceptions_{}; // Number of active exceptions when SignalEmitter is constructed }; @@ -84,23 +93,27 @@ namespace sdbus { class MethodInvoker { public: - MethodInvoker(IProxy& proxy, const MethodName& methodName); - MethodInvoker(MethodInvoker&& other) = default; - ~MethodInvoker() noexcept(false); - MethodInvoker& onInterface(const InterfaceName& interfaceName); MethodInvoker& onInterface(const std::string& interfaceName); + MethodInvoker& onInterface(const char* interfaceName); MethodInvoker& withTimeout(uint64_t usec); template MethodInvoker& withTimeout(const std::chrono::duration<_Rep, _Period>& timeout); template MethodInvoker& withArguments(_Args&&... args); template void storeResultsTo(_Args&... args); - void dontExpectReply(); + MethodInvoker(MethodInvoker&& other) = default; + ~MethodInvoker() noexcept(false); + + private: + friend IProxy; + MethodInvoker(IProxy& proxy, const MethodName& methodName); + MethodInvoker(IProxy& proxy, const char* methodName); + private: IProxy& proxy_; - const MethodName& methodName_; + const char* methodName_; uint64_t timeout_{}; MethodCall method_; int exceptions_{}; // Number of active exceptions when MethodInvoker is constructed @@ -110,9 +123,9 @@ namespace sdbus { class AsyncMethodInvoker { public: - AsyncMethodInvoker(IProxy& proxy, const MethodName& methodName); AsyncMethodInvoker& onInterface(const InterfaceName& interfaceName); AsyncMethodInvoker& onInterface(const std::string& interfaceName); + AsyncMethodInvoker& onInterface(const char* interfaceName); AsyncMethodInvoker& withTimeout(uint64_t usec); template AsyncMethodInvoker& withTimeout(const std::chrono::duration<_Rep, _Period>& timeout); @@ -123,9 +136,14 @@ namespace sdbus { // or std::future> for multiple method return values template std::future> getResultAsFuture(); + private: + friend IProxy; + AsyncMethodInvoker(IProxy& proxy, const MethodName& methodName); + AsyncMethodInvoker(IProxy& proxy, const char* methodName); + private: IProxy& proxy_; - const MethodName& methodName_; + const char* methodName_; uint64_t timeout_{}; MethodCall method_; }; @@ -133,29 +151,34 @@ namespace sdbus { class SignalSubscriber { public: - SignalSubscriber(IProxy& proxy, const SignalName& signalName); - SignalSubscriber& onInterface(InterfaceName interfaceName); // TODO: This could be const char* - SignalSubscriber& onInterface(std::string interfaceName); // TODO: This could be const char* + SignalSubscriber& onInterface(const InterfaceName& interfaceName); + SignalSubscriber& onInterface(const std::string& interfaceName); + SignalSubscriber& onInterface(const char* interfaceName); template void call(_Function&& callback); template [[nodiscard]] Slot call(_Function&& callback, return_slot_t); private: + friend IProxy; + SignalSubscriber(IProxy& proxy, const SignalName& signalName); + SignalSubscriber(IProxy& proxy, const char* signalName); template signal_handler makeSignalHandler(_Function&& callback); private: IProxy& proxy_; - const SignalName& signalName_; - InterfaceName interfaceName_; + const char* signalName_; + const char* interfaceName_{}; }; class PropertyGetter { public: - PropertyGetter(IProxy& proxy, std::string_view propertyName); Variant onInterface(std::string_view interfaceName); private: - static inline const InterfaceName DBUS_PROPERTIES_INTERFACE_NAME{"org.freedesktop.DBus.Properties"}; + friend IProxy; + PropertyGetter(IProxy& proxy, std::string_view propertyName); + + static constexpr const char* DBUS_PROPERTIES_INTERFACE_NAME = "org.freedesktop.DBus.Properties"; private: IProxy& proxy_; @@ -165,13 +188,15 @@ namespace sdbus { class AsyncPropertyGetter { public: - AsyncPropertyGetter(IProxy& proxy, std::string_view propertyName); AsyncPropertyGetter& onInterface(std::string_view interfaceName); template PendingAsyncCall uponReplyInvoke(_Function&& callback); std::future getResultAsFuture(); private: - static inline const InterfaceName DBUS_PROPERTIES_INTERFACE_NAME{"org.freedesktop.DBus.Properties"}; + friend IProxy; + AsyncPropertyGetter(IProxy& proxy, std::string_view propertyName); + + static constexpr const char* DBUS_PROPERTIES_INTERFACE_NAME = "org.freedesktop.DBus.Properties"; private: IProxy& proxy_; @@ -182,7 +207,6 @@ namespace sdbus { class PropertySetter { public: - PropertySetter(IProxy& proxy, std::string_view propertyName); PropertySetter& onInterface(std::string_view interfaceName); template void toValue(const _Value& value); template void toValue(const _Value& value, dont_expect_reply_t); @@ -190,7 +214,10 @@ namespace sdbus { void toValue(const Variant& value, dont_expect_reply_t); private: - static inline const InterfaceName DBUS_PROPERTIES_INTERFACE_NAME{"org.freedesktop.DBus.Properties"}; + friend IProxy; + PropertySetter(IProxy& proxy, std::string_view propertyName); + + static constexpr const char* DBUS_PROPERTIES_INTERFACE_NAME = "org.freedesktop.DBus.Properties"; private: IProxy& proxy_; @@ -201,7 +228,6 @@ namespace sdbus { class AsyncPropertySetter { public: - AsyncPropertySetter(IProxy& proxy, std::string_view propertyName); AsyncPropertySetter& onInterface(std::string_view interfaceName); template AsyncPropertySetter& toValue(_Value&& value); AsyncPropertySetter& toValue(Variant value); @@ -209,7 +235,10 @@ namespace sdbus { std::future getResultAsFuture(); private: - static inline const InterfaceName DBUS_PROPERTIES_INTERFACE_NAME{"org.freedesktop.DBus.Properties"}; + friend IProxy; + AsyncPropertySetter(IProxy& proxy, std::string_view propertyName); + + static constexpr const char* DBUS_PROPERTIES_INTERFACE_NAME = "org.freedesktop.DBus.Properties"; private: IProxy& proxy_; @@ -221,11 +250,13 @@ namespace sdbus { class AllPropertiesGetter { public: - AllPropertiesGetter(IProxy& proxy); std::map onInterface(std::string_view interfaceName); private: - static inline const InterfaceName DBUS_PROPERTIES_INTERFACE_NAME{"org.freedesktop.DBus.Properties"}; + friend IProxy; + AllPropertiesGetter(IProxy& proxy); + + static constexpr const char* DBUS_PROPERTIES_INTERFACE_NAME = "org.freedesktop.DBus.Properties"; private: IProxy& proxy_; @@ -234,13 +265,15 @@ namespace sdbus { class AsyncAllPropertiesGetter { public: - AsyncAllPropertiesGetter(IProxy& proxy); AsyncAllPropertiesGetter& onInterface(std::string_view interfaceName); template PendingAsyncCall uponReplyInvoke(_Function&& callback); std::future> getResultAsFuture(); private: - static inline const InterfaceName DBUS_PROPERTIES_INTERFACE_NAME{"org.freedesktop.DBus.Properties"}; // TODO: Couldn't this be const char*? + friend IProxy; + AsyncAllPropertiesGetter(IProxy& proxy); + + static constexpr const char* DBUS_PROPERTIES_INTERFACE_NAME = "org.freedesktop.DBus.Properties"; private: IProxy& proxy_; diff --git a/include/sdbus-c++/ConvenienceApiClasses.inl b/include/sdbus-c++/ConvenienceApiClasses.inl index fb679733..93ea4a1e 100644 --- a/include/sdbus-c++/ConvenienceApiClasses.inl +++ b/include/sdbus-c++/ConvenienceApiClasses.inl @@ -78,6 +78,11 @@ namespace sdbus { /*** ------------- ***/ inline SignalEmitter::SignalEmitter(IObject& object, const SignalName& signalName) + : SignalEmitter(object, signalName.c_str()) + { + } + + inline SignalEmitter::SignalEmitter(IObject& object, const char* signalName) : object_(object) , signalName_(signalName) , exceptions_(std::uncaught_exceptions()) @@ -104,16 +109,19 @@ namespace sdbus { inline SignalEmitter& SignalEmitter::onInterface(const InterfaceName& interfaceName) { - signal_ = object_.createSignal(interfaceName, signalName_); - - return *this; + return onInterface(interfaceName.c_str()); } inline SignalEmitter& SignalEmitter::onInterface(const std::string& interfaceName) { - // Down-cast through static cast for performance reasons (no extra copy and object construction needed) - static_assert(sizeof(interfaceName) == sizeof(InterfaceName)); - return onInterface(static_cast(interfaceName)); + return onInterface(interfaceName.c_str()); + } + + inline SignalEmitter& SignalEmitter::onInterface(const char* interfaceName) + { + signal_ = object_.createSignal(interfaceName, signalName_); + + return *this; } template @@ -129,6 +137,11 @@ namespace sdbus { /*** ------------- ***/ inline MethodInvoker::MethodInvoker(IProxy& proxy, const MethodName& methodName) + : MethodInvoker(proxy, methodName.c_str()) + { + } + + inline MethodInvoker::MethodInvoker(IProxy& proxy, const char* methodName) : proxy_(proxy) , methodName_(methodName) , exceptions_(std::uncaught_exceptions()) @@ -156,16 +169,19 @@ namespace sdbus { inline MethodInvoker& MethodInvoker::onInterface(const InterfaceName& interfaceName) { - method_ = proxy_.createMethodCall(interfaceName, methodName_); - - return *this; + return onInterface(interfaceName.c_str()); } inline MethodInvoker& MethodInvoker::onInterface(const std::string& interfaceName) { - // Down-cast through static cast for performance reasons (no extra copy and object construction needed) - static_assert(sizeof(interfaceName) == sizeof(InterfaceName)); - return onInterface(static_cast(interfaceName)); + return onInterface(interfaceName.c_str()); + } + + inline MethodInvoker& MethodInvoker::onInterface(const char* interfaceName) + { + method_ = proxy_.createMethodCall(interfaceName, methodName_); + + return *this; } inline MethodInvoker& MethodInvoker::withTimeout(uint64_t usec) @@ -215,6 +231,11 @@ namespace sdbus { /*** ------------------ ***/ inline AsyncMethodInvoker::AsyncMethodInvoker(IProxy& proxy, const MethodName& methodName) + : AsyncMethodInvoker(proxy, methodName.c_str()) + { + } + + inline AsyncMethodInvoker::AsyncMethodInvoker(IProxy& proxy, const char* methodName) : proxy_(proxy) , methodName_(methodName) { @@ -222,16 +243,19 @@ namespace sdbus { inline AsyncMethodInvoker& AsyncMethodInvoker::onInterface(const InterfaceName& interfaceName) { - method_ = proxy_.createMethodCall(interfaceName, methodName_); - - return *this; + return onInterface(interfaceName.c_str()); } inline AsyncMethodInvoker& AsyncMethodInvoker::onInterface(const std::string& interfaceName) { - // Down-cast through static cast for performance reasons (no extra copy and object construction needed) - static_assert(sizeof(interfaceName) == sizeof(InterfaceName)); - return onInterface(static_cast(interfaceName)); + return onInterface(interfaceName.c_str()); + } + + inline AsyncMethodInvoker& AsyncMethodInvoker::onInterface(const char* interfaceName) + { + method_ = proxy_.createMethodCall(interfaceName, methodName_); + + return *this; } inline AsyncMethodInvoker& AsyncMethodInvoker::withTimeout(uint64_t usec) @@ -320,17 +344,27 @@ namespace sdbus { /*** ---------------- ***/ inline SignalSubscriber::SignalSubscriber(IProxy& proxy, const SignalName& signalName) + : SignalSubscriber(proxy, signalName.c_str()) + { + } + + inline SignalSubscriber::SignalSubscriber(IProxy& proxy, const char* signalName) : proxy_(proxy) , signalName_(signalName) { } - inline SignalSubscriber& SignalSubscriber::onInterface(std::string interfaceName) + inline SignalSubscriber& SignalSubscriber::onInterface(const InterfaceName& interfaceName) { - return onInterface(InterfaceName{std::move(interfaceName)}); + return onInterface(interfaceName.c_str()); } - inline SignalSubscriber& SignalSubscriber::onInterface(InterfaceName interfaceName) + inline SignalSubscriber& SignalSubscriber::onInterface(const std::string& interfaceName) + { + return onInterface(interfaceName.c_str()); + } + + inline SignalSubscriber& SignalSubscriber::onInterface(const char* interfaceName) { interfaceName_ = std::move(interfaceName); @@ -340,7 +374,7 @@ namespace sdbus { template inline void SignalSubscriber::call(_Function&& callback) { - assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function + assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function proxy_.registerSignalHandler( interfaceName_ , signalName_ @@ -350,7 +384,7 @@ namespace sdbus { template [[nodiscard]] inline Slot SignalSubscriber::call(_Function&& callback, return_slot_t) { - assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function + assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function return proxy_.registerSignalHandler( interfaceName_ , signalName_ diff --git a/include/sdbus-c++/IObject.h b/include/sdbus-c++/IObject.h index 97b31475..2606c879 100644 --- a/include/sdbus-c++/IObject.h +++ b/include/sdbus-c++/IObject.h @@ -225,26 +225,15 @@ namespace sdbus { [[nodiscard]] SignalEmitter emitSignal(const SignalName& signalName); /*! - * @brief Emits signal on D-Bus - * - * @param[in] signalName Name of the signal - * @return A helper object for convenient emission of signals - * - * This is a high-level, convenience way of emitting D-Bus signals that abstracts - * from the D-Bus message concept. Signal arguments are automatically serialized - * in a message and D-Bus signatures automatically deduced from the provided native arguments. - * - * Example of use: - * @code - * int arg1 = ...; - * double arg2 = ...; - * object_.emitSignal("fooSignal").onInterface("com.kistler.foo").withArguments(arg1, arg2); - * @endcode - * - * @throws sdbus::Error in case of failure + * @copydoc IObject::emitSignal(const SignalName&) */ [[nodiscard]] SignalEmitter emitSignal(const std::string& signalName); + /*! + * @copydoc IObject::emitSignal(const SignalName&) + */ + [[nodiscard]] SignalEmitter emitSignal(const char* signalName); + /*! * @brief Emits PropertyChanged signal for specified properties under a given interface of this object path * @@ -255,6 +244,11 @@ namespace sdbus { */ virtual void emitPropertiesChangedSignal(const InterfaceName& interfaceName, const std::vector& propNames) = 0; + /*! + * @copydoc IObject::emitPropertiesChangedSignal(const InterfaceName&,const std::vector&) + */ + virtual void emitPropertiesChangedSignal(const char* interfaceName, const std::vector& propNames) = 0; + /*! * @brief Emits PropertyChanged signal for all properties on a given interface of this object path * @@ -264,6 +258,11 @@ namespace sdbus { */ virtual void emitPropertiesChangedSignal(const InterfaceName& interfaceName) = 0; + /*! + * @copydoc IObject::emitPropertiesChangedSignal(const InterfaceName&) + */ + virtual void emitPropertiesChangedSignal(const char* interfaceName) = 0; + /*! * @brief Emits InterfacesAdded signal on this object path * @@ -361,6 +360,11 @@ namespace sdbus { * @return Currently processed D-Bus message */ [[nodiscard]] virtual Message getCurrentlyProcessedMessage() const = 0; + + protected: + friend SignalEmitter; + + [[nodiscard]] virtual Signal createSignal(const char* interfaceName, const char* signalName) = 0; }; // Out-of-line member definitions @@ -372,9 +376,12 @@ namespace sdbus { inline SignalEmitter IObject::emitSignal(const std::string& signalName) { - // Down-cast through static cast for performance reasons (no extra copy and object construction needed) - static_assert(sizeof(signalName) == sizeof(SignalName)); - return emitSignal(static_cast(signalName)); + return SignalEmitter(*this, signalName.c_str()); + } + + inline SignalEmitter IObject::emitSignal(const char* signalName) + { + return SignalEmitter(*this, signalName); } template diff --git a/include/sdbus-c++/IProxy.h b/include/sdbus-c++/IProxy.h index 46c7cbe2..8f0d0bdb 100644 --- a/include/sdbus-c++/IProxy.h +++ b/include/sdbus-c++/IProxy.h @@ -226,6 +226,11 @@ namespace sdbus { */ [[nodiscard]] MethodInvoker callMethod(const std::string& methodName); + /*! + * @copydoc IProxy::callMethod(const MethodName&) + */ + [[nodiscard]] MethodInvoker callMethod(const char* methodName); + /*! * @brief Calls method on the D-Bus object asynchronously * @@ -256,6 +261,11 @@ namespace sdbus { */ [[nodiscard]] AsyncMethodInvoker callMethodAsync(const std::string& methodName); + /*! + * @copydoc IProxy::callMethodAsync(const MethodName&) + */ + [[nodiscard]] AsyncMethodInvoker callMethodAsync(const char* methodName); + /*! * @brief Registers a handler for the desired signal emitted by the D-Bus object * @@ -323,6 +333,11 @@ namespace sdbus { */ [[nodiscard]] SignalSubscriber uponSignal(const std::string& signalName); + /*! + * @copydoc IProxy::uponSignal(const SignalName&) + */ + [[nodiscard]] SignalSubscriber uponSignal(const char* signalName); + /*! * @brief Unregisters proxy's signal handlers and stops receiving replies to pending async calls * @@ -384,7 +399,7 @@ namespace sdbus { /*! * @copydoc IProxy::getPropertyAsync(const PropertyName&) */ - [[nodiscard]] AsyncPropertyGetter getPropertyAsync(const std::string& propertyName); + [[nodiscard]] AsyncPropertyGetter getPropertyAsync(std::string_view propertyName); /*! * @brief Sets value of a property of the D-Bus object @@ -411,7 +426,7 @@ namespace sdbus { /*! * @copydoc IProxy::setProperty(const PropertyName&) */ - [[nodiscard]] PropertySetter setProperty(const std::string& propertyName); + [[nodiscard]] PropertySetter setProperty(std::string_view propertyName); /*! * @brief Sets value of a property of the D-Bus object asynchronously @@ -436,7 +451,7 @@ namespace sdbus { /*! * @copydoc IProxy::setPropertyAsync(const PropertyName&) */ - [[nodiscard]] AsyncPropertySetter setPropertyAsync(const std::string& propertyName); + [[nodiscard]] AsyncPropertySetter setPropertyAsync(std::string_view propertyName); /*! * @brief Gets values of all properties of the D-Bus object @@ -499,6 +514,20 @@ namespace sdbus { * @return Currently processed D-Bus message */ [[nodiscard]] virtual Message getCurrentlyProcessedMessage() const = 0; + + protected: + friend MethodInvoker; + friend AsyncMethodInvoker; + friend SignalSubscriber; + + [[nodiscard]] virtual MethodCall createMethodCall(const char* interfaceName, const char* methodName) = 0; + virtual void registerSignalHandler( const char* interfaceName + , const char* signalName + , signal_handler signalHandler ) = 0; + [[nodiscard]] virtual Slot registerSignalHandler( const char* interfaceName + , const char* signalName + , signal_handler signalHandler + , return_slot_t ) = 0; }; /********************************************//** @@ -577,9 +606,12 @@ namespace sdbus { inline MethodInvoker IProxy::callMethod(const std::string& methodName) { - // Down-cast through static cast for performance reasons (no extra copy and object construction needed) - static_assert(sizeof(methodName) == sizeof(MethodName)); - return callMethod(static_cast(methodName)); + return MethodInvoker(*this, methodName.c_str()); + } + + inline MethodInvoker IProxy::callMethod(const char* methodName) + { + return MethodInvoker(*this, methodName); } inline AsyncMethodInvoker IProxy::callMethodAsync(const MethodName& methodName) @@ -589,9 +621,12 @@ namespace sdbus { inline AsyncMethodInvoker IProxy::callMethodAsync(const std::string& methodName) { - // Down-cast through static cast for performance reasons (no extra copy and object construction needed) - static_assert(sizeof(methodName) == sizeof(MethodName)); - return callMethodAsync(static_cast(methodName)); + return AsyncMethodInvoker(*this, methodName.c_str()); + } + + inline AsyncMethodInvoker IProxy::callMethodAsync(const char* methodName) + { + return AsyncMethodInvoker(*this, methodName); } inline SignalSubscriber IProxy::uponSignal(const SignalName& signalName) @@ -601,9 +636,12 @@ namespace sdbus { inline SignalSubscriber IProxy::uponSignal(const std::string& signalName) { - // Down-cast through static cast for performance reasons (no extra copy and object construction needed) - static_assert(sizeof(signalName) == sizeof(SignalName)); - return uponSignal(static_cast(signalName)); + return SignalSubscriber(*this, signalName.c_str()); + } + + inline SignalSubscriber IProxy::uponSignal(const char* signalName) + { + return SignalSubscriber(*this, signalName); } inline PropertyGetter IProxy::getProperty(const PropertyName& propertyName) @@ -613,7 +651,7 @@ namespace sdbus { inline PropertyGetter IProxy::getProperty(std::string_view propertyName) { - return PropertyGetter(*this, propertyName); + return PropertyGetter(*this, std::move(propertyName)); } inline AsyncPropertyGetter IProxy::getPropertyAsync(const PropertyName& propertyName) @@ -621,11 +659,9 @@ namespace sdbus { return AsyncPropertyGetter(*this, propertyName); } - inline AsyncPropertyGetter IProxy::getPropertyAsync(const std::string& propertyName) + inline AsyncPropertyGetter IProxy::getPropertyAsync(std::string_view propertyName) { - // Down-cast through static cast for performance reasons (no extra copy and object construction needed) - static_assert(sizeof(propertyName) == sizeof(PropertyName)); - return getPropertyAsync(static_cast(propertyName)); + return AsyncPropertyGetter(*this, std::move(propertyName)); } inline PropertySetter IProxy::setProperty(const PropertyName& propertyName) @@ -633,11 +669,9 @@ namespace sdbus { return PropertySetter(*this, propertyName); } - inline PropertySetter IProxy::setProperty(const std::string& propertyName) + inline PropertySetter IProxy::setProperty(std::string_view propertyName) { - // Down-cast through static cast for performance reasons (no extra copy and object construction needed) - static_assert(sizeof(propertyName) == sizeof(PropertyName)); - return setProperty(static_cast(propertyName)); + return PropertySetter(*this, std::move(propertyName)); } inline AsyncPropertySetter IProxy::setPropertyAsync(const PropertyName& propertyName) @@ -645,11 +679,9 @@ namespace sdbus { return AsyncPropertySetter(*this, propertyName); } - inline AsyncPropertySetter IProxy::setPropertyAsync(const std::string& propertyName) + inline AsyncPropertySetter IProxy::setPropertyAsync(std::string_view propertyName) { - // Down-cast through static cast for performance reasons (no extra copy and object construction needed) - static_assert(sizeof(propertyName) == sizeof(PropertyName)); - return setPropertyAsync(static_cast(propertyName)); + return AsyncPropertySetter(*this, std::move(propertyName)); } inline AllPropertiesGetter IProxy::getAllProperties() diff --git a/include/sdbus-c++/StandardInterfaces.h b/include/sdbus-c++/StandardInterfaces.h index a4217d0e..272a724f 100644 --- a/include/sdbus-c++/StandardInterfaces.h +++ b/include/sdbus-c++/StandardInterfaces.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -39,7 +40,7 @@ namespace sdbus { // Proxy for peer class Peer_proxy { - static inline const InterfaceName INTERFACE_NAME{"org.freedesktop.DBus.Peer"}; + static inline const char* INTERFACE_NAME = "org.freedesktop.DBus.Peer"; protected: Peer_proxy(sdbus::IProxy& proxy) @@ -78,7 +79,7 @@ namespace sdbus { // Proxy for introspection class Introspectable_proxy { - static inline const InterfaceName INTERFACE_NAME{"org.freedesktop.DBus.Introspectable"}; + static inline const char* INTERFACE_NAME = "org.freedesktop.DBus.Introspectable"; protected: Introspectable_proxy(sdbus::IProxy& proxy) @@ -112,7 +113,7 @@ namespace sdbus { // Proxy for properties class Properties_proxy { - static inline const InterfaceName INTERFACE_NAME{"org.freedesktop.DBus.Properties"}; + static inline const char* INTERFACE_NAME = "org.freedesktop.DBus.Properties"; protected: Properties_proxy(sdbus::IProxy& proxy) @@ -150,7 +151,7 @@ namespace sdbus { return proxy_->getProperty(propertyName).onInterface(interfaceName); } - sdbus::Variant Get(const InterfaceName& interfaceName, std::string_view propertyName) + sdbus::Variant Get(std::string_view interfaceName, std::string_view propertyName) { return proxy_->getProperty(propertyName).onInterface(interfaceName); } @@ -162,7 +163,7 @@ namespace sdbus { } template - PendingAsyncCall GetAsync(const InterfaceName& interfaceName, const std::string& propertyName, _Function&& callback) + PendingAsyncCall GetAsync(std::string_view interfaceName, std::string_view propertyName, _Function&& callback) { return proxy_->getPropertyAsync(propertyName).onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback)); } @@ -172,7 +173,7 @@ namespace sdbus { return proxy_->getPropertyAsync(propertyName).onInterface(interfaceName).getResultAsFuture(); } - std::future GetAsync(const InterfaceName& interfaceName, const std::string& propertyName, with_future_t) + std::future GetAsync(std::string_view interfaceName, std::string_view propertyName, with_future_t) { return proxy_->getPropertyAsync(propertyName).onInterface(interfaceName).getResultAsFuture(); } @@ -182,7 +183,7 @@ namespace sdbus { proxy_->setProperty(propertyName).onInterface(interfaceName).toValue(value); } - void Set(const InterfaceName& interfaceName, const std::string& propertyName, const sdbus::Variant& value) + void Set(std::string_view interfaceName, const std::string_view propertyName, const sdbus::Variant& value) { proxy_->setProperty(propertyName).onInterface(interfaceName).toValue(value); } @@ -192,7 +193,7 @@ namespace sdbus { proxy_->setProperty(propertyName).onInterface(interfaceName).toValue(value, dont_expect_reply); } - void Set(const InterfaceName& interfaceName, const std::string& propertyName, const sdbus::Variant& value, dont_expect_reply_t) + void Set(std::string_view interfaceName, const std::string_view propertyName, const sdbus::Variant& value, dont_expect_reply_t) { proxy_->setProperty(propertyName).onInterface(interfaceName).toValue(value, dont_expect_reply); } @@ -204,7 +205,7 @@ namespace sdbus { } template - PendingAsyncCall SetAsync(const InterfaceName& interfaceName, const std::string& propertyName, const sdbus::Variant& value, _Function&& callback) + PendingAsyncCall SetAsync(std::string_view interfaceName, std::string_view propertyName, const sdbus::Variant& value, _Function&& callback) { return proxy_->setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).uponReplyInvoke(std::forward<_Function>(callback)); } @@ -214,7 +215,7 @@ namespace sdbus { return proxy_->setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).getResultAsFuture(); } - std::future SetAsync(const InterfaceName& interfaceName, const std::string& propertyName, const sdbus::Variant& value, with_future_t) + std::future SetAsync(std::string_view interfaceName, std::string_view propertyName, const sdbus::Variant& value, with_future_t) { return proxy_->setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).getResultAsFuture(); } @@ -224,17 +225,33 @@ namespace sdbus { return proxy_->getAllProperties().onInterface(interfaceName); } + std::map GetAll(std::string_view interfaceName) + { + return proxy_->getAllProperties().onInterface(interfaceName); + } + template PendingAsyncCall GetAllAsync(const InterfaceName& interfaceName, _Function&& callback) { return proxy_->getAllPropertiesAsync().onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback)); } + template + PendingAsyncCall GetAllAsync(std::string_view interfaceName, _Function&& callback) + { + return proxy_->getAllPropertiesAsync().onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback)); + } + std::future> GetAllAsync(const InterfaceName& interfaceName, with_future_t) { return proxy_->getAllPropertiesAsync().onInterface(interfaceName).getResultAsFuture(); } + std::future> GetAllAsync(std::string_view interfaceName, with_future_t) + { + return proxy_->getAllPropertiesAsync().onInterface(interfaceName).getResultAsFuture(); + } + private: sdbus::IProxy* proxy_; }; @@ -242,7 +259,7 @@ namespace sdbus { // Proxy for object manager class ObjectManager_proxy { - static inline const InterfaceName INTERFACE_NAME{"org.freedesktop.DBus.ObjectManager"}; + static inline const char* INTERFACE_NAME = "org.freedesktop.DBus.ObjectManager"; protected: ObjectManager_proxy(sdbus::IProxy& proxy) @@ -301,7 +318,7 @@ namespace sdbus { // Adaptor for properties class Properties_adaptor { - static inline const InterfaceName INTERFACE_NAME{"org.freedesktop.DBus.Properties"}; + static inline const char* INTERFACE_NAME = "org.freedesktop.DBus.Properties"; protected: Properties_adaptor(sdbus::IObject& object) : object_(&object) @@ -325,11 +342,21 @@ namespace sdbus { object_->emitPropertiesChangedSignal(interfaceName, properties); } + void emitPropertiesChangedSignal(const char* interfaceName, const std::vector& properties) + { + object_->emitPropertiesChangedSignal(interfaceName, properties); + } + void emitPropertiesChangedSignal(const InterfaceName& interfaceName) { object_->emitPropertiesChangedSignal(interfaceName); } + void emitPropertiesChangedSignal(const char* interfaceName) + { + object_->emitPropertiesChangedSignal(interfaceName); + } + private: sdbus::IObject* object_; }; @@ -346,7 +373,7 @@ namespace sdbus { */ class ObjectManager_adaptor { - static inline const InterfaceName INTERFACE_NAME{"org.freedesktop.DBus.ObjectManager"}; + static inline const char* INTERFACE_NAME = "org.freedesktop.DBus.ObjectManager"; protected: explicit ObjectManager_adaptor(sdbus::IObject& object) : object_(&object) diff --git a/src/Connection.cpp b/src/Connection.cpp index 1353fbf4..34819245 100644 --- a/src/Connection.cpp +++ b/src/Connection.cpp @@ -111,7 +111,7 @@ Connection::~Connection() void Connection::requestName(const ServiceName& name) { - SDBUS_CHECK_SERVICE_NAME(name); + SDBUS_CHECK_SERVICE_NAME(name.c_str()); auto r = sdbus_->sd_bus_request_name(bus_.get(), name.c_str(), 0); SDBUS_THROW_ERROR_IF(r < 0, "Failed to request bus name", -r); @@ -491,15 +491,23 @@ MethodCall Connection::createMethodCall( const ServiceName& destination , const ObjectPath& objectPath , const InterfaceName& interfaceName , const MethodName& methodName ) const +{ + return Connection::createMethodCall(destination.c_str(), objectPath.c_str(), interfaceName.c_str(), methodName.c_str()); +} + +MethodCall Connection::createMethodCall( const char* destination + , const char* objectPath + , const char* interfaceName + , const char* methodName ) const { sd_bus_message *sdbusMsg{}; auto r = sdbus_->sd_bus_message_new_method_call( bus_.get() , &sdbusMsg - , destination.empty() ? nullptr : destination.c_str() - , objectPath.c_str() - , interfaceName.c_str() - , methodName.c_str() ); + , !*destination ? nullptr : destination + , objectPath + , interfaceName + , methodName); SDBUS_THROW_ERROR_IF(r < 0, "Failed to create method call", -r); @@ -509,14 +517,17 @@ MethodCall Connection::createMethodCall( const ServiceName& destination Signal Connection::createSignal( const ObjectPath& objectPath , const InterfaceName& interfaceName , const SignalName& signalName ) const +{ + return Connection::createSignal(objectPath.c_str(), interfaceName.c_str(), signalName.c_str()); +} + +Signal Connection::createSignal( const char* objectPath + , const char* interfaceName + , const char* signalName ) const { sd_bus_message *sdbusMsg{}; - auto r = sdbus_->sd_bus_message_new_signal( bus_.get() - , &sdbusMsg - , objectPath.c_str() - , interfaceName.c_str() - , signalName.c_str() ); + auto r = sdbus_->sd_bus_message_new_signal(bus_.get(), &sdbusMsg, objectPath, interfaceName, signalName); SDBUS_THROW_ERROR_IF(r < 0, "Failed to create signal", -r); @@ -566,12 +577,19 @@ Slot Connection::callMethod(const MethodCall& message, void* callback, void* use void Connection::emitPropertiesChangedSignal( const ObjectPath& objectPath , const InterfaceName& interfaceName , const std::vector& propNames ) +{ + Connection::emitPropertiesChangedSignal(objectPath.c_str(), interfaceName.c_str(), propNames); +} + +void Connection::emitPropertiesChangedSignal( const char* objectPath + , const char* interfaceName + , const std::vector& propNames ) { auto names = to_strv(propNames); auto r = sdbus_->sd_bus_emit_properties_changed_strv( bus_.get() - , objectPath.c_str() - , interfaceName.c_str() + , objectPath + , interfaceName , propNames.empty() ? nullptr : &names[0] ); SDBUS_THROW_ERROR_IF(r < 0, "Failed to emit PropertiesChanged signal", -r); @@ -615,10 +633,10 @@ void Connection::emitInterfacesRemovedSignal( const ObjectPath& objectPath SDBUS_THROW_ERROR_IF(r < 0, "Failed to emit InterfacesRemoved signal", -r); } -Slot Connection::registerSignalHandler( const ServiceName& sender - , const ObjectPath& objectPath - , const InterfaceName& interfaceName - , const SignalName& signalName +Slot Connection::registerSignalHandler( const char* sender + , const char* objectPath + , const char* interfaceName + , const char* signalName , sd_bus_message_handler_t callback , void* userData ) { @@ -626,10 +644,10 @@ Slot Connection::registerSignalHandler( const ServiceName& sender auto r = sdbus_->sd_bus_match_signal( bus_.get() , &slot - , !sender.empty() ? sender.c_str() : nullptr - , !objectPath.empty() ? objectPath.c_str() : nullptr - , !interfaceName.empty() ? interfaceName.c_str() : nullptr - , !signalName.empty() ? signalName.c_str() : nullptr + , !*sender ? nullptr : sender + , !*objectPath ? nullptr : objectPath + , !*interfaceName ? nullptr : interfaceName + , !*signalName ? nullptr : signalName , callback , userData ); diff --git a/src/Connection.h b/src/Connection.h index 1f83f31e..b991772c 100644 --- a/src/Connection.h +++ b/src/Connection.h @@ -130,9 +130,16 @@ namespace sdbus::internal { , const ObjectPath& objectPath , const InterfaceName& interfaceName , const MethodName& methodName ) const override; + [[nodiscard]] MethodCall createMethodCall( const char* destination + , const char* objectPath + , const char* interfaceName + , const char* methodName ) const override; [[nodiscard]] Signal createSignal( const ObjectPath& objectPath , const InterfaceName& interfaceName , const SignalName& signalName ) const override; + [[nodiscard]] Signal createSignal( const char* objectPath + , const char* interfaceName + , const char* signalName ) const override; MethodReply callMethod(const MethodCall& message, uint64_t timeout) override; void callMethod(const MethodCall& message, void* callback, void* userData, uint64_t timeout, floating_slot_t) override; @@ -141,6 +148,9 @@ namespace sdbus::internal { void emitPropertiesChangedSignal( const ObjectPath& objectPath , const InterfaceName& interfaceName , const std::vector& propNames ) override; + void emitPropertiesChangedSignal( const char* objectPath + , const char* interfaceName + , const std::vector& propNames ) override; void emitInterfacesAddedSignal(const ObjectPath& objectPath) override; void emitInterfacesAddedSignal( const ObjectPath& objectPath , const std::vector& interfaces ) override; @@ -148,10 +158,10 @@ namespace sdbus::internal { void emitInterfacesRemovedSignal( const ObjectPath& objectPath , const std::vector& interfaces ) override; - Slot registerSignalHandler( const ServiceName& sender - , const ObjectPath& objectPath - , const InterfaceName& interfaceName - , const SignalName& signalName + Slot registerSignalHandler( const char* sender + , const char* objectPath + , const char* interfaceName + , const char* signalName , sd_bus_message_handler_t callback , void* userData ) override; diff --git a/src/IConnection.h b/src/IConnection.h index dc1b1892..4b3185c4 100644 --- a/src/IConnection.h +++ b/src/IConnection.h @@ -77,9 +77,16 @@ namespace sdbus::internal { , const ObjectPath& objectPath , const InterfaceName& interfaceName , const MethodName& methodName ) const = 0; + [[nodiscard]] virtual MethodCall createMethodCall( const char* destination + , const char* objectPath + , const char* interfaceName + , const char* methodName ) const = 0; [[nodiscard]] virtual Signal createSignal( const ObjectPath& objectPath , const InterfaceName& interfaceName , const SignalName& signalName ) const = 0; + [[nodiscard]] virtual Signal createSignal( const char* objectPath + , const char* interfaceName + , const char* signalName ) const = 0; virtual MethodReply callMethod(const MethodCall& message, uint64_t timeout) = 0; virtual void callMethod(const MethodCall& message, void* callback, void* userData, uint64_t timeout, floating_slot_t) = 0; @@ -88,6 +95,9 @@ namespace sdbus::internal { virtual void emitPropertiesChangedSignal( const ObjectPath& objectPath , const InterfaceName& interfaceName , const std::vector& propNames ) = 0; + virtual void emitPropertiesChangedSignal( const char* objectPath + , const char* interfaceName + , const std::vector& propNames ) = 0; virtual void emitInterfacesAddedSignal(const ObjectPath& objectPath) = 0; virtual void emitInterfacesAddedSignal( const ObjectPath& objectPath , const std::vector& interfaces ) = 0; @@ -98,10 +108,10 @@ namespace sdbus::internal { using sdbus::IConnection::addObjectManager; [[nodiscard]] virtual Slot addObjectManager(const ObjectPath& objectPath, return_slot_t) = 0; - [[nodiscard]] virtual Slot registerSignalHandler( const ServiceName& sender - , const ObjectPath& objectPath - , const InterfaceName& interfaceName - , const SignalName& signalName + [[nodiscard]] virtual Slot registerSignalHandler( const char* sender + , const char* objectPath + , const char* interfaceName + , const char* signalName , sd_bus_message_handler_t callback , void* userData ) = 0; }; diff --git a/src/Object.cpp b/src/Object.cpp index fa90db70..70300c12 100644 --- a/src/Object.cpp +++ b/src/Object.cpp @@ -46,7 +46,7 @@ namespace sdbus::internal { Object::Object(sdbus::internal::IConnection& connection, ObjectPath objectPath) : connection_(connection), objectPath_(std::move(objectPath)) { - SDBUS_CHECK_OBJECT_PATH(objectPath_); + SDBUS_CHECK_OBJECT_PATH(objectPath_.c_str()); } void Object::addVTable(InterfaceName interfaceName, std::vector vtable) @@ -58,7 +58,7 @@ void Object::addVTable(InterfaceName interfaceName, std::vector vtab Slot Object::addVTable(InterfaceName interfaceName, std::vector vtable, return_slot_t) { - SDBUS_CHECK_INTERFACE_NAME(interfaceName); + SDBUS_CHECK_INTERFACE_NAME(interfaceName.c_str()); // 1st pass -- create vtable structure for internal sdbus-c++ purposes auto internalVTable = std::make_unique(createInternalVTable(std::move(interfaceName), std::move(vtable))); @@ -84,6 +84,11 @@ sdbus::Signal Object::createSignal(const InterfaceName& interfaceName, const Sig return connection_.createSignal(objectPath_, interfaceName, signalName); } +sdbus::Signal Object::createSignal(const char* interfaceName, const char* signalName) +{ + return connection_.createSignal(objectPath_.c_str(), interfaceName, signalName); +} + void Object::emitSignal(const sdbus::Signal& message) { SDBUS_THROW_ERROR_IF(!message.isValid(), "Invalid signal message provided", EINVAL); @@ -96,11 +101,21 @@ void Object::emitPropertiesChangedSignal(const InterfaceName& interfaceName, con connection_.emitPropertiesChangedSignal(objectPath_, interfaceName, propNames); } +void Object::emitPropertiesChangedSignal(const char* interfaceName, const std::vector& propNames) +{ + connection_.emitPropertiesChangedSignal(objectPath_.c_str(), interfaceName, propNames); +} + void Object::emitPropertiesChangedSignal(const InterfaceName& interfaceName) { Object::emitPropertiesChangedSignal(interfaceName, {}); } +void Object::emitPropertiesChangedSignal(const char* interfaceName) +{ + Object::emitPropertiesChangedSignal(interfaceName, {}); +} + void Object::emitInterfacesAddedSignal() { connection_.emitInterfacesAddedSignal(objectPath_); @@ -183,7 +198,7 @@ void Object::writeInterfaceFlagsToVTable(InterfaceFlagsVTableItem flags, VTable& void Object::writeMethodRecordToVTable(MethodVTableItem method, VTable& vtable) { - SDBUS_CHECK_MEMBER_NAME(method.name); + SDBUS_CHECK_MEMBER_NAME(method.name.c_str()); SDBUS_THROW_ERROR_IF(!method.callbackHandler, "Invalid method callback provided", EINVAL); vtable.methods.push_back({ std::move(method.name) @@ -196,7 +211,7 @@ void Object::writeMethodRecordToVTable(MethodVTableItem method, VTable& vtable) void Object::writeSignalRecordToVTable(SignalVTableItem signal, VTable& vtable) { - SDBUS_CHECK_MEMBER_NAME(signal.name); + SDBUS_CHECK_MEMBER_NAME(signal.name.c_str()); vtable.signals.push_back({ std::move(signal.name) , std::move(signal.signature) @@ -206,7 +221,7 @@ void Object::writeSignalRecordToVTable(SignalVTableItem signal, VTable& vtable) void Object::writePropertyRecordToVTable(PropertyVTableItem property, VTable& vtable) { - SDBUS_CHECK_MEMBER_NAME(property.name); + SDBUS_CHECK_MEMBER_NAME(property.name.c_str()); SDBUS_THROW_ERROR_IF(!property.getter && !property.setter, "Invalid property callbacks provided", EINVAL); vtable.properties.push_back({ std::move(property.name) diff --git a/src/Object.h b/src/Object.h index f69fbb4d..549963da 100644 --- a/src/Object.h +++ b/src/Object.h @@ -54,9 +54,12 @@ namespace sdbus::internal { void unregister() override; sdbus::Signal createSignal(const InterfaceName& interfaceName, const SignalName& signalName) override; + sdbus::Signal createSignal(const char* interfaceName, const char* signalName) override; void emitSignal(const sdbus::Signal& message) override; void emitPropertiesChangedSignal(const InterfaceName& interfaceName, const std::vector& propNames) override; + void emitPropertiesChangedSignal(const char* interfaceName, const std::vector& propNames) override; void emitPropertiesChangedSignal(const InterfaceName& interfaceName) override; + void emitPropertiesChangedSignal(const char* interfaceName) override; void emitInterfacesAddedSignal() override; void emitInterfacesAddedSignal(const std::vector& interfaces) override; void emitInterfacesRemovedSignal() override; diff --git a/src/Proxy.cpp b/src/Proxy.cpp index a55c8652..a26ea3ae 100644 --- a/src/Proxy.cpp +++ b/src/Proxy.cpp @@ -36,6 +36,7 @@ #include "Utils.h" #include +#include #include SDBUS_HEADER #include @@ -46,8 +47,8 @@ Proxy::Proxy(sdbus::internal::IConnection& connection, ServiceName destination, , destination_(std::move(destination)) , objectPath_(std::move(objectPath)) { - SDBUS_CHECK_SERVICE_NAME(destination_); - SDBUS_CHECK_OBJECT_PATH(objectPath_); + SDBUS_CHECK_SERVICE_NAME(destination_.c_str()); + SDBUS_CHECK_OBJECT_PATH(objectPath_.c_str()); // The connection is not ours only, it is owned and managed by the user and we just reference // it here, so we expect the client to manage the event loop upon this connection themselves. @@ -60,8 +61,8 @@ Proxy::Proxy( std::unique_ptr&& connection , destination_(std::move(destination)) , objectPath_(std::move(objectPath)) { - SDBUS_CHECK_SERVICE_NAME(destination_); - SDBUS_CHECK_OBJECT_PATH(objectPath_); + SDBUS_CHECK_SERVICE_NAME(destination_.c_str()); + SDBUS_CHECK_OBJECT_PATH(objectPath_.c_str()); // The connection is ours only, i.e. it's us who has to manage the event loop upon this connection, // in order that we get and process signals, async call replies, and other messages from D-Bus. @@ -76,8 +77,8 @@ Proxy::Proxy( std::unique_ptr&& connection , destination_(std::move(destination)) , objectPath_(std::move(objectPath)) { - SDBUS_CHECK_SERVICE_NAME(destination_); - SDBUS_CHECK_OBJECT_PATH(objectPath_); + SDBUS_CHECK_SERVICE_NAME(destination_.c_str()); + SDBUS_CHECK_OBJECT_PATH(objectPath_.c_str()); // Even though the connection is ours only, we don't start an event loop thread. // This proxy is meant to be created, used for simple synchronous D-Bus call(s) and then dismissed. @@ -88,6 +89,11 @@ MethodCall Proxy::createMethodCall(const InterfaceName& interfaceName, const Met return connection_->createMethodCall(destination_, objectPath_, interfaceName, methodName); } +MethodCall Proxy::createMethodCall(const char* interfaceName, const char* methodName) +{ + return connection_->createMethodCall(destination_.c_str(), objectPath_.c_str(), interfaceName, methodName); +} + MethodReply Proxy::callMethod(const MethodCall& message, uint64_t timeout) { SDBUS_THROW_ERROR_IF(!message.isValid(), "Invalid method call message provided", EINVAL); @@ -137,6 +143,13 @@ std::future Proxy::callMethodAsync(const MethodCall& message, uint6 void Proxy::registerSignalHandler( const InterfaceName& interfaceName , const SignalName& signalName , signal_handler signalHandler ) +{ + Proxy::registerSignalHandler(interfaceName.c_str(), signalName.c_str(), std::move(signalHandler)); +} + +void Proxy::registerSignalHandler( const char* interfaceName + , const char* signalName + , signal_handler signalHandler ) { auto slot = Proxy::registerSignalHandler(interfaceName, signalName, std::move(signalHandler), return_slot); @@ -147,6 +160,14 @@ Slot Proxy::registerSignalHandler( const InterfaceName& interfaceName , const SignalName& signalName , signal_handler signalHandler , return_slot_t ) +{ + return Proxy::registerSignalHandler(interfaceName.c_str(), signalName.c_str(), std::move(signalHandler), return_slot); +} + +Slot Proxy::registerSignalHandler( const char* interfaceName + , const char* signalName + , signal_handler signalHandler + , return_slot_t ) { SDBUS_CHECK_INTERFACE_NAME(interfaceName); SDBUS_CHECK_MEMBER_NAME(signalName); @@ -154,8 +175,8 @@ Slot Proxy::registerSignalHandler( const InterfaceName& interfaceName auto signalInfo = std::make_unique(SignalInfo{std::move(signalHandler), *this, {}}); - signalInfo->slot = connection_->registerSignalHandler( destination_ - , objectPath_ + signalInfo->slot = connection_->registerSignalHandler( destination_.c_str() + , objectPath_.c_str() , interfaceName , signalName , &Proxy::sdbus_signal_handler diff --git a/src/Proxy.h b/src/Proxy.h index d2d606e4..24906ce0 100644 --- a/src/Proxy.h +++ b/src/Proxy.h @@ -57,6 +57,7 @@ namespace sdbus::internal { , dont_run_event_loop_thread_t ); MethodCall createMethodCall(const InterfaceName& interfaceName, const MethodName& methodName) override; + MethodCall createMethodCall(const char* interfaceName, const char* methodName) override; MethodReply callMethod(const MethodCall& message, uint64_t timeout) override; PendingAsyncCall callMethodAsync(const MethodCall& message, async_reply_handler asyncReplyCallback, uint64_t timeout) override; std::future callMethodAsync(const MethodCall& message, with_future_t) override; @@ -65,10 +66,17 @@ namespace sdbus::internal { void registerSignalHandler( const InterfaceName& interfaceName , const SignalName& signalName , signal_handler signalHandler ) override; + void registerSignalHandler( const char* interfaceName + , const char* signalName + , signal_handler signalHandler ) override; Slot registerSignalHandler( const InterfaceName& interfaceName , const SignalName& signalName , signal_handler signalHandler , return_slot_t ) override; + Slot registerSignalHandler( const char* interfaceName + , const char* signalName + , signal_handler signalHandler + , return_slot_t ) override; void unregister() override; [[nodiscard]] sdbus::IConnection& getConnection() const override; diff --git a/src/Utils.h b/src/Utils.h index 3c08e0b8..edf1c5d8 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -31,17 +31,17 @@ #include SDBUS_HEADER #if LIBSYSTEMD_VERSION>=246 -#define SDBUS_CHECK_OBJECT_PATH(_PATH) \ - SDBUS_THROW_ERROR_IF(!sd_bus_object_path_is_valid(_PATH.c_str()), "Invalid object path '" + _PATH + "' provided", EINVAL) \ +#define SDBUS_CHECK_OBJECT_PATH(_PATH) \ + SDBUS_THROW_ERROR_IF(!sd_bus_object_path_is_valid(_PATH), std::string("Invalid object path '") + _PATH + "' provided", EINVAL) \ /**/ -#define SDBUS_CHECK_INTERFACE_NAME(_NAME) \ - SDBUS_THROW_ERROR_IF(!sd_bus_interface_name_is_valid(_NAME.c_str()), "Invalid interface name '" + _NAME + "' provided", EINVAL) \ +#define SDBUS_CHECK_INTERFACE_NAME(_NAME) \ + SDBUS_THROW_ERROR_IF(!sd_bus_interface_name_is_valid(_NAME), std::string("Invalid interface name '") + _NAME + "' provided", EINVAL) \ /**/ -#define SDBUS_CHECK_SERVICE_NAME(_NAME) \ - SDBUS_THROW_ERROR_IF(!_NAME.empty() && !sd_bus_service_name_is_valid(_NAME.c_str()), "Invalid service name '" + _NAME + "' provided", EINVAL) \ +#define SDBUS_CHECK_SERVICE_NAME(_NAME) \ + SDBUS_THROW_ERROR_IF(*_NAME && !sd_bus_service_name_is_valid(_NAME), std::string("Invalid service name '") + _NAME + "' provided", EINVAL) \ /**/ -#define SDBUS_CHECK_MEMBER_NAME(_NAME) \ - SDBUS_THROW_ERROR_IF(!sd_bus_member_name_is_valid(_NAME.c_str()), std::string("Invalid member name '") + _NAME.c_str() + "' provided", EINVAL) \ +#define SDBUS_CHECK_MEMBER_NAME(_NAME) \ + SDBUS_THROW_ERROR_IF(!sd_bus_member_name_is_valid(_NAME), std::string("Invalid member name '") + _NAME + "' provided", EINVAL) \ /**/ #else #define SDBUS_CHECK_OBJECT_PATH(_PATH) diff --git a/tests/integrationtests/DBusStandardInterfacesTests.cpp b/tests/integrationtests/DBusStandardInterfacesTests.cpp index 578ba5b3..d62fe75a 100644 --- a/tests/integrationtests/DBusStandardInterfacesTests.cpp +++ b/tests/integrationtests/DBusStandardInterfacesTests.cpp @@ -234,10 +234,10 @@ TYPED_TEST(SdbusTestObject, GetsManagedObjectsSuccessfully) ASSERT_THAT(objectsInterfacesAndProperties, SizeIs(2)); EXPECT_THAT(objectsInterfacesAndProperties.at(OBJECT_PATH) - .at(org::sdbuscpp::integrationtests_adaptor::INTERFACE_NAME) + .at(sdbus::InterfaceName{org::sdbuscpp::integrationtests_adaptor::INTERFACE_NAME}) .at(ACTION_PROPERTY).template get(), Eq(DEFAULT_ACTION_VALUE)); EXPECT_THAT(objectsInterfacesAndProperties.at(OBJECT_PATH_2) - .at(org::sdbuscpp::integrationtests_adaptor::INTERFACE_NAME) + .at(sdbus::InterfaceName{org::sdbuscpp::integrationtests_adaptor::INTERFACE_NAME}) .at(ACTION_PROPERTY).template get(), Eq(DEFAULT_ACTION_VALUE)); } diff --git a/tests/integrationtests/integrationtests-adaptor.h b/tests/integrationtests/integrationtests-adaptor.h index fe651a9f..a34146fa 100644 --- a/tests/integrationtests/integrationtests-adaptor.h +++ b/tests/integrationtests/integrationtests-adaptor.h @@ -16,7 +16,7 @@ namespace sdbuscpp { class integrationtests_adaptor { public: - static inline const sdbus::InterfaceName INTERFACE_NAME{"org.sdbuscpp.integrationtests"}; + static constexpr const char* INTERFACE_NAME = "org.sdbuscpp.integrationtests"; protected: integrationtests_adaptor(sdbus::IObject& object) diff --git a/tests/integrationtests/integrationtests-proxy.h b/tests/integrationtests/integrationtests-proxy.h index edae7a52..48e91a56 100644 --- a/tests/integrationtests/integrationtests-proxy.h +++ b/tests/integrationtests/integrationtests-proxy.h @@ -16,7 +16,7 @@ namespace sdbuscpp { class integrationtests_proxy { public: - static inline const sdbus::InterfaceName INTERFACE_NAME{"org.sdbuscpp.integrationtests"}; + static constexpr const char* INTERFACE_NAME = "org.sdbuscpp.integrationtests"; protected: integrationtests_proxy(sdbus::IProxy& proxy) diff --git a/tests/perftests/perftests-adaptor.h b/tests/perftests/perftests-adaptor.h index 49301176..6aefa1ae 100644 --- a/tests/perftests/perftests-adaptor.h +++ b/tests/perftests/perftests-adaptor.h @@ -16,7 +16,7 @@ namespace sdbuscpp { class perftests_adaptor { public: - static inline const sdbus::InterfaceName INTERFACE_NAME{"org.sdbuscpp.perftests"}; + static constexpr const char* INTERFACE_NAME = "org.sdbuscpp.perftests"; protected: perftests_adaptor(sdbus::IObject& object) diff --git a/tests/perftests/perftests-proxy.h b/tests/perftests/perftests-proxy.h index 22544ae2..dee4bf97 100644 --- a/tests/perftests/perftests-proxy.h +++ b/tests/perftests/perftests-proxy.h @@ -16,7 +16,7 @@ namespace sdbuscpp { class perftests_proxy { public: - static inline const sdbus::InterfaceName INTERFACE_NAME{"org.sdbuscpp.perftests"}; + static constexpr const char* INTERFACE_NAME = "org.sdbuscpp.perftests"; protected: perftests_proxy(sdbus::IProxy& proxy) diff --git a/tests/stresstests/celsius-thermometer-adaptor.h b/tests/stresstests/celsius-thermometer-adaptor.h index 6b4b0303..36a32bab 100644 --- a/tests/stresstests/celsius-thermometer-adaptor.h +++ b/tests/stresstests/celsius-thermometer-adaptor.h @@ -18,7 +18,7 @@ namespace celsius { class thermometer_adaptor { public: - static inline const sdbus::InterfaceName INTERFACE_NAME{"org.sdbuscpp.stresstests.celsius.thermometer"}; + static constexpr const char* INTERFACE_NAME = "org.sdbuscpp.stresstests.celsius.thermometer"; protected: thermometer_adaptor(sdbus::IObject& object) diff --git a/tests/stresstests/celsius-thermometer-proxy.h b/tests/stresstests/celsius-thermometer-proxy.h index 480f79a1..b4a57158 100644 --- a/tests/stresstests/celsius-thermometer-proxy.h +++ b/tests/stresstests/celsius-thermometer-proxy.h @@ -18,7 +18,7 @@ namespace celsius { class thermometer_proxy { public: - static inline const sdbus::InterfaceName INTERFACE_NAME{"org.sdbuscpp.stresstests.celsius.thermometer"}; + static constexpr const char* INTERFACE_NAME = "org.sdbuscpp.stresstests.celsius.thermometer"; protected: thermometer_proxy(sdbus::IProxy& proxy) diff --git a/tests/stresstests/concatenator-adaptor.h b/tests/stresstests/concatenator-adaptor.h index 42eae54e..0bd47e70 100644 --- a/tests/stresstests/concatenator-adaptor.h +++ b/tests/stresstests/concatenator-adaptor.h @@ -17,7 +17,7 @@ namespace stresstests { class concatenator_adaptor { public: - static inline const sdbus::InterfaceName INTERFACE_NAME{"org.sdbuscpp.stresstests.concatenator"}; + static constexpr const char* INTERFACE_NAME = "org.sdbuscpp.stresstests.concatenator"; protected: concatenator_adaptor(sdbus::IObject& object) diff --git a/tests/stresstests/concatenator-proxy.h b/tests/stresstests/concatenator-proxy.h index b5fdfefd..2383dc73 100644 --- a/tests/stresstests/concatenator-proxy.h +++ b/tests/stresstests/concatenator-proxy.h @@ -17,7 +17,7 @@ namespace stresstests { class concatenator_proxy { public: - static inline const sdbus::InterfaceName INTERFACE_NAME{"org.sdbuscpp.stresstests.concatenator"}; + static constexpr const char* INTERFACE_NAME = "org.sdbuscpp.stresstests.concatenator"; protected: concatenator_proxy(sdbus::IProxy& proxy) diff --git a/tests/stresstests/fahrenheit-thermometer-adaptor.h b/tests/stresstests/fahrenheit-thermometer-adaptor.h index 883e5745..0710fbb6 100644 --- a/tests/stresstests/fahrenheit-thermometer-adaptor.h +++ b/tests/stresstests/fahrenheit-thermometer-adaptor.h @@ -18,7 +18,7 @@ namespace fahrenheit { class thermometer_adaptor { public: - static inline const sdbus::InterfaceName INTERFACE_NAME{"org.sdbuscpp.stresstests.fahrenheit.thermometer"}; + static constexpr const char* INTERFACE_NAME = "org.sdbuscpp.stresstests.fahrenheit.thermometer"; protected: thermometer_adaptor(sdbus::IObject& object) @@ -56,7 +56,7 @@ namespace thermometer { class factory_adaptor { public: - static inline const sdbus::InterfaceName INTERFACE_NAME{"org.sdbuscpp.stresstests.fahrenheit.thermometer.factory"}; + static constexpr const char* INTERFACE_NAME = "org.sdbuscpp.stresstests.fahrenheit.thermometer.factory"; protected: factory_adaptor(sdbus::IObject& object) diff --git a/tests/stresstests/fahrenheit-thermometer-proxy.h b/tests/stresstests/fahrenheit-thermometer-proxy.h index d24a2b57..3a1fc265 100644 --- a/tests/stresstests/fahrenheit-thermometer-proxy.h +++ b/tests/stresstests/fahrenheit-thermometer-proxy.h @@ -18,7 +18,7 @@ namespace fahrenheit { class thermometer_proxy { public: - static inline const sdbus::InterfaceName INTERFACE_NAME{"org.sdbuscpp.stresstests.fahrenheit.thermometer"}; + static constexpr const char* INTERFACE_NAME = "org.sdbuscpp.stresstests.fahrenheit.thermometer"; protected: thermometer_proxy(sdbus::IProxy& proxy) @@ -60,7 +60,7 @@ namespace thermometer { class factory_proxy { public: - static inline const sdbus::InterfaceName INTERFACE_NAME{"org.sdbuscpp.stresstests.fahrenheit.thermometer.factory"}; + static constexpr const char* INTERFACE_NAME = "org.sdbuscpp.stresstests.fahrenheit.thermometer.factory"; protected: factory_proxy(sdbus::IProxy& proxy) diff --git a/tools/xml2cpp-codegen/AdaptorGenerator.cpp b/tools/xml2cpp-codegen/AdaptorGenerator.cpp index 1ee3f0d0..3c7c24ce 100644 --- a/tools/xml2cpp-codegen/AdaptorGenerator.cpp +++ b/tools/xml2cpp-codegen/AdaptorGenerator.cpp @@ -82,7 +82,7 @@ std::string AdaptorGenerator::processInterface(Node& interface) const body << "class " << className << endl << "{" << endl << "public:" << endl - << tab << "static inline const sdbus::InterfaceName INTERFACE_NAME{\"" << ifaceName << "\"};" << endl << endl + << tab << "static constexpr const char* INTERFACE_NAME = \"" << ifaceName << "\";" << endl << endl << "protected:" << endl << tab << className << "(sdbus::IObject& object)" << endl << tab << tab << ": object_(&object)" << endl diff --git a/tools/xml2cpp-codegen/ProxyGenerator.cpp b/tools/xml2cpp-codegen/ProxyGenerator.cpp index f95a2fc6..99225a7c 100644 --- a/tools/xml2cpp-codegen/ProxyGenerator.cpp +++ b/tools/xml2cpp-codegen/ProxyGenerator.cpp @@ -81,7 +81,7 @@ std::string ProxyGenerator::processInterface(Node& interface) const body << "class " << className << endl << "{" << endl << "public:" << endl - << tab << "static inline const sdbus::InterfaceName INTERFACE_NAME{\"" << ifaceName << "\"};" << endl << endl + << tab << "static constexpr const char* INTERFACE_NAME = \"" << ifaceName << "\";" << endl << endl << "protected:" << endl << tab << className << "(sdbus::IProxy& proxy)" << endl << tab << tab << ": proxy_(&proxy)" << endl From e7819b5d289a69afdc98435673527526f262aea4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Tue, 16 Apr 2024 22:50:37 +0200 Subject: [PATCH 45/52] chore: update years in header comments --- include/sdbus-c++/VTableItems.h | 2 +- include/sdbus-c++/VTableItems.inl | 2 +- tests/unittests/PollData_test.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/sdbus-c++/VTableItems.h b/include/sdbus-c++/VTableItems.h index b0aa3f08..019a57b8 100644 --- a/include/sdbus-c++/VTableItems.h +++ b/include/sdbus-c++/VTableItems.h @@ -1,5 +1,5 @@ /** - * (C) 2023 Stanislav Angelovic + * (C) 2016 - 2024 Stanislav Angelovic * * @file VTableItems.h * diff --git a/include/sdbus-c++/VTableItems.inl b/include/sdbus-c++/VTableItems.inl index b8e5e09c..127006df 100644 --- a/include/sdbus-c++/VTableItems.inl +++ b/include/sdbus-c++/VTableItems.inl @@ -1,5 +1,5 @@ /** - * (C) 2023 Stanislav Angelovic + * (C) 2016 - 2024 Stanislav Angelovic * * @file VTableItems.inl * diff --git a/tests/unittests/PollData_test.cpp b/tests/unittests/PollData_test.cpp index 68259d01..7aabf51e 100644 --- a/tests/unittests/PollData_test.cpp +++ b/tests/unittests/PollData_test.cpp @@ -1,6 +1,6 @@ /** * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland - * (C) 2016 - 2022 Stanislav Angelovic + * (C) 2016 - 2024 Stanislav Angelovic * * @file PollData_test.cpp * From ef01757510feb41c129f716790e0f07bc776c466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Wed, 17 Apr 2024 23:42:46 +0200 Subject: [PATCH 46/52] refactor: improve and make more efficient some Message API (#432) --- docs/using-sdbus-c++.md | 2 + include/sdbus-c++/Message.h | 37 ++++++++--------- include/sdbus-c++/Types.h | 5 ++- src/Message.cpp | 45 ++++++++++----------- src/Object.cpp | 6 +-- src/Proxy.cpp | 1 - src/Types.cpp | 6 +-- tests/integrationtests/DBusGeneralTests.cpp | 3 +- tests/unittests/Message_test.cpp | 20 ++++----- 9 files changed, 58 insertions(+), 67 deletions(-) diff --git a/docs/using-sdbus-c++.md b/docs/using-sdbus-c++.md index 0771d73b..e83dae5d 100644 --- a/docs/using-sdbus-c++.md +++ b/docs/using-sdbus-c++.md @@ -1780,6 +1780,8 @@ sdbus-c++ v2 is a major release that comes with a number of breaking API/ABI/beh * `createDefaultBusConnection()` has been renamed to `createBusConnection()`. * Change in behavior: `Proxy`s now by default call `createBusConnection()` to get a connection when the connection is not provided explicitly by the caller, so they connect to either the session bus or the system bus depending on the context (as opposed to always to the system bus like before). * Callbacks taking `const sdbus::Error* error` were changed to take `std::optional`, which better expresses the intent and meaning. +* `getInterfaceName()`, `getMemberName()`, `getSender()`, `getPath()` and `getDestination()` methods of `Message` class now return `const char*` instead of `std::string`, for efficiency reasons. +* `peekType()` method of `Message` class now returns a pair of `char` (type signature) and `const char*` (contents signature), for expressiveness and efficiency reasons. * D-Bus signatures when using high-level API are now assembled at compile time. There are breaking changes inside `signature_of` type traits and `Message` serialization/deserialization methods. This only interests you if you extend sdbus-c++ type system with your own types. See the updated tutorial on extending sdbus-c++ type system. * Types and methods marked deprecated in sdbus-c++ v1 were removed completely. * CMake options got `SDBUSCPP_` prefix for better usability and minimal risk of conflicts in downstream CMake projects. `SDBUSCPP_INSTALL` CMake option was added. diff --git a/include/sdbus-c++/Message.h b/include/sdbus-c++/Message.h index de735ab2..bc872f33 100644 --- a/include/sdbus-c++/Message.h +++ b/include/sdbus-c++/Message.h @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #ifdef __has_include @@ -52,14 +53,10 @@ namespace sdbus { class Variant; class ObjectPath; - class InterfaceName; - class MemberName; class Signature; template class Struct; class UnixFd; class MethodReply; - class BusName; - using ConnectionName = BusName; namespace internal { class ISdBus; } @@ -202,12 +199,13 @@ namespace sdbus { explicit operator bool() const; void clearFlags(); - InterfaceName getInterfaceName() const; - MemberName getMemberName() const; - ConnectionName getSender() const; - ObjectPath getPath() const; - ConnectionName getDestination() const; - void peekType(std::string& type, std::string& contents) const; + const char* getInterfaceName() const; + const char* getMemberName() const; + const char* getSender() const; + const char* getPath() const; + const char* getDestination() const; + // TODO: short docs in whole Message API + std::pair peekType() const; bool isValid() const; bool isEmpty() const; bool isAtEnd(bool complete) const; @@ -302,7 +300,8 @@ namespace sdbus { public: Signal() = default; - void setDestination(const ConnectionName& destination); + void setDestination(const std::string& destination); + void setDestination(const char* destination); void send() const; }; @@ -474,15 +473,14 @@ namespace sdbus { namespace detail { template - bool deserialize_variant(Message& msg, std::variant<_Elements...>& value, const std::string& signature) + bool deserialize_variant(Message& msg, std::variant<_Elements...>& value, const char* signature) { - constexpr auto elemSignature = sdbus::signature_of_v<_Element>; - // TODO: Try to optimize - if (signature != std::string(elemSignature.begin(), elemSignature.end())) + constexpr auto elemSignature = as_null_terminated(sdbus::signature_of_v<_Element>); + if (std::strcmp(signature, elemSignature.data()) != 0) return false; _Element temp; - msg.enterVariant(signature.c_str()); + msg.enterVariant(signature); msg >> temp; msg.exitVariant(); value = std::move(temp); @@ -493,11 +491,8 @@ namespace sdbus { template inline Message& Message::operator>>(std::variant& value) { - std::string type; - std::string contentType; - // TODO: Refactor ppekType prior to release/v2.0 to return pair of const char* or string_view... - peekType(type, contentType); - bool result = (detail::deserialize_variant(*this, value, contentType) || ...); + auto [type, contents] = peekType(); + bool result = (detail::deserialize_variant(*this, value, contents) || ...); SDBUS_THROW_ERROR_IF(!result, "Failed to deserialize variant: signature did not match any of the variant types", EINVAL); return *this; } diff --git a/include/sdbus-c++/Types.h b/include/sdbus-c++/Types.h index 776bdc84..c927903a 100644 --- a/include/sdbus-c++/Types.h +++ b/include/sdbus-c++/Types.h @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -105,14 +106,14 @@ namespace sdbus { bool containsValueOfType() const { constexpr auto signature = as_null_terminated(signature_of_v<_Type>); - return signature.data() == peekValueType(); + return std::strcmp(signature.data(), peekValueType()) == 0; } bool isEmpty() const; void serializeTo(Message& msg) const; void deserializeFrom(Message& msg); - std::string peekValueType() const; + const char* peekValueType() const; private: mutable PlainMessage msg_{}; diff --git a/src/Message.cpp b/src/Message.cpp index cda9ccd0..0d04a5de 100644 --- a/src/Message.cpp +++ b/src/Message.cpp @@ -612,44 +612,38 @@ void Message::rewind(bool complete) SDBUS_THROW_ERROR_IF(r < 0, "Failed to rewind the message", -r); } -InterfaceName Message::getInterfaceName() const +const char* Message::getInterfaceName() const { - const auto* interface = sd_bus_message_get_interface((sd_bus_message*)msg_); - return interface != nullptr ? InterfaceName{interface} : InterfaceName{}; + return sd_bus_message_get_interface((sd_bus_message*)msg_); } -MemberName Message::getMemberName() const +const char* Message::getMemberName() const { - const auto* member = sd_bus_message_get_member((sd_bus_message*)msg_); - return member != nullptr ? MemberName{member} : MemberName{}; + return sd_bus_message_get_member((sd_bus_message*)msg_); } -ConnectionName Message::getSender() const +const char* Message::getSender() const { - const auto* sender = sd_bus_message_get_sender((sd_bus_message*)msg_); - return ConnectionName{sender}; + return sd_bus_message_get_sender((sd_bus_message*)msg_); } -ObjectPath Message::getPath() const +const char* Message::getPath() const { - const auto* path = sd_bus_message_get_path((sd_bus_message*)msg_); - return path != nullptr ? ObjectPath{path} : ObjectPath{}; + return sd_bus_message_get_path((sd_bus_message*)msg_); } -ConnectionName Message::getDestination() const +const char* Message::getDestination() const { - const auto* destination = sd_bus_message_get_destination((sd_bus_message*)msg_); - return destination != nullptr ? ConnectionName{destination} : ConnectionName{}; + return sd_bus_message_get_destination((sd_bus_message*)msg_); } -void Message::peekType(std::string& type, std::string& contents) const +std::pair Message::peekType() const { - char typeSig; - const char* contentsSig; - auto r = sd_bus_message_peek_type((sd_bus_message*)msg_, &typeSig, &contentsSig); + char typeSignature{}; + const char* contentsSignature{}; + auto r = sd_bus_message_peek_type((sd_bus_message*)msg_, &typeSignature, &contentsSignature); SDBUS_THROW_ERROR_IF(r < 0, "Failed to peek message type", -r); - type = typeSig; - contents = contentsSig ? contentsSig : ""; + return {typeSignature, contentsSignature}; } bool Message::isValid() const @@ -877,9 +871,14 @@ void Signal::send() const SDBUS_THROW_ERROR_IF(r < 0, "Failed to emit signal", -r); } -void Signal::setDestination(const ConnectionName& destination) +void Signal::setDestination(const std::string& destination) { - auto r = sdbus_->sd_bus_message_set_destination((sd_bus_message*)msg_, destination.c_str()); + return setDestination(destination.c_str()); +} + +void Signal::setDestination(const char* destination) +{ + auto r = sdbus_->sd_bus_message_set_destination((sd_bus_message*)msg_, destination); SDBUS_THROW_ERROR_IF(r < 0, "Failed to set signal destination", -r); } diff --git a/src/Object.cpp b/src/Object.cpp index 70300c12..3e2a8db6 100644 --- a/src/Object.cpp +++ b/src/Object.cpp @@ -300,8 +300,6 @@ const Object::VTable::MethodItem* Object::findMethod(const VTable& vtable, std:: return methodItem.name < methodName; }); - (void)it; - return it != vtable.methods.end() && it->name == methodName ? &*it : nullptr; } @@ -312,8 +310,6 @@ const Object::VTable::PropertyItem* Object::findProperty(const VTable& vtable, s return propertyItem.name < propertyName; }); - (void)it; - return it != vtable.properties.end() && it->name == propertyName ? &*it : nullptr; } @@ -333,7 +329,7 @@ int Object::sdbus_method_callback(sd_bus_message *sdbusMessage, void *userData, auto message = Message::Factory::create(sdbusMessage, &vtable->object->connection_.getSdBusInterface()); - const auto* methodItem = findMethod(*vtable, message.getMemberName()); // TODO: optimize the situation around getMemberName() + const auto* methodItem = findMethod(*vtable, message.getMemberName()); assert(methodItem != nullptr); assert(methodItem->callback); diff --git a/src/Proxy.cpp b/src/Proxy.cpp index a26ea3ae..135b7020 100644 --- a/src/Proxy.cpp +++ b/src/Proxy.cpp @@ -113,7 +113,6 @@ PendingAsyncCall Proxy::callMethodAsync(const MethodCall& message, async_reply_h pendingAsyncCalls_.addCall(std::move(callData)); - // TODO: Instead of PendingAsyncCall consider using Slot implementation for simplicity and consistency return {weakData}; } diff --git a/src/Types.cpp b/src/Types.cpp index 81d27e8d..4f91e939 100644 --- a/src/Types.cpp +++ b/src/Types.cpp @@ -55,12 +55,10 @@ void Variant::deserializeFrom(Message& msg) msg_.seal(); } -std::string Variant::peekValueType() const +const char* Variant::peekValueType() const { msg_.rewind(false); - std::string type; - std::string contents; - msg_.peekType(type, contents); + auto [type, contents] = msg_.peekType(); return contents; } diff --git a/tests/integrationtests/DBusGeneralTests.cpp b/tests/integrationtests/DBusGeneralTests.cpp index 7f9dae06..294cd0f3 100644 --- a/tests/integrationtests/DBusGeneralTests.cpp +++ b/tests/integrationtests/DBusGeneralTests.cpp @@ -44,6 +44,7 @@ using ::testing::ElementsAre; using ::testing::Eq; using namespace std::chrono_literals; using namespace sdbus::test; +using namespace std::string_view_literals; using ADirectConnection = TestFixtureWithDirectConnection; @@ -157,7 +158,7 @@ TYPED_TEST(AConnection, WillNotPassToMatchCallbackMessagesThatDoNotMatchTheRule) std::atomic numberOfMatchingMessages{}; auto slot = this->s_proxyConnection->addMatch(matchRule, [&](sdbus::Message msg) { - if(msg.getMemberName() == "simpleSignal") + if(msg.getMemberName() == "simpleSignal"sv) numberOfMatchingMessages++; }); auto adaptor2 = std::make_unique(*this->s_adaptorConnection, OBJECT_PATH_2); diff --git a/tests/unittests/Message_test.cpp b/tests/unittests/Message_test.cpp index d1d4175d..05e44686 100644 --- a/tests/unittests/Message_test.cpp +++ b/tests/unittests/Message_test.cpp @@ -32,8 +32,10 @@ #include using ::testing::Eq; +using ::testing::StrEq; using ::testing::Gt; using ::testing::DoubleEq; +using ::testing::IsNull; using namespace std::string_literals; namespace @@ -465,11 +467,10 @@ TEST(AMessage, CanPeekASimpleType) msg << 123; msg.seal(); - std::string type; - std::string contents; - msg.peekType(type, contents); - ASSERT_THAT(type, "i"); - ASSERT_THAT(contents, ""); + auto [type, contents] = msg.peekType(); + + ASSERT_THAT(type, Eq('i')); + ASSERT_THAT(contents, IsNull()); } TEST(AMessage, CanPeekContainerContents) @@ -478,11 +479,10 @@ TEST(AMessage, CanPeekContainerContents) msg << std::map{{1, "one"}, {2, "two"}}; msg.seal(); - std::string type; - std::string contents; - msg.peekType(type, contents); - ASSERT_THAT(type, "a"); - ASSERT_THAT(contents, "{is}"); + auto [type, contents] = msg.peekType(); + + ASSERT_THAT(type, Eq('a')); + ASSERT_THAT(contents, StrEq("{is}")); } TEST(AMessage, CanCarryDBusArrayGivenAsCustomType) From 4c8eaed19ffb39be06462e8c37dd982347f1f366 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Thu, 18 Apr 2024 19:53:35 +0200 Subject: [PATCH 47/52] feat: add Slot-returning overloads of async method calls (#433) --- ChangeLog | 1 + docs/using-sdbus-c++.md | 4 + include/sdbus-c++/ConvenienceApiClasses.h | 5 + include/sdbus-c++/ConvenienceApiClasses.inl | 69 +++++++- include/sdbus-c++/IProxy.h | 163 ++++++++++++++++-- include/sdbus-c++/StandardInterfaces.h | 36 ++++ include/sdbus-c++/TypeTraits.h | 2 +- src/Proxy.cpp | 113 +++++++++--- src/Proxy.h | 88 ++++------ .../DBusAsyncMethodsTests.cpp | 14 ++ tests/integrationtests/TestProxy.cpp | 11 ++ tests/integrationtests/TestProxy.h | 1 + 12 files changed, 413 insertions(+), 94 deletions(-) diff --git a/ChangeLog b/ChangeLog index fbc8b7df..488cf918 100644 --- a/ChangeLog +++ b/ChangeLog @@ -262,6 +262,7 @@ v2.0.0 - Fix for external event loops in which the event loop thread ID was not correctly initialized (now fixed and simplified by not needing the thread ID anymore) - Introduce native integration for sd-event - Add method to get currently processed message also to `IConnection` +- Add Slot-returning overloads of `callMethodAsync()` functions - `[[nodiscard]]` attribute has been added to relevant API methods. - Add new `SDBUSCPP_SDBUS_LIB` CMake configuration variable determining which sd-bus library shall be picked - Switch to C++20 standard (but C++20 is not required, and the used C++20 features are conditionally compiled) diff --git a/docs/using-sdbus-c++.md b/docs/using-sdbus-c++.md index e83dae5d..7d6be9fd 100644 --- a/docs/using-sdbus-c++.md +++ b/docs/using-sdbus-c++.md @@ -1231,6 +1231,10 @@ int main(int argc, char *argv[]) Empty `error` parameter means that no D-Bus error occurred while making the call, and subsequent arguments are valid D-Bus method return values. However, `error` parameter containing a value means that an error occurred during the call (and subsequent arguments are simply default-constructed), and the underlying `Error` instance provides us with the error name and message. +> **_Tip_:** The function returns the `sdbus::PendingAsyncCall` object, a non-owning, observing handle to the async call. It can be used to query whether the call is still in progress, and to cancel the call. + +> **_Tip_:** There is also the `.uponReplyInvoke(callback, sdbus::return_slot);` variant with the `return_slot` tag, which returns `Slot` object, an owning RAII handle to the async call. This makes the client an owner of the pending async call. Letting go of the handle means cancelling the call. + Another option is to finish the async call statement with `getResultAsFuture()`, which is a template function which takes the list of types returned by the D-Bus method (empty list in case of `void`-returning method) which returns a `std::future` object, which will later, when the reply arrives, be set to contain the return value(s). Or if the call returns an error, `sdbus::Error` will be thrown by `std::future::get()`. The future object will contain void for a void-returning D-Bus method, a single type for a single value returning D-Bus method, and a `std::tuple` to hold multiple return values of a D-Bus method. diff --git a/include/sdbus-c++/ConvenienceApiClasses.h b/include/sdbus-c++/ConvenienceApiClasses.h index 6a1d5c07..d644ab8e 100644 --- a/include/sdbus-c++/ConvenienceApiClasses.h +++ b/include/sdbus-c++/ConvenienceApiClasses.h @@ -131,6 +131,7 @@ namespace sdbus { AsyncMethodInvoker& withTimeout(const std::chrono::duration<_Rep, _Period>& timeout); template AsyncMethodInvoker& withArguments(_Args&&... args); template PendingAsyncCall uponReplyInvoke(_Function&& callback); + template [[nodiscard]] Slot uponReplyInvoke(_Function&& callback, return_slot_t); // Returned future will be std::future for no (void) D-Bus method return value // or std::future for single D-Bus method return value // or std::future> for multiple method return values @@ -140,6 +141,7 @@ namespace sdbus { friend IProxy; AsyncMethodInvoker(IProxy& proxy, const MethodName& methodName); AsyncMethodInvoker(IProxy& proxy, const char* methodName); + template async_reply_handler makeAsyncReplyHandler(_Function&& callback); private: IProxy& proxy_; @@ -190,6 +192,7 @@ namespace sdbus { public: AsyncPropertyGetter& onInterface(std::string_view interfaceName); template PendingAsyncCall uponReplyInvoke(_Function&& callback); + template [[nodiscard]] Slot uponReplyInvoke(_Function&& callback, return_slot_t); std::future getResultAsFuture(); private: @@ -232,6 +235,7 @@ namespace sdbus { template AsyncPropertySetter& toValue(_Value&& value); AsyncPropertySetter& toValue(Variant value); template PendingAsyncCall uponReplyInvoke(_Function&& callback); + template [[nodiscard]] Slot uponReplyInvoke(_Function&& callback, return_slot_t); std::future getResultAsFuture(); private: @@ -267,6 +271,7 @@ namespace sdbus { public: AsyncAllPropertiesGetter& onInterface(std::string_view interfaceName); template PendingAsyncCall uponReplyInvoke(_Function&& callback); + template [[nodiscard]] Slot uponReplyInvoke(_Function&& callback, return_slot_t); std::future> getResultAsFuture(); private: diff --git a/include/sdbus-c++/ConvenienceApiClasses.inl b/include/sdbus-c++/ConvenienceApiClasses.inl index 93ea4a1e..4be40a9f 100644 --- a/include/sdbus-c++/ConvenienceApiClasses.inl +++ b/include/sdbus-c++/ConvenienceApiClasses.inl @@ -287,7 +287,24 @@ namespace sdbus { { assert(method_.isValid()); // onInterface() must be placed/called prior to this function - auto asyncReplyHandler = [callback = std::forward<_Function>(callback)](MethodReply reply, std::optional error) + return proxy_.callMethodAsync(method_, makeAsyncReplyHandler(std::forward<_Function>(callback)), timeout_); + } + + template + [[nodiscard]] Slot AsyncMethodInvoker::uponReplyInvoke(_Function&& callback, return_slot_t) + { + assert(method_.isValid()); // onInterface() must be placed/called prior to this function + + return proxy_.callMethodAsync( method_ + , makeAsyncReplyHandler(std::forward<_Function>(callback)) + , timeout_ + , return_slot ); + } + + template + inline async_reply_handler AsyncMethodInvoker::makeAsyncReplyHandler(_Function&& callback) + { + return [callback = std::forward<_Function>(callback)](MethodReply reply, std::optional error) { // Create a tuple of callback input arguments' types, which will be used // as a storage for the argument values deserialized from the message. @@ -312,8 +329,6 @@ namespace sdbus { // Invoke callback with input arguments from the tuple. sdbus::apply(callback, std::move(error), args); }; - - return proxy_.callMethodAsync(method_, std::move(asyncReplyHandler), timeout_); } template @@ -474,7 +489,8 @@ namespace sdbus { template PendingAsyncCall AsyncPropertyGetter::uponReplyInvoke(_Function&& callback) { - static_assert(std::is_invocable_r_v, Variant>, "Property get callback function must accept std::optional and property value as Variant"); + static_assert( std::is_invocable_r_v, Variant> + , "Property get callback function must accept std::optional and property value as Variant" ); assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function @@ -484,6 +500,20 @@ namespace sdbus { .uponReplyInvoke(std::forward<_Function>(callback)); } + template + [[nodiscard]] Slot AsyncPropertyGetter::uponReplyInvoke(_Function&& callback, return_slot_t) + { + static_assert( std::is_invocable_r_v, Variant> + , "Property get callback function must accept std::optional and property value as Variant" ); + + assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function + + return proxy_.callMethodAsync("Get") + .onInterface(DBUS_PROPERTIES_INTERFACE_NAME) + .withArguments(interfaceName_, propertyName_) + .uponReplyInvoke(std::forward<_Function>(callback), return_slot); + } + inline std::future AsyncPropertyGetter::getResultAsFuture() { assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function @@ -575,7 +605,8 @@ namespace sdbus { template PendingAsyncCall AsyncPropertySetter::uponReplyInvoke(_Function&& callback) { - static_assert(std::is_invocable_r_v>, "Property set callback function must accept std::optional only"); + static_assert( std::is_invocable_r_v> + , "Property set callback function must accept std::optional only" ); assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function @@ -585,6 +616,20 @@ namespace sdbus { .uponReplyInvoke(std::forward<_Function>(callback)); } + template + [[nodiscard]] Slot AsyncPropertySetter::uponReplyInvoke(_Function&& callback, return_slot_t) + { + static_assert( std::is_invocable_r_v> + , "Property set callback function must accept std::optional only" ); + + assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function + + return proxy_.callMethodAsync("Set") + .onInterface(DBUS_PROPERTIES_INTERFACE_NAME) + .withArguments(interfaceName_, propertyName_, std::move(value_)) + .uponReplyInvoke(std::forward<_Function>(callback), return_slot); + } + inline std::future AsyncPropertySetter::getResultAsFuture() { assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function @@ -644,6 +689,20 @@ namespace sdbus { .uponReplyInvoke(std::forward<_Function>(callback)); } + template + [[nodiscard]] Slot AsyncAllPropertiesGetter::uponReplyInvoke(_Function&& callback, return_slot_t) + { + static_assert( std::is_invocable_r_v, std::map> + , "All properties get callback function must accept std::optional and a map of property names to their values" ); + + assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function + + return proxy_.callMethodAsync("GetAll") + .onInterface(DBUS_PROPERTIES_INTERFACE_NAME) + .withArguments(interfaceName_) + .uponReplyInvoke(std::forward<_Function>(callback), return_slot); + } + inline std::future> AsyncAllPropertiesGetter::getResultAsFuture() { assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function diff --git a/include/sdbus-c++/IProxy.h b/include/sdbus-c++/IProxy.h index 8f0d0bdb..9ea16a6d 100644 --- a/include/sdbus-c++/IProxy.h +++ b/include/sdbus-c++/IProxy.h @@ -89,7 +89,6 @@ namespace sdbus { * @brief Calls method on the remote D-Bus object * * @param[in] message Message representing a method call - * @param[in] timeout Timeout for dbus call in microseconds * @return A method reply message * * The call does not block if the method call has dont-expect-reply flag set. In that case, @@ -108,11 +107,44 @@ namespace sdbus { * its own bus connection. So-called light-weight proxies (ones created with `dont_run_event_loop_thread` * tag are designed for exactly that purpose. * + * The default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout(). + * * Note: To avoid messing with messages, use API on a higher level of abstraction defined below. * * @throws sdbus::Error in case of failure (also in case the remote function returned an error) */ - virtual MethodReply callMethod(const MethodCall& message, uint64_t timeout = 0) = 0; + virtual MethodReply callMethod(const MethodCall& message) = 0; + + /*! + * @brief Calls method on the remote D-Bus object + * + * @param[in] message Message representing a method call + * @param[in] timeout Method call timeout (in microseconds) + * @return A method reply message + * + * The call does not block if the method call has dont-expect-reply flag set. In that case, + * the call returns immediately and the return value is an empty, invalid method reply. + * + * The call blocks otherwise, waiting for the remote peer to send back a reply or an error, + * or until the call times out. + * + * While blocking, other concurrent operations (in other threads) on the underlying bus + * connection are stalled until the call returns. This is not an issue in vast majority of + * (simple, single-threaded) applications. In asynchronous, multi-threaded designs involving + * shared bus connections, this may be an issue. It is advised to instead use an asynchronous + * callMethod() function overload, which does not block the bus connection, or do the synchronous + * call from another Proxy instance created just before the call and then destroyed (which is + * anyway quite a typical approach in D-Bus implementations). Such proxy instance must have + * its own bus connection. So-called light-weight proxies (ones created with `dont_run_event_loop_thread` + * tag are designed for exactly that purpose. + * + * If timeout is zero, the default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout(). + * + * Note: To avoid messing with messages, use API on a higher level of abstraction defined below. + * + * @throws sdbus::Error in case of failure (also in case the remote function returned an error) + */ + virtual MethodReply callMethod(const MethodCall& message, uint64_t timeout) = 0; /*! * @copydoc IProxy::callMethod(const MethodCall&,uint64_t) @@ -125,8 +157,57 @@ namespace sdbus { * * @param[in] message Message representing an async method call * @param[in] asyncReplyCallback Handler for the async reply - * @param[in] timeout Timeout for dbus call in microseconds - * @return Cookie for the the pending asynchronous call + * @return Observing handle for the the pending asynchronous call + * + * This is a callback-based way of asynchronously calling a remote D-Bus method. + * + * The call itself 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 bus + * connection I/O event loop thread. + * + * An non-owning, observing async call handle is returned that can be used to query call status or cancel the call. + * + * The default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout(). + * + * Note: To avoid messing with messages, use API on a higher level of abstraction defined below. + * + * @throws sdbus::Error in case of failure + */ + virtual PendingAsyncCall callMethodAsync(const MethodCall& message, async_reply_handler asyncReplyCallback) = 0; + + /*! + * @brief Calls method on the D-Bus object asynchronously + * + * @param[in] message Message representing an async method call + * @param[in] asyncReplyCallback Handler for the async reply + * @return RAII-style slot handle representing the ownership of the async call + * + * This is a callback-based way of asynchronously calling a remote D-Bus method. + * + * The call itself 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 bus + * connection I/O event loop thread. + * + * A slot (an owning handle) is returned for the async call. Lifetime of the call is bound to the lifetime of the slot. + * The slot can be used to cancel the method call at a later time by simply destroying it. + * + * The default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout(). + * + * Note: To avoid messing with messages, use API on a higher level of abstraction defined below. + * + * @throws sdbus::Error in case of failure + */ + [[nodiscard]] virtual Slot callMethodAsync( const MethodCall& message + , async_reply_handler asyncReplyCallback + , return_slot_t ) = 0; + + /*! + * @brief Calls method on the D-Bus object asynchronously, with custom timeout + * + * @param[in] message Message representing an async method call + * @param[in] asyncReplyCallback Handler for the async reply + * @param[in] timeout Method call timeout (in microseconds) + * @return Observing handle for the the pending asynchronous call * * This is a callback-based way of asynchronously calling a remote D-Bus method. * @@ -134,13 +215,45 @@ namespace sdbus { * the provided async reply handler will get invoked from the context of the bus * connection I/O event loop thread. * + * An non-owning, observing async call handle is returned that can be used to query call status or cancel the call. + * + * If timeout is zero, the default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout(). + * * Note: To avoid messing with messages, use API on a higher level of abstraction defined below. * * @throws sdbus::Error in case of failure */ virtual PendingAsyncCall callMethodAsync( const MethodCall& message , async_reply_handler asyncReplyCallback - , uint64_t timeout = 0 ) = 0; + , uint64_t timeout ) = 0; + + /*! + * @brief Calls method on the D-Bus object asynchronously, with custom timeout + * + * @param[in] message Message representing an async method call + * @param[in] asyncReplyCallback Handler for the async reply + * @param[in] timeout Method call timeout (in microseconds) + * @return RAII-style slot handle representing the ownership of the async call + * + * This is a callback-based way of asynchronously calling a remote D-Bus method. + * + * The call itself 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 bus + * connection I/O event loop thread. + * + * A slot (an owning handle) is returned for the async call. Lifetime of the call is bound to the lifetime of the slot. + * The slot can be used to cancel the method call at a later time by simply destroying it. + * + * If timeout is zero, the default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout(). + * + * Note: To avoid messing with messages, use API on a higher level of abstraction defined below. + * + * @throws sdbus::Error in case of failure + */ + [[nodiscard]] virtual Slot callMethodAsync( const MethodCall& message + , async_reply_handler asyncReplyCallback + , uint64_t timeout + , return_slot_t ) = 0; /*! * @copydoc IProxy::callMethod(const MethodCall&,async_reply_handler,uint64_t) @@ -150,6 +263,15 @@ namespace sdbus { , async_reply_handler asyncReplyCallback , const std::chrono::duration<_Rep, _Period>& timeout ); + /*! + * @copydoc IProxy::callMethod(const MethodCall&,async_reply_handler,uint64_t,return_slot_t) + */ + template + [[nodiscard]] Slot callMethodAsync( const MethodCall& message + , async_reply_handler asyncReplyCallback + , const std::chrono::duration<_Rep, _Period>& timeout + , return_slot_t ); + /*! * @brief Calls method on the D-Bus object asynchronously * @@ -163,6 +285,8 @@ namespace sdbus { * the provided future object will be set to contain the reply (or sdbus::Error * in case the remote method threw an exception). * + * The default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout(). + * * Note: To avoid messing with messages, use higher-level API defined below. * * @throws sdbus::Error in case of failure @@ -183,6 +307,8 @@ namespace sdbus { * the provided future object will be set to contain the reply (or sdbus::Error * in case the remote method threw an exception, or the call timed out). * + * If timeout is zero, the default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout(). + * * Note: To avoid messing with messages, use higher-level API defined below. * * @throws sdbus::Error in case of failure @@ -273,8 +399,12 @@ namespace sdbus { * @param[in] signalName Name of the signal * @param[in] signalHandler Callback that implements the body of the signal handler * - * A signal can be subscribed to and unsubscribed from at any time during proxy - * lifetime. The subscription is active immediately after the call. + * A signal can be subscribed to at any time during proxy lifetime. The subscription + * is active immediately after the call, and stays active for the entire lifetime + * of the Proxy object. + * + * To be able to unsubscribe from the signal at a later time, use the registerSignalHandler() + * overload with request_slot tag. * * @throws sdbus::Error in case of failure */ @@ -292,8 +422,9 @@ namespace sdbus { * @return RAII-style slot handle representing the ownership of the subscription * * A signal can be subscribed to and unsubscribed from at any time during proxy - * lifetime. The subscription is active immediately after the call. The subscription - * is unregistered when the client destroys the returned slot object. + * lifetime. The subscription is active immediately after the call. The lifetime + * of the subscription is bound to the lifetime of the slot object. The subscription + * is unregistered by letting go of the slot object. * * @throws sdbus::Error in case of failure */ @@ -566,10 +697,10 @@ namespace sdbus { private: friend internal::Proxy; - PendingAsyncCall(std::weak_ptr callData); + PendingAsyncCall(std::weak_ptr callInfo); private: - std::weak_ptr callData_; + std::weak_ptr callInfo_; }; // Out-of-line member definitions @@ -590,6 +721,16 @@ namespace sdbus { return callMethodAsync(message, std::move(asyncReplyCallback), microsecs.count()); } + template + inline Slot IProxy::callMethodAsync( const MethodCall& message + , async_reply_handler asyncReplyCallback + , const std::chrono::duration<_Rep, _Period>& timeout + , return_slot_t ) + { + auto microsecs = std::chrono::duration_cast(timeout); + return callMethodAsync(message, std::move(asyncReplyCallback), microsecs.count(), return_slot); + } + template inline std::future IProxy::callMethodAsync( const MethodCall& message , const std::chrono::duration<_Rep, _Period>& timeout diff --git a/include/sdbus-c++/StandardInterfaces.h b/include/sdbus-c++/StandardInterfaces.h index 272a724f..ad447841 100644 --- a/include/sdbus-c++/StandardInterfaces.h +++ b/include/sdbus-c++/StandardInterfaces.h @@ -162,12 +162,24 @@ namespace sdbus { return proxy_->getPropertyAsync(propertyName).onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback)); } + template + [[nodiscard]] Slot GetAsync(const InterfaceName& interfaceName, const PropertyName& propertyName, _Function&& callback, return_slot_t) + { + return proxy_->getPropertyAsync(propertyName).onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback), return_slot); + } + template PendingAsyncCall GetAsync(std::string_view interfaceName, std::string_view propertyName, _Function&& callback) { return proxy_->getPropertyAsync(propertyName).onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback)); } + template + [[nodiscard]] Slot GetAsync(std::string_view interfaceName, std::string_view propertyName, _Function&& callback, return_slot_t) + { + return proxy_->getPropertyAsync(propertyName).onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback), return_slot); + } + std::future GetAsync(const InterfaceName& interfaceName, const PropertyName& propertyName, with_future_t) { return proxy_->getPropertyAsync(propertyName).onInterface(interfaceName).getResultAsFuture(); @@ -204,12 +216,24 @@ namespace sdbus { return proxy_->setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).uponReplyInvoke(std::forward<_Function>(callback)); } + template + PendingAsyncCall SetAsync(const InterfaceName& interfaceName, const PropertyName& propertyName, const sdbus::Variant& value, _Function&& callback, return_slot_t) + { + return proxy_->setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).uponReplyInvoke(std::forward<_Function>(callback), return_slot); + } + template PendingAsyncCall SetAsync(std::string_view interfaceName, std::string_view propertyName, const sdbus::Variant& value, _Function&& callback) { return proxy_->setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).uponReplyInvoke(std::forward<_Function>(callback)); } + template + PendingAsyncCall SetAsync(std::string_view interfaceName, std::string_view propertyName, const sdbus::Variant& value, _Function&& callback, return_slot_t) + { + return proxy_->setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).uponReplyInvoke(std::forward<_Function>(callback), return_slot); + } + std::future SetAsync(const InterfaceName& interfaceName, const PropertyName& propertyName, const sdbus::Variant& value, with_future_t) { return proxy_->setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).getResultAsFuture(); @@ -236,12 +260,24 @@ namespace sdbus { return proxy_->getAllPropertiesAsync().onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback)); } + template + PendingAsyncCall GetAllAsync(const InterfaceName& interfaceName, _Function&& callback, return_slot_t) + { + return proxy_->getAllPropertiesAsync().onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback), return_slot); + } + template PendingAsyncCall GetAllAsync(std::string_view interfaceName, _Function&& callback) { return proxy_->getAllPropertiesAsync().onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback)); } + template + PendingAsyncCall GetAllAsync(std::string_view interfaceName, _Function&& callback, return_slot_t) + { + return proxy_->getAllPropertiesAsync().onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback), return_slot); + } + std::future> GetAllAsync(const InterfaceName& interfaceName, with_future_t) { return proxy_->getAllPropertiesAsync().onInterface(interfaceName).getResultAsFuture(); diff --git a/include/sdbus-c++/TypeTraits.h b/include/sdbus-c++/TypeTraits.h index 7b146c57..164cd156 100644 --- a/include/sdbus-c++/TypeTraits.h +++ b/include/sdbus-c++/TypeTraits.h @@ -84,7 +84,7 @@ namespace sdbus { // Type-erased RAII-style handle to callbacks/subscriptions registered to sdbus-c++ using Slot = std::unique_ptr>; - // Tag specifying that an owning slot handle shall be returned from a registration/subscription function to the caller + // Tag specifying that an owning handle (so-called slot) of the logical resource shall be provided to the client struct return_slot_t { explicit return_slot_t() = default; }; inline constexpr return_slot_t return_slot{}; // Tag specifying that the library shall own the slot resulting from the call of the function (so-called floating slot) diff --git a/src/Proxy.cpp b/src/Proxy.cpp index 135b7020..0cb525c7 100644 --- a/src/Proxy.cpp +++ b/src/Proxy.cpp @@ -94,6 +94,11 @@ MethodCall Proxy::createMethodCall(const char* interfaceName, const char* method return connection_->createMethodCall(destination_.c_str(), objectPath_.c_str(), interfaceName, methodName); } +MethodReply Proxy::callMethod(const MethodCall& message) +{ + return Proxy::callMethod(message, /*timeout*/ 0); +} + MethodReply Proxy::callMethod(const MethodCall& message, uint64_t timeout) { SDBUS_THROW_ERROR_IF(!message.isValid(), "Invalid method call message provided", EINVAL); @@ -101,19 +106,44 @@ MethodReply Proxy::callMethod(const MethodCall& message, uint64_t timeout) return connection_->callMethod(message, timeout); } +PendingAsyncCall Proxy::callMethodAsync(const MethodCall& message, async_reply_handler asyncReplyCallback) +{ + return Proxy::callMethodAsync(message, std::move(asyncReplyCallback), /*timeout*/ 0); +} + +Slot Proxy::callMethodAsync(const MethodCall& message, async_reply_handler asyncReplyCallback, return_slot_t) +{ + return Proxy::callMethodAsync(message, std::move(asyncReplyCallback), /*timeout*/ 0, return_slot); +} + PendingAsyncCall Proxy::callMethodAsync(const MethodCall& message, async_reply_handler asyncReplyCallback, uint64_t timeout) { SDBUS_THROW_ERROR_IF(!message.isValid(), "Invalid async method call message provided", EINVAL); - auto callback = (void*)&Proxy::sdbus_async_reply_handler; - auto callData = std::make_shared(AsyncCalls::CallData{*this, std::move(asyncReplyCallback)}); - auto weakData = std::weak_ptr{callData}; + auto asyncCallInfo = std::make_shared(AsyncCallInfo{ .callback = std::move(asyncReplyCallback) + , .proxy = *this + , .floating = false }); + + asyncCallInfo->slot = connection_->callMethod(message, (void*)&Proxy::sdbus_async_reply_handler, asyncCallInfo.get(), timeout); - callData->slot = connection_->callMethod(message, callback, callData.get(), timeout); + auto asyncCallInfoWeakPtr = std::weak_ptr{asyncCallInfo}; - pendingAsyncCalls_.addCall(std::move(callData)); + floatingAsyncCallSlots_.push_back(std::move(asyncCallInfo)); - return {weakData}; + return {asyncCallInfoWeakPtr}; +} + +Slot Proxy::callMethodAsync(const MethodCall& message, async_reply_handler asyncReplyCallback, uint64_t timeout, return_slot_t) +{ + SDBUS_THROW_ERROR_IF(!message.isValid(), "Invalid async method call message provided", EINVAL); + + auto asyncCallInfo = std::make_unique(AsyncCallInfo{ .callback = std::move(asyncReplyCallback) + , .proxy = *this + , .floating = true }); + + asyncCallInfo->slot = connection_->callMethod(message, (void*)&Proxy::sdbus_async_reply_handler, asyncCallInfo.get(), timeout); + + return {asyncCallInfo.release(), [](void *ptr){ delete static_cast(ptr); }}; } std::future Proxy::callMethodAsync(const MethodCall& message, with_future_t) @@ -186,7 +216,7 @@ Slot Proxy::registerSignalHandler( const char* interfaceName void Proxy::unregister() { - pendingAsyncCalls_.clear(); + floatingAsyncCallSlots_.clear(); floatingSignalSlots_.clear(); } @@ -207,17 +237,17 @@ Message Proxy::getCurrentlyProcessedMessage() const int Proxy::sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError) { - auto* asyncCallData = static_cast(userData); - assert(asyncCallData != nullptr); - assert(asyncCallData->callback); - auto& proxy = asyncCallData->proxy; + auto* asyncCallInfo = static_cast(userData); + assert(asyncCallInfo != nullptr); + assert(asyncCallInfo->callback); + auto& proxy = asyncCallInfo->proxy; // We are removing the CallData item at the complete scope exit, after the callback has been invoked. // We can't do it earlier (before callback invocation for example), because CallBack data (slot release) // is the synchronization point between callback invocation and Proxy::unregister. SCOPE_EXIT { - proxy.pendingAsyncCalls_.removeCall(asyncCallData); + proxy.floatingAsyncCallSlots_.erase(asyncCallInfo); }; auto message = Message::Factory::create(sdbusMessage, &proxy.connection_->getSdBusInterface()); @@ -227,12 +257,12 @@ int Proxy::sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userDat const auto* error = sd_bus_message_get_error(sdbusMessage); if (error == nullptr) { - asyncCallData->callback(std::move(message), {}); + asyncCallInfo->callback(std::move(message), {}); } else { Error exception(Error::Name{error->name}, error->message); - asyncCallData->callback(std::move(message), std::move(exception)); + asyncCallInfo->callback(std::move(message), std::move(exception)); } }, retError); @@ -253,21 +283,64 @@ int Proxy::sdbus_signal_handler(sd_bus_message *sdbusMessage, void *userData, sd return ok ? 0 : -1; } +Proxy::FloatingAsyncCallSlots::~FloatingAsyncCallSlots() +{ + clear(); +} + +void Proxy::FloatingAsyncCallSlots::push_back(std::shared_ptr asyncCallInfo) +{ + std::lock_guard lock(mutex_); + if (!asyncCallInfo->finished) // The call may have finished in the meantime + slots_.emplace_back(std::move(asyncCallInfo)); +} + +void Proxy::FloatingAsyncCallSlots::erase(AsyncCallInfo* info) +{ + std::unique_lock lock(mutex_); + info->finished = true; + auto it = std::find_if(slots_.begin(), slots_.end(), [info](auto const& entry){ return entry.get() == info; }); + if (it != slots_.end()) + { + auto callInfo = std::move(*it); + slots_.erase(it); + lock.unlock(); + + // Releasing call slot pointer acquires global sd-bus mutex. We have to perform the release + // out of the `mutex_' critical section here, because if the `removeCall` is called by some + // thread and at the same time Proxy's async reply handler (which already holds global sd-bus + // mutex) is in progress in a different thread, we get double-mutex deadlock. + } +} + +void Proxy::FloatingAsyncCallSlots::clear() +{ + std::unique_lock lock(mutex_); + auto asyncCallSlots = std::move(slots_); + slots_ = {}; + lock.unlock(); + + // Releasing call slot pointer acquires global sd-bus mutex. We have to perform the release + // out of the `mutex_' critical section here, because if the `clear` is called by some thread + // and at the same time Proxy's async reply handler (which already holds global sd-bus + // mutex) is in progress in a different thread, we get double-mutex deadlock. +} + } namespace sdbus { -PendingAsyncCall::PendingAsyncCall(std::weak_ptr callData) - : callData_(std::move(callData)) +PendingAsyncCall::PendingAsyncCall(std::weak_ptr callInfo) + : callInfo_(std::move(callInfo)) { } void PendingAsyncCall::cancel() { - if (auto ptr = callData_.lock(); ptr != nullptr) + if (auto ptr = callInfo_.lock(); ptr != nullptr) { - auto* callData = static_cast(ptr.get()); - callData->proxy.pendingAsyncCalls_.removeCall(callData); + auto* asyncCallInfo = static_cast(ptr.get()); + asyncCallInfo->proxy.floatingAsyncCallSlots_.erase(asyncCallInfo); // At this point, the callData item is being deleted, leading to the release of the // sd-bus slot pointer. This release locks the global sd-bus mutex. If the async @@ -278,7 +351,7 @@ void PendingAsyncCall::cancel() bool PendingAsyncCall::isPending() const { - return !callData_.expired(); + return !callInfo_.expired(); } } diff --git a/src/Proxy.h b/src/Proxy.h index 24906ce0..4ee20474 100644 --- a/src/Proxy.h +++ b/src/Proxy.h @@ -58,8 +58,19 @@ namespace sdbus::internal { MethodCall createMethodCall(const InterfaceName& interfaceName, const MethodName& methodName) override; MethodCall createMethodCall(const char* interfaceName, const char* methodName) override; + MethodReply callMethod(const MethodCall& message) override; MethodReply callMethod(const MethodCall& message, uint64_t timeout) override; - PendingAsyncCall callMethodAsync(const MethodCall& message, async_reply_handler asyncReplyCallback, uint64_t timeout) override; + PendingAsyncCall callMethodAsync(const MethodCall& message, async_reply_handler asyncReplyCallback) override; + Slot callMethodAsync( const MethodCall& message + , async_reply_handler asyncReplyCallback + , return_slot_t ) override; + PendingAsyncCall callMethodAsync( const MethodCall& message + , async_reply_handler asyncReplyCallback + , uint64_t timeout ) override; + Slot callMethodAsync( const MethodCall& message + , async_reply_handler asyncReplyCallback + , uint64_t timeout + , return_slot_t ) override; std::future callMethodAsync(const MethodCall& message, with_future_t) override; std::future callMethodAsync(const MethodCall& message, uint64_t timeout, with_future_t) override; @@ -105,67 +116,30 @@ namespace sdbus::internal { Slot slot; }; - // We need to keep track of pending async calls. When the proxy is being destructed, we must - // remove all slots of these pending calls, otherwise in case when the connection outlives - // the proxy, we might get async reply handlers invoked for pending async calls after the proxy - // has been destroyed, which is a free ticket into the realm of undefined behavior. - class AsyncCalls + struct AsyncCallInfo + { + async_reply_handler callback; + Proxy& proxy; + Slot slot{}; + bool finished{false}; + bool floating; + }; + + // Container keeping track of pending async calls + class FloatingAsyncCallSlots { public: - struct CallData - { - Proxy& proxy; - async_reply_handler callback; - Slot slot{}; - bool finished{false}; - }; - - ~AsyncCalls() - { - clear(); - } - - void addCall(std::shared_ptr asyncCallData) - { - std::lock_guard lock(mutex_); - if (!asyncCallData->finished) // The call may have finished in the meantime - calls_.emplace_back(std::move(asyncCallData)); - } - - void removeCall(CallData* data) - { - std::unique_lock lock(mutex_); - data->finished = true; - if (auto it = std::find_if(calls_.begin(), calls_.end(), [data](auto const& entry){ return entry.get() == data; }); it != calls_.end()) - { - auto callData = std::move(*it); - calls_.erase(it); - lock.unlock(); - - // Releasing call slot pointer acquires global sd-bus mutex. We have to perform the release - // out of the `mutex_' critical section here, because if the `removeCall` is called by some - // thread and at the same time Proxy's async reply handler (which already holds global sd-bus - // mutex) is in progress in a different thread, we get double-mutex deadlock. - } - } - - void clear() - { - std::unique_lock lock(mutex_); - auto asyncCallSlots = std::move(calls_); - calls_ = {}; - lock.unlock(); - - // Releasing call slot pointer acquires global sd-bus mutex. We have to perform the release - // out of the `mutex_' critical section here, because if the `clear` is called by some thread - // and at the same time Proxy's async reply handler (which already holds global sd-bus - // mutex) is in progress in a different thread, we get double-mutex deadlock. - } + ~FloatingAsyncCallSlots(); + void push_back(std::shared_ptr asyncCallInfo); + void erase(AsyncCallInfo* info); + void clear(); private: std::mutex mutex_; - std::deque> calls_; - } pendingAsyncCalls_; + std::deque> slots_; + }; + + FloatingAsyncCallSlots floatingAsyncCallSlots_; }; } diff --git a/tests/integrationtests/DBusAsyncMethodsTests.cpp b/tests/integrationtests/DBusAsyncMethodsTests.cpp index c868649e..0012da10 100644 --- a/tests/integrationtests/DBusAsyncMethodsTests.cpp +++ b/tests/integrationtests/DBusAsyncMethodsTests.cpp @@ -198,6 +198,20 @@ TYPED_TEST(AsyncSdbusTestObject, CancelsPendingAsyncCallOnClientSide) ASSERT_THAT(future.wait_for(300ms), Eq(std::future_status::timeout)); } +TYPED_TEST(AsyncSdbusTestObject, CancelsPendingAsyncCallOnClientSideByDestroyingOwningSlot) +{ + std::promise promise; + auto future = promise.get_future(); + this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t /*res*/, std::optional /*err*/){ promise.set_value(1); }); + + { + auto slot = this->m_proxy->doOperationClientSideAsync(100, sdbus::return_slot); + // Now the slot is destroyed, cancelling the async call + } + + ASSERT_THAT(future.wait_for(300ms), Eq(std::future_status::timeout)); +} + TYPED_TEST(AsyncSdbusTestObject, AnswersThatAsyncCallIsNotPendingAfterItHasBeenCancelled) { std::promise promise; diff --git a/tests/integrationtests/TestProxy.cpp b/tests/integrationtests/TestProxy.cpp index 2eddffab..9248c79d 100644 --- a/tests/integrationtests/TestProxy.cpp +++ b/tests/integrationtests/TestProxy.cpp @@ -124,6 +124,17 @@ sdbus::PendingAsyncCall TestProxy::doOperationClientSideAsync(uint32_t param) }); } +Slot TestProxy::doOperationClientSideAsync(uint32_t param, sdbus::return_slot_t) +{ + return getProxy().callMethodAsync("doOperation") + .onInterface(sdbus::test::INTERFACE_NAME) + .withArguments(param) + .uponReplyInvoke([this](std::optional error, uint32_t returnValue) + { + this->onDoOperationReply(returnValue, std::move(error)); + }, sdbus::return_slot); +} + std::future TestProxy::doOperationClientSideAsync(uint32_t param, with_future_t) { return getProxy().callMethodAsync("doOperation") diff --git a/tests/integrationtests/TestProxy.h b/tests/integrationtests/TestProxy.h index 45797fc9..cd1cfca4 100644 --- a/tests/integrationtests/TestProxy.h +++ b/tests/integrationtests/TestProxy.h @@ -96,6 +96,7 @@ class TestProxy final : public sdbus::ProxyInterfaces< org::sdbuscpp::integratio void installDoOperationClientSideAsyncReplyHandler(std::function err)> handler); uint32_t doOperationWithTimeout(const std::chrono::microseconds &timeout, uint32_t param); sdbus::PendingAsyncCall doOperationClientSideAsync(uint32_t param); + [[nodiscard]] sdbus::Slot doOperationClientSideAsync(uint32_t param, sdbus::return_slot_t); std::future doOperationClientSideAsync(uint32_t param, with_future_t); std::future doOperationClientSideAsyncOnBasicAPILevel(uint32_t param); std::future doErroneousOperationClientSideAsync(with_future_t); From 4e8b4555cc7ae50b1a58cf53e9a6385b1945a5b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Fri, 19 Apr 2024 21:36:45 +0200 Subject: [PATCH 48/52] fix: disable move in generated adaptor and proxy classes (#435) Moving adaptor or proxy instances changes their `this` pointer. But `this` is captured by value in closures used by those instances, and this remains unchanged on move, leading to accessing an invalid instance when a lambda expression executes. Supporting move semantics would require unsubscribing/unregistering vtable, handlers, etc. and re-subscribing and re-registering all that, which is too complicated and may have side effects. Hence it has been decided that these classes are not moveable. One may use an indirection with e.g. `std::unique_ptr` to get move semantics. --- docs/using-sdbus-c++.md | 45 +++--- .../examplemanager-planet1-client-glue.h | 12 +- .../examplemanager-planet1-server-glue.h | 10 +- include/sdbus-c++/AdaptorInterfaces.h | 4 +- include/sdbus-c++/ProxyInterfaces.h | 4 +- include/sdbus-c++/StandardInterfaces.h | 145 +++++++++--------- tests/integrationtests/DBusGeneralTests.cpp | 12 +- .../integrationtests-adaptor.h | 16 +- .../integrationtests/integrationtests-proxy.h | 70 ++++----- tests/perftests/perftests-adaptor.h | 12 +- tests/perftests/perftests-proxy.h | 14 +- .../stresstests/celsius-thermometer-adaptor.h | 10 +- tests/stresstests/celsius-thermometer-proxy.h | 10 +- tests/stresstests/concatenator-adaptor.h | 12 +- tests/stresstests/concatenator-proxy.h | 12 +- .../fahrenheit-thermometer-adaptor.h | 20 +-- .../fahrenheit-thermometer-proxy.h | 22 +-- tools/xml2cpp-codegen/AdaptorGenerator.cpp | 14 +- tools/xml2cpp-codegen/ProxyGenerator.cpp | 24 +-- 19 files changed, 237 insertions(+), 231 deletions(-) diff --git a/docs/using-sdbus-c++.md b/docs/using-sdbus-c++.md index 7d6be9fd..86e31f0a 100644 --- a/docs/using-sdbus-c++.md +++ b/docs/using-sdbus-c++.md @@ -694,7 +694,7 @@ After running this through the code generator, we get the generated code that is For each interface in the XML IDL file the generator creates one class that represents it. The class is de facto an interface which shall be implemented by the class inheriting 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. -Generated adaptor classes support move semantics. They are moveable but not copyable. +Generated adaptor classes are not copyable and not moveable by design. One can create them on the heap and manage them in e.g. a `std::unique_ptr` if move semantics is needed (for example, when they are stored in a container). ```cpp /* @@ -718,30 +718,30 @@ public: protected: Concatenator_adaptor(sdbus::IObject& object) - : object_(&object) + : m_object(object) { - object_->registerMethod("concatenate").onInterface(INTERFACE_NAME).withInputParamNames("numbers", "separator").withOutputParamNames("concatenatedString").implementedAs([this](const std::vector& numbers, const std::string& separator){ return this->concatenate(numbers, separator); }); - object_->registerSignal("concatenated").onInterface(INTERFACE_NAME).withParameters("concatenatedString"); + m_object.registerMethod("concatenate").onInterface(INTERFACE_NAME).withInputParamNames("numbers", "separator").withOutputParamNames("concatenatedString").implementedAs([this](const std::vector& numbers, const std::string& separator){ return this->concatenate(numbers, separator); }); + m_object.registerSignal("concatenated").onInterface(INTERFACE_NAME).withParameters("concatenatedString"); } Concatenator_adaptor(const Concatenator_adaptor&) = delete; Concatenator_adaptor& operator=(const Concatenator_adaptor&) = delete; - Concatenator_adaptor(Concatenator_adaptor&&) = default; - Concatenator_adaptor& operator=(Concatenator_adaptor&&) = default; + Concatenator_adaptor(Concatenator_adaptor&&) = delete; + Concatenator_adaptor& operator=(Concatenator_adaptor&&) = delete; ~Concatenator_adaptor() = default; public: void emitConcatenated(const std::string& concatenatedString) { - object_->emitSignal("concatenated").onInterface(INTERFACE_NAME).withArguments(concatenatedString); + m_object.emitSignal("concatenated").onInterface(INTERFACE_NAME).withArguments(concatenatedString); } private: virtual std::string concatenate(const std::vector& numbers, const std::string& separator) = 0; private: - sdbus::IObject* object_; + sdbus::IObject& m_object; }; }} // namespaces @@ -753,7 +753,7 @@ private: Analogously to the adaptor classes described above, there is one proxy class generated for one interface in the XML IDL file. The class is de facto a proxy to the concrete single 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. -Generated proxy classes support move semantics. They are moveable but not copyable. +Generated proxy classes are not copyable and not moveable by design. One can create them on the heap and manage them in e.g. a `std::unique_ptr` if move semantics is needed (for example, when they are stored in a container). ```cpp /* @@ -777,15 +777,15 @@ public: protected: Concatenator_proxy(sdbus::IProxy& proxy) - : proxy_(&proxy) + : m_proxy(proxy) { - proxy_->uponSignal("concatenated").onInterface(INTERFACE_NAME).call([this](const std::string& concatenatedString){ this->onConcatenated(concatenatedString); }); + m_proxy.uponSignal("concatenated").onInterface(INTERFACE_NAME).call([this](const std::string& concatenatedString){ this->onConcatenated(concatenatedString); }); } Concatenator_proxy(const Concatenator_proxy&) = delete; Concatenator_proxy& operator=(const Concatenator_proxy&) = delete; - Concatenator_proxy(Concatenator_proxy&&) = default; - Concatenator_proxy& operator=(Concatenator_proxy&&) = default; + Concatenator_proxy(Concatenator_proxy&&) = delete; + Concatenator_proxy& operator=(Concatenator_proxy&&) = delete; ~Concatenator_proxy() = default; @@ -795,12 +795,12 @@ public: std::string concatenate(const std::vector& numbers, const std::string& separator) { std::string result; - proxy_->callMethod("concatenate").onInterface(INTERFACE_NAME).withArguments(numbers, separator).storeResultsTo(result); + m_proxy.callMethod("concatenate").onInterface(INTERFACE_NAME).withArguments(numbers, separator).storeResultsTo(result); return result; } private: - sdbus::IProxy* proxy_; + sdbus::IProxy& m_proxy; }; }} // namespaces @@ -1405,9 +1405,9 @@ class PropertyProvider_adaptor public: PropertyProvider_adaptor(sdbus::IObject& object) - : object_(&object) + : m_object(object) { - object_->registerProperty("status").onInterface(INTERFACE_NAME).withGetter([this](){ return this->status(); }).withSetter([this](const uint32_t& value){ this->status(value); }); + m_object.registerProperty("status").onInterface(INTERFACE_NAME).withGetter([this](){ return this->status(); }).withSetter([this](const uint32_t& value){ this->status(value); }); } ~PropertyProvider_adaptor() = default; @@ -1434,13 +1434,13 @@ public: // getting the property value uint32_t status() { - return object_->getProperty("status").onInterface(INTERFACE_NAME); + return m_object.getProperty("status").onInterface(INTERFACE_NAME); } // setting the property value void status(const uint32_t& value) { - object_->setProperty("status").onInterface(INTERFACE_NAME).toValue(value); + m_object.setProperty("status").onInterface(INTERFACE_NAME).toValue(value); } /*...*/ @@ -1487,13 +1487,13 @@ public: // getting the property value sdbus::PendingAsyncCall status() { - return object_->getPropertyAsync("status").onInterface(INTERFACE_NAME).uponReplyInvoke([this](std::optional error, const sdbus::Variant& value){ this->onStatusPropertyGetReply(value.get(), std::move(error)); }); + return m_object.getPropertyAsync("status").onInterface(INTERFACE_NAME).uponReplyInvoke([this](std::optional error, const sdbus::Variant& value){ this->onStatusPropertyGetReply(value.get(), std::move(error)); }); } // setting the property value void status(const uint32_t& value) { - object_->setProperty("status").onInterface(INTERFACE_NAME).toValue(value); + m_object.setProperty("status").onInterface(INTERFACE_NAME).toValue(value); } /*...*/ @@ -1786,7 +1786,8 @@ sdbus-c++ v2 is a major release that comes with a number of breaking API/ABI/beh * Callbacks taking `const sdbus::Error* error` were changed to take `std::optional`, which better expresses the intent and meaning. * `getInterfaceName()`, `getMemberName()`, `getSender()`, `getPath()` and `getDestination()` methods of `Message` class now return `const char*` instead of `std::string`, for efficiency reasons. * `peekType()` method of `Message` class now returns a pair of `char` (type signature) and `const char*` (contents signature), for expressiveness and efficiency reasons. -* D-Bus signatures when using high-level API are now assembled at compile time. There are breaking changes inside `signature_of` type traits and `Message` serialization/deserialization methods. This only interests you if you extend sdbus-c++ type system with your own types. See the updated tutorial on extending sdbus-c++ type system. +* D-Bus signatures when using high-level API are now assembled at compile time. There are breaking changes inside `signature_of` type traits and `Message` serialization/deserialization methods. This only interests you if you extend sdbus-c++ type system with your own types. See the updated tutorial on extending sdbus-c++ type system. +* Generated adaptor and proxy classes are not moveable anymore. * Types and methods marked deprecated in sdbus-c++ v1 were removed completely. * CMake options got `SDBUSCPP_` prefix for better usability and minimal risk of conflicts in downstream CMake projects. `SDBUSCPP_INSTALL` CMake option was added. * CMake components got `sdbus-c++-` prefix. diff --git a/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-client-glue.h b/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-client-glue.h index b312656e..cc68dfae 100644 --- a/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-client-glue.h +++ b/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-client-glue.h @@ -21,14 +21,14 @@ class Planet1_proxy protected: Planet1_proxy(sdbus::IProxy& proxy) - : proxy_(&proxy) + : m_proxy(proxy) { } Planet1_proxy(const Planet1_proxy&) = delete; Planet1_proxy& operator=(const Planet1_proxy&) = delete; - Planet1_proxy(Planet1_proxy&&) = default; - Planet1_proxy& operator=(Planet1_proxy&&) = default; + Planet1_proxy(Planet1_proxy&&) = delete; + Planet1_proxy& operator=(Planet1_proxy&&) = delete; ~Planet1_proxy() = default; @@ -40,18 +40,18 @@ class Planet1_proxy uint64_t GetPopulation() { uint64_t result; - proxy_->callMethod("GetPopulation").onInterface(INTERFACE_NAME).storeResultsTo(result); + m_proxy.callMethod("GetPopulation").onInterface(INTERFACE_NAME).storeResultsTo(result); return result; } public: std::string Name() { - return proxy_->getProperty("Name").onInterface(INTERFACE_NAME).get(); + return m_proxy.getProperty("Name").onInterface(INTERFACE_NAME).get(); } private: - sdbus::IProxy* proxy_; + sdbus::IProxy& m_proxy; }; }}} // namespaces diff --git a/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-server-glue.h b/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-server-glue.h index b2ca89ce..3cc1a0a7 100644 --- a/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-server-glue.h +++ b/examples/org.freedesktop.DBus.ObjectManager/examplemanager-planet1-server-glue.h @@ -21,20 +21,20 @@ class Planet1_adaptor protected: Planet1_adaptor(sdbus::IObject& object) - : object_(&object) + : m_object(object) { } Planet1_adaptor(const Planet1_adaptor&) = delete; Planet1_adaptor& operator=(const Planet1_adaptor&) = delete; - Planet1_adaptor(Planet1_adaptor&&) = default; - Planet1_adaptor& operator=(Planet1_adaptor&&) = default; + Planet1_adaptor(Planet1_adaptor&&) = delete; + Planet1_adaptor& operator=(Planet1_adaptor&&) = delete; ~Planet1_adaptor() = default; void registerAdaptor() { - object_->addVTable( sdbus::registerMethod("GetPopulation").withOutputParamNames("population").implementedAs([this](){ return this->GetPopulation(); }) + m_object.addVTable( sdbus::registerMethod("GetPopulation").withOutputParamNames("population").implementedAs([this](){ return this->GetPopulation(); }) , sdbus::registerProperty("Name").withGetter([this](){ return this->Name(); }) ).forInterface(INTERFACE_NAME); } @@ -46,7 +46,7 @@ class Planet1_adaptor virtual std::string Name() = 0; private: - sdbus::IObject* object_; + sdbus::IObject& m_object; }; }}} // namespaces diff --git a/include/sdbus-c++/AdaptorInterfaces.h b/include/sdbus-c++/AdaptorInterfaces.h index b0898134..550a0959 100644 --- a/include/sdbus-c++/AdaptorInterfaces.h +++ b/include/sdbus-c++/AdaptorInterfaces.h @@ -142,8 +142,8 @@ namespace sdbus { AdaptorInterfaces(const AdaptorInterfaces&) = delete; AdaptorInterfaces& operator=(const AdaptorInterfaces&) = delete; - AdaptorInterfaces(AdaptorInterfaces&&) = default; - AdaptorInterfaces& operator=(AdaptorInterfaces&&) = default; + AdaptorInterfaces(AdaptorInterfaces&&) = delete; + AdaptorInterfaces& operator=(AdaptorInterfaces&&) = delete; ~AdaptorInterfaces() = default; }; diff --git a/include/sdbus-c++/ProxyInterfaces.h b/include/sdbus-c++/ProxyInterfaces.h index 6cbb804c..72f25c6b 100644 --- a/include/sdbus-c++/ProxyInterfaces.h +++ b/include/sdbus-c++/ProxyInterfaces.h @@ -207,8 +207,8 @@ namespace sdbus { ProxyInterfaces(const ProxyInterfaces&) = delete; ProxyInterfaces& operator=(const ProxyInterfaces&) = delete; - ProxyInterfaces(ProxyInterfaces&&) = default; - ProxyInterfaces& operator=(ProxyInterfaces&&) = default; + ProxyInterfaces(ProxyInterfaces&&) = delete; + ProxyInterfaces& operator=(ProxyInterfaces&&) = delete; ~ProxyInterfaces() = default; }; diff --git a/include/sdbus-c++/StandardInterfaces.h b/include/sdbus-c++/StandardInterfaces.h index ad447841..4508327c 100644 --- a/include/sdbus-c++/StandardInterfaces.h +++ b/include/sdbus-c++/StandardInterfaces.h @@ -44,14 +44,14 @@ namespace sdbus { protected: Peer_proxy(sdbus::IProxy& proxy) - : proxy_(&proxy) + : m_proxy(proxy) { } Peer_proxy(const Peer_proxy&) = delete; Peer_proxy& operator=(const Peer_proxy&) = delete; - Peer_proxy(Peer_proxy&&) = default; - Peer_proxy& operator=(Peer_proxy&&) = default; + Peer_proxy(Peer_proxy&&) = delete; + Peer_proxy& operator=(Peer_proxy&&) = delete; ~Peer_proxy() = default; @@ -62,18 +62,18 @@ namespace sdbus { public: void Ping() { - proxy_->callMethod("Ping").onInterface(INTERFACE_NAME); + m_proxy.callMethod("Ping").onInterface(INTERFACE_NAME); } std::string GetMachineId() { std::string machineUUID; - proxy_->callMethod("GetMachineId").onInterface(INTERFACE_NAME).storeResultsTo(machineUUID); + m_proxy.callMethod("GetMachineId").onInterface(INTERFACE_NAME).storeResultsTo(machineUUID); return machineUUID; } private: - sdbus::IProxy* proxy_; + sdbus::IProxy& m_proxy; }; // Proxy for introspection @@ -83,14 +83,14 @@ namespace sdbus { protected: Introspectable_proxy(sdbus::IProxy& proxy) - : proxy_(&proxy) + : m_proxy(proxy) { } Introspectable_proxy(const Introspectable_proxy&) = delete; Introspectable_proxy& operator=(const Introspectable_proxy&) = delete; - Introspectable_proxy(Introspectable_proxy&&) = default; - Introspectable_proxy& operator=(Introspectable_proxy&&) = default; + Introspectable_proxy(Introspectable_proxy&&) = delete; + Introspectable_proxy& operator=(Introspectable_proxy&&) = delete; ~Introspectable_proxy() = default; @@ -102,12 +102,12 @@ namespace sdbus { std::string Introspect() { std::string xml; - proxy_->callMethod("Introspect").onInterface(INTERFACE_NAME).storeResultsTo(xml); + m_proxy.callMethod("Introspect").onInterface(INTERFACE_NAME).storeResultsTo(xml); return xml; } private: - sdbus::IProxy* proxy_; + sdbus::IProxy& m_proxy; }; // Proxy for properties @@ -117,21 +117,21 @@ namespace sdbus { protected: Properties_proxy(sdbus::IProxy& proxy) - : proxy_(&proxy) + : m_proxy(proxy) { } Properties_proxy(const Properties_proxy&) = delete; Properties_proxy& operator=(const Properties_proxy&) = delete; - Properties_proxy(Properties_proxy&&) = default; - Properties_proxy& operator=(Properties_proxy&&) = default; + Properties_proxy(Properties_proxy&&) = delete; + Properties_proxy& operator=(Properties_proxy&&) = delete; ~Properties_proxy() = default; void registerProxy() { - proxy_ - ->uponSignal("PropertiesChanged") + m_proxy + .uponSignal("PropertiesChanged") .onInterface(INTERFACE_NAME) .call([this]( const InterfaceName& interfaceName , const std::map& changedProperties @@ -148,148 +148,148 @@ namespace sdbus { public: sdbus::Variant Get(const InterfaceName& interfaceName, const PropertyName& propertyName) { - return proxy_->getProperty(propertyName).onInterface(interfaceName); + return m_proxy.getProperty(propertyName).onInterface(interfaceName); } sdbus::Variant Get(std::string_view interfaceName, std::string_view propertyName) { - return proxy_->getProperty(propertyName).onInterface(interfaceName); + return m_proxy.getProperty(propertyName).onInterface(interfaceName); } template PendingAsyncCall GetAsync(const InterfaceName& interfaceName, const PropertyName& propertyName, _Function&& callback) { - return proxy_->getPropertyAsync(propertyName).onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback)); + return m_proxy.getPropertyAsync(propertyName).onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback)); } template [[nodiscard]] Slot GetAsync(const InterfaceName& interfaceName, const PropertyName& propertyName, _Function&& callback, return_slot_t) { - return proxy_->getPropertyAsync(propertyName).onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback), return_slot); + return m_proxy.getPropertyAsync(propertyName).onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback), return_slot); } template PendingAsyncCall GetAsync(std::string_view interfaceName, std::string_view propertyName, _Function&& callback) { - return proxy_->getPropertyAsync(propertyName).onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback)); + return m_proxy.getPropertyAsync(propertyName).onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback)); } template [[nodiscard]] Slot GetAsync(std::string_view interfaceName, std::string_view propertyName, _Function&& callback, return_slot_t) { - return proxy_->getPropertyAsync(propertyName).onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback), return_slot); + return m_proxy.getPropertyAsync(propertyName).onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback), return_slot); } std::future GetAsync(const InterfaceName& interfaceName, const PropertyName& propertyName, with_future_t) { - return proxy_->getPropertyAsync(propertyName).onInterface(interfaceName).getResultAsFuture(); + return m_proxy.getPropertyAsync(propertyName).onInterface(interfaceName).getResultAsFuture(); } std::future GetAsync(std::string_view interfaceName, std::string_view propertyName, with_future_t) { - return proxy_->getPropertyAsync(propertyName).onInterface(interfaceName).getResultAsFuture(); + return m_proxy.getPropertyAsync(propertyName).onInterface(interfaceName).getResultAsFuture(); } void Set(const InterfaceName& interfaceName, const PropertyName& propertyName, const sdbus::Variant& value) { - proxy_->setProperty(propertyName).onInterface(interfaceName).toValue(value); + m_proxy.setProperty(propertyName).onInterface(interfaceName).toValue(value); } void Set(std::string_view interfaceName, const std::string_view propertyName, const sdbus::Variant& value) { - proxy_->setProperty(propertyName).onInterface(interfaceName).toValue(value); + m_proxy.setProperty(propertyName).onInterface(interfaceName).toValue(value); } void Set(const InterfaceName& interfaceName, const PropertyName& propertyName, const sdbus::Variant& value, dont_expect_reply_t) { - proxy_->setProperty(propertyName).onInterface(interfaceName).toValue(value, dont_expect_reply); + m_proxy.setProperty(propertyName).onInterface(interfaceName).toValue(value, dont_expect_reply); } void Set(std::string_view interfaceName, const std::string_view propertyName, const sdbus::Variant& value, dont_expect_reply_t) { - proxy_->setProperty(propertyName).onInterface(interfaceName).toValue(value, dont_expect_reply); + m_proxy.setProperty(propertyName).onInterface(interfaceName).toValue(value, dont_expect_reply); } template PendingAsyncCall SetAsync(const InterfaceName& interfaceName, const PropertyName& propertyName, const sdbus::Variant& value, _Function&& callback) { - return proxy_->setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).uponReplyInvoke(std::forward<_Function>(callback)); + return m_proxy.setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).uponReplyInvoke(std::forward<_Function>(callback)); } template PendingAsyncCall SetAsync(const InterfaceName& interfaceName, const PropertyName& propertyName, const sdbus::Variant& value, _Function&& callback, return_slot_t) { - return proxy_->setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).uponReplyInvoke(std::forward<_Function>(callback), return_slot); + return m_proxy.setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).uponReplyInvoke(std::forward<_Function>(callback), return_slot); } template PendingAsyncCall SetAsync(std::string_view interfaceName, std::string_view propertyName, const sdbus::Variant& value, _Function&& callback) { - return proxy_->setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).uponReplyInvoke(std::forward<_Function>(callback)); + return m_proxy.setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).uponReplyInvoke(std::forward<_Function>(callback)); } template PendingAsyncCall SetAsync(std::string_view interfaceName, std::string_view propertyName, const sdbus::Variant& value, _Function&& callback, return_slot_t) { - return proxy_->setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).uponReplyInvoke(std::forward<_Function>(callback), return_slot); + return m_proxy.setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).uponReplyInvoke(std::forward<_Function>(callback), return_slot); } std::future SetAsync(const InterfaceName& interfaceName, const PropertyName& propertyName, const sdbus::Variant& value, with_future_t) { - return proxy_->setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).getResultAsFuture(); + return m_proxy.setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).getResultAsFuture(); } std::future SetAsync(std::string_view interfaceName, std::string_view propertyName, const sdbus::Variant& value, with_future_t) { - return proxy_->setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).getResultAsFuture(); + return m_proxy.setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).getResultAsFuture(); } std::map GetAll(const InterfaceName& interfaceName) { - return proxy_->getAllProperties().onInterface(interfaceName); + return m_proxy.getAllProperties().onInterface(interfaceName); } std::map GetAll(std::string_view interfaceName) { - return proxy_->getAllProperties().onInterface(interfaceName); + return m_proxy.getAllProperties().onInterface(interfaceName); } template PendingAsyncCall GetAllAsync(const InterfaceName& interfaceName, _Function&& callback) { - return proxy_->getAllPropertiesAsync().onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback)); + return m_proxy.getAllPropertiesAsync().onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback)); } template PendingAsyncCall GetAllAsync(const InterfaceName& interfaceName, _Function&& callback, return_slot_t) { - return proxy_->getAllPropertiesAsync().onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback), return_slot); + return m_proxy.getAllPropertiesAsync().onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback), return_slot); } template PendingAsyncCall GetAllAsync(std::string_view interfaceName, _Function&& callback) { - return proxy_->getAllPropertiesAsync().onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback)); + return m_proxy.getAllPropertiesAsync().onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback)); } template PendingAsyncCall GetAllAsync(std::string_view interfaceName, _Function&& callback, return_slot_t) { - return proxy_->getAllPropertiesAsync().onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback), return_slot); + return m_proxy.getAllPropertiesAsync().onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback), return_slot); } std::future> GetAllAsync(const InterfaceName& interfaceName, with_future_t) { - return proxy_->getAllPropertiesAsync().onInterface(interfaceName).getResultAsFuture(); + return m_proxy.getAllPropertiesAsync().onInterface(interfaceName).getResultAsFuture(); } std::future> GetAllAsync(std::string_view interfaceName, with_future_t) { - return proxy_->getAllPropertiesAsync().onInterface(interfaceName).getResultAsFuture(); + return m_proxy.getAllPropertiesAsync().onInterface(interfaceName).getResultAsFuture(); } private: - sdbus::IProxy* proxy_; + sdbus::IProxy& m_proxy; }; // Proxy for object manager @@ -299,21 +299,21 @@ namespace sdbus { protected: ObjectManager_proxy(sdbus::IProxy& proxy) - : proxy_(&proxy) + : m_proxy(proxy) { } ObjectManager_proxy(const ObjectManager_proxy&) = delete; ObjectManager_proxy& operator=(const ObjectManager_proxy&) = delete; - ObjectManager_proxy(ObjectManager_proxy&&) = default; - ObjectManager_proxy& operator=(ObjectManager_proxy&&) = default; + ObjectManager_proxy(ObjectManager_proxy&&) = delete; + ObjectManager_proxy& operator=(ObjectManager_proxy&&) = delete; ~ObjectManager_proxy() = default; void registerProxy() { - proxy_ - ->uponSignal("InterfacesAdded") + m_proxy + .uponSignal("InterfacesAdded") .onInterface(INTERFACE_NAME) .call([this]( const sdbus::ObjectPath& objectPath , const std::map>& interfacesAndProperties ) @@ -321,7 +321,8 @@ namespace sdbus { this->onInterfacesAdded(objectPath, interfacesAndProperties); }); - proxy_->uponSignal("InterfacesRemoved") + m_proxy + .uponSignal("InterfacesRemoved") .onInterface(INTERFACE_NAME) .call([this]( const sdbus::ObjectPath& objectPath , const std::vector& interfaces ) @@ -339,12 +340,12 @@ namespace sdbus { std::map>> GetManagedObjects() { std::map>> objectsInterfacesAndProperties; - proxy_->callMethod("GetManagedObjects").onInterface(INTERFACE_NAME).storeResultsTo(objectsInterfacesAndProperties); + m_proxy.callMethod("GetManagedObjects").onInterface(INTERFACE_NAME).storeResultsTo(objectsInterfacesAndProperties); return objectsInterfacesAndProperties; } private: - sdbus::IProxy* proxy_; + sdbus::IProxy& m_proxy; }; // Adaptors for the above-listed standard D-Bus interfaces are not necessary because the functionality @@ -357,14 +358,14 @@ namespace sdbus { static inline const char* INTERFACE_NAME = "org.freedesktop.DBus.Properties"; protected: - Properties_adaptor(sdbus::IObject& object) : object_(&object) + Properties_adaptor(sdbus::IObject& object) : m_object(object) { } Properties_adaptor(const Properties_adaptor&) = delete; Properties_adaptor& operator=(const Properties_adaptor&) = delete; - Properties_adaptor(Properties_adaptor&&) = default; - Properties_adaptor& operator=(Properties_adaptor&&) = default; + Properties_adaptor(Properties_adaptor&&) = delete; + Properties_adaptor& operator=(Properties_adaptor&&) = delete; ~Properties_adaptor() = default; @@ -375,26 +376,26 @@ namespace sdbus { public: void emitPropertiesChangedSignal(const InterfaceName& interfaceName, const std::vector& properties) { - object_->emitPropertiesChangedSignal(interfaceName, properties); + m_object.emitPropertiesChangedSignal(interfaceName, properties); } void emitPropertiesChangedSignal(const char* interfaceName, const std::vector& properties) { - object_->emitPropertiesChangedSignal(interfaceName, properties); + m_object.emitPropertiesChangedSignal(interfaceName, properties); } void emitPropertiesChangedSignal(const InterfaceName& interfaceName) { - object_->emitPropertiesChangedSignal(interfaceName); + m_object.emitPropertiesChangedSignal(interfaceName); } void emitPropertiesChangedSignal(const char* interfaceName) { - object_->emitPropertiesChangedSignal(interfaceName); + m_object.emitPropertiesChangedSignal(interfaceName); } private: - sdbus::IObject* object_; + sdbus::IObject& m_object; }; /*! @@ -412,24 +413,24 @@ namespace sdbus { static inline const char* INTERFACE_NAME = "org.freedesktop.DBus.ObjectManager"; protected: - explicit ObjectManager_adaptor(sdbus::IObject& object) : object_(&object) + explicit ObjectManager_adaptor(sdbus::IObject& object) : m_object(object) { } ObjectManager_adaptor(const ObjectManager_adaptor&) = delete; ObjectManager_adaptor& operator=(const ObjectManager_adaptor&) = delete; - ObjectManager_adaptor(ObjectManager_adaptor&&) = default; - ObjectManager_adaptor& operator=(ObjectManager_adaptor&&) = default; + ObjectManager_adaptor(ObjectManager_adaptor&&) = delete; + ObjectManager_adaptor& operator=(ObjectManager_adaptor&&) = delete; ~ObjectManager_adaptor() = default; void registerAdaptor() { - object_->addObjectManager(); + m_object.addObjectManager(); } private: - sdbus::IObject* object_; + sdbus::IObject& m_object; }; /*! @@ -447,14 +448,14 @@ namespace sdbus { { protected: explicit ManagedObject_adaptor(sdbus::IObject& object) - : object_(&object) + : m_object(object) { } ManagedObject_adaptor(const ManagedObject_adaptor&) = delete; ManagedObject_adaptor& operator=(const ManagedObject_adaptor&) = delete; - ManagedObject_adaptor(ManagedObject_adaptor&&) = default; - ManagedObject_adaptor& operator=(ManagedObject_adaptor&&) = default; + ManagedObject_adaptor(ManagedObject_adaptor&&) = delete; + ManagedObject_adaptor& operator=(ManagedObject_adaptor&&) = delete; ~ManagedObject_adaptor() = default; @@ -470,7 +471,7 @@ namespace sdbus { */ void emitInterfacesAddedSignal() { - object_->emitInterfacesAddedSignal(); + m_object.emitInterfacesAddedSignal(); } /*! @@ -480,7 +481,7 @@ namespace sdbus { */ void emitInterfacesAddedSignal(const std::vector& interfaces) { - object_->emitInterfacesAddedSignal(interfaces); + m_object.emitInterfacesAddedSignal(interfaces); } /*! @@ -490,7 +491,7 @@ namespace sdbus { */ void emitInterfacesRemovedSignal() { - object_->emitInterfacesRemovedSignal(); + m_object.emitInterfacesRemovedSignal(); } /*! @@ -500,11 +501,11 @@ namespace sdbus { */ void emitInterfacesRemovedSignal(const std::vector& interfaces) { - object_->emitInterfacesRemovedSignal(interfaces); + m_object.emitInterfacesRemovedSignal(interfaces); } private: - sdbus::IObject* object_; + sdbus::IObject& m_object; }; } diff --git a/tests/integrationtests/DBusGeneralTests.cpp b/tests/integrationtests/DBusGeneralTests.cpp index 294cd0f3..c5c92b40 100644 --- a/tests/integrationtests/DBusGeneralTests.cpp +++ b/tests/integrationtests/DBusGeneralTests.cpp @@ -63,16 +63,16 @@ TEST(AdaptorAndProxy, CanBeConstructedSuccesfully) connection->releaseName(SERVICE_NAME); } -TEST(AProxy, SupportsMoveSemantics) +TEST(AProxy, DoesNotSupportMoveSemantics) { - static_assert(std::is_move_constructible_v); - static_assert(std::is_move_assignable_v); + static_assert(!std::is_move_constructible_v); + static_assert(!std::is_move_assignable_v); } -TEST(AnAdaptor, SupportsMoveSemantics) +TEST(AnAdaptor, DoesNotSupportMoveSemantics) { - static_assert(std::is_move_constructible_v); - static_assert(std::is_move_assignable_v); + static_assert(!std::is_move_constructible_v); + static_assert(!std::is_move_assignable_v); } TYPED_TEST(AConnection, WillCallCallbackHandlerForIncomingMessageMatchingMatchRule) diff --git a/tests/integrationtests/integrationtests-adaptor.h b/tests/integrationtests/integrationtests-adaptor.h index a34146fa..fbe25e5d 100644 --- a/tests/integrationtests/integrationtests-adaptor.h +++ b/tests/integrationtests/integrationtests-adaptor.h @@ -20,20 +20,20 @@ class integrationtests_adaptor protected: integrationtests_adaptor(sdbus::IObject& object) - : object_(&object) + : m_object(object) { } integrationtests_adaptor(const integrationtests_adaptor&) = delete; integrationtests_adaptor& operator=(const integrationtests_adaptor&) = delete; - integrationtests_adaptor(integrationtests_adaptor&&) = default; - integrationtests_adaptor& operator=(integrationtests_adaptor&&) = default; + integrationtests_adaptor(integrationtests_adaptor&&) = delete; + integrationtests_adaptor& operator=(integrationtests_adaptor&&) = delete; ~integrationtests_adaptor() = default; void registerAdaptor() { - object_->addVTable( sdbus::setInterfaceFlags().markAsDeprecated().withPropertyUpdateBehavior(sdbus::Flags::EMITS_NO_SIGNAL) + m_object.addVTable( sdbus::setInterfaceFlags().markAsDeprecated().withPropertyUpdateBehavior(sdbus::Flags::EMITS_NO_SIGNAL) , sdbus::registerMethod("noArgNoReturn").implementedAs([this](){ return this->noArgNoReturn(); }) , sdbus::registerMethod("getInt").withOutputParamNames("anInt").implementedAs([this](){ return this->getInt(); }) , sdbus::registerMethod("getTuple").withOutputParamNames("arg0", "arg1").implementedAs([this](){ return this->getTuple(); }) @@ -67,17 +67,17 @@ class integrationtests_adaptor public: void emitSimpleSignal() { - object_->emitSignal("simpleSignal").onInterface(INTERFACE_NAME); + m_object.emitSignal("simpleSignal").onInterface(INTERFACE_NAME); } void emitSignalWithMap(const std::map& aMap) { - object_->emitSignal("signalWithMap").onInterface(INTERFACE_NAME).withArguments(aMap); + m_object.emitSignal("signalWithMap").onInterface(INTERFACE_NAME).withArguments(aMap); } void emitSignalWithVariant(const sdbus::Variant& aVariant) { - object_->emitSignal("signalWithVariant").onInterface(INTERFACE_NAME).withArguments(aVariant); + m_object.emitSignal("signalWithVariant").onInterface(INTERFACE_NAME).withArguments(aVariant); } private: @@ -111,7 +111,7 @@ class integrationtests_adaptor virtual std::string state() = 0; private: - sdbus::IObject* object_; + sdbus::IObject& m_object; }; }} // namespaces diff --git a/tests/integrationtests/integrationtests-proxy.h b/tests/integrationtests/integrationtests-proxy.h index 48e91a56..4e8ed86b 100644 --- a/tests/integrationtests/integrationtests-proxy.h +++ b/tests/integrationtests/integrationtests-proxy.h @@ -20,22 +20,22 @@ class integrationtests_proxy protected: integrationtests_proxy(sdbus::IProxy& proxy) - : proxy_(&proxy) + : m_proxy(proxy) { } integrationtests_proxy(const integrationtests_proxy&) = delete; integrationtests_proxy& operator=(const integrationtests_proxy&) = delete; - integrationtests_proxy(integrationtests_proxy&&) = default; - integrationtests_proxy& operator=(integrationtests_proxy&&) = default; + integrationtests_proxy(integrationtests_proxy&&) = delete; + integrationtests_proxy& operator=(integrationtests_proxy&&) = delete; ~integrationtests_proxy() = default; void registerProxy() { - simpleSignalSlot_ = proxy_->uponSignal("simpleSignal").onInterface(INTERFACE_NAME).call([this](){ this->onSimpleSignal(); }, sdbus::return_slot); - proxy_->uponSignal("signalWithMap").onInterface(INTERFACE_NAME).call([this](const std::map& aMap){ this->onSignalWithMap(aMap); }); - proxy_->uponSignal("signalWithVariant").onInterface(INTERFACE_NAME).call([this](const sdbus::Variant& aVariant){ this->onSignalWithVariant(aVariant); }); + simpleSignalSlot_ = m_proxy.uponSignal("simpleSignal").onInterface(INTERFACE_NAME).call([this](){ this->onSimpleSignal(); }, sdbus::return_slot); + m_proxy.uponSignal("signalWithMap").onInterface(INTERFACE_NAME).call([this](const std::map& aMap){ this->onSignalWithMap(aMap); }); + m_proxy.uponSignal("signalWithVariant").onInterface(INTERFACE_NAME).call([this](const sdbus::Variant& aVariant){ this->onSignalWithVariant(aVariant); }); } virtual void onSimpleSignal() = 0; @@ -45,144 +45,144 @@ class integrationtests_proxy public: void noArgNoReturn() { - proxy_->callMethod("noArgNoReturn").onInterface(INTERFACE_NAME); + m_proxy.callMethod("noArgNoReturn").onInterface(INTERFACE_NAME); } int32_t getInt() { int32_t result; - proxy_->callMethod("getInt").onInterface(INTERFACE_NAME).storeResultsTo(result); + m_proxy.callMethod("getInt").onInterface(INTERFACE_NAME).storeResultsTo(result); return result; } std::tuple getTuple() { std::tuple result; - proxy_->callMethod("getTuple").onInterface(INTERFACE_NAME).storeResultsTo(result); + m_proxy.callMethod("getTuple").onInterface(INTERFACE_NAME).storeResultsTo(result); return result; } double multiply(const int64_t& a, const double& b) { double result; - proxy_->callMethod("multiply").onInterface(INTERFACE_NAME).withArguments(a, b).storeResultsTo(result); + m_proxy.callMethod("multiply").onInterface(INTERFACE_NAME).withArguments(a, b).storeResultsTo(result); return result; } void multiplyWithNoReply(const int64_t& a, const double& b) { - proxy_->callMethod("multiplyWithNoReply").onInterface(INTERFACE_NAME).withArguments(a, b).dontExpectReply(); + m_proxy.callMethod("multiplyWithNoReply").onInterface(INTERFACE_NAME).withArguments(a, b).dontExpectReply(); } std::vector getInts16FromStruct(const sdbus::Struct>& arg0) { std::vector result; - proxy_->callMethod("getInts16FromStruct").onInterface(INTERFACE_NAME).withArguments(arg0).storeResultsTo(result); + m_proxy.callMethod("getInts16FromStruct").onInterface(INTERFACE_NAME).withArguments(arg0).storeResultsTo(result); return result; } sdbus::Variant processVariant(const sdbus::Variant& variant) { sdbus::Variant result; - proxy_->callMethod("processVariant").onInterface(INTERFACE_NAME).withArguments(variant).storeResultsTo(result); + m_proxy.callMethod("processVariant").onInterface(INTERFACE_NAME).withArguments(variant).storeResultsTo(result); return result; } std::variant processVariant(const std::variant& variant) { std::variant result; - proxy_->callMethod("processVariant").onInterface(INTERFACE_NAME).withArguments(variant).storeResultsTo(result); + m_proxy.callMethod("processVariant").onInterface(INTERFACE_NAME).withArguments(variant).storeResultsTo(result); return result; } std::map getMapOfVariants(const std::vector& x, const sdbus::Struct& y) { std::map result; - proxy_->callMethod("getMapOfVariants").onInterface(INTERFACE_NAME).withArguments(x, y).storeResultsTo(result); + m_proxy.callMethod("getMapOfVariants").onInterface(INTERFACE_NAME).withArguments(x, y).storeResultsTo(result); return result; } sdbus::Struct>> getStructInStruct() { sdbus::Struct>> result; - proxy_->callMethod("getStructInStruct").onInterface(INTERFACE_NAME).storeResultsTo(result); + m_proxy.callMethod("getStructInStruct").onInterface(INTERFACE_NAME).storeResultsTo(result); return result; } int32_t sumStructItems(const sdbus::Struct& arg0, const sdbus::Struct& arg1) { int32_t result; - proxy_->callMethod("sumStructItems").onInterface(INTERFACE_NAME).withArguments(arg0, arg1).storeResultsTo(result); + m_proxy.callMethod("sumStructItems").onInterface(INTERFACE_NAME).withArguments(arg0, arg1).storeResultsTo(result); return result; } uint32_t sumArrayItems(const std::vector& arg0, const std::array& arg1) { uint32_t result; - proxy_->callMethod("sumArrayItems").onInterface(INTERFACE_NAME).withArguments(arg0, arg1).storeResultsTo(result); + m_proxy.callMethod("sumArrayItems").onInterface(INTERFACE_NAME).withArguments(arg0, arg1).storeResultsTo(result); return result; } uint32_t doOperation(const uint32_t& arg0) { uint32_t result; - proxy_->callMethod("doOperation").onInterface(INTERFACE_NAME).withArguments(arg0).storeResultsTo(result); + m_proxy.callMethod("doOperation").onInterface(INTERFACE_NAME).withArguments(arg0).storeResultsTo(result); return result; } uint32_t doOperationAsync(const uint32_t& arg0) { uint32_t result; - proxy_->callMethod("doOperationAsync").onInterface(INTERFACE_NAME).withArguments(arg0).storeResultsTo(result); + m_proxy.callMethod("doOperationAsync").onInterface(INTERFACE_NAME).withArguments(arg0).storeResultsTo(result); return result; } sdbus::Signature getSignature() { sdbus::Signature result; - proxy_->callMethod("getSignature").onInterface(INTERFACE_NAME).storeResultsTo(result); + m_proxy.callMethod("getSignature").onInterface(INTERFACE_NAME).storeResultsTo(result); return result; } sdbus::ObjectPath getObjPath() { sdbus::ObjectPath result; - proxy_->callMethod("getObjPath").onInterface(INTERFACE_NAME).storeResultsTo(result); + m_proxy.callMethod("getObjPath").onInterface(INTERFACE_NAME).storeResultsTo(result); return result; } sdbus::UnixFd getUnixFd() { sdbus::UnixFd result; - proxy_->callMethod("getUnixFd").onInterface(INTERFACE_NAME).storeResultsTo(result); + m_proxy.callMethod("getUnixFd").onInterface(INTERFACE_NAME).storeResultsTo(result); return result; } std::map>>>, sdbus::Signature, std::string>> getComplex() { std::map>>>, sdbus::Signature, std::string>> result; - proxy_->callMethod("getComplex").onInterface(INTERFACE_NAME).storeResultsTo(result); + m_proxy.callMethod("getComplex").onInterface(INTERFACE_NAME).storeResultsTo(result); return result; } void throwError() { - proxy_->callMethod("throwError").onInterface(INTERFACE_NAME); + m_proxy.callMethod("throwError").onInterface(INTERFACE_NAME); } void throwErrorWithNoReply() { - proxy_->callMethod("throwErrorWithNoReply").onInterface(INTERFACE_NAME).dontExpectReply(); + m_proxy.callMethod("throwErrorWithNoReply").onInterface(INTERFACE_NAME).dontExpectReply(); } void doPrivilegedStuff() { - proxy_->callMethod("doPrivilegedStuff").onInterface(INTERFACE_NAME); + m_proxy.callMethod("doPrivilegedStuff").onInterface(INTERFACE_NAME); } void emitTwoSimpleSignals() { - proxy_->callMethod("emitTwoSimpleSignals").onInterface(INTERFACE_NAME); + m_proxy.callMethod("emitTwoSimpleSignals").onInterface(INTERFACE_NAME); } void unregisterSimpleSignalHandler() @@ -192,37 +192,37 @@ class integrationtests_proxy void reRegisterSimpleSignalHandler() { - simpleSignalSlot_ = proxy_->uponSignal("simpleSignal").onInterface(INTERFACE_NAME).call([this](){ this->onSimpleSignal(); }, sdbus::return_slot); + simpleSignalSlot_ = m_proxy.uponSignal("simpleSignal").onInterface(INTERFACE_NAME).call([this](){ this->onSimpleSignal(); }, sdbus::return_slot); } public: uint32_t action() { - return proxy_->getProperty("action").onInterface(INTERFACE_NAME).get(); + return m_proxy.getProperty("action").onInterface(INTERFACE_NAME).get(); } void action(const uint32_t& value) { - proxy_->setProperty("action").onInterface(INTERFACE_NAME).toValue(value); + m_proxy.setProperty("action").onInterface(INTERFACE_NAME).toValue(value); } bool blocking() { - return proxy_->getProperty("blocking").onInterface(INTERFACE_NAME).get(); + return m_proxy.getProperty("blocking").onInterface(INTERFACE_NAME).get(); } void blocking(const bool& value) { - proxy_->setProperty("blocking").onInterface(INTERFACE_NAME).toValue(value); + m_proxy.setProperty("blocking").onInterface(INTERFACE_NAME).toValue(value); } std::string state() { - return proxy_->getProperty("state").onInterface(INTERFACE_NAME).get(); + return m_proxy.getProperty("state").onInterface(INTERFACE_NAME).get(); } private: - sdbus::IProxy* proxy_; + sdbus::IProxy& m_proxy; sdbus::Slot simpleSignalSlot_; }; diff --git a/tests/perftests/perftests-adaptor.h b/tests/perftests/perftests-adaptor.h index 6aefa1ae..eb6ea9e7 100644 --- a/tests/perftests/perftests-adaptor.h +++ b/tests/perftests/perftests-adaptor.h @@ -20,20 +20,20 @@ class perftests_adaptor protected: perftests_adaptor(sdbus::IObject& object) - : object_(&object) + : m_object(object) { } perftests_adaptor(const perftests_adaptor&) = delete; perftests_adaptor& operator=(const perftests_adaptor&) = delete; - perftests_adaptor(perftests_adaptor&&) = default; - perftests_adaptor& operator=(perftests_adaptor&&) = default; + perftests_adaptor(perftests_adaptor&&) = delete; + perftests_adaptor& operator=(perftests_adaptor&&) = delete; ~perftests_adaptor() = default; void registerAdaptor() { - object_->addVTable( sdbus::registerMethod("sendDataSignals").withInputParamNames("numberOfSignals", "signalMsgSize").implementedAs([this](const uint32_t& numberOfSignals, const uint32_t& signalMsgSize){ return this->sendDataSignals(numberOfSignals, signalMsgSize); }) + m_object.addVTable( sdbus::registerMethod("sendDataSignals").withInputParamNames("numberOfSignals", "signalMsgSize").implementedAs([this](const uint32_t& numberOfSignals, const uint32_t& signalMsgSize){ return this->sendDataSignals(numberOfSignals, signalMsgSize); }) , sdbus::registerMethod("concatenateTwoStrings").withInputParamNames("string1", "string2").withOutputParamNames("result").implementedAs([this](const std::string& string1, const std::string& string2){ return this->concatenateTwoStrings(string1, string2); }) , sdbus::registerSignal("dataSignal").withParameters("data") ).forInterface(INTERFACE_NAME); @@ -42,7 +42,7 @@ class perftests_adaptor public: void emitDataSignal(const std::string& data) { - object_->emitSignal("dataSignal").onInterface(INTERFACE_NAME).withArguments(data); + m_object.emitSignal("dataSignal").onInterface(INTERFACE_NAME).withArguments(data); } private: @@ -50,7 +50,7 @@ class perftests_adaptor virtual std::string concatenateTwoStrings(const std::string& string1, const std::string& string2) = 0; private: - sdbus::IObject* object_; + sdbus::IObject& m_object; }; }} // namespaces diff --git a/tests/perftests/perftests-proxy.h b/tests/perftests/perftests-proxy.h index dee4bf97..448ed412 100644 --- a/tests/perftests/perftests-proxy.h +++ b/tests/perftests/perftests-proxy.h @@ -20,20 +20,20 @@ class perftests_proxy protected: perftests_proxy(sdbus::IProxy& proxy) - : proxy_(&proxy) + : m_proxy(proxy) { } perftests_proxy(const perftests_proxy&) = delete; perftests_proxy& operator=(const perftests_proxy&) = delete; - perftests_proxy(perftests_proxy&&) = default; - perftests_proxy& operator=(perftests_proxy&&) = default; + perftests_proxy(perftests_proxy&&) = delete; + perftests_proxy& operator=(perftests_proxy&&) = delete; ~perftests_proxy() = default; void registerProxy() { - proxy_->uponSignal("dataSignal").onInterface(INTERFACE_NAME).call([this](const std::string& data){ this->onDataSignal(data); }); + m_proxy.uponSignal("dataSignal").onInterface(INTERFACE_NAME).call([this](const std::string& data){ this->onDataSignal(data); }); } virtual void onDataSignal(const std::string& data) = 0; @@ -41,18 +41,18 @@ class perftests_proxy public: void sendDataSignals(const uint32_t& numberOfSignals, const uint32_t& signalMsgSize) { - proxy_->callMethod("sendDataSignals").onInterface(INTERFACE_NAME).withArguments(numberOfSignals, signalMsgSize); + m_proxy.callMethod("sendDataSignals").onInterface(INTERFACE_NAME).withArguments(numberOfSignals, signalMsgSize); } std::string concatenateTwoStrings(const std::string& string1, const std::string& string2) { std::string result; - proxy_->callMethod("concatenateTwoStrings").onInterface(INTERFACE_NAME).withArguments(string1, string2).storeResultsTo(result); + m_proxy.callMethod("concatenateTwoStrings").onInterface(INTERFACE_NAME).withArguments(string1, string2).storeResultsTo(result); return result; } private: - sdbus::IProxy* proxy_; + sdbus::IProxy& m_proxy; }; }} // namespaces diff --git a/tests/stresstests/celsius-thermometer-adaptor.h b/tests/stresstests/celsius-thermometer-adaptor.h index 36a32bab..ff3a23e6 100644 --- a/tests/stresstests/celsius-thermometer-adaptor.h +++ b/tests/stresstests/celsius-thermometer-adaptor.h @@ -22,27 +22,27 @@ class thermometer_adaptor protected: thermometer_adaptor(sdbus::IObject& object) - : object_(&object) + : m_object(object) { } thermometer_adaptor(const thermometer_adaptor&) = delete; thermometer_adaptor& operator=(const thermometer_adaptor&) = delete; - thermometer_adaptor(thermometer_adaptor&&) = default; - thermometer_adaptor& operator=(thermometer_adaptor&&) = default; + thermometer_adaptor(thermometer_adaptor&&) = delete; + thermometer_adaptor& operator=(thermometer_adaptor&&) = delete; ~thermometer_adaptor() = default; void registerAdaptor() { - object_->addVTable(sdbus::registerMethod("getCurrentTemperature").withOutputParamNames("result").implementedAs([this](){ return this->getCurrentTemperature(); })).forInterface(INTERFACE_NAME); + m_object.addVTable(sdbus::registerMethod("getCurrentTemperature").withOutputParamNames("result").implementedAs([this](){ return this->getCurrentTemperature(); })).forInterface(INTERFACE_NAME); } private: virtual uint32_t getCurrentTemperature() = 0; private: - sdbus::IObject* object_; + sdbus::IObject& m_object; }; }}}} // namespaces diff --git a/tests/stresstests/celsius-thermometer-proxy.h b/tests/stresstests/celsius-thermometer-proxy.h index b4a57158..a29e80e9 100644 --- a/tests/stresstests/celsius-thermometer-proxy.h +++ b/tests/stresstests/celsius-thermometer-proxy.h @@ -22,14 +22,14 @@ class thermometer_proxy protected: thermometer_proxy(sdbus::IProxy& proxy) - : proxy_(&proxy) + : m_proxy(proxy) { } thermometer_proxy(const thermometer_proxy&) = delete; thermometer_proxy& operator=(const thermometer_proxy&) = delete; - thermometer_proxy(thermometer_proxy&&) = default; - thermometer_proxy& operator=(thermometer_proxy&&) = default; + thermometer_proxy(thermometer_proxy&&) = delete; + thermometer_proxy& operator=(thermometer_proxy&&) = delete; ~thermometer_proxy() = default; @@ -41,12 +41,12 @@ class thermometer_proxy uint32_t getCurrentTemperature() { uint32_t result; - proxy_->callMethod("getCurrentTemperature").onInterface(INTERFACE_NAME).storeResultsTo(result); + m_proxy.callMethod("getCurrentTemperature").onInterface(INTERFACE_NAME).storeResultsTo(result); return result; } private: - sdbus::IProxy* proxy_; + sdbus::IProxy& m_proxy; }; }}}} // namespaces diff --git a/tests/stresstests/concatenator-adaptor.h b/tests/stresstests/concatenator-adaptor.h index 0bd47e70..16379880 100644 --- a/tests/stresstests/concatenator-adaptor.h +++ b/tests/stresstests/concatenator-adaptor.h @@ -21,20 +21,20 @@ class concatenator_adaptor protected: concatenator_adaptor(sdbus::IObject& object) - : object_(&object) + : m_object(object) { } concatenator_adaptor(const concatenator_adaptor&) = delete; concatenator_adaptor& operator=(const concatenator_adaptor&) = delete; - concatenator_adaptor(concatenator_adaptor&&) = default; - concatenator_adaptor& operator=(concatenator_adaptor&&) = default; + concatenator_adaptor(concatenator_adaptor&&) = delete; + concatenator_adaptor& operator=(concatenator_adaptor&&) = delete; ~concatenator_adaptor() = default; void registerAdaptor() { - object_->addVTable( sdbus::registerMethod("concatenate").withInputParamNames("params").withOutputParamNames("result").implementedAs([this](sdbus::Result&& result, std::map params){ this->concatenate(std::move(result), std::move(params)); }) + m_object.addVTable( sdbus::registerMethod("concatenate").withInputParamNames("params").withOutputParamNames("result").implementedAs([this](sdbus::Result&& result, std::map params){ this->concatenate(std::move(result), std::move(params)); }) , sdbus::registerSignal("concatenatedSignal").withParameters("concatenatedString") ).forInterface(INTERFACE_NAME); } @@ -42,14 +42,14 @@ class concatenator_adaptor public: void emitConcatenatedSignal(const std::string& concatenatedString) { - object_->emitSignal("concatenatedSignal").onInterface(INTERFACE_NAME).withArguments(concatenatedString); + m_object.emitSignal("concatenatedSignal").onInterface(INTERFACE_NAME).withArguments(concatenatedString); } private: virtual void concatenate(sdbus::Result&& result, std::map params) = 0; private: - sdbus::IObject* object_; + sdbus::IObject& m_object; }; }}} // namespaces diff --git a/tests/stresstests/concatenator-proxy.h b/tests/stresstests/concatenator-proxy.h index 2383dc73..445ebac4 100644 --- a/tests/stresstests/concatenator-proxy.h +++ b/tests/stresstests/concatenator-proxy.h @@ -21,20 +21,20 @@ class concatenator_proxy protected: concatenator_proxy(sdbus::IProxy& proxy) - : proxy_(&proxy) + : m_proxy(proxy) { } concatenator_proxy(const concatenator_proxy&) = delete; concatenator_proxy& operator=(const concatenator_proxy&) = delete; - concatenator_proxy(concatenator_proxy&&) = default; - concatenator_proxy& operator=(concatenator_proxy&&) = default; + concatenator_proxy(concatenator_proxy&&) = delete; + concatenator_proxy& operator=(concatenator_proxy&&) = delete; ~concatenator_proxy() = default; void registerProxy() { - proxy_->uponSignal("concatenatedSignal").onInterface(INTERFACE_NAME).call([this](const std::string& concatenatedString){ this->onConcatenatedSignal(concatenatedString); }); + m_proxy.uponSignal("concatenatedSignal").onInterface(INTERFACE_NAME).call([this](const std::string& concatenatedString){ this->onConcatenatedSignal(concatenatedString); }); } virtual void onConcatenatedSignal(const std::string& concatenatedString) = 0; @@ -44,11 +44,11 @@ class concatenator_proxy public: sdbus::PendingAsyncCall concatenate(const std::map& params) { - return proxy_->callMethodAsync("concatenate").onInterface(INTERFACE_NAME).withArguments(params).uponReplyInvoke([this](std::optional error, const std::string& result){ this->onConcatenateReply(result, std::move(error)); }); + return m_proxy.callMethodAsync("concatenate").onInterface(INTERFACE_NAME).withArguments(params).uponReplyInvoke([this](std::optional error, const std::string& result){ this->onConcatenateReply(result, std::move(error)); }); } private: - sdbus::IProxy* proxy_; + sdbus::IProxy& m_proxy; }; }}} // namespaces diff --git a/tests/stresstests/fahrenheit-thermometer-adaptor.h b/tests/stresstests/fahrenheit-thermometer-adaptor.h index 0710fbb6..8890ae58 100644 --- a/tests/stresstests/fahrenheit-thermometer-adaptor.h +++ b/tests/stresstests/fahrenheit-thermometer-adaptor.h @@ -22,27 +22,27 @@ class thermometer_adaptor protected: thermometer_adaptor(sdbus::IObject& object) - : object_(&object) + : m_object(object) { } thermometer_adaptor(const thermometer_adaptor&) = delete; thermometer_adaptor& operator=(const thermometer_adaptor&) = delete; - thermometer_adaptor(thermometer_adaptor&&) = default; - thermometer_adaptor& operator=(thermometer_adaptor&&) = default; + thermometer_adaptor(thermometer_adaptor&&) = delete; + thermometer_adaptor& operator=(thermometer_adaptor&&) = delete; ~thermometer_adaptor() = default; void registerAdaptor() { - object_->addVTable(sdbus::registerMethod("getCurrentTemperature").withOutputParamNames("result").implementedAs([this](){ return this->getCurrentTemperature(); })).forInterface(INTERFACE_NAME); + m_object.addVTable(sdbus::registerMethod("getCurrentTemperature").withOutputParamNames("result").implementedAs([this](){ return this->getCurrentTemperature(); })).forInterface(INTERFACE_NAME); } private: virtual uint32_t getCurrentTemperature() = 0; private: - sdbus::IObject* object_; + sdbus::IObject& m_object; }; }}}} // namespaces @@ -60,20 +60,20 @@ class factory_adaptor protected: factory_adaptor(sdbus::IObject& object) - : object_(&object) + : m_object(object) { } factory_adaptor(const factory_adaptor&) = delete; factory_adaptor& operator=(const factory_adaptor&) = delete; - factory_adaptor(factory_adaptor&&) = default; - factory_adaptor& operator=(factory_adaptor&&) = default; + factory_adaptor(factory_adaptor&&) = delete; + factory_adaptor& operator=(factory_adaptor&&) = delete; ~factory_adaptor() = default; void registerAdaptor() { - object_->addVTable( sdbus::registerMethod("createDelegateObject").withOutputParamNames("delegate").implementedAs([this](sdbus::Result&& result){ this->createDelegateObject(std::move(result)); }) + m_object.addVTable( sdbus::registerMethod("createDelegateObject").withOutputParamNames("delegate").implementedAs([this](sdbus::Result&& result){ this->createDelegateObject(std::move(result)); }) , sdbus::registerMethod("destroyDelegateObject").withInputParamNames("delegate").implementedAs([this](sdbus::Result<>&& result, sdbus::ObjectPath delegate){ this->destroyDelegateObject(std::move(result), std::move(delegate)); }).withNoReply() ).forInterface(INTERFACE_NAME); } @@ -83,7 +83,7 @@ class factory_adaptor virtual void destroyDelegateObject(sdbus::Result<>&& result, sdbus::ObjectPath delegate) = 0; private: - sdbus::IObject* object_; + sdbus::IObject& m_object; }; }}}}} // namespaces diff --git a/tests/stresstests/fahrenheit-thermometer-proxy.h b/tests/stresstests/fahrenheit-thermometer-proxy.h index 3a1fc265..74da67a6 100644 --- a/tests/stresstests/fahrenheit-thermometer-proxy.h +++ b/tests/stresstests/fahrenheit-thermometer-proxy.h @@ -22,14 +22,14 @@ class thermometer_proxy protected: thermometer_proxy(sdbus::IProxy& proxy) - : proxy_(&proxy) + : m_proxy(proxy) { } thermometer_proxy(const thermometer_proxy&) = delete; thermometer_proxy& operator=(const thermometer_proxy&) = delete; - thermometer_proxy(thermometer_proxy&&) = default; - thermometer_proxy& operator=(thermometer_proxy&&) = default; + thermometer_proxy(thermometer_proxy&&) = delete; + thermometer_proxy& operator=(thermometer_proxy&&) = delete; ~thermometer_proxy() = default; @@ -41,12 +41,12 @@ class thermometer_proxy uint32_t getCurrentTemperature() { uint32_t result; - proxy_->callMethod("getCurrentTemperature").onInterface(INTERFACE_NAME).storeResultsTo(result); + m_proxy.callMethod("getCurrentTemperature").onInterface(INTERFACE_NAME).storeResultsTo(result); return result; } private: - sdbus::IProxy* proxy_; + sdbus::IProxy& m_proxy; }; }}}} // namespaces @@ -64,14 +64,14 @@ class factory_proxy protected: factory_proxy(sdbus::IProxy& proxy) - : proxy_(&proxy) + : m_proxy(proxy) { } factory_proxy(const factory_proxy&) = delete; factory_proxy& operator=(const factory_proxy&) = delete; - factory_proxy(factory_proxy&&) = default; - factory_proxy& operator=(factory_proxy&&) = default; + factory_proxy(factory_proxy&&) = delete; + factory_proxy& operator=(factory_proxy&&) = delete; ~factory_proxy() = default; @@ -83,17 +83,17 @@ class factory_proxy sdbus::ObjectPath createDelegateObject() { sdbus::ObjectPath result; - proxy_->callMethod("createDelegateObject").onInterface(INTERFACE_NAME).storeResultsTo(result); + m_proxy.callMethod("createDelegateObject").onInterface(INTERFACE_NAME).storeResultsTo(result); return result; } void destroyDelegateObject(const sdbus::ObjectPath& delegate) { - proxy_->callMethod("destroyDelegateObject").onInterface(INTERFACE_NAME).withArguments(delegate).dontExpectReply(); + m_proxy.callMethod("destroyDelegateObject").onInterface(INTERFACE_NAME).withArguments(delegate).dontExpectReply(); } private: - sdbus::IProxy* proxy_; + sdbus::IProxy& m_proxy; }; }}}}} // namespaces diff --git a/tools/xml2cpp-codegen/AdaptorGenerator.cpp b/tools/xml2cpp-codegen/AdaptorGenerator.cpp index 3c7c24ce..db1ee043 100644 --- a/tools/xml2cpp-codegen/AdaptorGenerator.cpp +++ b/tools/xml2cpp-codegen/AdaptorGenerator.cpp @@ -85,15 +85,15 @@ std::string AdaptorGenerator::processInterface(Node& interface) const << tab << "static constexpr const char* INTERFACE_NAME = \"" << ifaceName << "\";" << endl << endl << "protected:" << endl << tab << className << "(sdbus::IObject& object)" << endl - << tab << tab << ": object_(&object)" << endl + << tab << tab << ": m_object(object)" << endl << tab << "{" << endl << tab << "}" << endl << endl; // Rule of Five body << tab << className << "(const " << className << "&) = delete;" << endl; body << tab << className << "& operator=(const " << className << "&) = delete;" << endl; - body << tab << className << "(" << className << "&&) = default;" << endl; - body << tab << className << "& operator=(" << className << "&&) = default;" << endl << endl; + body << tab << className << "(" << className << "&&) = delete;" << endl; + body << tab << className << "& operator=(" << className << "&&) = delete;" << endl << endl; body << tab << "~" << className << "() = default;" << endl << endl; @@ -160,7 +160,7 @@ std::string AdaptorGenerator::processInterface(Node& interface) const } body << "private:" << endl - << tab << "sdbus::IObject* object_;" << endl + << tab << "sdbus::IObject& m_object;" << endl << "};" << endl << endl << std::string(namespacesCount, '}') << " // namespaces" << endl << endl; @@ -303,7 +303,7 @@ std::tuple, std::string> AdaptorGenerator::processSigna signalMethodSS << tab << "void emit" << nameWithCapFirstLetter << "(" << argTypeStr << ")" << endl << tab << "{" << endl - << tab << tab << "object_->emitSignal(\"" << name << "\")" + << tab << tab << "m_object.emitSignal(\"" << name << "\")" ".onInterface(INTERFACE_NAME)"; if (!argStr.empty()) @@ -402,11 +402,11 @@ std::string AdaptorGenerator::createVTableRegistration(const std::string& annota std::ostringstream registrationSS; if (allRegistrations.size() == 1) { - registrationSS << tab << tab << "object_->addVTable(" << allRegistrations[0] << ").forInterface(INTERFACE_NAME);"; + registrationSS << tab << tab << "m_object.addVTable(" << allRegistrations[0] << ").forInterface(INTERFACE_NAME);"; } else { - registrationSS << tab << tab << "object_->addVTable( " << allRegistrations[0] << endl; + registrationSS << tab << tab << "m_object.addVTable( " << allRegistrations[0] << endl; for (size_t i = 1; i < allRegistrations.size(); ++i) registrationSS << tab << tab << " , " << allRegistrations[i] << endl; registrationSS << tab << tab << " ).forInterface(INTERFACE_NAME);"; diff --git a/tools/xml2cpp-codegen/ProxyGenerator.cpp b/tools/xml2cpp-codegen/ProxyGenerator.cpp index 99225a7c..0e107485 100644 --- a/tools/xml2cpp-codegen/ProxyGenerator.cpp +++ b/tools/xml2cpp-codegen/ProxyGenerator.cpp @@ -84,15 +84,15 @@ std::string ProxyGenerator::processInterface(Node& interface) const << tab << "static constexpr const char* INTERFACE_NAME = \"" << ifaceName << "\";" << endl << endl << "protected:" << endl << tab << className << "(sdbus::IProxy& proxy)" << endl - << tab << tab << ": proxy_(&proxy)" << endl + << tab << tab << ": m_proxy(proxy)" << endl << tab << "{" << endl << tab << "}" << endl << endl; // Rule of Five body << tab << className << "(const " << className << "&) = delete;" << endl; body << tab << className << "& operator=(const " << className << "&) = delete;" << endl; - body << tab << className << "(" << className << "&&) = default;" << endl; - body << tab << className << "& operator=(" << className << "&&) = default;" << endl << endl; + body << tab << className << "(" << className << "&&) = delete;" << endl; + body << tab << className << "& operator=(" << className << "&&) = delete;" << endl << endl; body << tab << "~" << className << "() = default;" << endl << endl; @@ -136,7 +136,7 @@ std::string ProxyGenerator::processInterface(Node& interface) const } body << "private:" << endl - << tab << "sdbus::IProxy* proxy_;" << endl + << tab << "sdbus::IProxy& m_proxy;" << endl << "};" << endl << endl << std::string(namespacesCount, '}') << " // namespaces" << endl << endl; @@ -226,7 +226,7 @@ std::tuple ProxyGenerator::processMethods(const Nodes& } definitionSS << tab << tab << (async && !dontExpectReply ? "return " : "") - << "proxy_->callMethod" << (async ? "Async" : "") << "(\"" << name << "\").onInterface(INTERFACE_NAME)"; + << "m_proxy.callMethod" << (async ? "Async" : "") << "(\"" << name << "\").onInterface(INTERFACE_NAME)"; if (!timeoutValue.empty()) { @@ -289,8 +289,8 @@ std::tuple ProxyGenerator::processSignals(const Nodes& std::string argStr, argTypeStr; std::tie(argStr, argTypeStr, std::ignore, std::ignore) = argsToNamesAndTypes(args); - registrationSS << tab << tab << "proxy_" - "->uponSignal(\"" << name << "\")" + registrationSS << tab << tab << "m_proxy" + ".uponSignal(\"" << name << "\")" ".onInterface(INTERFACE_NAME)" ".call([this](" << argTypeStr << ")" "{ this->on" << nameBigFirst << "(" << argStr << "); });" << endl; @@ -346,9 +346,13 @@ std::tuple ProxyGenerator::processProperties(const Nod propertySS << tab << realRetType << " " << propertyNameSafe << "()" << endl << tab << "{" << endl; - propertySS << tab << tab << "return proxy_->getProperty" << (asyncGet ? "Async" : "") << "(\"" << propertyName << "\")" + propertySS << tab << tab << "return m_proxy.getProperty" << (asyncGet ? "Async" : "") << "(\"" << propertyName << "\")" ".onInterface(INTERFACE_NAME)"; - if (asyncGet) + if (!asyncGet) + { + propertySS << ".get<" << realRetType << ">()"; + } + else { auto nameBigFirst = propertyName; nameBigFirst[0] = islower(nameBigFirst[0]) ? nameBigFirst[0] + 'A' - 'a' : nameBigFirst[0]; @@ -375,7 +379,7 @@ std::tuple ProxyGenerator::processProperties(const Nod propertySS << tab << realRetType << " " << propertyNameSafe << "(" << propertyTypeArg << ")" << endl << tab << "{" << endl; - propertySS << tab << tab << (asyncSet ? "return " : "") << "proxy_->setProperty" << (asyncSet ? "Async" : "") + propertySS << tab << tab << (asyncSet ? "return " : "") << "m_proxy.setProperty" << (asyncSet ? "Async" : "") << "(\"" << propertyName << "\")" ".onInterface(INTERFACE_NAME)" ".toValue(" << propertyArg << ")"; From eee2de3795ac41dbc740bf30fc394a7076838bf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Wed, 24 Apr 2024 08:44:40 +0200 Subject: [PATCH 49/52] docs: fix generated class examples in the tutorial (#436) --- docs/using-sdbus-c++.md | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/docs/using-sdbus-c++.md b/docs/using-sdbus-c++.md index 86e31f0a..ee45e058 100644 --- a/docs/using-sdbus-c++.md +++ b/docs/using-sdbus-c++.md @@ -720,8 +720,6 @@ protected: Concatenator_adaptor(sdbus::IObject& object) : m_object(object) { - m_object.registerMethod("concatenate").onInterface(INTERFACE_NAME).withInputParamNames("numbers", "separator").withOutputParamNames("concatenatedString").implementedAs([this](const std::vector& numbers, const std::string& separator){ return this->concatenate(numbers, separator); }); - m_object.registerSignal("concatenated").onInterface(INTERFACE_NAME).withParameters("concatenatedString"); } Concatenator_adaptor(const Concatenator_adaptor&) = delete; @@ -731,6 +729,13 @@ protected: ~Concatenator_adaptor() = default; + void registerAdaptor() + { + m_object.addVTable( sdbus::registerMethod("concatenate").withInputParamNames("numbers", "separator").withOutputParamNames("concatenatedString").implementedAs([this](const std::vector& numbers, const std::string& separator){ return this->concatenate(numbers, separator); }) + , sdbus::registerSignal("concatenated").withParameters("concatenatedString") + ).forInterface(INTERFACE_NAME); + } + public: void emitConcatenated(const std::string& concatenatedString) { @@ -779,7 +784,6 @@ protected: Concatenator_proxy(sdbus::IProxy& proxy) : m_proxy(proxy) { - m_proxy.uponSignal("concatenated").onInterface(INTERFACE_NAME).call([this](const std::string& concatenatedString){ this->onConcatenated(concatenatedString); }); } Concatenator_proxy(const Concatenator_proxy&) = delete; @@ -789,6 +793,11 @@ protected: ~Concatenator_proxy() = default; + void registerProxy() + { + m_proxy.uponSignal("concatenated").onInterface(INTERFACE_NAME).call([this](const std::string& concatenatedString){ this->onConcatenated(concatenatedString); }); + } + virtual void onConcatenated(const std::string& concatenatedString) = 0; public: @@ -1407,10 +1416,15 @@ public: PropertyProvider_adaptor(sdbus::IObject& object) : m_object(object) { - m_object.registerProperty("status").onInterface(INTERFACE_NAME).withGetter([this](){ return this->status(); }).withSetter([this](const uint32_t& value){ this->status(value); }); } - ~PropertyProvider_adaptor() = default; + /*...*/ + + void registerAdaptor() + { + m_object.addVTable( sdbus::registerProperty("status").withGetter([this](){ return this->status(); }).withSetter([this](const uint32_t& value){ this->status(value); }) + ).forInterface(INTERFACE_NAME); + } private: // property getter @@ -1434,7 +1448,7 @@ public: // getting the property value uint32_t status() { - return m_object.getProperty("status").onInterface(INTERFACE_NAME); + return m_object.getProperty("status").onInterface(INTERFACE_NAME).get(); } // setting the property value From b7b39eab6e8265c4ebb8cc5faf71d4417e8a6301 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Wed, 24 Apr 2024 08:48:39 +0200 Subject: [PATCH 50/52] refactor: reorganize public API of main abstract classes (#437) --- include/sdbus-c++/IConnection.h | 50 +-- include/sdbus-c++/IObject.h | 249 +++++++------ include/sdbus-c++/IProxy.h | 619 ++++++++++++++++---------------- 3 files changed, 484 insertions(+), 434 deletions(-) diff --git a/include/sdbus-c++/IConnection.h b/include/sdbus-c++/IConnection.h index a95ee20a..c8b82c44 100644 --- a/include/sdbus-c++/IConnection.h +++ b/include/sdbus-c++/IConnection.h @@ -64,31 +64,6 @@ namespace sdbus { virtual ~IConnection() = default; - /*! - * @brief Requests a well-known D-Bus service name on a bus - * - * @param[in] name Name to request - * - * @throws sdbus::Error in case of failure - */ - virtual void requestName(const ServiceName& name) = 0; - - /*! - * @brief Releases an acquired well-known D-Bus service name on a bus - * - * @param[in] name Name to release - * - * @throws sdbus::Error in case of failure - */ - virtual void releaseName(const ServiceName& name) = 0; - - /*! - * @brief Retrieves the unique name of a connection. E.g. ":1.xx" - * - * @throws sdbus::Error in case of failure - */ - [[nodiscard]] virtual BusName getUniqueName() const = 0; - /*! * @brief Enters I/O event loop on this bus connection * @@ -342,6 +317,31 @@ namespace sdbus { */ virtual void addMatchAsync(const std::string& match, message_handler callback, message_handler installCallback, floating_slot_t) = 0; + /*! + * @brief Retrieves the unique name of a connection. E.g. ":1.xx" + * + * @throws sdbus::Error in case of failure + */ + [[nodiscard]] virtual BusName getUniqueName() const = 0; + + /*! + * @brief Requests a well-known D-Bus service name on a bus + * + * @param[in] name Name to request + * + * @throws sdbus::Error in case of failure + */ + virtual void requestName(const ServiceName& name) = 0; + + /*! + * @brief Releases an acquired well-known D-Bus service name on a bus + * + * @param[in] name Name to release + * + * @throws sdbus::Error in case of failure + */ + virtual void releaseName(const ServiceName& name) = 0; + /*! * @struct PollData * diff --git a/include/sdbus-c++/IObject.h b/include/sdbus-c++/IObject.h index 2606c879..3458fa6c 100644 --- a/include/sdbus-c++/IObject.h +++ b/include/sdbus-c++/IObject.h @@ -61,80 +61,66 @@ namespace sdbus { ***********************************************/ class IObject { - public: + public: // High-level, convenience API virtual ~IObject() = default; /*! * @brief Adds a declaration of methods, properties and signals of the object at a given interface * - * @param[in] interfaceName Name of an interface the the vtable is registered for - * @param[in] items Individual instances of VTable item structures + * @param[in] vtable Individual instances of VTable item structures stored in a vector + * @return VTableAdder high-level helper class * * This method is used to declare attributes for the object under the given interface. - * Parameter `items' represents a vtable definition that may contain method declarations + * Parameter `vtable' represents a vtable definition that may contain method declarations * (using MethodVTableItem struct), property declarations (using PropertyVTableItem * struct), signal declarations (using SignalVTableItem struct), or global interface * flags (using InterfaceFlagsVTableItem struct). * * An interface can have any number of vtables attached to it. * - * Consult manual pages for underlying `sd_bus_add_object_vtable` function for more information. - * - * The method can be called at any time during object's lifetime. For each vtable an internal - * match slot is created and its lifetime is tied to the lifetime of the Object instance. - * - * The function provides strong exception guarantee. The state of the object remains - * unmodified in face of an exception. - * - * @throws sdbus::Error in case of failure - */ - template < typename... VTableItems - , typename = std::enable_if_t<(is_one_of_variants_types> && ...)> > - void addVTable(InterfaceName interfaceName, VTableItems&&... items); - - /*! - * @brief Adds a declaration of methods, properties and signals of the object at a given interface - * - * @param[in] interfaceName Name of an interface the the vtable is registered for - * @param[in] vtable A list of individual descriptions in the form of VTable item instances - * - * This method is used to declare attributes for the object under the given interface. - * The `vtable' parameter may contain method declarations (using MethodVTableItem struct), - * property declarations (using PropertyVTableItem struct), signal declarations (using - * SignalVTableItem struct), or global interface flags (using InterfaceFlagsVTableItem struct). - * - * An interface can have any number of vtables attached to it. + * Consult manual pages for the underlying `sd_bus_add_object_vtable` function for more information. * - * Consult manual pages for underlying `sd_bus_add_object_vtable` function for more information. + * The method can be called at any time during object's lifetime. * - * The method can be called at any time during object's lifetime. For each vtable an internal - * match slot is created and its lifetime is tied to the lifetime of the Object instance. + * When called like `addVTable(vtable).forInterface(interface)`, then an internal registration + * slot is created for that vtable and its lifetime is tied to the lifetime of the Object instance. + * When called like `addVTable(items...).forInterface(interface, sdbus::return_slot)`, then an internal + * registration slot is created for the vtable and is returned to the caller. Keeping the slot means + * keep the registration "alive". Destroying the slot means that the vtable is not needed anymore, + * and the vtable gets removed from the object. This allows for "dynamic" object API where vtables + * can be added or removed by the user at runtime. * * The function provides strong exception guarantee. The state of the object remains * unmodified in face of an exception. * * @throws sdbus::Error in case of failure */ - virtual void addVTable(InterfaceName interfaceName, std::vector vtable) = 0; + [[nodiscard]] VTableAdder addVTable(std::vector vtable); /*! * @brief Adds a declaration of methods, properties and signals of the object at a given interface * - * @param[in] interfaceName Name of an interface the the vtable is registered for - * @param[in] vtable A list of individual descriptions in the form of VTable item instances + * @param[in] items Individual instances of VTable item structures + * @return VTableAdder high-level helper class * * This method is used to declare attributes for the object under the given interface. - * The `vtable' parameter may contain method declarations (using MethodVTableItem struct), - * property declarations (using PropertyVTableItem struct), signal declarations (using - * SignalVTableItem struct), or global interface flags (using InterfaceFlagsVTableItem struct). + * Parameter pack contains vtable definition that may contain method declarations + * (using MethodVTableItem struct), property declarations (using PropertyVTableItem + * struct), signal declarations (using SignalVTableItem struct), or global interface + * flags (using InterfaceFlagsVTableItem struct). * * An interface can have any number of vtables attached to it. * - * Consult manual pages for underlying `sd_bus_add_object_vtable` function for more information. + * Consult manual pages for the underlying `sd_bus_add_object_vtable` function for more information. * - * The method can be called at any time during object's lifetime. For each vtable an internal - * match slot is created and is returned to the caller. The returned slot should be destroyed - * when the vtable is not needed anymore. This allows for "dynamic" object API where vtables + * The method can be called at any time during object's lifetime. + * + * When called like `addVTable(items...).forInterface(interface)`, then an internal registration + * slot is created for that vtable and its lifetime is tied to the lifetime of the Object instance. + * When called like `addVTable(items...).forInterface(interface, sdbus::return_slot)`, then an internal + * registration slot is created for the vtable and is returned to the caller. Keeping the slot means + * keep the registration "alive". Destroying the slot means that the vtable is not needed anymore, + * and the vtable gets removed from the object. This allows for "dynamic" object API where vtables * can be added or removed by the user at runtime. * * The function provides strong exception guarantee. The state of the object remains @@ -142,66 +128,10 @@ namespace sdbus { * * @throws sdbus::Error in case of failure */ - [[nodiscard]] virtual Slot addVTable(InterfaceName interfaceName, std::vector vtable, return_slot_t) = 0; - - /*! - * @brief A little more convenient overload of addVTable() above - * - * This version allows method chaining for a little safer and more readable VTable registration. - * - * See addVTable() overloads above for detailed documentation. - */ template < typename... VTableItems , typename = std::enable_if_t<(is_one_of_variants_types> && ...)> > [[nodiscard]] VTableAdder addVTable(VTableItems&&... items); - /*! - * @brief A little more convenient overload of addVTable() above - * - * This version allows method chaining for a little safer and more readable VTable registration. - * - * See addVTable() overloads above for detailed documentation. - */ - [[nodiscard]] VTableAdder addVTable(std::vector vtable); - - /*! - * @brief Unregisters object's API and removes object from the bus - * - * This method unregisters the object, its interfaces, methods, signals and properties - * from the bus. Unregistration is done automatically also in object's destructor. This - * method makes sense if, in the process of object removal, we need to make sure that - * callbacks are unregistered explicitly before the final destruction of the object instance. - * - * @throws sdbus::Error in case of failure - */ - virtual void unregister() = 0; - - /*! - * @brief Creates a signal message - * - * @param[in] interfaceName Name of an interface that the signal belongs under - * @param[in] signalName Name of the signal - * @return A signal message - * - * Serialize signal arguments into the returned message and emit the signal by passing - * the message with serialized arguments to the @c emitSignal function. - * Alternatively, use higher-level API @c emitSignal(const std::string& signalName) defined below. - * - * @throws sdbus::Error in case of failure - */ - [[nodiscard]] virtual Signal createSignal(const InterfaceName& interfaceName, const SignalName& signalName) = 0; - - /*! - * @brief Emits signal for this object path - * - * @param[in] message Signal message to be sent out - * - * Note: To avoid messing with messages, use higher-level API defined below. - * - * @throws sdbus::Error in case of failure - */ - virtual void emitSignal(const sdbus::Signal& message) = 0; - /*! * @brief Emits signal on D-Bus * @@ -361,7 +291,126 @@ namespace sdbus { */ [[nodiscard]] virtual Message getCurrentlyProcessedMessage() const = 0; - protected: + /*! + * @brief Unregisters object's API and removes object from the bus + * + * This method unregisters the object, its interfaces, methods, signals and properties + * from the bus. Unregistration is done automatically also in object's destructor. This + * method makes sense if, in the process of object removal, we need to make sure that + * callbacks are unregistered explicitly before the final destruction of the object instance. + * + * @throws sdbus::Error in case of failure + */ + virtual void unregister() = 0; + + public: // Lower-level, message-based API + /*! + * @brief Adds a declaration of methods, properties and signals of the object at a given interface + * + * @param[in] interfaceName Name of an interface the the vtable is registered for + * @param[in] items Individual instances of VTable item structures + * + * This method is used to declare attributes for the object under the given interface. + * Parameter `items' represents a vtable definition that may contain method declarations + * (using MethodVTableItem struct), property declarations (using PropertyVTableItem + * struct), signal declarations (using SignalVTableItem struct), or global interface + * flags (using InterfaceFlagsVTableItem struct). + * + * An interface can have any number of vtables attached to it. + * + * Consult manual pages for the underlying `sd_bus_add_object_vtable` function for more information. + * + * The method can be called at any time during object's lifetime. For each vtable an internal + * registration slot is created and its lifetime is tied to the lifetime of the Object instance. + * + * The function provides strong exception guarantee. The state of the object remains + * unmodified in face of an exception. + * + * @throws sdbus::Error in case of failure + */ + template < typename... VTableItems + , typename = std::enable_if_t<(is_one_of_variants_types> && ...)> > + void addVTable(InterfaceName interfaceName, VTableItems&&... items); + + /*! + * @brief Adds a declaration of methods, properties and signals of the object at a given interface + * + * @param[in] interfaceName Name of an interface the the vtable is registered for + * @param[in] vtable A list of individual descriptions in the form of VTable item instances + * + * This method is used to declare attributes for the object under the given interface. + * The `vtable' parameter may contain method declarations (using MethodVTableItem struct), + * property declarations (using PropertyVTableItem struct), signal declarations (using + * SignalVTableItem struct), or global interface flags (using InterfaceFlagsVTableItem struct). + * + * An interface can have any number of vtables attached to it. + * + * Consult manual pages for the underlying `sd_bus_add_object_vtable` function for more information. + * + * The method can be called at any time during object's lifetime. For each vtable an internal + * registration slot is created and its lifetime is tied to the lifetime of the Object instance. + * + * The function provides strong exception guarantee. The state of the object remains + * unmodified in face of an exception. + * + * @throws sdbus::Error in case of failure + */ + virtual void addVTable(InterfaceName interfaceName, std::vector vtable) = 0; + + /*! + * @brief Adds a declaration of methods, properties and signals of the object at a given interface + * + * @param[in] interfaceName Name of an interface the the vtable is registered for + * @param[in] vtable A list of individual descriptions in the form of VTable item instances + * + * This method is used to declare attributes for the object under the given interface. + * The `vtable' parameter may contain method declarations (using MethodVTableItem struct), + * property declarations (using PropertyVTableItem struct), signal declarations (using + * SignalVTableItem struct), or global interface flags (using InterfaceFlagsVTableItem struct). + * + * An interface can have any number of vtables attached to it. + * + * Consult manual pages for the underlying `sd_bus_add_object_vtable` function for more information. + * + * The method can be called at any time during object's lifetime. For each vtable an internal + * registration slot is created and is returned to the caller. The returned slot should be destroyed + * when the vtable is not needed anymore. This allows for "dynamic" object API where vtables + * can be added or removed by the user at runtime. + * + * The function provides strong exception guarantee. The state of the object remains + * unmodified in face of an exception. + * + * @throws sdbus::Error in case of failure + */ + [[nodiscard]] virtual Slot addVTable(InterfaceName interfaceName, std::vector vtable, return_slot_t) = 0; + + /*! + * @brief Creates a signal message + * + * @param[in] interfaceName Name of an interface that the signal belongs under + * @param[in] signalName Name of the signal + * @return A signal message + * + * Serialize signal arguments into the returned message and emit the signal by passing + * the message with serialized arguments to the @c emitSignal function. + * Alternatively, use higher-level API @c emitSignal(const std::string& signalName) defined below. + * + * @throws sdbus::Error in case of failure + */ + [[nodiscard]] virtual Signal createSignal(const InterfaceName& interfaceName, const SignalName& signalName) = 0; + + /*! + * @brief Emits signal for this object path + * + * @param[in] message Signal message to be sent out + * + * Note: To avoid messing with messages, use higher-level API defined below. + * + * @throws sdbus::Error in case of failure + */ + virtual void emitSignal(const sdbus::Signal& message) = 0; + + protected: // Internal API for efficiency reasons used by high-level API helper classes friend SignalEmitter; [[nodiscard]] virtual Signal createSignal(const char* interfaceName, const char* signalName) = 0; diff --git a/include/sdbus-c++/IProxy.h b/include/sdbus-c++/IProxy.h index 9ea16a6d..029127f8 100644 --- a/include/sdbus-c++/IProxy.h +++ b/include/sdbus-c++/IProxy.h @@ -67,264 +67,9 @@ namespace sdbus { ***********************************************/ class IProxy { - public: + public: // High-level, convenience API virtual ~IProxy() = default; - /*! - * @brief Creates a method call message - * - * @param[in] interfaceName Name of an interface that provides a given method - * @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 callMethod(const std::string& methodName) defined below. - * - * @throws sdbus::Error in case of failure - */ - [[nodiscard]] virtual MethodCall createMethodCall(const InterfaceName& interfaceName, const MethodName& methodName) = 0; - - /*! - * @brief Calls method on the remote D-Bus object - * - * @param[in] message Message representing a method call - * @return A method reply message - * - * The call does not block if the method call has dont-expect-reply flag set. In that case, - * the call returns immediately and the return value is an empty, invalid method reply. - * - * The call blocks otherwise, waiting for the remote peer to send back a reply or an error, - * or until the call times out. - * - * While blocking, other concurrent operations (in other threads) on the underlying bus - * connection are stalled until the call returns. This is not an issue in vast majority of - * (simple, single-threaded) applications. In asynchronous, multi-threaded designs involving - * shared bus connections, this may be an issue. It is advised to instead use an asynchronous - * callMethod() function overload, which does not block the bus connection, or do the synchronous - * call from another Proxy instance created just before the call and then destroyed (which is - * anyway quite a typical approach in D-Bus implementations). Such proxy instance must have - * its own bus connection. So-called light-weight proxies (ones created with `dont_run_event_loop_thread` - * tag are designed for exactly that purpose. - * - * The default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout(). - * - * Note: To avoid messing with messages, use API on a higher level of abstraction defined below. - * - * @throws sdbus::Error in case of failure (also in case the remote function returned an error) - */ - virtual MethodReply callMethod(const MethodCall& message) = 0; - - /*! - * @brief Calls method on the remote D-Bus object - * - * @param[in] message Message representing a method call - * @param[in] timeout Method call timeout (in microseconds) - * @return A method reply message - * - * The call does not block if the method call has dont-expect-reply flag set. In that case, - * the call returns immediately and the return value is an empty, invalid method reply. - * - * The call blocks otherwise, waiting for the remote peer to send back a reply or an error, - * or until the call times out. - * - * While blocking, other concurrent operations (in other threads) on the underlying bus - * connection are stalled until the call returns. This is not an issue in vast majority of - * (simple, single-threaded) applications. In asynchronous, multi-threaded designs involving - * shared bus connections, this may be an issue. It is advised to instead use an asynchronous - * callMethod() function overload, which does not block the bus connection, or do the synchronous - * call from another Proxy instance created just before the call and then destroyed (which is - * anyway quite a typical approach in D-Bus implementations). Such proxy instance must have - * its own bus connection. So-called light-weight proxies (ones created with `dont_run_event_loop_thread` - * tag are designed for exactly that purpose. - * - * If timeout is zero, the default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout(). - * - * Note: To avoid messing with messages, use API on a higher level of abstraction defined below. - * - * @throws sdbus::Error in case of failure (also in case the remote function returned an error) - */ - virtual MethodReply callMethod(const MethodCall& message, uint64_t timeout) = 0; - - /*! - * @copydoc IProxy::callMethod(const MethodCall&,uint64_t) - */ - template - MethodReply callMethod(const MethodCall& message, const std::chrono::duration<_Rep, _Period>& timeout); - - /*! - * @brief Calls method on the D-Bus object asynchronously - * - * @param[in] message Message representing an async method call - * @param[in] asyncReplyCallback Handler for the async reply - * @return Observing handle for the the pending asynchronous call - * - * This is a callback-based way of asynchronously calling a remote D-Bus method. - * - * The call itself 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 bus - * connection I/O event loop thread. - * - * An non-owning, observing async call handle is returned that can be used to query call status or cancel the call. - * - * The default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout(). - * - * Note: To avoid messing with messages, use API on a higher level of abstraction defined below. - * - * @throws sdbus::Error in case of failure - */ - virtual PendingAsyncCall callMethodAsync(const MethodCall& message, async_reply_handler asyncReplyCallback) = 0; - - /*! - * @brief Calls method on the D-Bus object asynchronously - * - * @param[in] message Message representing an async method call - * @param[in] asyncReplyCallback Handler for the async reply - * @return RAII-style slot handle representing the ownership of the async call - * - * This is a callback-based way of asynchronously calling a remote D-Bus method. - * - * The call itself 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 bus - * connection I/O event loop thread. - * - * A slot (an owning handle) is returned for the async call. Lifetime of the call is bound to the lifetime of the slot. - * The slot can be used to cancel the method call at a later time by simply destroying it. - * - * The default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout(). - * - * Note: To avoid messing with messages, use API on a higher level of abstraction defined below. - * - * @throws sdbus::Error in case of failure - */ - [[nodiscard]] virtual Slot callMethodAsync( const MethodCall& message - , async_reply_handler asyncReplyCallback - , return_slot_t ) = 0; - - /*! - * @brief Calls method on the D-Bus object asynchronously, with custom timeout - * - * @param[in] message Message representing an async method call - * @param[in] asyncReplyCallback Handler for the async reply - * @param[in] timeout Method call timeout (in microseconds) - * @return Observing handle for the the pending asynchronous call - * - * This is a callback-based way of asynchronously calling a remote D-Bus method. - * - * The call itself 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 bus - * connection I/O event loop thread. - * - * An non-owning, observing async call handle is returned that can be used to query call status or cancel the call. - * - * If timeout is zero, the default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout(). - * - * Note: To avoid messing with messages, use API on a higher level of abstraction defined below. - * - * @throws sdbus::Error in case of failure - */ - virtual PendingAsyncCall callMethodAsync( const MethodCall& message - , async_reply_handler asyncReplyCallback - , uint64_t timeout ) = 0; - - /*! - * @brief Calls method on the D-Bus object asynchronously, with custom timeout - * - * @param[in] message Message representing an async method call - * @param[in] asyncReplyCallback Handler for the async reply - * @param[in] timeout Method call timeout (in microseconds) - * @return RAII-style slot handle representing the ownership of the async call - * - * This is a callback-based way of asynchronously calling a remote D-Bus method. - * - * The call itself 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 bus - * connection I/O event loop thread. - * - * A slot (an owning handle) is returned for the async call. Lifetime of the call is bound to the lifetime of the slot. - * The slot can be used to cancel the method call at a later time by simply destroying it. - * - * If timeout is zero, the default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout(). - * - * Note: To avoid messing with messages, use API on a higher level of abstraction defined below. - * - * @throws sdbus::Error in case of failure - */ - [[nodiscard]] virtual Slot callMethodAsync( const MethodCall& message - , async_reply_handler asyncReplyCallback - , uint64_t timeout - , return_slot_t ) = 0; - - /*! - * @copydoc IProxy::callMethod(const MethodCall&,async_reply_handler,uint64_t) - */ - template - PendingAsyncCall callMethodAsync( const MethodCall& message - , async_reply_handler asyncReplyCallback - , const std::chrono::duration<_Rep, _Period>& timeout ); - - /*! - * @copydoc IProxy::callMethod(const MethodCall&,async_reply_handler,uint64_t,return_slot_t) - */ - template - [[nodiscard]] Slot callMethodAsync( const MethodCall& message - , async_reply_handler asyncReplyCallback - , const std::chrono::duration<_Rep, _Period>& timeout - , return_slot_t ); - - /*! - * @brief Calls method on the D-Bus object asynchronously - * - * @param[in] message Message representing an async method call - * @param[in] Tag denoting a std::future-based overload - * @return Future object providing access to the future method reply message - * - * This is a std::future-based way of asynchronously calling a remote D-Bus method. - * - * The call itself is non-blocking. It doesn't wait for the reply. Once the reply arrives, - * the provided future object will be set to contain the reply (or sdbus::Error - * in case the remote method threw an exception). - * - * The default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout(). - * - * Note: To avoid messing with messages, use higher-level API defined below. - * - * @throws sdbus::Error in case of failure - */ - virtual std::future callMethodAsync(const MethodCall& message, with_future_t) = 0; - - /*! - * @brief Calls method on the D-Bus object asynchronously, with custom timeout - * - * @param[in] message Message representing an async method call - * @param[in] timeout Method call timeout - * @param[in] Tag denoting a std::future-based overload - * @return Future object providing access to the future method reply message - * - * This is a std::future-based way of asynchronously calling a remote D-Bus method. - * - * The call itself is non-blocking. It doesn't wait for the reply. Once the reply arrives, - * the provided future object will be set to contain the reply (or sdbus::Error - * in case the remote method threw an exception, or the call timed out). - * - * If timeout is zero, the default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout(). - * - * Note: To avoid messing with messages, use higher-level API defined below. - * - * @throws sdbus::Error in case of failure - */ - virtual std::future callMethodAsync( const MethodCall& message - , uint64_t timeout - , with_future_t ) = 0; - - /*! - * @copydoc IProxy::callMethod(const MethodCall&,uint64_t,with_future_t) - */ - template - std::future callMethodAsync( const MethodCall& message - , const std::chrono::duration<_Rep, _Period>& timeout - , with_future_t ); - /*! * @brief Calls method on the D-Bus object * @@ -392,47 +137,6 @@ namespace sdbus { */ [[nodiscard]] AsyncMethodInvoker callMethodAsync(const char* methodName); - /*! - * @brief Registers a handler for the desired signal emitted by the D-Bus object - * - * @param[in] interfaceName Name of an interface that the signal belongs to - * @param[in] signalName Name of the signal - * @param[in] signalHandler Callback that implements the body of the signal handler - * - * A signal can be subscribed to at any time during proxy lifetime. The subscription - * is active immediately after the call, and stays active for the entire lifetime - * of the Proxy object. - * - * To be able to unsubscribe from the signal at a later time, use the registerSignalHandler() - * overload with request_slot tag. - * - * @throws sdbus::Error in case of failure - */ - virtual void registerSignalHandler( const InterfaceName& interfaceName - , const SignalName& signalName - , signal_handler signalHandler ) = 0; - - /*! - * @brief Registers a handler for the desired signal emitted by the D-Bus object - * - * @param[in] interfaceName Name of an interface that the signal belongs to - * @param[in] signalName Name of the signal - * @param[in] signalHandler Callback that implements the body of the signal handler - * - * @return RAII-style slot handle representing the ownership of the subscription - * - * A signal can be subscribed to and unsubscribed from at any time during proxy - * lifetime. The subscription is active immediately after the call. The lifetime - * of the subscription is bound to the lifetime of the slot object. The subscription - * is unregistered by letting go of the slot object. - * - * @throws sdbus::Error in case of failure - */ - [[nodiscard]] virtual Slot registerSignalHandler( const InterfaceName& interfaceName - , const SignalName& signalName - , signal_handler signalHandler - , return_slot_t ) = 0; - /*! * @brief Registers signal handler for a given signal of the D-Bus object * @@ -469,17 +173,6 @@ namespace sdbus { */ [[nodiscard]] SignalSubscriber uponSignal(const char* signalName); - /*! - * @brief Unregisters proxy's signal handlers and stops receiving replies to pending async calls - * - * Unregistration is done automatically also in proxy's destructor. This method makes - * sense if, in the process of proxy removal, we need to make sure that callbacks - * are unregistered explicitly before the final destruction of the proxy instance. - * - * @throws sdbus::Error in case of failure - */ - virtual void unregister() = 0; - /*! * @brief Gets value of a property of the D-Bus object * @@ -646,7 +339,315 @@ namespace sdbus { */ [[nodiscard]] virtual Message getCurrentlyProcessedMessage() const = 0; - protected: + /*! + * @brief Unregisters proxy's signal handlers and stops receiving replies to pending async calls + * + * Unregistration is done automatically also in proxy's destructor. This method makes + * sense if, in the process of proxy removal, we need to make sure that callbacks + * are unregistered explicitly before the final destruction of the proxy instance. + * + * @throws sdbus::Error in case of failure + */ + virtual void unregister() = 0; + + public: // Lower-level, message-based API + /*! + * @brief Creates a method call message + * + * @param[in] interfaceName Name of an interface that provides a given method + * @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 callMethod(const std::string& methodName) defined below. + * + * @throws sdbus::Error in case of failure + */ + [[nodiscard]] virtual MethodCall createMethodCall(const InterfaceName& interfaceName, const MethodName& methodName) = 0; + + /*! + * @brief Calls method on the remote D-Bus object + * + * @param[in] message Message representing a method call + * @return A method reply message + * + * The call does not block if the method call has dont-expect-reply flag set. In that case, + * the call returns immediately and the return value is an empty, invalid method reply. + * + * The call blocks otherwise, waiting for the remote peer to send back a reply or an error, + * or until the call times out. + * + * While blocking, other concurrent operations (in other threads) on the underlying bus + * connection are stalled until the call returns. This is not an issue in vast majority of + * (simple, single-threaded) applications. In asynchronous, multi-threaded designs involving + * shared bus connections, this may be an issue. It is advised to instead use an asynchronous + * callMethod() function overload, which does not block the bus connection, or do the synchronous + * call from another Proxy instance created just before the call and then destroyed (which is + * anyway quite a typical approach in D-Bus implementations). Such proxy instance must have + * its own bus connection. So-called light-weight proxies (ones created with `dont_run_event_loop_thread` + * tag are designed for exactly that purpose. + * + * The default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout(). + * + * Note: To avoid messing with messages, use API on a higher level of abstraction defined below. + * + * @throws sdbus::Error in case of failure (also in case the remote function returned an error) + */ + virtual MethodReply callMethod(const MethodCall& message) = 0; + + /*! + * @brief Calls method on the remote D-Bus object + * + * @param[in] message Message representing a method call + * @param[in] timeout Method call timeout (in microseconds) + * @return A method reply message + * + * The call does not block if the method call has dont-expect-reply flag set. In that case, + * the call returns immediately and the return value is an empty, invalid method reply. + * + * The call blocks otherwise, waiting for the remote peer to send back a reply or an error, + * or until the call times out. + * + * While blocking, other concurrent operations (in other threads) on the underlying bus + * connection are stalled until the call returns. This is not an issue in vast majority of + * (simple, single-threaded) applications. In asynchronous, multi-threaded designs involving + * shared bus connections, this may be an issue. It is advised to instead use an asynchronous + * callMethod() function overload, which does not block the bus connection, or do the synchronous + * call from another Proxy instance created just before the call and then destroyed (which is + * anyway quite a typical approach in D-Bus implementations). Such proxy instance must have + * its own bus connection. So-called light-weight proxies (ones created with `dont_run_event_loop_thread` + * tag are designed for exactly that purpose. + * + * If timeout is zero, the default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout(). + * + * Note: To avoid messing with messages, use API on a higher level of abstraction defined below. + * + * @throws sdbus::Error in case of failure (also in case the remote function returned an error) + */ + virtual MethodReply callMethod(const MethodCall& message, uint64_t timeout) = 0; + + /*! + * @copydoc IProxy::callMethod(const MethodCall&,uint64_t) + */ + template + MethodReply callMethod(const MethodCall& message, const std::chrono::duration<_Rep, _Period>& timeout); + + /*! + * @brief Calls method on the D-Bus object asynchronously + * + * @param[in] message Message representing an async method call + * @param[in] asyncReplyCallback Handler for the async reply + * @return Observing handle for the the pending asynchronous call + * + * This is a callback-based way of asynchronously calling a remote D-Bus method. + * + * The call itself 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 bus + * connection I/O event loop thread. + * + * An non-owning, observing async call handle is returned that can be used to query call status or cancel the call. + * + * The default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout(). + * + * Note: To avoid messing with messages, use API on a higher level of abstraction defined below. + * + * @throws sdbus::Error in case of failure + */ + virtual PendingAsyncCall callMethodAsync(const MethodCall& message, async_reply_handler asyncReplyCallback) = 0; + + /*! + * @brief Calls method on the D-Bus object asynchronously + * + * @param[in] message Message representing an async method call + * @param[in] asyncReplyCallback Handler for the async reply + * @return RAII-style slot handle representing the ownership of the async call + * + * This is a callback-based way of asynchronously calling a remote D-Bus method. + * + * The call itself 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 bus + * connection I/O event loop thread. + * + * A slot (an owning handle) is returned for the async call. Lifetime of the call is bound to the lifetime of the slot. + * The slot can be used to cancel the method call at a later time by simply destroying it. + * + * The default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout(). + * + * Note: To avoid messing with messages, use API on a higher level of abstraction defined below. + * + * @throws sdbus::Error in case of failure + */ + [[nodiscard]] virtual Slot callMethodAsync( const MethodCall& message + , async_reply_handler asyncReplyCallback + , return_slot_t ) = 0; + + /*! + * @brief Calls method on the D-Bus object asynchronously, with custom timeout + * + * @param[in] message Message representing an async method call + * @param[in] asyncReplyCallback Handler for the async reply + * @param[in] timeout Method call timeout (in microseconds) + * @return Observing handle for the the pending asynchronous call + * + * This is a callback-based way of asynchronously calling a remote D-Bus method. + * + * The call itself 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 bus + * connection I/O event loop thread. + * + * An non-owning, observing async call handle is returned that can be used to query call status or cancel the call. + * + * If timeout is zero, the default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout(). + * + * Note: To avoid messing with messages, use API on a higher level of abstraction defined below. + * + * @throws sdbus::Error in case of failure + */ + virtual PendingAsyncCall callMethodAsync( const MethodCall& message + , async_reply_handler asyncReplyCallback + , uint64_t timeout ) = 0; + + /*! + * @brief Calls method on the D-Bus object asynchronously, with custom timeout + * + * @param[in] message Message representing an async method call + * @param[in] asyncReplyCallback Handler for the async reply + * @param[in] timeout Method call timeout (in microseconds) + * @return RAII-style slot handle representing the ownership of the async call + * + * This is a callback-based way of asynchronously calling a remote D-Bus method. + * + * The call itself 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 bus + * connection I/O event loop thread. + * + * A slot (an owning handle) is returned for the async call. Lifetime of the call is bound to the lifetime of the slot. + * The slot can be used to cancel the method call at a later time by simply destroying it. + * + * If timeout is zero, the default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout(). + * + * Note: To avoid messing with messages, use API on a higher level of abstraction defined below. + * + * @throws sdbus::Error in case of failure + */ + [[nodiscard]] virtual Slot callMethodAsync( const MethodCall& message + , async_reply_handler asyncReplyCallback + , uint64_t timeout + , return_slot_t ) = 0; + + /*! + * @copydoc IProxy::callMethod(const MethodCall&,async_reply_handler,uint64_t) + */ + template + PendingAsyncCall callMethodAsync( const MethodCall& message + , async_reply_handler asyncReplyCallback + , const std::chrono::duration<_Rep, _Period>& timeout ); + + /*! + * @copydoc IProxy::callMethod(const MethodCall&,async_reply_handler,uint64_t,return_slot_t) + */ + template + [[nodiscard]] Slot callMethodAsync( const MethodCall& message + , async_reply_handler asyncReplyCallback + , const std::chrono::duration<_Rep, _Period>& timeout + , return_slot_t ); + + /*! + * @brief Calls method on the D-Bus object asynchronously + * + * @param[in] message Message representing an async method call + * @param[in] Tag denoting a std::future-based overload + * @return Future object providing access to the future method reply message + * + * This is a std::future-based way of asynchronously calling a remote D-Bus method. + * + * The call itself is non-blocking. It doesn't wait for the reply. Once the reply arrives, + * the provided future object will be set to contain the reply (or sdbus::Error + * in case the remote method threw an exception). + * + * The default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout(). + * + * Note: To avoid messing with messages, use higher-level API defined below. + * + * @throws sdbus::Error in case of failure + */ + virtual std::future callMethodAsync(const MethodCall& message, with_future_t) = 0; + + /*! + * @brief Calls method on the D-Bus object asynchronously, with custom timeout + * + * @param[in] message Message representing an async method call + * @param[in] timeout Method call timeout + * @param[in] Tag denoting a std::future-based overload + * @return Future object providing access to the future method reply message + * + * This is a std::future-based way of asynchronously calling a remote D-Bus method. + * + * The call itself is non-blocking. It doesn't wait for the reply. Once the reply arrives, + * the provided future object will be set to contain the reply (or sdbus::Error + * in case the remote method threw an exception, or the call timed out). + * + * If timeout is zero, the default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout(). + * + * Note: To avoid messing with messages, use higher-level API defined below. + * + * @throws sdbus::Error in case of failure + */ + virtual std::future callMethodAsync( const MethodCall& message + , uint64_t timeout + , with_future_t ) = 0; + + /*! + * @copydoc IProxy::callMethod(const MethodCall&,uint64_t,with_future_t) + */ + template + std::future callMethodAsync( const MethodCall& message + , const std::chrono::duration<_Rep, _Period>& timeout + , with_future_t ); + + /*! + * @brief Registers a handler for the desired signal emitted by the D-Bus object + * + * @param[in] interfaceName Name of an interface that the signal belongs to + * @param[in] signalName Name of the signal + * @param[in] signalHandler Callback that implements the body of the signal handler + * + * A signal can be subscribed to at any time during proxy lifetime. The subscription + * is active immediately after the call, and stays active for the entire lifetime + * of the Proxy object. + * + * To be able to unsubscribe from the signal at a later time, use the registerSignalHandler() + * overload with request_slot tag. + * + * @throws sdbus::Error in case of failure + */ + virtual void registerSignalHandler( const InterfaceName& interfaceName + , const SignalName& signalName + , signal_handler signalHandler ) = 0; + + /*! + * @brief Registers a handler for the desired signal emitted by the D-Bus object + * + * @param[in] interfaceName Name of an interface that the signal belongs to + * @param[in] signalName Name of the signal + * @param[in] signalHandler Callback that implements the body of the signal handler + * + * @return RAII-style slot handle representing the ownership of the subscription + * + * A signal can be subscribed to and unsubscribed from at any time during proxy + * lifetime. The subscription is active immediately after the call. The lifetime + * of the subscription is bound to the lifetime of the slot object. The subscription + * is unregistered by letting go of the slot object. + * + * @throws sdbus::Error in case of failure + */ + [[nodiscard]] virtual Slot registerSignalHandler( const InterfaceName& interfaceName + , const SignalName& signalName + , signal_handler signalHandler + , return_slot_t ) = 0; + + protected: // Internal API for efficiency reasons used by high-level API helper classes friend MethodInvoker; friend AsyncMethodInvoker; friend SignalSubscriber; From a6b6490f57c92e36c492c35f6bbc57ee58133069 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Wed, 24 Apr 2024 09:18:40 +0200 Subject: [PATCH 51/52] refactor: object manager API (#438) This makes the signature of addObjectManager() function overloads consistent with common API design of the library. --- ChangeLog | 2 ++ docs/using-sdbus-c++.md | 2 ++ include/sdbus-c++/IConnection.h | 24 +++++++++++++++++++++--- include/sdbus-c++/IObject.h | 22 ++++++++++++++-------- src/Connection.cpp | 2 +- src/Connection.h | 2 +- src/IConnection.h | 3 --- src/Object.cpp | 11 +++-------- src/Object.h | 3 +-- 9 files changed, 45 insertions(+), 26 deletions(-) diff --git a/ChangeLog b/ChangeLog index 488cf918..687afe8d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -263,6 +263,8 @@ v2.0.0 - Introduce native integration for sd-event - Add method to get currently processed message also to `IConnection` - Add Slot-returning overloads of `callMethodAsync()` functions +- Add Slot-returning overload of `addObjectManager` to the `IObject` +- Add Slot-returning overload of `addObjectManager` to the `IConnection` - `[[nodiscard]]` attribute has been added to relevant API methods. - Add new `SDBUSCPP_SDBUS_LIB` CMake configuration variable determining which sd-bus library shall be picked - Switch to C++20 standard (but C++20 is not required, and the used C++20 features are conditionally compiled) diff --git a/docs/using-sdbus-c++.md b/docs/using-sdbus-c++.md index ee45e058..e97a979c 100644 --- a/docs/using-sdbus-c++.md +++ b/docs/using-sdbus-c++.md @@ -1796,6 +1796,8 @@ sdbus-c++ v2 is a major release that comes with a number of breaking API/ABI/beh * `AdaptorInterfaces::getObjectPath()` was removed. It can be replaced with `AdaptorInterfaces::getObject().getObjectPath()`. * `createConnection()` has been removed. To create a connection to the system bus use `createSystemConnection()` instead. * `createDefaultBusConnection()` has been renamed to `createBusConnection()`. +* `IObject::removeObjectManager()` and `IObject::hasObjectManager()` were removed. Clients should now use the slot-returning `IObject::addObjectManager()` to control the `ObjectManager` interface lifetime. +* `floating_slot_t` tag was removed from `IConnection::addObjectManager()`, the function is now by default floating-slot-based. * Change in behavior: `Proxy`s now by default call `createBusConnection()` to get a connection when the connection is not provided explicitly by the caller, so they connect to either the session bus or the system bus depending on the context (as opposed to always to the system bus like before). * Callbacks taking `const sdbus::Error* error` were changed to take `std::optional`, which better expresses the intent and meaning. * `getInterfaceName()`, `getMemberName()`, `getSender()`, `getPath()` and `getDestination()` methods of `Message` class now return `const char*` instead of `std::string`, for efficiency reasons. diff --git a/include/sdbus-c++/IConnection.h b/include/sdbus-c++/IConnection.h index c8b82c44..29291fd5 100644 --- a/include/sdbus-c++/IConnection.h +++ b/include/sdbus-c++/IConnection.h @@ -223,6 +223,7 @@ namespace sdbus { /*! * @brief Adds an ObjectManager at the specified D-Bus object path + * @param[in] objectPath Object path at which the ObjectManager interface shall be installed * * Creates an ObjectManager interface at the specified object path on * the connection. This is a convenient way to interrogate a connection @@ -231,12 +232,29 @@ namespace sdbus { * This call creates a floating registration. The ObjectManager will * be there for the object path until the connection is destroyed. * - * Another, recommended way to add object managers is directly through - * IObject API. + * Another, recommended way to add object managers is directly through IObject API. * * @throws sdbus::Error in case of failure */ - virtual void addObjectManager(const ObjectPath& objectPath, floating_slot_t) = 0; + virtual void addObjectManager(const ObjectPath& objectPath) = 0; + + /*! + * @brief Adds an ObjectManager at the specified D-Bus object path + * @param[in] objectPath Object path at which the ObjectManager interface shall be installed + * @return Slot handle owning the registration + * + * Creates an ObjectManager interface at the specified object path on + * the connection. This is a convenient way to interrogate a connection + * to see what objects it has. + * + * This call returns an owning slot. The lifetime of the ObjectManager + * interface is bound to the lifetime of the returned slot instance. + * + * Another, recommended way to add object managers is directly through IObject API. + * + * @throws sdbus::Error in case of failure + */ + [[nodiscard]] virtual Slot addObjectManager(const ObjectPath& objectPath, return_slot_t) = 0; /*! * @brief Installs a match rule for messages received on this bus connection diff --git a/include/sdbus-c++/IObject.h b/include/sdbus-c++/IObject.h index 3458fa6c..d79e116b 100644 --- a/include/sdbus-c++/IObject.h +++ b/include/sdbus-c++/IObject.h @@ -247,22 +247,28 @@ namespace sdbus { * the connection. This is a convenient way to interrogate a connection * to see what objects it has. * + * This call creates a so-called floating registration. This means that + * the ObjectManager interface stays there for the lifetime of the object. + * * @throws sdbus::Error in case of failure */ virtual void addObjectManager() = 0; /*! - * @brief Removes an ObjectManager interface from the path of this D-Bus object + * @brief Adds an ObjectManager interface at the path of this D-Bus object + * + * @return Slot handle owning the registration + * + * Creates an ObjectManager interface at the specified object path on + * the connection. This is a convenient way to interrogate a connection + * to see what objects it has. + * + * The lifetime of the ObjectManager interface is bound to the lifetime + * of the returned slot instance. * * @throws sdbus::Error in case of failure */ - virtual void removeObjectManager() = 0; - - /*! - * @brief Tests whether ObjectManager interface is added at the path of this D-Bus object - * @return True if ObjectManager interface is there, false otherwise - */ - [[nodiscard]] virtual bool hasObjectManager() const = 0; + [[nodiscard]] virtual Slot addObjectManager(return_slot_t) = 0; /*! * @brief Provides D-Bus connection used by the object diff --git a/src/Connection.cpp b/src/Connection.cpp index 34819245..5c65faa5 100644 --- a/src/Connection.cpp +++ b/src/Connection.cpp @@ -189,7 +189,7 @@ ISdBus& Connection::getSdBusInterface() return *sdbus_.get(); } -void Connection::addObjectManager(const ObjectPath& objectPath, floating_slot_t) +void Connection::addObjectManager(const ObjectPath& objectPath) { auto r = sdbus_->sd_bus_add_object_manager(bus_.get(), nullptr, objectPath.c_str()); diff --git a/src/Connection.h b/src/Connection.h index b991772c..ac37a51b 100644 --- a/src/Connection.h +++ b/src/Connection.h @@ -102,7 +102,7 @@ namespace sdbus::internal { bool processPendingEvent() override; Message getCurrentlyProcessedMessage() const override; - void addObjectManager(const ObjectPath& objectPath, floating_slot_t) override; + void addObjectManager(const ObjectPath& objectPath) override; Slot addObjectManager(const ObjectPath& objectPath, return_slot_t) override; void setMethodCallTimeout(uint64_t timeout) override; diff --git a/src/IConnection.h b/src/IConnection.h index 4b3185c4..19d49973 100644 --- a/src/IConnection.h +++ b/src/IConnection.h @@ -105,9 +105,6 @@ namespace sdbus::internal { virtual void emitInterfacesRemovedSignal( const ObjectPath& objectPath , const std::vector& interfaces ) = 0; - using sdbus::IConnection::addObjectManager; - [[nodiscard]] virtual Slot addObjectManager(const ObjectPath& objectPath, return_slot_t) = 0; - [[nodiscard]] virtual Slot registerSignalHandler( const char* sender , const char* objectPath , const char* interfaceName diff --git a/src/Object.cpp b/src/Object.cpp index 3e2a8db6..05d0d2eb 100644 --- a/src/Object.cpp +++ b/src/Object.cpp @@ -76,7 +76,7 @@ Slot Object::addVTable(InterfaceName interfaceName, std::vector vtab void Object::unregister() { vtables_.clear(); - removeObjectManager(); + objectManagerSlot_.reset(); } sdbus::Signal Object::createSignal(const InterfaceName& interfaceName, const SignalName& signalName) @@ -141,14 +141,9 @@ void Object::addObjectManager() objectManagerSlot_ = connection_.addObjectManager(objectPath_, return_slot); } -void Object::removeObjectManager() -{ - objectManagerSlot_.reset(); -} - -bool Object::hasObjectManager() const +Slot Object::addObjectManager(return_slot_t) { - return objectManagerSlot_ != nullptr; + return connection_.addObjectManager(objectPath_, return_slot); } sdbus::IConnection& Object::getConnection() const diff --git a/src/Object.h b/src/Object.h index 549963da..e7d27273 100644 --- a/src/Object.h +++ b/src/Object.h @@ -66,8 +66,7 @@ namespace sdbus::internal { void emitInterfacesRemovedSignal(const std::vector& interfaces) override; void addObjectManager() override; - void removeObjectManager() override; - [[nodiscard]] bool hasObjectManager() const override; + [[nodiscard]] Slot addObjectManager(return_slot_t) override; [[nodiscard]] sdbus::IConnection& getConnection() const override; [[nodiscard]] const ObjectPath& getObjectPath() const override; From 6eb0cff27331bb90c41371c1b6bed3fa093eb412 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Angelovi=C4=8D?= Date: Wed, 24 Apr 2024 19:28:30 +0200 Subject: [PATCH 52/52] refactor: remove floating_slot_t tag and use return_slot_t instead (#439) Make all the API consistent by using return_slot_t-based overloads for returning the slots to clients, and overloads without that tag for "floating" slots. floating_slot_t tag was previously added for API backwards compatibility reasons, but now is a (counter-)duplicate to the return_slot_t. --- docs/using-sdbus-c++.md | 4 +- include/sdbus-c++/IConnection.h | 64 +++++++++++++-------- include/sdbus-c++/Message.h | 3 +- src/Connection.cpp | 44 ++++++-------- src/Connection.h | 20 ++++--- src/IConnection.h | 13 +++-- src/Message.cpp | 8 +-- src/Object.cpp | 6 +- src/Proxy.cpp | 15 ++++- tests/integrationtests/DBusGeneralTests.cpp | 11 ++-- 10 files changed, 107 insertions(+), 81 deletions(-) diff --git a/docs/using-sdbus-c++.md b/docs/using-sdbus-c++.md index e97a979c..f8add929 100644 --- a/docs/using-sdbus-c++.md +++ b/docs/using-sdbus-c++.md @@ -1798,6 +1798,8 @@ sdbus-c++ v2 is a major release that comes with a number of breaking API/ABI/beh * `createDefaultBusConnection()` has been renamed to `createBusConnection()`. * `IObject::removeObjectManager()` and `IObject::hasObjectManager()` were removed. Clients should now use the slot-returning `IObject::addObjectManager()` to control the `ObjectManager` interface lifetime. * `floating_slot_t` tag was removed from `IConnection::addObjectManager()`, the function is now by default floating-slot-based. +* Slot-returning `IConnection::addMatch()` has gotten the `return_slot_t` tag parameter, while `floating_slot_t` was removed from the floating slot-based overload of the method. +* Slot-returning `IConnection::addMatchAsync()` has gotten the `return_slot_t` tag parameter, while `floating_slot_t` was removed from the floating slot-based overload of the method. * Change in behavior: `Proxy`s now by default call `createBusConnection()` to get a connection when the connection is not provided explicitly by the caller, so they connect to either the session bus or the system bus depending on the context (as opposed to always to the system bus like before). * Callbacks taking `const sdbus::Error* error` were changed to take `std::optional`, which better expresses the intent and meaning. * `getInterfaceName()`, `getMemberName()`, `getSender()`, `getPath()` and `getDestination()` methods of `Message` class now return `const char*` instead of `std::string`, for efficiency reasons. @@ -1808,7 +1810,7 @@ sdbus-c++ v2 is a major release that comes with a number of breaking API/ABI/beh * CMake options got `SDBUSCPP_` prefix for better usability and minimal risk of conflicts in downstream CMake projects. `SDBUSCPP_INSTALL` CMake option was added. * CMake components got `sdbus-c++-` prefix. -Some of these changes required correspoding adaptations in the sdbus-c++ codegen. Hence, your **C++ bindings (if any) must be re-generated** with the new sdbus-c++-xml2cpp v2 in order to use them with sdbus-c++ v2 API. +An important note: **C++ bindings generated from XML files must be re-generated** with the new `sdbus-c++-xml2cpp` when migrating to sdbus-c++ v2.0. Conclusion ---------- diff --git a/include/sdbus-c++/IConnection.h b/include/sdbus-c++/IConnection.h index 29291fd5..42caa76b 100644 --- a/include/sdbus-c++/IConnection.h +++ b/include/sdbus-c++/IConnection.h @@ -257,11 +257,10 @@ namespace sdbus { [[nodiscard]] virtual Slot addObjectManager(const ObjectPath& objectPath, return_slot_t) = 0; /*! - * @brief Installs a match rule for messages received on this bus connection + * @brief Installs a floating match rule for messages received on this bus connection * * @param[in] match Match expression to filter incoming D-Bus message * @param[in] callback Callback handler to be called upon processing an inbound D-Bus message matching the rule - * @return RAII-style slot handle representing the ownership of the subscription * * The method installs a match rule for messages received on the specified bus connection. * The syntax of the match rule expression passed in match is described in the D-Bus specification. @@ -270,39 +269,45 @@ namespace sdbus { * sends a control message requesting the match to be added to the broker and waits until the broker * confirms the match has been installed successfully. * - * Simply let go of the slot instance to uninstall the match rule from the bus connection. The slot - * must not outlive the connection for the slot is associated with it. + * The method installs a floating match rule for messages received on the specified bus connection. + * Floating means that the bus connection object owns the match rule, i.e. lifetime of the match rule + * is bound to the lifetime of the bus connection. * * For more information, consult `man sd_bus_add_match`. * * @throws sdbus::Error in case of failure */ - [[nodiscard]] virtual Slot addMatch(const std::string& match, message_handler callback) = 0; + virtual void addMatch(const std::string& match, message_handler callback) = 0; /*! - * @brief Installs a floating match rule for messages received on this bus connection + * @brief Installs a match rule for messages received on this bus connection * * @param[in] match Match expression to filter incoming D-Bus message * @param[in] callback Callback handler to be called upon processing an inbound D-Bus message matching the rule + * @return RAII-style slot handle representing the ownership of the subscription * - * The method installs a floating match rule for messages received on the specified bus connection. - * Floating means that the bus connection object owns the match rule, i.e. lifetime of the match rule - * is bound to the lifetime of the bus connection. + * The method installs a match rule for messages received on the specified bus connection. + * The syntax of the match rule expression passed in match is described in the D-Bus specification. + * The specified handler function callback is called for each incoming message matching the specified + * expression. The match is installed synchronously when connected to a bus broker, i.e. the call + * sends a control message requesting the match to be added to the broker and waits until the broker + * confirms the match has been installed successfully. * - * Refer to the @c addMatch(const std::string& match, message_handler callback) documentation for more - * information. + * The lifetime of the match rule is bound to the lifetime of the returned slot instance. Destroying + * the slot instance implies uninstalling of the match rule from the bus connection. + * + * For more information, consult `man sd_bus_add_match`. * * @throws sdbus::Error in case of failure */ - virtual void addMatch(const std::string& match, message_handler callback, floating_slot_t) = 0; + [[nodiscard]] virtual Slot addMatch(const std::string& match, message_handler callback, return_slot_t) = 0; /*! - * @brief Asynchronously installs a match rule for messages received on this bus connection + * @brief Asynchronously installs a floating match rule for messages received on this bus connection * * @param[in] match Match expression to filter incoming D-Bus message * @param[in] callback Callback handler to be called upon processing an inbound D-Bus message matching the rule * @param[in] installCallback Callback handler to be called upon processing an inbound D-Bus message matching the rule - * @return RAII-style slot handle representing the ownership of the subscription * * This method operates the same as `addMatch()` above, just that it installs the match rule asynchronously, * in a non-blocking fashion. A request is sent to the broker, but the call does not wait for a response. @@ -310,30 +315,41 @@ namespace sdbus { * from the broker as parameter. If it's an empty function object, a default implementation is used that * terminates the bus connection should installing the match fail. * - * Refer to the @c addMatch(const std::string& match, message_handler callback) documentation, and consult - * `man sd_bus_add_match`, for more information. + * The method installs a floating match rule for messages received on the specified bus connection. + * Floating means that the bus connection object owns the match rule, i.e. lifetime of the match rule + * is bound to the lifetime of the bus connection. + * + * For more information, consult `man sd_bus_add_match_async`. * * @throws sdbus::Error in case of failure */ - [[nodiscard]] virtual Slot addMatchAsync(const std::string& match, message_handler callback, message_handler installCallback) = 0; + virtual void addMatchAsync(const std::string& match, message_handler callback, message_handler installCallback) = 0; /*! - * @brief Asynchronously installs a floating match rule for messages received on this bus connection + * @brief Asynchronously installs a match rule for messages received on this bus connection * * @param[in] match Match expression to filter incoming D-Bus message * @param[in] callback Callback handler to be called upon processing an inbound D-Bus message matching the rule * @param[in] installCallback Callback handler to be called upon processing an inbound D-Bus message matching the rule + * @return RAII-style slot handle representing the ownership of the subscription * - * The method installs a floating match rule for messages received on the specified bus connection. - * Floating means that the bus connection object owns the match rule, i.e. lifetime of the match rule - * is bound to the lifetime of the bus connection. + * This method operates the same as `addMatch()` above, just that it installs the match rule asynchronously, + * in a non-blocking fashion. A request is sent to the broker, but the call does not wait for a response. + * The `installCallback' callable is called when the response is later received, with the response message + * from the broker as parameter. If it's an empty function object, a default implementation is used that + * terminates the bus connection should installing the match fail. + * + * The lifetime of the match rule is bound to the lifetime of the returned slot instance. Destroying + * the slot instance implies the uninstalling of the match rule from the bus connection. * - * Refer to the @c addMatch(const std::string& match, message_handler callback, message_handler installCallback) - * documentation for more information. + * For more information, consult `man sd_bus_add_match_async`. * * @throws sdbus::Error in case of failure */ - virtual void addMatchAsync(const std::string& match, message_handler callback, message_handler installCallback, floating_slot_t) = 0; + [[nodiscard]] virtual Slot addMatchAsync( const std::string& match + , message_handler callback + , message_handler installCallback + , return_slot_t ) = 0; /*! * @brief Retrieves the unique name of a connection. E.g. ":1.xx" diff --git a/include/sdbus-c++/Message.h b/include/sdbus-c++/Message.h index bc872f33..117f7e87 100644 --- a/include/sdbus-c++/Message.h +++ b/include/sdbus-c++/Message.h @@ -266,8 +266,7 @@ namespace sdbus { MethodCall() = default; MethodReply send(uint64_t timeout) const; - void send(void* callback, void* userData, uint64_t timeout, floating_slot_t) const; - [[nodiscard]] Slot send(void* callback, void* userData, uint64_t timeout) const; + [[nodiscard]] Slot send(void* callback, void* userData, uint64_t timeout, return_slot_t) const; MethodReply createReply() const; MethodReply createErrorReply(const sdbus::Error& error) const; diff --git a/src/Connection.cpp b/src/Connection.cpp index 5c65faa5..ff1d3250 100644 --- a/src/Connection.cpp +++ b/src/Connection.cpp @@ -225,7 +225,12 @@ uint64_t Connection::getMethodCallTimeout() const return timeout; } -Slot Connection::addMatch(const std::string& match, message_handler callback) +void Connection::addMatch(const std::string& match, message_handler callback) +{ + floatingMatchRules_.push_back(addMatch(match, std::move(callback), return_slot)); +} + +Slot Connection::addMatch(const std::string& match, message_handler callback, return_slot_t) { SDBUS_THROW_ERROR_IF(!callback, "Invalid match callback handler provided", EINVAL); @@ -240,12 +245,15 @@ Slot Connection::addMatch(const std::string& match, message_handler callback) return {matchInfo.release(), [](void *ptr){ delete static_cast(ptr); }}; } -void Connection::addMatch(const std::string& match, message_handler callback, floating_slot_t) +void Connection::addMatchAsync(const std::string& match, message_handler callback, message_handler installCallback) { - floatingMatchRules_.push_back(addMatch(match, std::move(callback))); + floatingMatchRules_.push_back(addMatchAsync(match, std::move(callback), std::move(installCallback), return_slot)); } -Slot Connection::addMatchAsync(const std::string& match, message_handler callback, message_handler installCallback) +Slot Connection::addMatchAsync( const std::string& match + , message_handler callback + , message_handler installCallback + , return_slot_t ) { SDBUS_THROW_ERROR_IF(!callback, "Invalid match callback handler provided", EINVAL); @@ -266,11 +274,6 @@ Slot Connection::addMatchAsync(const std::string& match, message_handler callbac return {matchInfo.release(), [](void *ptr){ delete static_cast(ptr); }}; } -void Connection::addMatchAsync(const std::string& match, message_handler callback, message_handler installCallback, floating_slot_t) -{ - floatingMatchRules_.push_back(addMatchAsync(match, std::move(callback), std::move(installCallback))); -} - void Connection::attachSdEventLoop(sd_event *event, int priority) { #ifndef SDBUS_basu @@ -460,7 +463,8 @@ void Connection::deleteSdEventSource(sd_event_source *s) Slot Connection::addObjectVTable( const ObjectPath& objectPath , const InterfaceName& interfaceName , const sd_bus_vtable* vtable - , void* userData ) + , void* userData + , return_slot_t ) { sd_bus_slot *slot{}; @@ -546,24 +550,11 @@ MethodReply Connection::callMethod(const MethodCall& message, uint64_t timeout) return reply; } -void Connection::callMethod(const MethodCall& message, void* callback, void* userData, uint64_t timeout, floating_slot_t) -{ - // TODO: Think of ways of optimizing these three locking/unlocking of sdbus mutex (merge into one call?) - auto timeoutBefore = getEventLoopPollData().timeout; - message.send(callback, userData, timeout, floating_slot); - auto timeoutAfter = getEventLoopPollData().timeout; - - // An event loop may wait in poll with timeout `t1', while in another thread an async call is made with - // timeout `t2'. If `t2' < `t1', then we have to wake up the event loop thread to update its poll timeout. - if (timeoutAfter < timeoutBefore) - notifyEventLoopToWakeUpFromPoll(); -} - -Slot Connection::callMethod(const MethodCall& message, void* callback, void* userData, uint64_t timeout) +Slot Connection::callMethod(const MethodCall& message, void* callback, void* userData, uint64_t timeout, return_slot_t) { // TODO: Think of ways of optimizing these three locking/unlocking of sdbus mutex (merge into one call?) auto timeoutBefore = getEventLoopPollData().timeout; - auto slot = message.send(callback, userData, timeout); + auto slot = message.send(callback, userData, timeout, return_slot); auto timeoutAfter = getEventLoopPollData().timeout; // An event loop may wait in poll with timeout `t1', while in another thread an async call is made with @@ -638,7 +629,8 @@ Slot Connection::registerSignalHandler( const char* sender , const char* interfaceName , const char* signalName , sd_bus_message_handler_t callback - , void* userData ) + , void* userData + , return_slot_t ) { sd_bus_slot *slot{}; diff --git a/src/Connection.h b/src/Connection.h index ac37a51b..0bd8b99d 100644 --- a/src/Connection.h +++ b/src/Connection.h @@ -108,10 +108,13 @@ namespace sdbus::internal { void setMethodCallTimeout(uint64_t timeout) override; [[nodiscard]] uint64_t getMethodCallTimeout() const override; - [[nodiscard]] Slot addMatch(const std::string& match, message_handler callback) override; - void addMatch(const std::string& match, message_handler callback, floating_slot_t) override; - [[nodiscard]] Slot addMatchAsync(const std::string& match, message_handler callback, message_handler installCallback) override; - void addMatchAsync(const std::string& match, message_handler callback, message_handler installCallback, floating_slot_t) override; + void addMatch(const std::string& match, message_handler callback) override; + [[nodiscard]] Slot addMatch(const std::string& match, message_handler callback, return_slot_t) override; + void addMatchAsync(const std::string& match, message_handler callback, message_handler installCallback) override; + [[nodiscard]] Slot addMatchAsync( const std::string& match + , message_handler callback + , message_handler installCallback + , return_slot_t ) override; void attachSdEventLoop(sd_event *event, int priority) override; void detachSdEventLoop() override; @@ -123,7 +126,8 @@ namespace sdbus::internal { Slot addObjectVTable( const ObjectPath& objectPath , const InterfaceName& interfaceName , const sd_bus_vtable* vtable - , void* userData ) override; + , void* userData + , return_slot_t ) override; [[nodiscard]] PlainMessage createPlainMessage() const override; [[nodiscard]] MethodCall createMethodCall( const ServiceName& destination @@ -142,8 +146,7 @@ namespace sdbus::internal { , const char* signalName ) const override; MethodReply callMethod(const MethodCall& message, uint64_t timeout) override; - void callMethod(const MethodCall& message, void* callback, void* userData, uint64_t timeout, floating_slot_t) override; - Slot callMethod(const MethodCall& message, void* callback, void* userData, uint64_t timeout) override; + Slot callMethod(const MethodCall& message, void* callback, void* userData, uint64_t timeout, return_slot_t) override; void emitPropertiesChangedSignal( const ObjectPath& objectPath , const InterfaceName& interfaceName @@ -163,7 +166,8 @@ namespace sdbus::internal { , const char* interfaceName , const char* signalName , sd_bus_message_handler_t callback - , void* userData ) override; + , void* userData + , return_slot_t ) override; private: using BusFactory = std::function; diff --git a/src/IConnection.h b/src/IConnection.h index 19d49973..412b17c9 100644 --- a/src/IConnection.h +++ b/src/IConnection.h @@ -70,7 +70,8 @@ namespace sdbus::internal { [[nodiscard]] virtual Slot addObjectVTable( const ObjectPath& objectPath , const InterfaceName& interfaceName , const sd_bus_vtable* vtable - , void* userData ) = 0; + , void* userData + , return_slot_t ) = 0; [[nodiscard]] virtual PlainMessage createPlainMessage() const = 0; [[nodiscard]] virtual MethodCall createMethodCall( const ServiceName& destination @@ -89,8 +90,11 @@ namespace sdbus::internal { , const char* signalName ) const = 0; virtual MethodReply callMethod(const MethodCall& message, uint64_t timeout) = 0; - virtual void callMethod(const MethodCall& message, void* callback, void* userData, uint64_t timeout, floating_slot_t) = 0; - [[nodiscard]] virtual Slot callMethod(const MethodCall& message, void* callback, void* userData, uint64_t timeout) = 0; + [[nodiscard]] virtual Slot callMethod( const MethodCall& message + , void* callback + , void* userData + , uint64_t timeout + , return_slot_t ) = 0; virtual void emitPropertiesChangedSignal( const ObjectPath& objectPath , const InterfaceName& interfaceName @@ -110,7 +114,8 @@ namespace sdbus::internal { , const char* interfaceName , const char* signalName , sd_bus_message_handler_t callback - , void* userData ) = 0; + , void* userData + , return_slot_t ) = 0; }; [[nodiscard]] std::unique_ptr createPseudoConnection(); diff --git a/src/Message.cpp b/src/Message.cpp index 0d04a5de..77eb0f48 100644 --- a/src/Message.cpp +++ b/src/Message.cpp @@ -821,13 +821,7 @@ MethodReply MethodCall::sendWithNoReply() const return Factory::create(); // No reply } -void MethodCall::send(void* callback, void* userData, uint64_t timeout, floating_slot_t) const -{ - auto r = sdbus_->sd_bus_call_async(nullptr, nullptr, (sd_bus_message*)msg_, (sd_bus_message_handler_t)callback, userData, timeout); - SDBUS_THROW_ERROR_IF(r < 0, "Failed to call method", -r); -} - -Slot MethodCall::send(void* callback, void* userData, uint64_t timeout) const +Slot MethodCall::send(void* callback, void* userData, uint64_t timeout, return_slot_t) const { sd_bus_slot* slot; diff --git a/src/Object.cpp b/src/Object.cpp index 05d0d2eb..63bb9c7b 100644 --- a/src/Object.cpp +++ b/src/Object.cpp @@ -67,7 +67,11 @@ Slot Object::addVTable(InterfaceName interfaceName, std::vector vtab internalVTable->sdbusVTable = createInternalSdBusVTable(*internalVTable); // 3rd step -- register the vtable with sd-bus - internalVTable->slot = connection_.addObjectVTable(objectPath_, internalVTable->interfaceName, &internalVTable->sdbusVTable[0], internalVTable.get()); + internalVTable->slot = connection_.addObjectVTable( objectPath_ + , internalVTable->interfaceName + , &internalVTable->sdbusVTable[0] + , internalVTable.get() + , return_slot ); // Return vtable wrapped in a Slot object return {internalVTable.release(), [](void *ptr){ delete static_cast(ptr); }}; diff --git a/src/Proxy.cpp b/src/Proxy.cpp index 0cb525c7..0cab73f3 100644 --- a/src/Proxy.cpp +++ b/src/Proxy.cpp @@ -124,7 +124,11 @@ PendingAsyncCall Proxy::callMethodAsync(const MethodCall& message, async_reply_h , .proxy = *this , .floating = false }); - asyncCallInfo->slot = connection_->callMethod(message, (void*)&Proxy::sdbus_async_reply_handler, asyncCallInfo.get(), timeout); + asyncCallInfo->slot = connection_->callMethod( message + , (void*)&Proxy::sdbus_async_reply_handler + , asyncCallInfo.get() + , timeout + , return_slot ); auto asyncCallInfoWeakPtr = std::weak_ptr{asyncCallInfo}; @@ -141,7 +145,11 @@ Slot Proxy::callMethodAsync(const MethodCall& message, async_reply_handler async , .proxy = *this , .floating = true }); - asyncCallInfo->slot = connection_->callMethod(message, (void*)&Proxy::sdbus_async_reply_handler, asyncCallInfo.get(), timeout); + asyncCallInfo->slot = connection_->callMethod( message + , (void*)&Proxy::sdbus_async_reply_handler + , asyncCallInfo.get() + , timeout + , return_slot ); return {asyncCallInfo.release(), [](void *ptr){ delete static_cast(ptr); }}; } @@ -209,7 +217,8 @@ Slot Proxy::registerSignalHandler( const char* interfaceName , interfaceName , signalName , &Proxy::sdbus_signal_handler - , signalInfo.get() ); + , signalInfo.get() + , return_slot ); return {signalInfo.release(), [](void *ptr){ delete static_cast(ptr); }}; } diff --git a/tests/integrationtests/DBusGeneralTests.cpp b/tests/integrationtests/DBusGeneralTests.cpp index c5c92b40..38bcceec 100644 --- a/tests/integrationtests/DBusGeneralTests.cpp +++ b/tests/integrationtests/DBusGeneralTests.cpp @@ -83,7 +83,7 @@ TYPED_TEST(AConnection, WillCallCallbackHandlerForIncomingMessageMatchingMatchRu { if(msg.getPath() == OBJECT_PATH) matchingMessageReceived = true; - }); + }, sdbus::return_slot); this->m_adaptor->emitSimpleSignal(); @@ -104,7 +104,8 @@ TYPED_TEST(AConnection, CanInstallMatchRuleAsynchronously) , [&](sdbus::Message /*msg*/) { matchRuleInstalled = true; - } ); + } + , sdbus::return_slot ); EXPECT_TRUE(waitUntil(matchRuleInstalled)); @@ -121,7 +122,7 @@ TYPED_TEST(AConnection, WillUnsubscribeMatchRuleWhenClientDestroysTheAssociatedS { if(msg.getPath() == OBJECT_PATH) matchingMessageReceived = true; - }); + }, sdbus::return_slot); slot.reset(); this->m_adaptor->emitSimpleSignal(); @@ -140,7 +141,7 @@ TYPED_TEST(AConnection, CanAddFloatingMatchRule) if(msg.getPath() == OBJECT_PATH) matchingMessageReceived = true; }; - con->addMatch(matchRule, std::move(callback), sdbus::floating_slot); + con->addMatch(matchRule, std::move(callback)); this->m_adaptor->emitSimpleSignal(); [[maybe_unused]] auto gotMessage = waitUntil(matchingMessageReceived, 2s); assert(gotMessage); @@ -160,7 +161,7 @@ TYPED_TEST(AConnection, WillNotPassToMatchCallbackMessagesThatDoNotMatchTheRule) { if(msg.getMemberName() == "simpleSignal"sv) numberOfMatchingMessages++; - }); + }, sdbus::return_slot); auto adaptor2 = std::make_unique(*this->s_adaptorConnection, OBJECT_PATH_2); this->m_adaptor->emitSignalWithMap({});