Compare commits

...

56 Commits

Author SHA1 Message Date
30d9f1d462 chore: release v1.5.0 2024-02-26 08:29:35 +01:00
28921ad424 fix: request name signal handling issue (#392)
In case a signal arrives during the `RequestName` or `ReleaseName` D-Bus call (which is a synchronous call), the signal may not be processed immediately, which is a bug. This is solved now by waking up the event loop.
2023-12-30 17:37:00 +01:00
721f583db1 refactor: handle exceptions correctly also in match callback handlers (#386)
This is a follow-up to #375. It covers match callback handlers that were missed in #375.
2023-12-05 19:06:41 +01:00
47a84ab889 feat: implement Overload pattern in utils (#385) 2023-12-05 18:16:59 +01:00
d80483cdc0 feat: extend ScopeGuard with success and failure overloads (#384) 2023-12-05 18:16:45 +01:00
934d51fa8a chore: add INSTALL_TESTS CMake option (#382)
* Change default test installation path to tests/sdbus-c++ , while also respecting CMAKE_INSTALL_PREFIX
* Introduce INSTALL_TESTS CMake option, so BUILD_TESTS is now split into BUILD_TESTS just for building and INSTALL_TESTS for installing the test binaries

Per discussion in #358 (comment), the change in the default settings is fine a for a minor release.
2023-11-23 21:05:44 +01:00
fb9e4ae371 fix: correctly add libsystemd dependency to pkgconfig (#378)
* fix: correctly add libsystemd dependency to pkgconfig

* refactor: solve sd-bus dependencies uniformly

---------

Co-authored-by: Stanislav Angelovič <stanislav.angelovic@protonmail.com>
2023-11-20 14:41:59 +01:00
6e348f3910 refactor: minor UnixFd cleanups (#376)
* chore: Use std::exchange in UnixFd

This was suggested in code review for #376 .

* fix: Protect against UnixFd self-assignment

While self-assignment is rare, it is expected to be safe.  Add a check
to prevent putting the object in an invalid state.

* fix: Improve hygiene around dup system call

- Don't try to call dup on a negative value.
- Check dup return code and throw if it fails, rather than returning an
  empty UnixFd object.

* chore: Move UnixFd::close to Types.cpp

Minor convenience for applications: unistd.h doesn't have to be included
in the public header.

---------

Co-authored-by: David Reiss <dreiss@meta.com>
2023-11-18 18:11:00 +01:00
f50e4676fe fix: add missing algorithm header include (#380)
* https://gcc.gnu.org/gcc-14/porting_to.html

Using gcc 14 uncovers a missing include in Message.h

Signed-off-by: Alfred Wingate <parona@protonmail.com>
2023-11-16 22:26:42 +01:00
1aa30e3a20 chore(ci): run freebsd job on ubuntu 2023-11-10 16:01:33 +01:00
e2b3e98374 refactor: improve handling of exceptions from callback handlers (#375)
* Catch and process all exceptions (not just sdbus::Error) from callback handlers
* Unify handling of exceptions from all types of callbacks -- always set sd_bus_error and return a negative result number in case of exception

Although libsystemd logs (with DEBUG severity) all errors from such callback handlers (except method callback handler), it seems to be out of our control. One of handy sdbus-c++ features could be the ability for clients to install a log callback, which sdbus-c++ would call in case of exceptions flying from callback handlers. In case something doesn't work for clients (especially novices), they can first look into these logs.

This may be handy in common situations like ignored signals on client side because of the inadvertent mismatch between real signal signature and signal handler signature. Like here: #373. (Although in this specific case of signals, there is a solution with an additional const sdbus::Error* argument that would reveal such an error.)
2023-11-03 18:03:01 +01:00
9490b3351f feat: add support for async registration of matches (#374) 2023-11-03 17:57:52 +01:00
9da18aec25 chore: remove obsolete TODO comments 2023-10-31 15:54:49 +01:00
b7b454ba38 doc: add section on bus connection factories 2023-10-16 19:28:27 +02:00
f420b216aa docs: update documentation 2023-10-16 15:30:23 +02:00
b482cd6d08 chore: version 1.4.0 2023-10-10 19:26:21 +02:00
aac7e590ea docs: add recommendation on destroying direct D-Bus connections 2023-10-10 19:15:21 +02:00
0ad2553417 fix: use-after-return in synchronous calls (#362)
* fix: Use-after-return in synchronous calls

This bug was introduced by c39bc637b8 and can be reproduced by
configuring with

  cmake -S . -B build -DBUILD_TESTS=yes -DCMAKE_CXX_COMPILER=clang++ \
    -DCMAKE_CXX_FLAGS="-Wno-error=deprecated-copy -fno-omit-frame-pointer -fsanitize=address -fsanitize-address-use-after-scope"

and running `cmake --buid build && cmake --build build -t test` or
`build/tests/sdbus-c++-integration-tests --gtest_filter=SdbusTestObject.HandlesCorrectlyABulkOfParallelServerSideAsyncMethods`

The issue is that `sdbus_async_reply_handler` can call `removeCall`, which
writes to `data->finished`, but `data` can point to the stack of
`sendMethodCallMessageAndWaitForReply`, which can return as soon as
`asyncCallData->callback` is called.

As a fix, I restored some of the logic removed in c39bc637b8.
Specifically, in `sdbus_async_reply_handler`, I make a copy of some data
from `asyncCallData` (a new `state` field instead of `slot`), and in the
`SCOPE_GUARD`, I don't call `removeCall` if the call was actually
synchronous.

* refactor: use enum class instead of int

---------

Co-authored-by: David Reiss <dreiss@meta.com>
Co-authored-by: Stanislav Angelovič <stanislav.angelovic@protonmail.com>
2023-10-09 20:01:45 +02:00
621b3d0862 feat: enable creation of IConnection from sd_bus object (#363) 2023-10-09 15:46:39 +02:00
189fd23744 fix(tests): fix ETIMEDOUT message on FreeBSD (#365)
[ RUN      ] SdbusTestObject/0.ThrowsTimeoutErrorWhenMethodTimesOut
tests/integrationtests/DBusMethodsTests.cpp:181: Failure
Value of: e.getMessage()
Expected: (is equal to "Connection timed out") or (is equal to "Method call timed out")
  Actual: "Operation timed out"

[  FAILED  ] SdbusTestObject/0.ThrowsTimeoutErrorWhenMethodTimesOut, where TypeParam = sdbus::test::SdBusCppLoop (3 ms)
2023-10-04 15:48:51 +02:00
cfb71bd6cf feat: add support for direct connections (#350)
* feat: add support for direct connections

* refactor: simplify a bit, change comments, extend tests

* fix: compiler warning about unused variable

* docs: add section on direct connections to the tutorial

---------

Co-authored-by: Maksim Fedyarov <m.fedyarov@omp.ru>
Co-authored-by: Stanislav Angelovič <stanislav.angelovic@protonmail.com>
2023-09-25 20:12:34 +02:00
c437b4d508 fix: honor CMAKE_POSITION_INDEPENDENT_CODE when building (#361) 2023-09-25 08:53:24 +02:00
1e2d13a04a feat: add FreeBSD support (#358)
* chore: don't use systemd headers with elogind

In file included from src/VTableUtils.c:27:
src/VTableUtils.h:30:10: fatal error: 'systemd/sd-bus.h' file not found
 #include <systemd/sd-bus.h>
          ^~~~~~~~~~~~~~~~~~

* chore: add basu support

Similar to elogind but also supported on non-Linux.

* chore(tests): permit /var/lib/machine-id on non-systemd

https://github.com/elogind/elogind/commit/84fdc0fc61c1
https://git.sr.ht/~emersion/basu/commit/8324e6729231

* chore(ci): add simple freebsd job

Mainly to cover libc++ and basu.

* chore(ci): explicitly pass CMAKE_INSTALL_PREFIX

Some sdbus-cpp tests require configuring system bus. However, Linux
testing relies on writing outside of prefix in order to affect current
system bus instance instead of launching a dedicated one.

* chore(tests): respect CMAKE_INSTALL_PREFIX for system bus config

DBus isn't part of base system on BSDs, so may not use /etc for configs.
Also, testing installation failed as non-root:

$ cmake -DBUILD_TESTS=1 -DCMAKE_INSTALL_PREFIX=/tmp/sdbus-cpp_prefix -DTESTS_INSTALL_PATH=/tmp/sdbus-cpp_prefix/tests
$ cmake --build .
$ cmake --install .
[...]
CMake Error at tests/cmake_install.cmake:105 (file):
  file cannot create directory: /etc/dbus-1/system.d.  Maybe need
  administrative privileges.

* chore(tests): temporarily skip 1 test on FreeBSD to keep CI happy

* chore(ci): run tests in freebsd job
2023-09-18 11:35:23 +02:00
290078d6af feat: add support for async property get/set on client-side (#354)
* feat: add async property get/set convenience support classes

* feat: add no-reply and async overloads to Properties_proxy

* feat: add convenience functions for GetAll functionality

* test: add tests for new functionality

* add codegen IDL support and documentation
2023-09-14 10:54:57 +02:00
0eda855745 chore: version 1.3.0 2023-08-20 11:45:44 +02:00
2a992ca84d fix: remove explicit from Variant ctor to avoid potential breaking client code 2023-08-20 11:17:10 +02:00
3717e63c64 feat: support std::future-based async methods in codegen tool (#353) 2023-08-19 20:57:32 +02:00
8113bf88ad chore(cmake): add support for libelogind (#352)
* CMakeLists.txt: Fallback to elogind when libsystemd could not be
found.  Set LIBSYSTEMD variable.
* pkgconfig/sdbus-c++.pc.in (Description): Parameterize with above
LIBSYSTEMD variable.

Co-authored-by: Sven Eden <sven.eden@prydeworx.com>
2023-08-18 12:08:37 +02:00
3e84b254e9 refactor: improve type extensibility and its safety (#348) 2023-08-09 12:39:16 +02:00
8728653359 test: add tests for type extensibility (#347) 2023-08-09 12:14:33 +02:00
6620a447d1 docs: add tutorial on extending sdbus-c++ types (#346) 2023-08-09 12:13:47 +02:00
24a3d83c3f chore: fix file permissions 2023-08-04 13:30:34 +02:00
dcd9d46b9c style: remove trailing whitespace (#345)
* style: restore correct 644 file permission

* style: remove trailing whitespace
2023-08-04 13:26:45 +02:00
605fbe48c0 fix: moving instead of copying std::string argument 2023-08-03 14:25:33 +02:00
fb61420bf0 feat: support serialization of array, span and unordered_map (#342)
* feat: support serialization of array, span and unordered_map

* fix some spelling mistakes

* docs: update table of valid c++ types

---------

Co-authored-by: Marcel Hellwig <github@cookiesoft.de>
2023-08-03 13:55:37 +02:00
0a2bda9c67 feat: make Struct tuple-like class (#343) 2023-08-03 13:00:01 +02:00
f6e597a583 fix: add cast to void to silence compiler warning in case of empty parameter list 2023-08-03 12:59:48 +02:00
29c877a89a perf: optimize serialization of arrays of trivial D-Bus types (#340)
* Improve performance of std::vector<> per Issue #339.

* refactor: improve performance of vector serialization

---------

Co-authored-by: Plinio Andrade <plinio.andrade@oracle.com>
Co-authored-by: Stanislav Angelovič <stanislav.angelovic@protonmail.com>
2023-07-27 18:31:49 +02:00
98f4929337 fix: pseudo-connection static lifetime issue 2023-07-26 08:57:21 +02:00
8d0d9b0d40 chore(ci): remove ubuntu-18.04 container image 2023-07-26 08:46:00 +02:00
c39bc637b8 fix: race condition in async Proxy::callMethod
Signed-off-by: Anthony Brandon <anthony@amarulasolutions.com>
2023-02-07 22:23:35 +01:00
737f04abc7 feat: add support for std::future-based async calls 2023-02-07 12:31:31 +01:00
3a56113422 fix: fix namespace name in ScopeGuard 2023-01-21 01:34:34 +01:00
f332f46087 fix: use correct runtime component in CMake file 2023-01-17 09:58:21 +01:00
c9e157e3e1 fix: flush long messages after sending 2023-01-05 15:12:39 +01:00
8ca3fdd5ce chore: update issue templates 2023-01-04 22:05:56 +01:00
55c306ce05 docs: strip absolute paths from doxygen documentation 2023-01-04 21:51:28 +01:00
6c5e72326c refactor(ci): use newer github actions and ubuntu v22 image 2023-01-04 21:51:13 +01:00
8bbeeeb4ce fix: integration tests in release mode 2023-01-03 16:39:54 +01:00
7a09e9bcc8 chore: install namelink to dev component 2023-01-03 15:24:17 +01:00
c812d03bc7 fix: integration tests for libsystemd v251 2023-01-03 15:20:30 +01:00
e7d4e07926 fix: broken D-Bus specs link in README (#297)
- D-Bus specification broken url link is corrected

Signed-off-by: Bhavith C <bhavithc.acharya@gmail.com>

Signed-off-by: Bhavith C <bhavithc.acharya@gmail.com>
2023-01-02 15:31:08 +01:00
031f4687ca fix: compilation warnings 2022-09-21 15:37:57 +02:00
aeae79003a refactor: support move semantics in generated adaptor and proxy classes 2022-09-20 17:05:59 +02:00
74d849d933 feat: add support for proxy with no event loop thread 2022-09-05 17:25:37 +02:00
f336811fc7 docs: add section on D-Bus types to C++ types mapping (#285)
* Added D-Bus type C++ type table

* docs: do corrections and rewordings in the proposed chapter

Co-authored-by: Stanislav Angelovič <stanislav.angelovic@protonmail.com>
2022-09-05 13:51:05 +02:00
78 changed files with 3426 additions and 646 deletions

23
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,23 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Real (buggy) behavior**
A clear and concise description of what really happened in contrast to your expectation.
**Additional context**
Add any other context about the problem here, like version of sdbus-c++ library, version of systemd used, OS used.

View File

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@ -12,7 +12,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-18.04, ubuntu-20.04]
os: [ubuntu-20.04, ubuntu-22.04]
compiler: [g++, clang]
build: [shared-libsystemd]
include:
@ -20,7 +20,7 @@ jobs:
compiler: g++
build: embedded-static-libsystemd
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: install-libsystemd-toolchain
if: matrix.build == 'embedded-static-libsystemd'
run: |
@ -39,33 +39,37 @@ 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: configure-debug
if: matrix.build == 'shared-libsystemd' && matrix.os == 'ubuntu-18.04'
- 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
run: |
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="-O0 -g -W -Wextra -Wall -Wnon-virtual-dtor -Werror" -DBUILD_TESTS=ON -DENABLE_PERF_TESTS=ON -DENABLE_STRESS_TESTS=ON -DBUILD_CODE_GEN=ON ..
- name: configure-release
sudo apt-get install -y libgmock-dev
- name: configure-debug
if: matrix.build == 'shared-libsystemd' && matrix.os == 'ubuntu-20.04'
run: |
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_CXX_FLAGS="-O3 -DNDEBUG -W -Wextra -Wall -Wnon-virtual-dtor -Werror" -DBUILD_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 -DBUILD_TESTS=ON -DINSTALL_TESTS=ON -DENABLE_PERF_TESTS=ON -DENABLE_STRESS_TESTS=ON -DBUILD_CODE_GEN=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 ..
- name: configure-with-embedded-libsystemd
if: matrix.build == 'embedded-static-libsystemd'
run: |
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_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=244 ..
- name: make
run: |
cd build
cmake --build . -j2
cmake --build . -j4
- name: verify
run: |
cd build
sudo cmake --build . --target install
ctest
ctest --output-on-failure
- name: pack
if: matrix.build == 'shared-libsystemd' && matrix.os == 'ubuntu-20.04'
run: |
@ -73,10 +77,29 @@ jobs:
cpack -G DEB
- name: 'Upload Artifact'
if: matrix.build == 'shared-libsystemd' && matrix.os == 'ubuntu-20.04' && matrix.compiler == 'g++'
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
with:
name: "debian-packages-${{ matrix.os }}-${{ matrix.compiler }}"
path: |
build/sdbus-c++*.deb
build/sdbus-c++*.ddeb
retention-days: 10
freebsd-build:
name: build (freebsd, clang/libc++, basu)
runs-on: ubuntu-22.04 # until https://github.com/actions/runner/issues/385
steps:
- uses: actions/checkout@v2
- name: Test in FreeBSD VM
uses: vmactions/freebsd-vm@v1
with:
copyback: false
usesh: true
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 --build _build
cmake --install _build
pkg install -y dbus
service dbus onestart
ctest --output-on-failure --test-dir _build

View File

@ -4,7 +4,7 @@
cmake_minimum_required(VERSION 3.13)
project(sdbus-c++ VERSION 1.2.0 LANGUAGES C CXX)
project(sdbus-c++ VERSION 1.5.0 LANGUAGES C CXX)
include(GNUInstallDirs) # Installation directories for `install` command and pkgconfig file
@ -12,11 +12,31 @@ include(GNUInstallDirs) # Installation directories for `install` command and pkg
# PERFORMING CHECKS & PREPARING THE DEPENDENCIES
#-------------------------------
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)
find_package(PkgConfig REQUIRED)
pkg_check_modules(Systemd IMPORTED_TARGET GLOBAL libsystemd>=236)
if(NOT TARGET PkgConfig::Systemd)
message(WARNING "libsystemd not found, checking for libelogind instead")
pkg_check_modules(Systemd IMPORTED_TARGET GLOBAL libelogind>=236)
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")
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")
endif()
endif()
if(NOT TARGET PkgConfig::Systemd)
message(FATAL_ERROR "libsystemd of version at least 236 is required, but was not found "
"(if you have systemd in your OS, you may want to install package containing pkgconfig "
@ -98,18 +118,22 @@ set(CMAKE_CXX_STANDARD 17)
set(SDBUSCPP_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}")
set(SDBUSCPP_VERSION "${PROJECT_VERSION}")
# We promote BUILD_SHARED_LIBS flags to (global) option only if we are the main project
# 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})
target_compile_definitions(sdbus-c++-objlib PRIVATE
BUILD_LIB=1
LIBSYSTEMD_VERSION=${LIBSYSTEMD_VERSION}
SDBUS_${LIBSYSTEMD_IMPL}
SDBUS_HEADER=<${LIBSYSTEMD_IMPL}/sd-bus.h>)
target_include_directories(sdbus-c++-objlib PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>)
if(DEFINED BUILD_SHARED_LIBS)
set_target_properties(sdbus-c++-objlib PROPERTIES POSITION_INDEPENDENT_CODE ${BUILD_SHARED_LIBS})
if(BUILD_SHARED_LIBS)
set_target_properties(sdbus-c++-objlib PROPERTIES POSITION_INDEPENDENT_CODE ON)
endif()
if(BUILD_LIBSYSTEMD)
add_dependencies(sdbus-c++-objlib LibsystemdBuildProject)
@ -140,7 +164,7 @@ endif()
install(TARGETS ${EXPORT_SET}
EXPORT sdbus-c++-targets
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 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)
@ -148,7 +172,7 @@ install(TARGETS ${EXPORT_SET}
# TESTS
#----------------------------------
option(BUILD_TESTS "Build and install tests (default OFF)" OFF)
option(BUILD_TESTS "Build tests (default OFF)" OFF)
if(BUILD_TESTS)
message(STATUS "Building with tests")
@ -215,6 +239,7 @@ if(BUILD_SHARED_LIBS AND (BUILD_LIBSYSTEMD OR Systemd_LINK_LIBRARIES MATCHES "/l
else()
set(PKGCONFIG_REQS "")
endif()
set(PKGCONFIG_DEPS ${LIBSYSTEMD_LIB})
configure_file(pkgconfig/sdbus-c++.pc.in pkgconfig/sdbus-c++.pc @ONLY)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/pkgconfig/sdbus-c++.pc
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig COMPONENT dev)

View File

@ -4,7 +4,7 @@ As an additional permission to the GNU Lesser General Public License version
2.1, the object code form of a "work that uses the Library" may incorporate
material from a header file that is part of the Library. You may distribute
such object code under terms of your choice, provided that:
(i) the header files of the Library have not been modified; and
(i) the header files of the Library have not been modified; and
(ii) the incorporated material is limited to numerical parameters, data
structure layouts, accessors, macros, inline functions and
templates; and

View File

@ -216,3 +216,35 @@ v1.2.0
- Add printer for std::chrono in googletest v1.11.0
- Fix potential undefined behavior in creation of sdbus::Error
- Additional little fixes and improvements in code, build system, and documentation
v1.3.0
- Add support for light-weight proxies (proxies without own event loop threads)
- Extend documentation with explicit mapping between D-Bus and corresponding C++ types
- Support move semantics in generated adaptor and proxy classes
- Adaptations for libsystemd v251
- Fix for proper complete sending of long D-Bus messages by explicitly flushing them
- Add support for std::future-based async calls
- Fix race condition in async Proxy::callMethod
- Fix pseudo-connection static lifetime issue with Phoenix pattern
- Speed up performance of of serialization of arrays of trivial D-Bus types
- Make sdbus::Struct a tuple-like class, so it's usable wherever std::tuple is
- Add support for std::array, std::span and std::unordered_map as additional C++ types for D-Bus array types
- Add support for libelogind as an addition to libsystemd
- Add support for std::future-based async methods in codegen tool
- Additional little fixes and improvements in code, build system, CI, and documentation
v1.4.0
- Implement API for convenient asynchronous property get/set on the client-side
- Add support for FreeBSD systems (including support for basu implementation of sd-bus on non-systemd machines)
- Add support for direct, peer-to-peer connections
- Add option to create IConnection directly from an underlying sd_bus instance
- Some additional fixes
v1.5.0
- Improve handling of exceptions from callback handlers
- Add support for async registration of matches
- Correctly add libsystemd dependency to pkgconfi
- Fix request name signal handling issue
- Add INSTALL_TESTS CMake option
- Minor UnixFd cleanups
- Additional little fixes and updates in code, build system, CI, and documentation

View File

@ -50,9 +50,13 @@ $ sudo cmake --build . --target install
Option for building sdbus-c++ stress tests. Default value: `OFF`.
* `INSTALL_TESTS` [boolean]
Option for installing tests that were built. Default value: `OFF`.
* `TESTS_INSTALL_PATH` [string]
Path where the test binaries shall get installed. Default value: `/opt/test/bin`.
Path where the test binaries shall get installed. Default value: `${CMAKE_INSTALL_PREFIX}/tests/sdbus-c++` (previously: `/opt/test/bin`).
* `BUILD_LIBSYSTEMD` [boolean]
@ -82,7 +86,7 @@ Dependencies
------------
* `C++17` - the library uses C++17 features.
* `libsystemd` - systemd library containing sd-bus implementation. This library is part of systemd. Systemd at least v236 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 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).
@ -97,7 +101,7 @@ References/documentation
* [Using sdbus-c++](docs/using-sdbus-c++.md) - *the* main, comprehensive tutorial on sdbus-c++
* [Systemd and dbus configuration](docs/systemd-dbus-config.md)
* [D-Bus Specification](https://dbus.freedesktop.org/docs/dbus-specification.html)
* [D-Bus Specification](https://dbus.freedesktop.org/doc/dbus-specification.html)
* [sd-bus Overview](http://0pointer.net/blog/the-new-sd-bus-api-of-systemd.html)
Contributing

View File

@ -46,7 +46,7 @@ ExternalProject_Add(LibsystemdBuildProject
GIT_SHALLOW 1
UPDATE_COMMAND ""
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E remove <BINARY_DIR>/*
COMMAND ${MESON} --prefix=<INSTALL_DIR> --buildtype=${LIBSYSTEMD_BUILD_TYPE} -Dstatic-libsystemd=pic -Dselinux=false <SOURCE_DIR> <BINARY_DIR> ${LIBSYSTEMD_EXTRA_CONFIG_OPTS}
COMMAND ${MESON} --prefix=<INSTALL_DIR> --buildtype=${LIBSYSTEMD_BUILD_TYPE} -Drootprefix=<INSTALL_DIR> -Dstatic-libsystemd=pic -Dselinux=false <SOURCE_DIR> <BINARY_DIR> ${LIBSYSTEMD_EXTRA_CONFIG_OPTS}
BUILD_COMMAND ${BUILD_VERSION_H}
COMMAND ${NINJA} -C <BINARY_DIR> libsystemd.a
BUILD_ALWAYS 0

View File

@ -15,7 +15,7 @@ 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")

View File

@ -162,7 +162,7 @@ FULL_PATH_NAMES = YES
# will be relative from the directory where doxygen is started.
# This tag requires that the tag FULL_PATH_NAMES is set to YES.
STRIP_FROM_PATH =
STRIP_FROM_PATH = @PROJECT_SOURCE_DIR@ @PROJECT_BINARY_DIR@
# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
# path mentioned in the documentation of a class, which tells the reader which

View File

@ -14,13 +14,15 @@ Using sdbus-c++ library
9. [An example: Number concatenator](#an-example-number-concatenator)
10. [Implementing the Concatenator example using basic sdbus-c++ API layer](#implementing-the-concatenator-example-using-basic-sdbus-c-api-layer)
11. [Implementing the Concatenator example using convenience sdbus-c++ API layer](#implementing-the-concatenator-example-using-convenience-sdbus-c-api-layer)
12. [Implementing the Concatenator example using sdbus-c++-generated stubs](#implementing-the-concatenator-example-using-sdbus-c-generated-stubs)
12. [Implementing the Concatenator example using generated C++ bindings](#implementing-the-concatenator-example-using-generated-c-bindings)
13. [Asynchronous server-side methods](#asynchronous-server-side-methods)
14. [Asynchronous client-side methods](#asynchronous-client-side-methods)
15. [Using D-Bus properties](#using-d-bus-properties)
16. [Standard D-Bus interfaces](#standard-d-bus-interfaces)
17. [Support for match rules](#support-for-match-rules)
18. [Conclusion](#conclusion)
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)
Introduction
------------
@ -54,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).
If you intend to use stub generator (explained later) in your project to generate interface headers from XML, you can integrate that too with CMake or `pkg-config`:
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`:
```cmake
# First, find sdbus-c++-tools
@ -73,13 +75,19 @@ add_custom_command(
Solving libsystemd dependency
-----------------------------
sdbus-c++ depends on libsystemd, a C library that is part of [systemd](https://github.com/systemd/systemd) and that contains underlying sd-bus implementation.
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).
Minimum required libsystemd shared library version is 0.20.0 (which corresponds to minimum systemd version 236).
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.
However, sdbus-c++ can perfectly be used in non-systemd environments as well. There are two ways to approach this:
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
1. sdbus-c++ will try to find `libelogind`, which is an extracted logind from original systemd containing sd-bus implementation. If not found, then
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.
### Building and distributing libsystemd as a shared library yourself
@ -99,7 +107,7 @@ $ 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 enviroments. 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). Be sure to check out the systemd documentation for the Also, when distributing, make sure these dependency libraries are installed on the production machine.
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.
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).
@ -202,7 +210,7 @@ sdbus-c++ is completely thread-aware by design. Though sdbus-c++ is not thread-s
* Creating and emitting signals on an `Object` instance.
* Creating and sending method calls (both synchronously and asynchronously) on an `Proxy` instance. (But it's generally better that our threads use their own exclusive instances of proxy, to minimize shared state and contention.)
sdbus-c++ is designed such that all the above operations are thread-safe also on a connection that is running an event loop (usually in a separate thread) at that time. It's an internal thread safety. For example, a signal arrives and is processed by sdbus-c++ even loop at an appropriate `Proxy` instance, while the user is going to destroy that instance in their application thread. The user cannot explicitly control these situations (or they could, but that would be very limiting and cubersome on the API level).
sdbus-c++ is designed such that all the above operations are thread-safe also on a connection that is running an event loop (usually in a separate thread) at that time. It's an internal thread safety. For example, a signal arrives and is processed by sdbus-c++ even loop at an appropriate `Proxy` instance, while the user is going to destroy that instance in their application thread. The user cannot explicitly control these situations (or they could, but that would be very limiting and cumbersome on the API level).
However, other combinations, that the user invokes explicitly from within more threads are NOT thread-safe in sdbus-c++ by design, and the user should make sure by their design that these cases never occur. For example, destroying an `Object` instance in one thread while emitting a signal on it in another thread is not thread-safe. In this specific case, the user should make sure in their application that all threads stop working with a specific instance before a thread proceeds with deleting that instance.
@ -214,7 +222,7 @@ sdbus-c++ API comes in two layers:
* [the basic layer](#implementing-the-concatenator-example-using-basic-sdbus-c-api-layer), which is a simple wrapper layer on top of sd-bus, using mechanisms that are native to C++ (e.g. serialization/deserialization of data from messages),
* [the convenience layer](#implementing-the-concatenator-example-using-convenience-sdbus-c-api-layer), building on top of the basic layer, which aims at alleviating users from unnecessary details and enables them to write shorter, safer, and more expressive code.
sdbus-c++ also ships with a stub generator tool that converts D-Bus IDL in XML format into stub code for the adaptor as well as the proxy part. Hierarchically, these stubs provide yet another layer of convenience (the "stubs layer"), making it possible for D-Bus RPC calls to completely look like native C++ calls on a local object.
sdbus-c++ also ships with sdbus-c++-xml2cpp tool that converts D-Bus IDL in XML format into C++ bindings for the adaptor as well as the proxy part. This is the highest level of API provided by sdbus-c++ (the "C++ bindings layer"), which makes it possible for D-Bus RPC calls to completely look like native C++ calls on a local object.
An example: Number concatenator
-------------------------------
@ -306,7 +314,7 @@ int main(int argc, char *argv[])
}
```
We establish a D-Bus sytem connection and request `org.sdbuscpp.concatenator` D-Bus name on it. This name will be used by D-Bus clients to find the service. We then create an object with path `/org/sdbuscpp/concatenator` on this connection. We register interfaces, its methods, signals that the object provides, and, through `finishRegistration()`, export the object (i.e., make it visible to clients) on the bus. Then we need to make sure to run the event loop upon the connection, which handles all incoming, outgoing and other requests.
We establish a D-Bus 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.
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()`.
@ -386,6 +394,27 @@ Subsequently, we invoke two RPC calls to object's `concatenate()` method. We cre
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.
### Opening bus connections in sdbus-c++
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
* `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
* `createSessionBusConnection(const std::string& name)` - opens a connection with the given name to the session 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))
* `createDirectBusConnection(int fd)` - opens direct D-Bus connection at the given file descriptor (see [Using direct (peer-to-peer) D-Bus connections](#using-direct-peer-to-peer-d-bus-connections))
* `createServerBus(int fd)` - opens direct D-Bus connection at the given file descriptor as a server (see [Using direct (peer-to-peer) D-Bus connections](#using-direct-peer-to-peer-d-bus-connections))
* `createBusConnection(sd_bus *bus)` - creates a connection directly from the underlying sd_bus connection instance (which has been created and set up upfront directly through sd-bus API).
For more information, peek into [`IConnection.h`](/include/sdbus-c++/IConnection.h) where these functions are declared and documented.
### Working with D-Bus connections in sdbus-c++
The design of D-Bus connections in sdbus-c++ allows for certain flexibility and enables users to choose simplicity over scalability or scalability (at a finer granularity of user's choice) at the cost of slightly decreased simplicity.
@ -410,19 +439,23 @@ On the **server** side, we generally need to create D-Bus objects and publish th
* 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.
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.
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.
#### Using D-Bus connections on the client side
On the **client** side we have more options when creating D-Bus proxies. That corresponds to three overloads of the `createProxy()` factory:
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:
* In case we (the application) already maintain a D-Bus connection, e.g. because we a D-Bus service anyway, the simple and typical approach is to create proxy upon that connection. The proxy will share the connection with others. So we pass connection reference to proxy factory. 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.
* Or -- and this is typical when we have a simple D-Bus client application -- we have another option: we let proxy maintain its own connection (and an associated thread):
* 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 will run the event loop on that connection. This had the advantage that we may choose the type of connection (system, session, remote).
* 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. This is **the simplest approach** for non-complex D-Bus clients. For more complex ones, with big number of proxies, this hurts scalability but may improve concurrency (see discussion higher above), so we should make a conscious choice.
* 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.
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.
#### Stopping I/O event loops graciously
@ -460,7 +493,7 @@ int main(int argc, char *argv[])
// Create concatenator D-Bus object.
const char* objectPath = "/org/sdbuscpp/concatenator";
auto concatenator = sdbus::createObject(*connection, objectPath);
auto concatenate = [&concatenator](const std::vector<int> numbers, const std::string& separator)
{
// Return error if there are no numbers in the collection
@ -549,7 +582,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.
sdbus-c++ users shall prefer the convenience API to the lower level, basic API. When feasible, using generated adaptor and proxy stubs is even better. These stubs provide yet another, higher API level built on top of the convenience API. They are described in the following section.
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`):
> ```c++
@ -559,6 +592,7 @@ sdbus-c++ users shall prefer the convenience API to the lower level, basic API.
> 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.
> **_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++
@ -582,7 +616,7 @@ Yes, there is -- we can access the corresponding D-Bus message in:
* property set implementation callback handlers (server side),
* signal callback handlers (client side).
Both `IObject` and `IProxy` provide the `getCurrentlyProcessedMessage()` method. This method is meant to be called from within a callback handler. It returns a pointer to the corresponding D-Bus message that caused invocation of the handler. The pointer is only valid (dereferencable) as long as the flow of execution does not leave the callback handler. When called from other contexts/threads, the pointer may be both zero or non-zero, and its dereferencing is undefined behavior.
Both `IObject` and `IProxy` provide the `getCurrentlyProcessedMessage()` method. This method is meant to be called from within a callback handler. It returns a pointer to the corresponding D-Bus message that caused invocation of the handler. The pointer is only valid (dereferenceable) as long as the flow of execution does not leave the callback handler. When called from other contexts/threads, the pointer may be both zero or non-zero, and its dereferencing is undefined behavior.
An excerpt of the above example of concatenator modified to print out a name of the sender of method call:
@ -596,15 +630,15 @@ An excerpt of the above example of concatenator modified to print out a name of
};
```
Implementing the Concatenator example using sdbus-c++-generated stubs
---------------------------------------------------------------------
Implementing the Concatenator example using generated C++ bindings
------------------------------------------------------------------
sdbus-c++ ships with the native stub generator tool called `sdbus-c++-xml2cpp`. The tool is very similar to `dbusxx-xml2cpp` tool that comes with the dbus-c++ library.
sdbus-c++ ships with native C++ binding generator tool called `sdbus-c++-xml2cpp`. The tool is very similar to `dbusxx-xml2cpp` tool that comes with the dbus-c++ library.
The generator tool takes D-Bus XML IDL description of D-Bus interfaces on its input, and can be instructed to generate one or both of these: an adaptor header file for use on the server side, and a proxy header file for use on the client side. Like this:
```bash
sdbus-c++-xml2cpp database-bindings.xml --adaptor=database-server-glue.h --proxy=database-client-glue.h
sdbus-c++-xml2cpp concatenator-bindings.xml --adaptor=concatenator-server-glue.h --proxy=concatenator-client-glue.h
```
The adaptor header file contains classes that can be used to implement interfaces described in the IDL (these classes represent object interfaces). The proxy header file contains classes that can be used to make calls to remote objects (these classes represent remote object interfaces).
@ -630,12 +664,14 @@ As an example, let's look at an XML description of our Concatenator's interfaces
</node>
```
After running this through the stubs generator, we get the stub code that is described in the following two subsections.
After running this through the code generator, we get the generated code that is described in the following two subsections.
### concatenator-server-glue.h
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.
```cpp
/*
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
@ -658,25 +694,30 @@ public:
protected:
Concatenator_adaptor(sdbus::IObject& object)
: object_(object)
: object_(&object)
{
object_.registerMethod("concatenate").onInterface(INTERFACE_NAME).withInputParamNames("numbers", "separator").withOutputParamNames("concatenatedString").implementedAs([this](const std::vector<int32_t>& numbers, const std::string& separator){ return this->concatenate(numbers, separator); });
object_.registerSignal("concatenated").onInterface(INTERFACE_NAME).withParameters<std::string>("concatenatedString");
object_->registerMethod("concatenate").onInterface(INTERFACE_NAME).withInputParamNames("numbers", "separator").withOutputParamNames("concatenatedString").implementedAs([this](const std::vector<int32_t>& numbers, const std::string& separator){ return this->concatenate(numbers, separator); });
object_->registerSignal("concatenated").onInterface(INTERFACE_NAME).withParameters<std::string>("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() = default;
public:
void emitConcatenated(const std::string& concatenatedString)
{
object_.emitSignal("concatenated").onInterface(INTERFACE_NAME).withArguments(concatenatedString);
object_->emitSignal("concatenated").onInterface(INTERFACE_NAME).withArguments(concatenatedString);
}
private:
virtual std::string concatenate(const std::vector<int32_t>& numbers, const std::string& separator) = 0;
private:
sdbus::IObject& object_;
sdbus::IObject* object_;
};
}} // namespaces
@ -686,7 +727,9 @@ private:
### concatenator-client-glue.h
Analogously to the adaptor classes described above, there is one 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.
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.
```cpp
/*
@ -710,11 +753,16 @@ public:
protected:
Concatenator_proxy(sdbus::IProxy& proxy)
: proxy_(proxy)
: proxy_(&proxy)
{
proxy_.uponSignal("concatenated").onInterface(INTERFACE_NAME).call([this](const std::string& concatenatedString){ this->onConcatenated(concatenatedString); });
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() = default;
virtual void onConcatenated(const std::string& concatenatedString) = 0;
@ -723,12 +771,12 @@ public:
std::string concatenate(const std::vector<int32_t>& numbers, const std::string& separator)
{
std::string result;
proxy_.callMethod("concatenate").onInterface(INTERFACE_NAME).withArguments(numbers, separator).storeResultsTo(result);
proxy_->callMethod("concatenate").onInterface(INTERFACE_NAME).withArguments(numbers, separator).storeResultsTo(result);
return result;
}
private:
sdbus::IProxy& proxy_;
sdbus::IProxy* proxy_;
};
}} // namespaces
@ -746,7 +794,7 @@ In our object class we need to:
* Give an implementation to the D-Bus object's methods by overriding corresponding virtual functions,
* call `registerAdaptor()` in the constructor, which makes the adaptor (the D-Bus object underneath it) available for remote calls,
* call `unregisterAdaptor()`, which, conversely, unregisters the adaptor from the bus.
* call `unregisterAdaptor()`, which, conversely, deregisters the adaptor from the bus.
Calling `registerAdaptor()` and `unregisterAdaptor()` was not necessary in previous sdbus-c++ versions, as it was handled by the parent class. This was convenient, but suffered from a potential pure virtual function call issue. Only the class that implements virtual functions can do the registration, hence this slight inconvenience on user's shoulders.
@ -825,7 +873,7 @@ In our proxy class we need to:
* Give an implementation to signal handlers and asynchronous method reply handlers (if any) by overriding corresponding virtual functions,
* call `registerProxy()` in the constructor, which makes the proxy (the D-Bus proxy object underneath it) ready to receive signals and async call replies,
* call `unregisterProxy()`, which, conversely, unregisters the proxy from the bus.
* call `unregisterProxy()`, which, conversely, deregisters the proxy from the bus.
Calling `registerProxy()` and `unregisterProxy()` was not necessary in previous versions of sdbus-c++, as it was handled by the parent class. This was convenient, but suffered from a potential pure virtual function call issue. Only the class that implements virtual functions can do the registration, hence this slight inconvenience on user's shoulders.
@ -983,7 +1031,7 @@ Callbacks of async methods based on convenience sdbus-c++ API have slightly diff
* The result holder is of type `Result<Types...>&&`, where `Types...` is a list of method output argument types.
* The result object must be the first physical parameter of the callback taken by r-value ref. `Result` class template is move-only.
* The callback itself is physically a void-returning function.
* Method input arguments are taken by value rathern than by const ref, because we usually want to `std::move` them to the worker thread. Moving is usually a lot cheaper than copying, and it's idiomatic. For non-movable types, this falls back to copying.
* Method input arguments are taken by value rather than by const ref, because we usually want to `std::move` them to the worker thread. Moving is usually a lot cheaper than copying, and it's idiomatic. For non-movable types, this falls back to copying.
So the concatenate callback signature would change from `std::string concatenate(const std::vector<int32_t>& numbers, const std::string& separator)` to `void concatenate(sdbus::Result<std::string>&& result, std::vector<int32_t> numbers, std::string separator)`:
@ -1018,11 +1066,11 @@ void concatenate(sdbus::Result<std::string>&& result, std::vector<int32_t> numbe
The `Result` is a convenience class that represents a future method result, and it is where we write the results (`returnResults()`) or an error (`returnError()`) which we want to send back to the client.
Registraion (`implementedAs()`) doesn't change. Nothing else needs to change.
Registration (`implementedAs()`) doesn't change. Nothing else needs to change.
### Marking server-side async methods in the IDL
sdbus-c++ stub generator can generate stub code for server-side async methods. We just need to annotate the method with `org.freedesktop.DBus.Method.Async`. The annotation element value must be either `server` (async method on server-side only) or `clientserver` (async method on both client- and server-side):
sdbus-c++-xml2cpp tool can generate C++ code for server-side async methods. We just need to annotate the method with `org.freedesktop.DBus.Method.Async`. The annotation element value must be either `server` (async method on server-side only) or `client-server` (async method on both client- and server-side):
```xml
<?xml version="1.0" encoding="UTF-8"?>
@ -1047,11 +1095,11 @@ For a real example of a server-side asynchronous D-Bus method, please look at sd
Asynchronous client-side methods
--------------------------------
sdbus-c++ also supports asynchronous approach at the client (the proxy) side. With this approach, we can issue a D-Bus method call without blocking current thread's execution while waiting for the reply. We go on doing other things, and when the reply comes, a given callback is invoked within the context of the D-Bus dispatcher thread.
sdbus-c++ also supports asynchronous approach at the client (the proxy) side. With this approach, we can issue a D-Bus method call without blocking current thread's execution while waiting for the reply. We go on doing other things, and when the reply comes, either a given callback handler will be invoked within the context of the event loop thread, or a future object returned by the async call will be set the returned value.6
### Lower-level API
Considering the Concatenator example based on lower-level API, if we wanted to call `concatenate` in an async way, we'd have to pass a callback to the proxy when issuing the call, and that callback gets invoked when the reply arrives:
Considering the Concatenator example based on lower-level API, if we wanted to call `concatenate` in an async way, we have two options: We either pass a callback to the proxy when issuing the call, and that callback gets invoked when the reply arrives:
```c++
int main(int argc, char *argv[])
@ -1096,9 +1144,34 @@ 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.
There is also an overload of this `IProxy::callMethod()` function taking method call timeout argument.
Another option is to use `std::future`-based overload of the `IProxy::callMethod()` function. A future object will be returned which will later, when the reply arrives, be set to contain the returned reply message. Or if the call returns an error, `sdbus::Error` will be thrown by `std::future::get()`.
```c++
...
// Invoke concatenate on given interface of the object
{
auto method = concatenatorProxy->createMethodCall(interfaceName, "concatenate");
method << numbers << separator;
auto future = concatenatorProxy->callMethod(method, sdbus::with_future);
try
{
auto reply = future.get(); // This will throw if call ends with an error
std::string result;
reply >> result;
std::cout << "Got concatenate result: " << result << std::endl;
}
catch (const sdbus::Error& e)
{
std::cerr << "Got concatenate error " << e.getName() << " with message " << e.getMessage() << std::endl;
}
}
```
### Convenience API
On the convenience API level, the call statement starts with `callMethodAsync()`, and ends with `uponReplyInvoke()` that takes a callback handler. The callback is a void-returning function that takes at least one argument: pointer to the `sdbus::Error` instance. All subsequent arguments shall exactly reflect the D-Bus method output arguments. A concatenator example:
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:
```c++
int main(int argc, char *argv[])
@ -1133,9 +1206,28 @@ 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.
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.
```c++
...
auto future = concatenatorProxy->callMethodAsync("concatenate").onInterface(interfaceName).withArguments(numbers, separator).getResultAsFuture<std::string>();
try
{
auto concatenatedString = future.get(); // This waits for the reply
std::cout << "Got concatenate result: " << concatenatedString << std::endl;
}
catch (const sdbus::Error& e)
{
std::cerr << "Got concatenate error " << e.getName() << " with message " << e.getMessage() << std::endl;
}
...
```
### Marking client-side async methods in the IDL
sdbus-c++ stub generator can generate stub code for client-side async methods. We just need to annotate the method with `org.freedesktop.DBus.Method.Async`. The annotation element value must be either `client` (async on the client-side only) or `clientserver` (async method on both client- and server-side):
sdbus-c++-xml2cpp can generate C++ code for client-side async methods. We just need to annotate the method with `org.freedesktop.DBus.Method.Async`. The annotation element value must be either `client` (async on the client-side only) or `client-server` (async method on both client- and server-side):
```xml
<?xml version="1.0" encoding="UTF-8"?>
@ -1155,11 +1247,19 @@ sdbus-c++ stub generator can generate stub code for client-side async methods. W
</node>
```
An asynchronous method can be generated as a callback-based method or `std::future`-based method. This can optionally be customized through an additional `org.freedesktop.DBus.Method.Async.ClientImpl` annotation. Its supported values are `callback` and `std::future`. The default behavior is callback-based method.
#### Generating callback-based async methods
For each client-side async method, a corresponding `on<MethodName>Reply` pure virtual function, where `<MethodName>` 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 stub generator will generate a `Concatenator_proxy` class similar to one shown in a [dedicated section above](#concatenator-client-glueh), with the difference that it will also generate an additional `virtual void onConcatenateReply(const sdbus::Error* error, const std::string& concatenatedString);` method, which we shall override in derived `ConcatenatorProxy`.
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`.
For a real example of a client-side asynchronous D-Bus method, please look at sdbus-c++ [stress tests](/tests/stresstests).
#### Generating std:future-based async methods
In this case, a `std::future` is returned by the method, which will later, when the reply arrives, get set to contain the return value. Or if the call returns an error, `sdbus::Error` will be thrown by `std::future::get()`.
For a real example of a client-side asynchronous D-Bus methods, please look at sdbus-c++ [stress tests](/tests/stresstests).
## Method call timeout
@ -1185,9 +1285,67 @@ Annotate the element with `org.freedesktop.DBus.Method.Timeout` in order to spec
Using D-Bus properties
----------------------
sdbus-c++ provides functionality for convenient working with D-Bus properties, on both convenience and generated code API level.
### Convenience API
Let's say a remote D-Bus object provides property `status` of type `u` under interface `org.sdbuscpp.Concatenator`.
#### Reading a property
We read property value easily through `IProxy::getProperty()` method:
```c++
uint32_t status = proxy->getProperty("status").onInterface("org.sdbuscpp.Concatenator");
```
Getting a property in asynchronous manner is also possible, in both callback-based and future-based way, by calling `IProxy::getPropertyAsync()` method:
```c++
// Callback-based method:
auto callback = [](const sdbus::Error* err, sdbus::Variant value)
{
std::cout << "Got property value: " << value.get<uint32_t>() << std::endl;
};
uint32_t status = proxy->getPropertyAsync("status").onInterface("org.sdbuscpp.Concatenator").uponReplyInvoke(std::move(callback));
// Future-based method:
std::future<sdbus::Variant> statusFuture = object.getPropertyAsync("status").onInterface("org.sdbuscpp.Concatenator").getResultAsFuture();
...
std::cout << "Got property value: " << statusFuture.get().get<uint32_t>() << 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).
#### Writing a property
Writing a property is equally simple, through `IProxy::setProperty()`:
```c++
uint32_t status = ...;
proxy->setProperty("status").onInterface("org.sdbuscpp.Concatenator").toValue(status);
```
Setting a property in asynchronous manner is also possible, in both callback-based and future-based way, by calling `IProxy::setPropertyAsync()` method:
```c++
// Callback-based method:
auto callback = [](const sdbus::Error* err) { /*... Error handling in case err is non-null...*/ };
uint32_t status = proxy->setPropertyAsync("status").onInterface("org.sdbuscpp.Concatenator").toValue(status).uponReplyInvoke(std::move(callback));
// Future-based method:
std::future<void> statusFuture = object.setPropertyAsync("status").onInterface("org.sdbuscpp.Concatenator").getResultAsFuture();
```
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).
#### Getting all properties
In a very analogous way, with both synchronous and asynchronous options, it's possible to read all properties of an object under given interface at once. `IProxy::getAllProperties()` is what you're looking for.
### Generated bindings API
Defining and working with D-Bus properties using XML description is quite easy.
### Defining a property in the IDL
#### Defining a property in the IDL
A property element has no arg child element. It just has the attributes name, type and access, which are all mandatory. The access attribute allows the values readwrite, read, and write.
@ -1205,7 +1363,9 @@ An example of a read-write property `status`:
</node>
```
### Generated stubs
The property may also have annotations. In addition to standard annotations defined in D-Bus specification, there are sdbus-c++-specific ones, discussed further below.
#### Generated C++ bindings
This is how generated adaptor and proxy classes would look like with the read-write `status` property. The adaptor:
@ -1216,9 +1376,9 @@ class PropertyProvider_adaptor
public:
PropertyProvider_adaptor(sdbus::IObject& object)
: object_(object)
: object_(&object)
{
object_.registerProperty("status").onInterface(INTERFACE_NAME).withGetter([this](){ return this->status(); }).withSetter([this](const uint32_t& value){ this->status(value); });
object_->registerProperty("status").onInterface(INTERFACE_NAME).withGetter([this](){ return this->status(); }).withSetter([this](const uint32_t& value){ this->status(value); });
}
~PropertyProvider_adaptor() = default;
@ -1245,20 +1405,73 @@ public:
// getting the property value
uint32_t status()
{
return object_.getProperty("status").onInterface(INTERFACE_NAME);
return object_->getProperty("status").onInterface(INTERFACE_NAME);
}
// setting the property value
void status(const uint32_t& value)
{
object_.setProperty("status").onInterface(INTERFACE_NAME).toValue(value);
object_->setProperty("status").onInterface(INTERFACE_NAME).toValue(value);
}
/*...*/
};
```
When implementing the adaptor, we simply need to provide the body for `status` getter and setter method by overriding them. Then in the proxy, we just call them.
When implementing the adaptor, we simply need to provide the body for the `status` getter and setter methods by overriding them. Then in the proxy, we just call them.
#### Client-side asynchronous properties
We can mark the property so that the generator generates either asynchronous variant of getter method, or asynchronous variant of setter method, or both. Annotations names are `org.freedesktop.DBus.Property.Get.Async`, or `org.freedesktop.DBus.Property.Set.Async`, respectively. Their values must be set to `client`.
In addition, we can choose through annotations `org.freedesktop.DBus.Property.Get.Async.ClientImpl`, or `org.freedesktop.DBus.Property.Set.Async.ClientImpl`, respectively, whether a callback-based or future-based variant will be generated. The concept is analogous to the one for asynchronous D-Bus methods described above in this document.
The callback-based method will generate a pure virtual function `On<PropertyName>Property[Get|Set]Reply()`, which must be overridden by the derived class.
For example, this description:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<node name="/org/sdbuscpp/propertyprovider">
<interface name="org.sdbuscpp.PropertyProvider">
<!--...-->
<property name="status" type="u" access="readwrite">
<annotation name="org.freedesktop.DBus.Property.Get.Async" value="client"/>
<annotation name="org.freedesktop.DBus.Property.Get.Async.ClientImpl" value="callback"/>
</property>
<!--...-->
</interface>
</node>
```
will get generated into this C++ code on client side:
```cpp
class PropertyProvider_proxy
{
/*...*/
virtual void onStatusPropertyGetReply(const uint32_t& value, const sdbus::Error* 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<uint32_t>(), error); });
}
// setting the property value
void status(const uint32_t& value)
{
object_->setProperty("status").onInterface(INTERFACE_NAME).toValue(value);
}
/*...*/
};
```
In addition to custom generated code for getting/setting properties, `org.freedesktop.DBus.Properties` standard D-Bus interface, implemented through pre-defined `sdbus::Properties_proxy` in `sdbus-c++/StandardInterfaces.h`, can also be used for reading/writing properties. See next section.
Standard D-Bus interfaces
-------------------------
@ -1280,10 +1493,211 @@ Note that signals of afore-mentioned standard D-Bus interfaces are not emitted b
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.
Representing D-Bus Types in sdbus-c++
-------------------------------------
sdbus-c++ provides many default, pre-defined C++ type representations for D-Bus types. The table below shows which C++ type corresponds to which D-Bus type.
| 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` |
| container | 97 | a | ARRAY | `std::vector<T>`, `std::array<T>`, `std::span<T>` - if used as an array followed by a single complete type `T` <br /> `std::map<T1, T2>`, `std::unordered_map<T1, T2>` - if used as an array of dict entries |
| container | 114,40,41 | r() | STRUCT | `sdbus::Struct<T1, T2, ...>` variadic class template |
| container | 118 | v | VARIANT | `sdbus::Variant` |
| container | 101,123,125 | e{} | DICT_ENTRY | - |
| fixed, basic | 104 | h | UNIX_FD | `sdbus::UnixFd` |
| reserved | 109 | m | (reserved) | - |
| reserved | 42 | * | (reserved) | - |
| reserved | 63 | ? | (reserved) | - |
| reserved | 64,38,94 | @&^ | (reserved) | - |
A few examples:
* The D-Bus signature of an output argument of method `GetManagedObjects()` on standard interface `org.freedesktop.DBus.ObjectManager` is `a{oa{sa{sv}}}`. For this the corresponding C++ method return type is: `std::map<sdbus::ObjectPath, std::map<std::string, std::map<std::string, sdbus::Variant>>>`.
* Or an input argument of method `InterfacesRemoved` on that interface has signature `as`. Ths corresponds to the C++ parameter of type `std::vector<std::string>`.
* Or a D-Bus signature `a(bdh)` corresponds to the array of D-Bus structures: `std::vector<sdbus::Struct<bool, double, sdbus::UnixFd>>`.
To see how C++ types are mapped to D-Bus types (including container types) in sdbus-c++, have a look at individual [specializations of `sdbus::signature_of` class template](https://github.com/Kistler-Group/sdbus-cpp/blob/master/include/sdbus-c%2B%2B/TypeTraits.h#L87) in TypeTraits.h header file. For more examples of type mappings, look into [TypeTraits unit tests](https://github.com/Kistler-Group/sdbus-cpp/blob/master/tests/unittests/TypeTraits_test.cpp#L62).
For more information on basic D-Bus types, D-Bus container types, and D-Bus type system in general, make sure to consult the [D-Bus specification](https://dbus.freedesktop.org/doc/dbus-specification.html#type-system).
### 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.
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`:
```c++
#include <list>
#include <sdbus-c++/sdbus-c++.h>
namespace sdbus {
// Implementing serialization for std::list
template <typename _ElementType>
sdbus::Message& operator<<(sdbus::Message& msg, const std::list<_ElementType>& items)
{
msg.openContainer(sdbus::signature_of<_ElementType>::str());
for (const auto& item : items)
msg << item;
msg.closeContainer();
return msg;
}
// Implementing deserialization for std::list
template <typename _ElementType>
sdbus::Message& operator>>(sdbus::Message& msg, std::list<_ElementType>& items)
{
if(!msg.enterContainer(sdbus::signature_of<_ElementType>::str()))
return msg;
while (true)
{
_ElementType elem;
if (msg >> elem)
items.emplace_back(std::move(elem));
else
break;
}
msg.clearFlags();
msg.exitContainer();
return msg;
}
} // 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.
template <typename _Element, typename _Allocator>
struct sdbus::signature_of<std::list<_Element, _Allocator>>
: sdbus::signature_of<std::vector<_Element, _Allocator>>
{};
```
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.
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++
namespace my {
struct Struct
{
int i;
std::string s;
std::list<double> l;
};
sdbus::Message& operator<<(sdbus::Message& msg, const Struct& items)
{
// Re-use sdbus::Struct functionality for simplicity -- view of my::Struct through sdbus::Struct with reference types
return msg << sdbus::Struct{std::forward_as_tuple(items.i, items.s, items.l)};
}
sdbus::Message& operator>>(sdbus::Message& msg, Struct& items)
{
// Re-use sdbus::Struct functionality for simplicity -- view of my::Struct through sdbus::Struct with reference types
sdbus::Struct s{std::forward_as_tuple(items.i, items.s, items.l)};
return msg >> s;
}
} // namespace my
template <>
struct sdbus::signature_of<my::Struct>
: sdbus::signature_of<sdbus::Struct<int, std::string, std::list<double>>>
{};
```
> **_Note_:** One of `my::Struct` members is `std::list`. Thanks to the above custom support for `std::list`, it's now automatically accepted by sdbus-c++ as a D-Bus array representation.
Live examples of extending sdbus-c++ types can be found in [Message unit tests](/tests/unittests/Message_test.cpp).
Support for match rules
-----------------------
`IConnection` class provides `addMatch` method that you can use to install match rules. An associated callback handler will be called upon an incoming message matching given match rule. There is support for both client-owned and floating (library-owned) match rules. Consult `IConnection` header or sdbus-c++ doxygen documentation for more information.
`IConnection` class provides `addMatch` and `addMatchAsync` family of methods that you can use to install match rules on that bus connection. An associated callback handler will be called when an incoming D-Bus message matches the given match rule. Clients can decide whether they own and control the match rule lifetime, or whether the match rule lifetime is bound the connection object lifetime (so-called floating match rule). Consult `IConnection` header or sdbus-c++ doxygen documentation for more information.
Using direct (peer-to-peer) D-Bus connections
---------------------------------------------
sdbus-c++ provides an API to establish a direct connection between two peers -- between a client and a server, without going via the D-Bus daemon. The methods of interest, which will create a D-Bus server bus, and a client connection to it, respectively, are:
* `sdbus::createServerBus()` creates and returns a new, custom bus object in server mode, out of provided file descriptor parameter.
* `sdbus::createDirectBusConnection()` opens and returns direct D-Bus connection at the provided custom address(es), or at the provided file descriptor.
Here is an example, extracted from the analogous test case in sdbus-c++ integration tests suite:
```c++
#include "Concatenator.h"
#include "ConcatenatorProxy.h"
#include <sdbus-c++/sdbus-c++.h>
#include <sys/types.h>
#include <sys/socket.h>
int main(int argc, char *argv[])
{
int fds[2];
socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
std::unique_ptr<sdbus::IConnection> serverConnection;
std::unique_ptr<sdbus::IConnection> clientConnection;
std::thread t([&]()
{
serverConnection = sdbus::createServerBus(fds[0]);
// This is necessary so that createDirectBusConnection() below does not block
serverConnection->enterEventLoopAsync();
});
clientConnection = sdbus::createDirectBusConnection(fds[1]);
clientConnection->enterEventLoopAsync();
t.join();
// 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);
// Perform call of concatenate D-Bus method
std::vector<int> numbers = {1, 2, 3};
std::string separator = ":";
auto concatenatedString = concatenatorProxy.concatenate(numbers, separator);
assert(concatenatedString == "1:2:3");
// Explicitly stop working on socket fd's to avoid "Connection reset by peer" errors
clientConnection->leaveEventLoop();
serverConnection->leaveEventLoop();
}
```
> **_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.
Conclusion
----------

View File

@ -141,6 +141,11 @@ namespace sdbus {
protected:
using base_type = AdaptorInterfaces;
AdaptorInterfaces(const AdaptorInterfaces&) = delete;
AdaptorInterfaces& operator=(const AdaptorInterfaces&) = delete;
AdaptorInterfaces(AdaptorInterfaces&&) = default;
AdaptorInterfaces& operator=(AdaptorInterfaces&&) = default;
~AdaptorInterfaces() = default;
};

View File

@ -29,18 +29,19 @@
#include <sdbus-c++/Message.h>
#include <sdbus-c++/TypeTraits.h>
#include <sdbus-c++/Types.h>
#include <sdbus-c++/Flags.h>
#include <string>
#include <vector>
#include <type_traits>
#include <chrono>
#include <future>
#include <cstdint>
// Forward declarations
namespace sdbus {
class IObject;
class IProxy;
class Variant;
class Error;
class PendingAsyncCall;
}
@ -50,7 +51,7 @@ namespace sdbus {
class MethodRegistrator
{
public:
MethodRegistrator(IObject& object, const std::string& methodName);
MethodRegistrator(IObject& object, std::string methodName);
MethodRegistrator(MethodRegistrator&& other) = default;
~MethodRegistrator() noexcept(false);
@ -66,7 +67,7 @@ namespace sdbus {
private:
IObject& object_;
const std::string& methodName_;
std::string methodName_;
std::string interfaceName_;
std::string inputSignature_;
std::vector<std::string> inputParamNames_;
@ -80,7 +81,7 @@ namespace sdbus {
class SignalRegistrator
{
public:
SignalRegistrator(IObject& object, const std::string& signalName);
SignalRegistrator(IObject& object, std::string signalName);
SignalRegistrator(SignalRegistrator&& other) = default;
~SignalRegistrator() noexcept(false);
@ -92,7 +93,7 @@ namespace sdbus {
private:
IObject& object_;
const std::string& signalName_;
std::string signalName_;
std::string interfaceName_;
std::string signalSignature_;
std::vector<std::string> paramNames_;
@ -195,6 +196,10 @@ namespace sdbus {
AsyncMethodInvoker& withTimeout(const std::chrono::duration<_Rep, _Period>& timeout);
template <typename... _Args> AsyncMethodInvoker& withArguments(_Args&&... args);
template <typename _Function> PendingAsyncCall uponReplyInvoke(_Function&& callback);
// Returned future will be std::future<void> for no (void) D-Bus method return value
// or std::future<T> for single D-Bus method return value
// or std::future<std::tuple<...>> for multiple method return values
template <typename... _Args> std::future<future_return_t<_Args...>> getResultAsFuture();
private:
IProxy& proxy_;
@ -220,7 +225,7 @@ namespace sdbus {
{
public:
SignalUnsubscriber(IProxy& proxy, const std::string& signalName);
void onInterface(std::string interfaceName);
void onInterface(const std::string& interfaceName);
private:
IProxy& proxy_;
@ -231,25 +236,81 @@ namespace sdbus {
{
public:
PropertyGetter(IProxy& proxy, const std::string& propertyName);
sdbus::Variant onInterface(const std::string& interfaceName);
Variant onInterface(const std::string& interfaceName);
private:
IProxy& proxy_;
const std::string& propertyName_;
};
class AsyncPropertyGetter
{
public:
AsyncPropertyGetter(IProxy& proxy, const std::string& propertyName);
AsyncPropertyGetter& onInterface(const std::string& interfaceName);
template <typename _Function> PendingAsyncCall uponReplyInvoke(_Function&& callback);
std::future<Variant> getResultAsFuture();
private:
IProxy& proxy_;
const std::string& propertyName_;
const std::string* interfaceName_{};
};
class PropertySetter
{
public:
PropertySetter(IProxy& proxy, const std::string& propertyName);
PropertySetter& onInterface(std::string interfaceName);
PropertySetter& onInterface(const std::string& interfaceName);
template <typename _Value> void toValue(const _Value& value);
void toValue(const sdbus::Variant& value);
template <typename _Value> void toValue(const _Value& value, dont_expect_reply_t);
void toValue(const Variant& value);
void toValue(const Variant& value, dont_expect_reply_t);
private:
IProxy& proxy_;
const std::string& propertyName_;
std::string interfaceName_;
const std::string* interfaceName_{};
};
class AsyncPropertySetter
{
public:
AsyncPropertySetter(IProxy& proxy, const std::string& propertyName);
AsyncPropertySetter& onInterface(const std::string& interfaceName);
template <typename _Value> AsyncPropertySetter& toValue(_Value&& value);
AsyncPropertySetter& toValue(Variant value);
template <typename _Function> PendingAsyncCall uponReplyInvoke(_Function&& callback);
std::future<void> getResultAsFuture();
private:
IProxy& proxy_;
const std::string& propertyName_;
const std::string* interfaceName_{};
Variant value_;
};
class AllPropertiesGetter
{
public:
AllPropertiesGetter(IProxy& proxy);
std::map<std::string, Variant> onInterface(const std::string& interfaceName);
private:
IProxy& proxy_;
};
class AsyncAllPropertiesGetter
{
public:
AsyncAllPropertiesGetter(IProxy& proxy);
AsyncAllPropertiesGetter& onInterface(const std::string& interfaceName);
template <typename _Function> PendingAsyncCall uponReplyInvoke(_Function&& callback);
std::future<std::map<std::string, Variant>> getResultAsFuture();
private:
IProxy& proxy_;
const std::string* interfaceName_{};
};
}

View File

@ -45,9 +45,9 @@ namespace sdbus {
/*** MethodRegistrator ***/
/*** ----------------- ***/
inline MethodRegistrator::MethodRegistrator(IObject& object, const std::string& methodName)
inline MethodRegistrator::MethodRegistrator(IObject& object, std::string methodName)
: object_(object)
, methodName_(methodName)
, methodName_(std::move(methodName))
, exceptions_(std::uncaught_exceptions())
{
}
@ -73,9 +73,9 @@ namespace sdbus {
object_.registerMethod( interfaceName_
, std::move(methodName_)
, std::move(inputSignature_)
, std::move(inputParamNames_)
, inputParamNames_
, std::move(outputSignature_)
, std::move(outputParamNames_)
, outputParamNames_
, std::move(methodCallback_)
, std::move(flags_));
}
@ -177,9 +177,9 @@ namespace sdbus {
/*** SignalRegistrator ***/
/*** ----------------- ***/
inline SignalRegistrator::SignalRegistrator(IObject& object, const std::string& signalName)
inline SignalRegistrator::SignalRegistrator(IObject& object, std::string signalName)
: object_(object)
, signalName_(signalName)
, signalName_(std::move(signalName))
, exceptions_(std::uncaught_exceptions())
{
}
@ -204,7 +204,7 @@ namespace sdbus {
object_.registerSignal( interfaceName_
, std::move(signalName_)
, std::move(signalSignature_)
, std::move(paramNames_)
, paramNames_
, std::move(flags_) );
}
@ -293,7 +293,7 @@ namespace sdbus {
template <typename _Function>
inline PropertyRegistrator& PropertyRegistrator::withGetter(_Function&& callback)
{
static_assert(function_traits<_Function>::arity == 0, "Property getter function must not take any arguments");
static_assert(function_argument_count_v<_Function> == 0, "Property getter function must not take any arguments");
static_assert(!std::is_void<function_result_t<_Function>>::value, "Property getter function must return property value");
if (propertySignature_.empty())
@ -311,7 +311,7 @@ namespace sdbus {
template <typename _Function>
inline PropertyRegistrator& PropertyRegistrator::withSetter(_Function&& callback)
{
static_assert(function_traits<_Function>::arity == 1, "Property setter function must take one parameter - the property value");
static_assert(function_argument_count_v<_Function> == 1, "Property setter function must take one parameter - the property value");
static_assert(std::is_void<function_result_t<_Function>>::value, "Property setter function must not return any value");
if (propertySignature_.empty())
@ -595,9 +595,8 @@ namespace sdbus {
}
catch (const Error& e)
{
// Catch message unpack exceptions and pass them to the callback
// in the expected manner to avoid propagating them up the call
// stack to the event loop.
// 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);
return;
}
@ -610,6 +609,29 @@ namespace sdbus {
return proxy_.callMethod(method_, std::move(asyncReplyHandler), timeout_);
}
template <typename... _Args>
std::future<future_return_t<_Args...>> AsyncMethodInvoker::getResultAsFuture()
{
auto promise = std::make_shared<std::promise<future_return_t<_Args...>>>();
auto future = promise->get_future();
uponReplyInvoke([promise = std::move(promise)](const Error* error, _Args... args)
{
if (error == nullptr)
if constexpr (!std::is_void_v<future_return_t<_Args...>>)
promise->set_value({std::move(args)...});
else
promise->set_value();
else
promise->set_exception(std::make_exception_ptr(*error));
});
// Will be std::future<void> for no D-Bus method return value
// or std::future<T> for single D-Bus method return value
// or std::future<std::tuple<...>> for multiple method return values
return future;
}
/*** ---------------- ***/
/*** SignalSubscriber ***/
/*** ---------------- ***/
@ -653,8 +675,10 @@ namespace sdbus {
}
catch (const sdbus::Error& e)
{
// Invoke callback with error argument and input arguments from the tuple.
// 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);
return;
}
// Invoke callback with no error and input arguments from the tuple.
@ -681,7 +705,7 @@ namespace sdbus {
{
}
inline void SignalUnsubscriber::onInterface(std::string interfaceName)
inline void SignalUnsubscriber::onInterface(const std::string& interfaceName)
{
proxy_.unregisterSignalHandler(interfaceName, signalName_);
}
@ -696,17 +720,56 @@ namespace sdbus {
{
}
inline sdbus::Variant PropertyGetter::onInterface(const std::string& interfaceName)
inline Variant PropertyGetter::onInterface(const std::string& interfaceName)
{
sdbus::Variant var;
proxy_
.callMethod("Get")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(interfaceName, propertyName_)
.storeResultsTo(var);
Variant var;
proxy_.callMethod("Get")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(interfaceName, propertyName_)
.storeResultsTo(var);
return var;
}
/*** ------------------- ***/
/*** AsyncPropertyGetter ***/
/*** ------------------- ***/
inline AsyncPropertyGetter::AsyncPropertyGetter(IProxy& proxy, const std::string& propertyName)
: proxy_(proxy)
, propertyName_(propertyName)
{
}
inline AsyncPropertyGetter& AsyncPropertyGetter::onInterface(const std::string& interfaceName)
{
interfaceName_ = &interfaceName;
return *this;
}
template <typename _Function>
PendingAsyncCall AsyncPropertyGetter::uponReplyInvoke(_Function&& callback)
{
static_assert(std::is_invocable_r_v<void, _Function, const Error*, Variant>, "Property get callback function must accept Error* and property value as Variant");
assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync("Get")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(*interfaceName_, propertyName_)
.uponReplyInvoke(std::forward<_Function>(callback));
}
inline std::future<Variant> AsyncPropertyGetter::getResultAsFuture()
{
assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync("Get")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(*interfaceName_, propertyName_)
.getResultAsFuture<Variant>();
}
/*** -------------- ***/
/*** PropertySetter ***/
/*** -------------- ***/
@ -717,9 +780,9 @@ namespace sdbus {
{
}
inline PropertySetter& PropertySetter::onInterface(std::string interfaceName)
inline PropertySetter& PropertySetter::onInterface(const std::string& interfaceName)
{
interfaceName_ = std::move(interfaceName);
interfaceName_ = &interfaceName;
return *this;
}
@ -727,17 +790,144 @@ namespace sdbus {
template <typename _Value>
inline void PropertySetter::toValue(const _Value& value)
{
PropertySetter::toValue(sdbus::Variant{value});
PropertySetter::toValue(Variant{value});
}
inline void PropertySetter::toValue(const sdbus::Variant& value)
template <typename _Value>
inline void PropertySetter::toValue(const _Value& value, dont_expect_reply_t)
{
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
PropertySetter::toValue(Variant{value}, dont_expect_reply);
}
proxy_
.callMethod("Set")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(interfaceName_, propertyName_, value);
inline void PropertySetter::toValue(const Variant& value)
{
assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function
proxy_.callMethod("Set")
.onInterface("org.freedesktop.DBus.Properties")
.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
proxy_.callMethod("Set")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(*interfaceName_, propertyName_, value)
.dontExpectReply();
}
/*** ------------------- ***/
/*** AsyncPropertySetter ***/
/*** ------------------- ***/
inline AsyncPropertySetter::AsyncPropertySetter(IProxy& proxy, const std::string& propertyName)
: proxy_(proxy)
, propertyName_(propertyName)
{
}
inline AsyncPropertySetter& AsyncPropertySetter::onInterface(const std::string& interfaceName)
{
interfaceName_ = &interfaceName;
return *this;
}
template <typename _Value>
inline AsyncPropertySetter& AsyncPropertySetter::toValue(_Value&& value)
{
return AsyncPropertySetter::toValue(Variant{std::forward<_Value>(value)});
}
inline AsyncPropertySetter& AsyncPropertySetter::toValue(Variant value)
{
value_ = std::move(value);
return *this;
}
template <typename _Function>
PendingAsyncCall AsyncPropertySetter::uponReplyInvoke(_Function&& callback)
{
static_assert(std::is_invocable_r_v<void, _Function, const Error*>, "Property set callback function must accept Error* only");
assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync("Set")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(*interfaceName_, propertyName_, std::move(value_))
.uponReplyInvoke(std::forward<_Function>(callback));
}
inline std::future<void> AsyncPropertySetter::getResultAsFuture()
{
assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync("Set")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(*interfaceName_, propertyName_, std::move(value_))
.getResultAsFuture<>();
}
/*** ------------------- ***/
/*** AllPropertiesGetter ***/
/*** ------------------- ***/
inline AllPropertiesGetter::AllPropertiesGetter(IProxy& proxy)
: proxy_(proxy)
{
}
inline std::map<std::string, Variant> AllPropertiesGetter::onInterface(const std::string& interfaceName)
{
std::map<std::string, Variant> props;
proxy_.callMethod("GetAll")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(interfaceName)
.storeResultsTo(props);
return props;
}
/*** ------------------------ ***/
/*** AsyncAllPropertiesGetter ***/
/*** ------------------------ ***/
inline AsyncAllPropertiesGetter::AsyncAllPropertiesGetter(IProxy& proxy)
: proxy_(proxy)
{
}
inline AsyncAllPropertiesGetter& AsyncAllPropertiesGetter::onInterface(const std::string& interfaceName)
{
interfaceName_ = &interfaceName;
return *this;
}
template <typename _Function>
PendingAsyncCall AsyncAllPropertiesGetter::uponReplyInvoke(_Function&& callback)
{
static_assert( std::is_invocable_r_v<void, _Function, const Error*, std::map<std::string, Variant>>
, "All properties get callback function must accept Error* 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")
.withArguments(*interfaceName_)
.uponReplyInvoke(std::forward<_Function>(callback));
}
inline std::future<std::map<std::string, Variant>> AsyncAllPropertiesGetter::getResultAsFuture()
{
assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync("GetAll")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(*interfaceName_)
.getResultAsFuture<std::map<std::string, Variant>>();
}
}

View File

@ -76,6 +76,8 @@ namespace sdbus {
};
sdbus::Error createError(int errNo, const std::string& customMsg);
inline const char* SDBUSCPP_ERROR_NAME = "org.sdbuscpp.Error";
}
#define SDBUS_THROW_ERROR(_MSG, _ERRNO) \

View File

@ -93,7 +93,7 @@ namespace sdbus {
private:
std::bitset<FLAG_COUNT> flags_;
};
}
#endif /* SDBUS_CXX_FLAGS_H_ */

View File

@ -34,6 +34,8 @@
#include <cstdint>
#include <optional>
struct sd_bus;
namespace sdbus {
/********************************************//**
@ -266,10 +268,10 @@ namespace sdbus {
virtual void addObjectManager(const std::string& objectPath, floating_slot_t) = 0;
/*!
* @brief Adds a match rule for incoming message dispatching
* @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 incoming D-Bus message matching the rule
* @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.
@ -289,11 +291,10 @@ namespace sdbus {
[[nodiscard]] virtual Slot addMatch(const std::string& match, message_handler callback) = 0;
/*!
* @brief Adds a floating match rule for incoming message dispatching
* @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 incoming D-Bus message matching the rule
* @param[in] Floating slot tag
* @param[in] callback Callback handler to be called upon processing an inbound D-Bus message matching the rule
*
* 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
@ -306,6 +307,45 @@ namespace sdbus {
*/
virtual void addMatch(const std::string& match, message_handler callback, floating_slot_t) = 0;
/*!
* @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
*
* 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.
*
* Refer to the @c addMatch(const std::string& match, message_handler callback) documentation, and consult
* `man sd_bus_add_match`, for more information.
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] virtual Slot 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
*
* @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
*
* 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.
*
* Refer to the @c addMatch(const std::string& match, message_handler callback, message_handler installCallback)
* documentation for more information.
*
* @throws sdbus::Error in case of failure
*/
virtual void addMatchAsync(const std::string& match, message_handler callback, message_handler installCallback, floating_slot_t) = 0;
/*!
* @copydoc IConnection::enterEventLoop()
*
@ -314,21 +354,21 @@ namespace sdbus {
[[deprecated("This function has been replaced by enterEventLoop()")]] void enterProcessingLoop();
/*!
* @copydoc IConnection::enterProcessingLoopAsync()
* @copydoc IConnection::enterEventLoopAsync()
*
* @deprecated This function has been replaced by enterEventLoopAsync()
*/
[[deprecated("This function has been replaced by enterEventLoopAsync()")]] void enterProcessingLoopAsync();
/*!
* @copydoc IConnection::leaveProcessingLoop()
* @copydoc IConnection::leaveEventLoop()
*
* @deprecated This function has been replaced by leaveEventLoop()
*/
[[deprecated("This function has been replaced by leaveEventLoop()")]] void leaveProcessingLoop();
/*!
* @copydoc IConnection::getProcessLoopPollData()
* @copydoc IConnection::getEventLoopPollData()
*
* @deprecated This function has been replaced by getEventLoopPollData()
*/
@ -459,6 +499,74 @@ namespace sdbus {
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] std::unique_ptr<sdbus::IConnection> createRemoteSystemBusConnection(const std::string& host);
/*!
* @brief Opens direct D-Bus connection at a custom address
*
* @param[in] address ";"-separated list of addresses of bus brokers to try to connect to
* @return Connection instance
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] std::unique_ptr<sdbus::IConnection> createDirectBusConnection(const std::string& address);
/*!
* @brief Opens direct D-Bus connection at the given file descriptor
*
* @param[in] fd File descriptor used to communicate directly from/to a D-Bus server
* @return Connection instance
*
* The underlying sdbus-c++ connection instance takes over ownership of fd, so the caller can let it go.
* If, however, the call throws an exception, the ownership of fd remains with the caller.
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] std::unique_ptr<sdbus::IConnection> createDirectBusConnection(int fd);
/*!
* @brief Opens direct D-Bus connection at fd as a server
*
* @param[in] fd File descriptor to use for server DBus connection
* @return Server connection instance
*
* This creates a new, custom bus object in server mode. One can then call createDirectBusConnection()
* on client side to connect to this bus.
*
* The underlying sdbus-c++ connection instance takes over ownership of fd, so the caller can let it go.
* If, however, the call throws an exception, the ownership of fd remains with the caller.
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] std::unique_ptr<sdbus::IConnection> createServerBus(int fd);
/*!
* @brief Creates sdbus-c++ bus connection representation out of underlying sd_bus instance
*
* @param[in] bus File descriptor to use for server DBus connection
* @return Connection instance
*
* This functions is helpful in cases where clients need a custom, tweaked configuration of their
* bus object. Since sdbus-c++ does not provide C++ API for all bus connection configuration
* functions of the underlying sd-bus library, clients can use these sd-bus functions themselves
* to create and configure their sd_bus object, and create sdbus-c++ IConnection on top of it.
*
* The IConnection instance assumes unique ownership of the provided bus object. The bus object
* must have been started by the client before this call.
* The bus object will get flushed, closed, and unreffed when the IConnection instance is destroyed.
*
* @throws sdbus::Error in case of failure
*
* Code example:
* @code
* sd_bus* bus{};
* ::sd_bus_new(&bus);
* ::sd_bus_set_address(bus, address);
* ::sd_bus_set_anonymous(bus, true);
* ::sd_bus_start(bus);
* auto con = sdbus::createBusConnection(bus); // IConnection consumes sd_bus object
* @endcode
*/
[[nodiscard]] std::unique_ptr<sdbus::IConnection> createBusConnection(sd_bus *bus);
}
#endif /* SDBUS_CXX_ICONNECTION_H_ */

View File

@ -28,10 +28,12 @@
#define SDBUS_CXX_IPROXY_H_
#include <sdbus-c++/ConvenienceApiClasses.h>
#include <sdbus-c++/TypeTraits.h>
#include <string>
#include <memory>
#include <functional>
#include <chrono>
#include <future>
// Forward declarations
namespace sdbus {
@ -81,7 +83,7 @@ namespace sdbus {
virtual MethodCall createMethodCall(const std::string& interfaceName, const std::string& methodName) = 0;
/*!
* @brief Calls method on the proxied D-Bus object
* @brief Calls method on the D-Bus object
*
* @param[in] message Message representing a method call
* @param[in] timeout Timeout for dbus call in microseconds
@ -107,7 +109,7 @@ namespace sdbus {
MethodReply callMethod(const MethodCall& message, const std::chrono::duration<_Rep, _Period>& timeout);
/*!
* @brief Calls method on the proxied D-Bus object asynchronously
* @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
@ -131,7 +133,7 @@ namespace sdbus {
PendingAsyncCall callMethod(const MethodCall& message, async_reply_handler asyncReplyCallback, const std::chrono::duration<_Rep, _Period>& timeout);
/*!
* @brief Registers a handler for the desired signal emitted by the proxied D-Bus object
* @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
@ -176,7 +178,7 @@ namespace sdbus {
virtual void unregister() = 0;
/*!
* @brief Calls method on the proxied D-Bus object
* @brief Calls method on the D-Bus object
*
* @param[in] methodName Name of the method
* @return A helper object for convenient invocation of the method
@ -197,7 +199,7 @@ namespace sdbus {
[[nodiscard]] MethodInvoker callMethod(const std::string& methodName);
/*!
* @brief Calls method on the proxied D-Bus object asynchronously
* @brief Calls method on the D-Bus object asynchronously
*
* @param[in] methodName Name of the method
* @return A helper object for convenient asynchronous invocation of the method
@ -221,7 +223,7 @@ namespace sdbus {
[[nodiscard]] AsyncMethodInvoker callMethodAsync(const std::string& methodName);
/*!
* @brief Registers signal handler for a given signal of the proxied D-Bus object
* @brief Registers signal handler for a given signal of the D-Bus object
*
* @param[in] signalName Name of the signal
* @return A helper object for convenient registration of the signal handler
@ -241,7 +243,7 @@ namespace sdbus {
[[nodiscard]] SignalSubscriber uponSignal(const std::string& signalName);
/*!
* @brief Unregisters signal handler of a given signal of the proxied D-Bus object
* @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
@ -258,7 +260,7 @@ namespace sdbus {
[[nodiscard]] SignalUnsubscriber muteSignal(const std::string& signalName);
/*!
* @brief Gets value of a property of the proxied D-Bus object
* @brief Gets value of a property of the D-Bus object
*
* @param[in] propertyName Name of the property
* @return A helper object for convenient getting of property value
@ -277,23 +279,101 @@ namespace sdbus {
[[nodiscard]] PropertyGetter getProperty(const std::string& propertyName);
/*!
* @brief Sets value of a property of the proxied D-Bus object
* @brief Gets value of a property of the D-Bus object asynchronously
*
* @param[in] propertyName Name of the property
* @return A helper object for convenient asynchronous getting of property value
*
* This is a high-level, convenience way of reading D-Bus property values that abstracts
* from the D-Bus message concept.
*
* Example of use:
* @code
* std::future<sdbus::Variant> state = object.getPropertyAsync("state").onInterface("com.kistler.foo").getResultAsFuture();
* auto callback = [](const sdbus::Error* err, const sdbus::Variant& value){ ... };
* object.getPropertyAsync("state").onInterface("com.kistler.foo").uponReplyInvoke(std::move(callback));
* @endcode
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] AsyncPropertyGetter getPropertyAsync(const std::string& propertyName);
/*!
* @brief Sets value of a property of the D-Bus object
*
* @param[in] propertyName Name of the property
* @return A helper object for convenient setting of property value
*
* This is a high-level, convenience way of writing D-Bus property values that abstracts
* from the D-Bus message concept.
* Setting property value with NoReply flag is also supported.
*
* Example of use:
* @code
* int state = ...;
* object_.setProperty("state").onInterface("com.kistler.foo").toValue(state);
* // Or we can just send the set message call without waiting for the reply
* object_.setProperty("state").onInterface("com.kistler.foo").toValue(state, dont_expect_reply);
* @endcode
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] PropertySetter setProperty(const std::string& propertyName);
/*!
* @brief Sets value of a property of the D-Bus object asynchronously
*
* @param[in] propertyName Name of the property
* @return A helper object for convenient asynchronous setting of property value
*
* This is a high-level, convenience way of writing D-Bus property values that abstracts
* from the D-Bus message concept.
*
* Example of use:
* @code
* int state = ...;
* object_.setProperty("state").onInterface("com.kistler.foo").toValue(state);
* // We can wait until the set operation finishes by waiting on the future
* std::future<void> res = object_.setPropertyAsync("state").onInterface("com.kistler.foo").toValue(state).getResultAsFuture();
* @endcode
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] PropertySetter setProperty(const std::string& propertyName);
[[nodiscard]] AsyncPropertySetter setPropertyAsync(const std::string& propertyName);
/*!
* @brief Gets values of all properties of the D-Bus object
*
* @return A helper object for convenient getting of properties' values
*
* This is a high-level, convenience way of reading D-Bus properties' values that abstracts
* from the D-Bus message concept.
*
* Example of use:
* @code
* auto props = object.getAllProperties().onInterface("com.kistler.foo");
* @endcode
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] AllPropertiesGetter getAllProperties();
/*!
* @brief Gets values of all properties of the D-Bus object asynchronously
*
* @return A helper object for convenient asynchronous getting of properties' values
*
* This is a high-level, convenience way of reading D-Bus properties' values that abstracts
* from the D-Bus message concept.
*
* Example of use:
* @code
* auto callback = [](const sdbus::Error* err, const std::map<std::string, Variant>>& properties){ ... };
* auto props = object.getAllPropertiesAsync().onInterface("com.kistler.foo").uponReplyInvoke(std::move(callback));
* @endcode
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] AsyncAllPropertiesGetter getAllPropertiesAsync();
/*!
* @brief Provides D-Bus connection used by the proxy
@ -322,6 +402,33 @@ namespace sdbus {
* @return A pointer to the 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<MethodReply> callMethod(const MethodCall& message, with_future_t) = 0;
virtual std::future<MethodReply> callMethod(const MethodCall& message, uint64_t timeout, with_future_t) = 0;
/*!
* @copydoc IProxy::callMethod(const MethodCall&,uint64_t,with_future_t)
*/
template <typename _Rep, typename _Period>
std::future<MethodReply> callMethod( const MethodCall& message
, const std::chrono::duration<_Rep, _Period>& timeout
, with_future_t );
};
/********************************************//**
@ -382,6 +489,15 @@ namespace sdbus {
return callMethod(message, std::move(asyncReplyCallback), microsecs.count());
}
template <typename _Rep, typename _Period>
inline std::future<MethodReply> IProxy::callMethod( const MethodCall& message
, const std::chrono::duration<_Rep, _Period>& timeout
, with_future_t )
{
auto microsecs = std::chrono::duration_cast<std::chrono::microseconds>(timeout);
return callMethod(message, microsecs.count(), with_future);
}
inline MethodInvoker IProxy::callMethod(const std::string& methodName)
{
return MethodInvoker(*this, methodName);
@ -407,11 +523,31 @@ namespace sdbus {
return PropertyGetter(*this, propertyName);
}
inline AsyncPropertyGetter IProxy::getPropertyAsync(const std::string& propertyName)
{
return AsyncPropertyGetter(*this, propertyName);
}
inline PropertySetter IProxy::setProperty(const std::string& propertyName)
{
return PropertySetter(*this, propertyName);
}
inline AsyncPropertySetter IProxy::setPropertyAsync(const std::string& propertyName)
{
return AsyncPropertySetter(*this, propertyName);
}
inline AllPropertiesGetter IProxy::getAllProperties()
{
return AllPropertiesGetter(*this);
}
inline AsyncAllPropertiesGetter IProxy::getAllPropertiesAsync()
{
return AsyncAllPropertiesGetter(*this);
}
/*!
* @brief Creates a proxy object for a specific remote D-Bus object
*
@ -426,6 +562,9 @@ namespace sdbus {
* should make sure that an I/O event loop is running on that connection, so the proxy
* may receive incoming signals and asynchronous method replies.
*
* The destination parameter may be an empty string (useful e.g. in case of direct
* D-Bus connections to a custom server bus).
*
* Code example:
* @code
* auto proxy = sdbus::createProxy(connection, "com.kistler.foo", "/com/kistler/foo");
@ -449,6 +588,9 @@ namespace sdbus {
* upon that connection in a separate internal thread. Handlers for incoming signals and
* asynchronous method replies will be executed in the context of that thread.
*
* The destination parameter may be an empty string (useful e.g. in case of direct
* D-Bus connections to a custom server bus).
*
* Code example:
* @code
* auto proxy = sdbus::createProxy(std::move(connection), "com.kistler.foo", "/com/kistler/foo");
@ -458,6 +600,34 @@ namespace sdbus {
, std::string destination
, std::string objectPath );
/*!
* @brief Creates a proxy object for a specific remote D-Bus object
*
* @param[in] connection D-Bus connection to be used by the proxy object
* @param[in] destination Bus name that provides the remote D-Bus object
* @param[in] objectPath Path of the remote D-Bus object
* @return Pointer to the object proxy instance
*
* The provided connection will be used by the proxy to issue calls against the object.
* The Object proxy becomes an exclusive owner of this connection, but will not start
* an event loop thread on this connection. This is cheap construction and is suitable
* for short-lived proxies created just to execute simple synchronous D-Bus calls and
* then destroyed. Such blocking request-reply calls will work without an event loop
* (but signals, async calls, etc. won't).
*
* The destination parameter may be an empty string (useful e.g. in case of direct
* D-Bus connections to a custom server bus).
*
* Code example:
* @code
* auto proxy = sdbus::createProxy(std::move(connection), "com.kistler.foo", "/com/kistler/foo", sdbus::dont_run_event_loop_thread);
* @endcode
*/
[[nodiscard]] std::unique_ptr<sdbus::IProxy> createProxy( std::unique_ptr<sdbus::IConnection>&& connection
, std::string destination
, std::string objectPath
, dont_run_event_loop_thread_t );
/*!
* @brief Creates a proxy object for a specific remote D-Bus object
*
@ -466,7 +636,7 @@ namespace sdbus {
* @return Pointer to the object proxy instance
*
* No D-Bus connection is provided here, so the object proxy will create and manage
* his own connection, and will automatically start a procesing loop upon that connection
* his own connection, and will automatically start an event loop upon that connection
* in a separate internal thread. Handlers for incoming signals and asynchronous
* method replies will be executed in the context of that thread.
*
@ -478,6 +648,28 @@ namespace sdbus {
[[nodiscard]] std::unique_ptr<sdbus::IProxy> createProxy( std::string destination
, std::string objectPath );
/*!
* @brief Creates a proxy object for a specific remote D-Bus object
*
* @param[in] destination Bus name that provides the remote D-Bus object
* @param[in] objectPath Path of the remote D-Bus object
* @return Pointer to the object proxy instance
*
* No D-Bus connection is provided here, so the object proxy will create and manage
* his own connection, but it will not start an event loop thread. This is cheap
* construction and is suitable for short-lived proxies created just to execute simple
* synchronous D-Bus calls and then destroyed. Such blocking request-reply calls
* will work without an event loop (but signals, async calls, etc. won't).
*
* Code example:
* @code
* auto proxy = sdbus::createProxy("com.kistler.foo", "/com/kistler/foo", sdbus::dont_run_event_loop_thread );
* @endcode
*/
[[nodiscard]] std::unique_ptr<sdbus::IProxy> createProxy( std::string destination
, std::string objectPath
, dont_run_event_loop_thread_t );
}
#include <sdbus-c++/ConvenienceApiClasses.inl>

View File

@ -31,12 +31,18 @@
#include <sdbus-c++/Error.h>
#include <string>
#include <vector>
#include <array>
#if __cplusplus >= 202002L
#include <span>
#endif
#include <map>
#include <unordered_map>
#include <utility>
#include <cstdint>
#include <cassert>
#include <functional>
#include <sys/types.h>
#include <algorithm>
// Forward declarations
namespace sdbus {
@ -86,6 +92,23 @@ namespace sdbus {
Message& operator<<(const Signature &item);
Message& operator<<(const UnixFd &item);
template <typename _Element, typename _Allocator>
Message& operator<<(const std::vector<_Element, _Allocator>& items);
template <typename _Element, std::size_t _Size>
Message& operator<<(const std::array<_Element, _Size>& items);
#if __cplusplus >= 202002L
template <typename _Element, std::size_t _Extent>
Message& operator<<(const std::span<_Element, _Extent>& items);
#endif
template <typename _Key, typename _Value, typename _Compare, typename _Allocator>
Message& operator<<(const std::map<_Key, _Value, _Compare, _Allocator>& items);
template <typename _Key, typename _Value, typename _Hash, typename _KeyEqual, typename _Allocator>
Message& operator<<(const std::unordered_map<_Key, _Value, _Hash, _KeyEqual, _Allocator>& items);
template <typename... _ValueTypes>
Message& operator<<(const Struct<_ValueTypes...>& item);
template <typename... _ValueTypes>
Message& operator<<(const std::tuple<_ValueTypes...>& item);
Message& operator>>(bool& item);
Message& operator>>(int16_t& item);
Message& operator>>(int32_t& item);
@ -101,6 +124,22 @@ namespace sdbus {
Message& operator>>(ObjectPath &item);
Message& operator>>(Signature &item);
Message& operator>>(UnixFd &item);
template <typename _Element, typename _Allocator>
Message& operator>>(std::vector<_Element, _Allocator>& items);
template <typename _Element, std::size_t _Size>
Message& operator>>(std::array<_Element, _Size>& items);
#if __cplusplus >= 202002L
template <typename _Element, std::size_t _Extent>
Message& operator>>(std::span<_Element, _Extent>& items);
#endif
template <typename _Key, typename _Value, typename _Compare, typename _Allocator>
Message& operator>>(std::map<_Key, _Value, _Compare, _Allocator>& items);
template <typename _Key, typename _Value, typename _Hash, typename _KeyEqual, typename _Allocator>
Message& operator>>(std::unordered_map<_Key, _Value, _Hash, _KeyEqual, _Allocator>& items);
template <typename... _ValueTypes>
Message& operator>>(Struct<_ValueTypes...>& item);
template <typename... _ValueTypes>
Message& operator>>(std::tuple<_ValueTypes...>& item);
Message& openContainer(const std::string& signature);
Message& closeContainer();
@ -120,6 +159,9 @@ namespace sdbus {
Message& enterStruct(const std::string& signature);
Message& exitStruct();
Message& appendArray(char type, const void *ptr, size_t size);
Message& readArray(char type, const void **ptr, size_t *size);
explicit operator bool() const;
void clearFlags();
@ -131,6 +173,7 @@ namespace sdbus {
void peekType(std::string& type, std::string& contents) const;
bool isValid() const;
bool isEmpty() const;
bool isAtEnd(bool complete) const;
void copyTo(Message& destination, bool complete) const;
void seal();
@ -146,6 +189,25 @@ namespace sdbus {
class Factory;
private:
template <typename _Array>
void serializeArray(const _Array& items);
template <typename _Array>
void deserializeArray(_Array& items);
template <typename _Array>
void deserializeArrayFast(_Array& items);
template <typename _Element, typename _Allocator>
void deserializeArrayFast(std::vector<_Element, _Allocator>& items);
template <typename _Array>
void deserializeArraySlow(_Array& items);
template <typename _Element, typename _Allocator>
void deserializeArraySlow(std::vector<_Element, _Allocator>& items);
template <typename _Dictionary>
void serializeDictionary(const _Dictionary& items);
template <typename _Dictionary>
void deserializeDictionary(_Dictionary& items);
protected:
Message() = default;
explicit Message(internal::ISdBus* sdbus) noexcept;
@ -244,38 +306,90 @@ namespace sdbus {
PlainMessage() = default;
};
template <typename _Element>
inline Message& operator<<(Message& msg, const std::vector<_Element>& items)
template <typename _Element, typename _Allocator>
inline Message& Message::operator<<(const std::vector<_Element, _Allocator>& items)
{
msg.openContainer(signature_of<_Element>::str());
serializeArray(items);
for (const auto& item : items)
msg << item;
msg.closeContainer();
return msg;
return *this;
}
template <typename _Key, typename _Value>
inline Message& operator<<(Message& msg, const std::map<_Key, _Value>& items)
template <typename _Element, std::size_t _Size>
inline Message& Message::operator<<(const std::array<_Element, _Size>& items)
{
const std::string dictEntrySignature = signature_of<_Key>::str() + signature_of<_Value>::str();
serializeArray(items);
return *this;
}
#if __cplusplus >= 202002L
template <typename _Element, std::size_t _Extent>
inline Message& Message::operator<<(const std::span<_Element, _Extent>& items)
{
serializeArray(items);
return *this;
}
#endif
template <typename _Array>
inline void Message::serializeArray(const _Array& items)
{
using ElementType = typename _Array::value_type;
// Use faster, one-step serialization of contiguous array of elements of trivial D-Bus types except bool,
// otherwise use step-by-step serialization of individual elements.
if constexpr (signature_of<ElementType>::is_trivial_dbus_type && !std::is_same_v<ElementType, bool>)
{
appendArray(*signature_of<ElementType>::str().c_str(), items.data(), items.size() * sizeof(ElementType));
}
else
{
openContainer(signature_of<ElementType>::str());
for (const auto& item : items)
*this << item;
closeContainer();
}
}
template <typename _Key, typename _Value, typename _Compare, typename _Allocator>
inline Message& Message::operator<<(const std::map<_Key, _Value, _Compare, _Allocator>& items)
{
serializeDictionary(items);
return *this;
}
template <typename _Key, typename _Value, typename _Hash, typename _KeyEqual, typename _Allocator>
inline Message& Message::operator<<(const std::unordered_map<_Key, _Value, _Hash, _KeyEqual, _Allocator>& items)
{
serializeDictionary(items);
return *this;
}
template <typename _Dictionary>
inline void Message::serializeDictionary(const _Dictionary& items)
{
using KeyType = typename _Dictionary::key_type;
using ValueType = typename _Dictionary::mapped_type;
const std::string dictEntrySignature = signature_of<KeyType>::str() + signature_of<ValueType>::str();
const std::string arraySignature = "{" + dictEntrySignature + "}";
msg.openContainer(arraySignature);
openContainer(arraySignature);
for (const auto& item : items)
{
msg.openDictEntry(dictEntrySignature);
msg << item.first;
msg << item.second;
msg.closeDictEntry();
openDictEntry(dictEntrySignature);
*this << item.first;
*this << item.second;
closeDictEntry();
}
msg.closeContainer();
return msg;
closeContainer();
}
namespace detail
@ -296,78 +410,182 @@ namespace sdbus {
}
template <typename... _ValueTypes>
inline Message& operator<<(Message& msg, const Struct<_ValueTypes...>& item)
inline Message& Message::operator<<(const Struct<_ValueTypes...>& item)
{
auto structSignature = signature_of<Struct<_ValueTypes...>>::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);
auto structContentSignature = structSignature.substr(1, structSignature.size() - 2);
msg.openStruct(structContentSignature);
detail::serialize_tuple(msg, item, std::index_sequence_for<_ValueTypes...>{});
msg.closeStruct();
openStruct(structContentSignature);
detail::serialize_tuple(*this, item, std::index_sequence_for<_ValueTypes...>{});
closeStruct();
return msg;
return *this;
}
template <typename... _ValueTypes>
inline Message& operator<<(Message& msg, const std::tuple<_ValueTypes...>& item)
inline Message& Message::operator<<(const std::tuple<_ValueTypes...>& item)
{
detail::serialize_tuple(msg, item, std::index_sequence_for<_ValueTypes...>{});
return msg;
detail::serialize_tuple(*this, item, std::index_sequence_for<_ValueTypes...>{});
return *this;
}
template <typename _Element>
inline Message& operator>>(Message& msg, std::vector<_Element>& items)
template <typename _Element, typename _Allocator>
inline Message& Message::operator>>(std::vector<_Element, _Allocator>& items)
{
if(!msg.enterContainer(signature_of<_Element>::str()))
return msg;
deserializeArray(items);
return *this;
}
template <typename _Element, std::size_t _Size>
inline Message& Message::operator>>(std::array<_Element, _Size>& items)
{
deserializeArray(items);
return *this;
}
#if __cplusplus >= 202002L
template <typename _Element, std::size_t _Extent>
inline Message& Message::operator>>(std::span<_Element, _Extent>& items)
{
deserializeArray(items);
return *this;
}
#endif
template <typename _Array>
inline void Message::deserializeArray(_Array& items)
{
using ElementType = typename _Array::value_type;
// Use faster, one-step deserialization of contiguous array of elements of trivial D-Bus types except bool,
// otherwise use step-by-step deserialization of individual elements.
if constexpr (signature_of<ElementType>::is_trivial_dbus_type && !std::is_same_v<ElementType, bool>)
{
deserializeArrayFast(items);
}
else
{
deserializeArraySlow(items);
}
}
template <typename _Array>
inline void Message::deserializeArrayFast(_Array& items)
{
using ElementType = typename _Array::value_type;
size_t arraySize{};
const ElementType* arrayPtr{};
readArray(*signature_of<ElementType>::str().c_str(), (const void**)&arrayPtr, &arraySize);
size_t elementsInMsg = arraySize / sizeof(ElementType);
bool notEnoughSpace = items.size() < elementsInMsg;
SDBUS_THROW_ERROR_IF(notEnoughSpace, "Failed to deserialize array: not enough space in destination sequence", EINVAL);
std::copy_n(arrayPtr, elementsInMsg, items.begin());
}
template <typename _Element, typename _Allocator>
void Message::deserializeArrayFast(std::vector<_Element, _Allocator>& items)
{
size_t arraySize{};
const _Element* arrayPtr{};
readArray(*signature_of<_Element>::str().c_str(), (const void**)&arrayPtr, &arraySize);
items.insert(items.end(), arrayPtr, arrayPtr + (arraySize / sizeof(_Element)));
}
template <typename _Array>
inline void Message::deserializeArraySlow(_Array& items)
{
using ElementType = typename _Array::value_type;
if(!enterContainer(signature_of<ElementType>::str()))
return;
for (auto& elem : items)
if (!(*this >> elem))
break; // Keep the rest in the destination sequence untouched
SDBUS_THROW_ERROR_IF(!isAtEnd(false), "Failed to deserialize array: not enough space in destination sequence", EINVAL);
clearFlags();
exitContainer();
}
template <typename _Element, typename _Allocator>
void Message::deserializeArraySlow(std::vector<_Element, _Allocator>& items)
{
if(!enterContainer(signature_of<_Element>::str()))
return;
while (true)
{
_Element elem;
if (msg >> elem)
if (*this >> elem)
items.emplace_back(std::move(elem));
else
break;
}
msg.clearFlags();
clearFlags();
msg.exitContainer();
return msg;
exitContainer();
}
template <typename _Key, typename _Value>
inline Message& operator>>(Message& msg, std::map<_Key, _Value>& items)
template <typename _Key, typename _Value, typename _Compare, typename _Allocator>
inline Message& Message::operator>>(std::map<_Key, _Value, _Compare, _Allocator>& items)
{
const std::string dictEntrySignature = signature_of<_Key>::str() + signature_of<_Value>::str();
deserializeDictionary(items);
return *this;
}
template <typename _Key, typename _Value, typename _Hash, typename _KeyEqual, typename _Allocator>
inline Message& Message::operator>>(std::unordered_map<_Key, _Value, _Hash, _KeyEqual, _Allocator>& items)
{
deserializeDictionary(items);
return *this;
}
template <typename _Dictionary>
inline void Message::deserializeDictionary(_Dictionary& items)
{
using KeyType = typename _Dictionary::key_type;
using ValueType = typename _Dictionary::mapped_type;
const std::string dictEntrySignature = signature_of<KeyType>::str() + signature_of<ValueType>::str();
const std::string arraySignature = "{" + dictEntrySignature + "}";
if (!msg.enterContainer(arraySignature))
return msg;
if (!enterContainer(arraySignature))
return;
while (true)
{
if (!msg.enterDictEntry(dictEntrySignature))
if (!enterDictEntry(dictEntrySignature))
break;
_Key key;
_Value value;
msg >> key >> value;
KeyType key;
ValueType value;
*this >> key >> value;
items.emplace(std::move(key), std::move(value));
msg.exitDictEntry();
exitDictEntry();
}
msg.clearFlags();
clearFlags();
msg.exitContainer();
return msg;
exitContainer();
}
namespace detail
@ -388,27 +606,27 @@ namespace sdbus {
}
template <typename... _ValueTypes>
inline Message& operator>>(Message& msg, Struct<_ValueTypes...>& item)
inline Message& Message::operator>>(Struct<_ValueTypes...>& item)
{
auto structSignature = signature_of<Struct<_ValueTypes...>>::str();
// Remove opening and closing parenthesis from the struct signature to get contents signature
auto structContentSignature = structSignature.substr(1, structSignature.size()-2);
if (!msg.enterStruct(structContentSignature))
return msg;
if (!enterStruct(structContentSignature))
return *this;
detail::deserialize_tuple(msg, item, std::index_sequence_for<_ValueTypes...>{});
detail::deserialize_tuple(*this, item, std::index_sequence_for<_ValueTypes...>{});
msg.exitStruct();
exitStruct();
return msg;
return *this;
}
template <typename... _ValueTypes>
inline Message& operator>>(Message& msg, std::tuple<_ValueTypes...>& item)
inline Message& Message::operator>>(std::tuple<_ValueTypes...>& item)
{
detail::deserialize_tuple(msg, item, std::index_sequence_for<_ValueTypes...>{});
return msg;
detail::deserialize_tuple(*this, item, std::index_sequence_for<_ValueTypes...>{});
return *this;
}
}

View File

@ -76,7 +76,7 @@ namespace sdbus {
{
assert(call_.isValid());
auto reply = call_.createReply();
(reply << ... << results);
(void)(reply << ... << results);
reply.send();
}

View File

@ -109,6 +109,21 @@ namespace sdbus {
{
}
/*!
* @brief Creates native-like proxy object instance
*
* @param[in] destination Bus name that provides a D-Bus object
* @param[in] objectPath Path of the D-Bus object
*
* 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)
: ProxyObjectHolder(createProxy(std::move(destination), std::move(objectPath), dont_run_event_loop_thread))
, _Interfaces(getProxy())...
{
}
/*!
* @brief Creates native-like proxy object instance
*
@ -141,6 +156,22 @@ namespace sdbus {
{
}
/*!
* @brief Creates native-like proxy object instance
*
* @param[in] connection D-Bus connection to be used by the proxy object
* @param[in] destination Bus name that provides a D-Bus object
* @param[in] objectPath Path of the D-Bus object
*
* The proxy created this way becomes an owner of the connection.
* For more information on its behavior, consult @ref createProxy(std::unique_ptr<sdbus::IConnection>&&,std::string,std::string,sdbus::dont_run_event_loop_thread_t)
*/
ProxyInterfaces(std::unique_ptr<sdbus::IConnection>&& connection, std::string destination, std::string 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())...
{
}
/*!
* @brief Finishes proxy registration and makes the proxy ready for use
*
@ -175,6 +206,11 @@ namespace sdbus {
protected:
using base_type = ProxyInterfaces;
ProxyInterfaces(const ProxyInterfaces&) = delete;
ProxyInterfaces& operator=(const ProxyInterfaces&) = delete;
ProxyInterfaces(ProxyInterfaces&&) = default;
ProxyInterfaces& operator=(ProxyInterfaces&&) = default;
~ProxyInterfaces() = default;
};

View File

@ -43,27 +43,32 @@ namespace sdbus {
protected:
Peer_proxy(sdbus::IProxy& proxy)
: proxy_(proxy)
: 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() = default;
public:
void Ping()
{
proxy_.callMethod("Ping").onInterface(INTERFACE_NAME);
proxy_->callMethod("Ping").onInterface(INTERFACE_NAME);
}
std::string GetMachineId()
{
std::string machineUUID;
proxy_.callMethod("GetMachineId").onInterface(INTERFACE_NAME).storeResultsTo(machineUUID);
proxy_->callMethod("GetMachineId").onInterface(INTERFACE_NAME).storeResultsTo(machineUUID);
return machineUUID;
}
private:
sdbus::IProxy& proxy_;
sdbus::IProxy* proxy_;
};
// Proxy for introspection
@ -73,22 +78,27 @@ namespace sdbus {
protected:
Introspectable_proxy(sdbus::IProxy& proxy)
: proxy_(proxy)
: 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() = default;
public:
std::string Introspect()
{
std::string xml;
proxy_.callMethod("Introspect").onInterface(INTERFACE_NAME).storeResultsTo(xml);
proxy_->callMethod("Introspect").onInterface(INTERFACE_NAME).storeResultsTo(xml);
return xml;
}
private:
sdbus::IProxy& proxy_;
sdbus::IProxy* proxy_;
};
// Proxy for properties
@ -98,10 +108,10 @@ namespace sdbus {
protected:
Properties_proxy(sdbus::IProxy& proxy)
: proxy_(proxy)
: proxy_(&proxy)
{
proxy_
.uponSignal("PropertiesChanged")
->uponSignal("PropertiesChanged")
.onInterface(INTERFACE_NAME)
.call([this]( const std::string& interfaceName
, const std::map<std::string, sdbus::Variant>& changedProperties
@ -111,6 +121,11 @@ 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
@ -120,23 +135,59 @@ namespace sdbus {
public:
sdbus::Variant Get(const std::string& interfaceName, const std::string& propertyName)
{
return proxy_.getProperty(propertyName).onInterface(interfaceName);
return proxy_->getProperty(propertyName).onInterface(interfaceName);
}
template <typename _Function>
PendingAsyncCall GetAsync(const std::string& interfaceName, const std::string& propertyName, _Function&& callback)
{
return proxy_->getPropertyAsync(propertyName).onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback));
}
std::future<sdbus::Variant> GetAsync(const std::string& interfaceName, const std::string& 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)
{
proxy_.setProperty(propertyName).onInterface(interfaceName).toValue(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)
{
proxy_->setProperty(propertyName).onInterface(interfaceName).toValue(value, dont_expect_reply);
}
template <typename _Function>
PendingAsyncCall SetAsync(const std::string& 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<void> SetAsync(const std::string& interfaceName, const std::string& propertyName, const sdbus::Variant& value, with_future_t)
{
return proxy_->setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).getResultAsFuture();
}
std::map<std::string, sdbus::Variant> GetAll(const std::string& interfaceName)
{
std::map<std::string, sdbus::Variant> props;
proxy_.callMethod("GetAll").onInterface(INTERFACE_NAME).withArguments(interfaceName).storeResultsTo(props);
return props;
return proxy_->getAllProperties().onInterface(interfaceName);
}
template <typename _Function>
PendingAsyncCall GetAllAsync(const std::string& interfaceName, _Function&& callback)
{
return proxy_->getAllPropertiesAsync().onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback));
}
std::future<std::map<std::string, sdbus::Variant>> GetAllAsync(const std::string& interfaceName, with_future_t)
{
return proxy_->getAllPropertiesAsync().onInterface(interfaceName).getResultAsFuture();
}
private:
sdbus::IProxy& proxy_;
sdbus::IProxy* proxy_;
};
// Proxy for object manager
@ -146,10 +197,10 @@ namespace sdbus {
protected:
ObjectManager_proxy(sdbus::IProxy& proxy)
: proxy_(proxy)
: proxy_(&proxy)
{
proxy_
.uponSignal("InterfacesAdded")
->uponSignal("InterfacesAdded")
.onInterface(INTERFACE_NAME)
.call([this]( const sdbus::ObjectPath& objectPath
, const std::map<std::string, std::map<std::string, sdbus::Variant>>& interfacesAndProperties )
@ -157,8 +208,7 @@ namespace sdbus {
this->onInterfacesAdded(objectPath, interfacesAndProperties);
});
proxy_
.uponSignal("InterfacesRemoved")
proxy_->uponSignal("InterfacesRemoved")
.onInterface(INTERFACE_NAME)
.call([this]( const sdbus::ObjectPath& objectPath
, const std::vector<std::string>& interfaces )
@ -167,6 +217,11 @@ 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
@ -178,12 +233,12 @@ namespace sdbus {
std::map<sdbus::ObjectPath, std::map<std::string, std::map<std::string, sdbus::Variant>>> GetManagedObjects()
{
std::map<sdbus::ObjectPath, std::map<std::string, std::map<std::string, sdbus::Variant>>> objectsInterfacesAndProperties;
proxy_.callMethod("GetManagedObjects").onInterface(INTERFACE_NAME).storeResultsTo(objectsInterfacesAndProperties);
proxy_->callMethod("GetManagedObjects").onInterface(INTERFACE_NAME).storeResultsTo(objectsInterfacesAndProperties);
return objectsInterfacesAndProperties;
}
private:
sdbus::IProxy& proxy_;
sdbus::IProxy* proxy_;
};
// Adaptors for the above-listed standard D-Bus interfaces are not necessary because the functionality
@ -197,25 +252,30 @@ namespace sdbus {
protected:
Properties_adaptor(sdbus::IObject& object)
: object_(object)
: 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() = default;
public:
void emitPropertiesChangedSignal(const std::string& interfaceName, const std::vector<std::string>& properties)
{
object_.emitPropertiesChangedSignal(interfaceName, properties);
object_->emitPropertiesChangedSignal(interfaceName, properties);
}
void emitPropertiesChangedSignal(const std::string& interfaceName)
{
object_.emitPropertiesChangedSignal(interfaceName);
object_->emitPropertiesChangedSignal(interfaceName);
}
private:
sdbus::IObject& object_;
sdbus::IObject* object_;
};
/*!
@ -234,15 +294,20 @@ namespace sdbus {
protected:
explicit ObjectManager_adaptor(sdbus::IObject& object)
: object_(object)
: object_(&object)
{
object_.addObjectManager();
object_->addObjectManager();
}
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() = default;
private:
sdbus::IObject& object_;
sdbus::IObject* object_;
};
/*!
@ -259,10 +324,16 @@ namespace sdbus {
class ManagedObject_adaptor
{
protected:
explicit ManagedObject_adaptor(sdbus::IObject& object) : object_(object)
explicit ManagedObject_adaptor(sdbus::IObject& object)
: 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() = default;
public:
@ -273,7 +344,7 @@ namespace sdbus {
*/
void emitInterfacesAddedSignal()
{
object_.emitInterfacesAddedSignal();
object_->emitInterfacesAddedSignal();
}
/*!
@ -283,7 +354,7 @@ namespace sdbus {
*/
void emitInterfacesAddedSignal(const std::vector<std::string>& interfaces)
{
object_.emitInterfacesAddedSignal(interfaces);
object_->emitInterfacesAddedSignal(interfaces);
}
/*!
@ -293,7 +364,7 @@ namespace sdbus {
*/
void emitInterfacesRemovedSignal()
{
object_.emitInterfacesRemovedSignal();
object_->emitInterfacesRemovedSignal();
}
/*!
@ -303,11 +374,11 @@ namespace sdbus {
*/
void emitInterfacesRemovedSignal(const std::vector<std::string>& interfaces)
{
object_.emitInterfacesRemovedSignal(interfaces);
object_->emitInterfacesRemovedSignal(interfaces);
}
private:
sdbus::IObject& object_;
sdbus::IObject* object_;
};
}

View File

@ -30,7 +30,12 @@
#include <type_traits>
#include <string>
#include <vector>
#include <array>
#if __cplusplus >= 202002L
#include <span>
#endif
#include <map>
#include <unordered_map>
#include <cstdint>
#include <functional>
#include <memory>
@ -81,12 +86,23 @@ namespace sdbus {
// Tag denoting the assumption that the caller has already obtained fd ownership
struct adopt_fd_t { explicit adopt_fd_t() = default; };
inline constexpr adopt_fd_t adopt_fd{};
// Tag specifying that the proxy shall not run an event loop thread on its D-Bus connection.
// Such proxies are typically created to carry out a simple synchronous D-Bus call(s) and then are destroyed.
struct dont_run_event_loop_thread_t { explicit dont_run_event_loop_thread_t() = default; };
inline constexpr dont_run_event_loop_thread_t dont_run_event_loop_thread{};
// Tag denoting an asynchronous call that returns std::future as a handle
struct with_future_t { explicit with_future_t() = default; };
inline constexpr with_future_t with_future{};
// Tag denoting a call where the reply shouldn't be waited for
struct dont_expect_reply_t { explicit dont_expect_reply_t() = default; };
inline constexpr dont_expect_reply_t dont_expect_reply{};
// Template specializations for getting D-Bus signatures from C++ types
template <typename _T>
struct signature_of
{
static constexpr bool is_valid = false;
static constexpr bool is_trivial_dbus_type = false;
static const std::string str()
{
@ -97,10 +113,21 @@ namespace sdbus {
}
};
template <typename _T>
struct signature_of<const _T>
: public signature_of<_T>
{};
template <typename _T>
struct signature_of<_T&>
: public signature_of<_T>
{};
template <>
struct signature_of<void>
{
static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = false;
static const std::string str()
{
@ -112,6 +139,7 @@ namespace sdbus {
struct signature_of<bool>
{
static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = true;
static const std::string str()
{
@ -123,6 +151,7 @@ namespace sdbus {
struct signature_of<uint8_t>
{
static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = true;
static const std::string str()
{
@ -134,6 +163,7 @@ namespace sdbus {
struct signature_of<int16_t>
{
static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = true;
static const std::string str()
{
@ -145,6 +175,7 @@ namespace sdbus {
struct signature_of<uint16_t>
{
static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = true;
static const std::string str()
{
@ -156,6 +187,7 @@ namespace sdbus {
struct signature_of<int32_t>
{
static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = true;
static const std::string str()
{
@ -167,6 +199,7 @@ namespace sdbus {
struct signature_of<uint32_t>
{
static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = true;
static const std::string str()
{
@ -178,6 +211,7 @@ namespace sdbus {
struct signature_of<int64_t>
{
static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = true;
static const std::string str()
{
@ -189,6 +223,7 @@ namespace sdbus {
struct signature_of<uint64_t>
{
static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = true;
static const std::string str()
{
@ -200,6 +235,7 @@ namespace sdbus {
struct signature_of<double>
{
static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = true;
static const std::string str()
{
@ -211,6 +247,7 @@ namespace sdbus {
struct signature_of<char*>
{
static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = false;
static const std::string str()
{
@ -222,6 +259,7 @@ namespace sdbus {
struct signature_of<const char*>
{
static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = false;
static const std::string str()
{
@ -233,6 +271,7 @@ namespace sdbus {
struct signature_of<char[_N]>
{
static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = false;
static const std::string str()
{
@ -244,6 +283,7 @@ namespace sdbus {
struct signature_of<const char[_N]>
{
static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = false;
static const std::string str()
{
@ -255,6 +295,7 @@ namespace sdbus {
struct signature_of<std::string>
{
static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = false;
static const std::string str()
{
@ -266,6 +307,7 @@ namespace sdbus {
struct signature_of<Struct<_ValueTypes...>>
{
static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = false;
static const std::string str()
{
@ -281,6 +323,7 @@ namespace sdbus {
struct signature_of<Variant>
{
static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = false;
static const std::string str()
{
@ -292,6 +335,7 @@ namespace sdbus {
struct signature_of<ObjectPath>
{
static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = false;
static const std::string str()
{
@ -303,6 +347,7 @@ namespace sdbus {
struct signature_of<Signature>
{
static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = false;
static const std::string str()
{
@ -314,6 +359,7 @@ namespace sdbus {
struct signature_of<UnixFd>
{
static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = false;
static const std::string str()
{
@ -321,10 +367,11 @@ namespace sdbus {
}
};
template <typename _Element>
struct signature_of<std::vector<_Element>>
template <typename _Element, typename _Allocator>
struct signature_of<std::vector<_Element, _Allocator>>
{
static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = false;
static const std::string str()
{
@ -332,10 +379,49 @@ namespace sdbus {
}
};
template <typename _Key, typename _Value>
struct signature_of<std::map<_Key, _Value>>
template <typename _Element, std::size_t _Size>
struct signature_of<std::array<_Element, _Size>>
{
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();
}
};
#if __cplusplus >= 202002L
template <typename _Element, std::size_t _Extent>
struct signature_of<std::span<_Element, _Extent>>
{
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
template <typename _Key, typename _Value, typename _Compare, typename _Allocator>
struct signature_of<std::map<_Key, _Value, _Compare, _Allocator>>
{
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 <typename _Key, typename _Value, typename _Hash, typename _KeyEqual, typename _Allocator>
struct signature_of<std::unordered_map<_Key, _Value, _Hash, _KeyEqual, _Allocator>>
{
static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = false;
static const std::string str()
{
@ -540,6 +626,26 @@ namespace sdbus {
}
};
template <typename... _Args> struct future_return
{
typedef std::tuple<_Args...> type;
};
template <> struct future_return<>
{
typedef void type;
};
template <typename _Type> struct future_return<_Type>
{
typedef _Type type;
};
template <typename... _Args>
using future_return_t = typename future_return<_Args...>::type;
namespace detail
{
template <class _Function, class _Tuple, typename... _Args, std::size_t... _I>

View File

@ -34,7 +34,7 @@
#include <typeinfo>
#include <memory>
#include <tuple>
#include <unistd.h>
#include <utility>
namespace sdbus {
@ -42,7 +42,7 @@ namespace sdbus {
* @class Variant
*
* Variant can hold value of any D-Bus-supported type.
*
*
* Note: Even though thread-aware, Variant objects are not thread-safe.
* Some const methods are conceptually const, but not physically const,
* thus are not thread-safe. This is by design: normally, clients
@ -104,6 +104,10 @@ namespace sdbus {
*
* Representation of struct D-Bus type
*
* Struct implements tuple protocol, i.e. it's a tuple-like class.
* It can be used with std::get<>(), std::tuple_element,
* std::tuple_size and in structured bindings.
*
***********************************************/
template <typename... _ValueTypes>
class Struct
@ -112,7 +116,7 @@ namespace sdbus {
public:
using std::tuple<_ValueTypes...>::tuple;
// Disable constructor if an older then 7.1.0 version of GCC is used
// 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;
@ -135,6 +139,9 @@ namespace sdbus {
}
};
template <typename... _Elements>
Struct(_Elements...) -> Struct<_Elements...>;
template<typename... _Elements>
constexpr Struct<std::decay_t<_Elements>...>
make_struct(_Elements&&... args)
@ -202,7 +209,7 @@ namespace sdbus {
UnixFd() = default;
explicit UnixFd(int fd)
: fd_(::dup(fd))
: fd_(checkedDup(fd))
{
}
@ -218,8 +225,12 @@ namespace sdbus {
UnixFd& operator=(const UnixFd& other)
{
if (this == &other)
{
return *this;
}
close();
fd_ = ::dup(other.fd_);
fd_ = checkedDup(other.fd_);
return *this;
}
@ -230,9 +241,12 @@ namespace sdbus {
UnixFd& operator=(UnixFd&& other)
{
if (this == &other)
{
return *this;
}
close();
fd_ = other.fd_;
other.fd_ = -1;
fd_ = std::exchange(other.fd_, -1);
return *this;
}
@ -258,9 +272,7 @@ namespace sdbus {
int release()
{
auto fd = fd_;
fd_ = -1;
return fd;
return std::exchange(fd_, -1);
}
bool isValid() const
@ -269,15 +281,26 @@ namespace sdbus {
}
private:
void close()
{
if (fd_ >= 0)
::close(fd_);
}
/// Closes file descriptor, but does not set it to -1.
void close();
/// Returns negative argument unchanged.
/// Otherwise, call ::dup and throw on failure.
static int checkedDup(int fd);
int fd_ = -1;
};
}
template <size_t _I, typename... _ValueTypes>
struct std::tuple_element<_I, sdbus::Struct<_ValueTypes...>>
: std::tuple_element<_I, std::tuple<_ValueTypes...>>
{};
template <typename... _ValueTypes>
struct std::tuple_size<sdbus::Struct<_ValueTypes...>>
: std::tuple_size<std::tuple<_ValueTypes...>>
{};
#endif /* SDBUS_CXX_TYPES_H_ */

View File

@ -5,7 +5,7 @@ includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@
Name: @PROJECT_NAME@
Description: C++ library on top of sd-bus, a systemd D-Bus library
Requires@PKGCONFIG_REQS@: libsystemd
Requires@PKGCONFIG_REQS@: @PKGCONFIG_DEPS@
Version: @SDBUSCPP_VERSION@
Libs: -L${libdir} -l@PROJECT_NAME@
Cflags: -I${includedir}

View File

@ -31,7 +31,7 @@
#include <sdbus-c++/Message.h>
#include <sdbus-c++/Error.h>
#include "ScopeGuard.h"
#include <systemd/sd-bus.h>
#include SDBUS_HEADER
#include <unistd.h>
#include <poll.h>
#include <sys/eventfd.h>
@ -70,6 +70,26 @@ Connection::Connection(std::unique_ptr<ISdBus>&& interface, remote_system_bus_t,
{
}
Connection::Connection(std::unique_ptr<ISdBus>&& 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::Connection(std::unique_ptr<ISdBus>&& interface, private_bus_t, int fd)
: Connection(std::move(interface), [&](sd_bus** bus) { return iface_->sd_bus_open_direct(bus, fd); })
{
}
Connection::Connection(std::unique_ptr<ISdBus>&& interface, server_bus_t, int fd)
: Connection(std::move(interface), [&](sd_bus** bus) { return iface_->sd_bus_open_server(bus, fd); })
{
}
Connection::Connection(std::unique_ptr<ISdBus>&& interface, sdbus_bus_t, sd_bus *bus)
: Connection(std::move(interface), [&](sd_bus** b) { *b = bus; return 0; })
{
}
Connection::Connection(std::unique_ptr<ISdBus>&& interface, pseudo_bus_t)
: iface_(std::move(interface))
, bus_(openPseudoBus())
@ -88,12 +108,20 @@ void Connection::requestName(const std::string& name)
auto r = iface_->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);
}
void Connection::releaseName(const std::string& name)
{
auto r = iface_->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);
}
std::string Connection::getUniqueName() const
@ -197,17 +225,11 @@ uint64_t Connection::getMethodCallTimeout() const
Slot Connection::addMatch(const std::string& match, message_handler callback)
{
auto matchInfo = std::make_unique<MatchInfo>(MatchInfo{std::move(callback), *this, {}});
SDBUS_THROW_ERROR_IF(!callback, "Invalid match callback handler provided", EINVAL);
auto messageHandler = [](sd_bus_message *sdbusMessage, void *userData, sd_bus_error */*retError*/) -> int
{
auto* matchInfo = static_cast<MatchInfo*>(userData);
auto message = Message::Factory::create<PlainMessage>(sdbusMessage, &matchInfo->connection.getSdBusInterface());
matchInfo->callback(message);
return 0;
};
auto matchInfo = std::make_unique<MatchInfo>(MatchInfo{std::move(callback), {}, *this, {}});
auto r = iface_->sd_bus_add_match(bus_.get(), &matchInfo->slot, match.c_str(), std::move(messageHandler), matchInfo.get());
auto r = iface_->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)
@ -223,6 +245,34 @@ void Connection::addMatch(const std::string& match, message_handler callback, fl
floatingMatchRules_.push_back(addMatch(match, std::move(callback)));
}
Slot Connection::addMatchAsync(const std::string& match, message_handler callback, message_handler installCallback)
{
SDBUS_THROW_ERROR_IF(!callback, "Invalid match callback handler provided", EINVAL);
sd_bus_message_handler_t sdbusInstallCallback = installCallback ? &Connection::sdbus_match_install_callback : nullptr;
auto matchInfo = std::make_unique<MatchInfo>(MatchInfo{std::move(callback), std::move(installCallback), *this, {}});
auto r = iface_->sd_bus_add_match_async( bus_.get()
, &matchInfo->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<MatchInfo*>(ptr);
iface_->sd_bus_slot_unref(matchInfo->slot);
std::default_delete<MatchInfo>{}(matchInfo);
}};
}
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)));
}
Slot Connection::addObjectVTable( const std::string& objectPath
, const std::string& interfaceName
, const sd_bus_vtable* vtable
@ -262,7 +312,7 @@ MethodCall Connection::createMethodCall( const std::string& destination
auto r = iface_->sd_bus_message_new_method_call( bus_.get()
, &sdbusMsg
, destination.c_str()
, destination.empty() ? nullptr : destination.c_str()
, objectPath.c_str()
, interfaceName.c_str()
, methodName.c_str() );
@ -527,7 +577,8 @@ std::string Connection::composeSignalMatchFilter( const std::string &sender
std::string filter;
filter += "type='signal',";
filter += "sender='" + sender + "',";
if (!sender.empty())
filter += "sender='" + sender + "',";
filter += "interface='" + interfaceName + "',";
filter += "member='" + signalName + "',";
filter += "path='" + objectPath + "'";
@ -544,6 +595,22 @@ std::vector</*const */char*> Connection::to_strv(const std::vector<std::string>&
return strv;
}
int Connection::sdbus_match_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError)
{
auto* matchInfo = static_cast<MatchInfo*>(userData);
auto message = Message::Factory::create<PlainMessage>(sdbusMessage, &matchInfo->connection.getSdBusInterface());
auto ok = invokeHandlerAndCatchErrors([&](){ matchInfo->callback(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<MatchInfo*>(userData);
auto message = Message::Factory::create<PlainMessage>(sdbusMessage, &matchInfo->connection.getSdBusInterface());
auto ok = invokeHandlerAndCatchErrors([&](){ matchInfo->installCallback(message); }, retError);
return ok ? 0 : -1;
}
Connection::EventFd::EventFd()
{
fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
@ -661,7 +728,6 @@ std::unique_ptr<sdbus::IConnection> createSessionBusConnection(const std::string
std::unique_ptr<sdbus::IConnection> createSessionBusConnectionWithAddress(const std::string &address)
{
auto interface = std::make_unique<sdbus::internal::SdBus>();
assert(interface != nullptr);
return std::make_unique<sdbus::internal::Connection>(std::move(interface), Connection::custom_session_bus, address);
}
@ -671,4 +737,30 @@ std::unique_ptr<sdbus::IConnection> createRemoteSystemBusConnection(const std::s
return std::make_unique<sdbus::internal::Connection>(std::move(interface), Connection::remote_system_bus, host);
}
std::unique_ptr<sdbus::IConnection> createDirectBusConnection(const std::string& address)
{
auto interface = std::make_unique<sdbus::internal::SdBus>();
return std::make_unique<sdbus::internal::Connection>(std::move(interface), Connection::private_bus, address);
}
std::unique_ptr<sdbus::IConnection> createDirectBusConnection(int fd)
{
auto interface = std::make_unique<sdbus::internal::SdBus>();
return std::make_unique<sdbus::internal::Connection>(std::move(interface), Connection::private_bus, fd);
}
std::unique_ptr<sdbus::IConnection> createServerBus(int fd)
{
auto interface = std::make_unique<sdbus::internal::SdBus>();
return std::make_unique<sdbus::internal::Connection>(std::move(interface), Connection::server_bus, fd);
}
std::unique_ptr<sdbus::IConnection> createBusConnection(sd_bus *bus)
{
SDBUS_THROW_ERROR_IF(bus == nullptr, "Invalid bus argument", EINVAL);
auto interface = std::make_unique<sdbus::internal::SdBus>();
return std::make_unique<sdbus::internal::Connection>(std::move(interface), Connection::sdbus_bus, bus);
}
} // namespace sdbus

View File

@ -32,7 +32,7 @@
#include "IConnection.h"
#include "ScopeGuard.h"
#include "ISdBus.h"
#include <systemd/sd-bus.h>
#include SDBUS_HEADER
#include <memory>
#include <thread>
#include <string>
@ -57,6 +57,12 @@ namespace sdbus::internal {
inline static constexpr custom_session_bus_t custom_session_bus{};
struct remote_system_bus_t{};
inline static constexpr remote_system_bus_t remote_system_bus{};
struct private_bus_t{};
inline static constexpr private_bus_t private_bus{};
struct server_bus_t{};
inline static constexpr server_bus_t server_bus{};
struct sdbus_bus_t{}; // A bus connection created directly from existing sd_bus instance
inline static constexpr sdbus_bus_t sdbus_bus{};
struct pseudo_bus_t{}; // A bus connection that is not really established with D-Bus daemon
inline static constexpr pseudo_bus_t pseudo_bus{};
@ -65,6 +71,10 @@ namespace sdbus::internal {
Connection(std::unique_ptr<ISdBus>&& interface, session_bus_t);
Connection(std::unique_ptr<ISdBus>&& interface, custom_session_bus_t, const std::string& address);
Connection(std::unique_ptr<ISdBus>&& interface, remote_system_bus_t, const std::string& host);
Connection(std::unique_ptr<ISdBus>&& interface, private_bus_t, const std::string& address);
Connection(std::unique_ptr<ISdBus>&& interface, private_bus_t, int fd);
Connection(std::unique_ptr<ISdBus>&& interface, server_bus_t, int fd);
Connection(std::unique_ptr<ISdBus>&& interface, sdbus_bus_t, sd_bus *bus);
Connection(std::unique_ptr<ISdBus>&& interface, pseudo_bus_t);
~Connection() override;
@ -86,6 +96,8 @@ namespace sdbus::internal {
[[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;
const ISdBus& getSdBusInterface() const override;
ISdBus& getSdBusInterface() override;
@ -141,10 +153,13 @@ namespace sdbus::internal {
void clearEventLoopNotification(int fd) const;
void notifyEventLoopNewTimeout() const override;
private:
void joinWithEventLoop();
static std::vector</*const */char*> to_strv(const std::vector<std::string>& 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);
private:
struct EventFd
{
EventFd();
@ -155,6 +170,7 @@ namespace sdbus::internal {
struct MatchInfo
{
message_handler callback;
message_handler installCallback;
Connection& connection;
sd_bus_slot *slot;
};

View File

@ -25,7 +25,7 @@
*/
#include <sdbus-c++/Error.h>
#include <systemd/sd-bus.h>
#include SDBUS_HEADER
#include "ScopeGuard.h"
namespace sdbus

View File

@ -25,7 +25,7 @@
*/
#include <sdbus-c++/Flags.h>
#include <systemd/sd-bus.h>
#include SDBUS_HEADER
namespace sdbus
{
@ -47,7 +47,7 @@ namespace sdbus
sdbusFlags |= SD_BUS_VTABLE_PROPERTY_CONST;
else if (flags_.test(Flags::EMITS_NO_SIGNAL))
sdbusFlags |= 0;
return sdbusFlags;
}

View File

@ -28,7 +28,7 @@
#define SDBUS_CXX_INTERNAL_ICONNECTION_H_
#include <sdbus-c++/IConnection.h>
#include <systemd/sd-bus.h>
#include SDBUS_HEADER
#include <string>
#include <memory>
#include <functional>

View File

@ -28,7 +28,7 @@
#ifndef SDBUS_CXX_ISDBUS_H
#define SDBUS_CXX_ISDBUS_H
#include <systemd/sd-bus.h>
#include SDBUS_HEADER
namespace sdbus::internal {
@ -71,12 +71,16 @@ namespace sdbus::internal {
virtual int sd_bus_open_user(sd_bus **ret) = 0;
virtual int sd_bus_open_user_with_address(sd_bus **ret, const char* address) = 0;
virtual int sd_bus_open_system_remote(sd_bus **ret, const char* host) = 0;
virtual int sd_bus_open_direct(sd_bus **ret, const char* address) = 0;
virtual int sd_bus_open_direct(sd_bus **ret, int fd) = 0;
virtual int sd_bus_open_server(sd_bus **ret, int fd) = 0;
virtual int sd_bus_request_name(sd_bus *bus, const char *name, uint64_t flags) = 0;
virtual int sd_bus_release_name(sd_bus *bus, const char *name) = 0;
virtual int sd_bus_get_unique_name(sd_bus *bus, const char **name) = 0;
virtual int sd_bus_add_object_vtable(sd_bus *bus, sd_bus_slot **slot, const char *path, const char *interface, const sd_bus_vtable *vtable, void *userdata) = 0;
virtual 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 sd_bus_slot* sd_bus_slot_unref(sd_bus_slot *slot) = 0;
virtual int sd_bus_new(sd_bus **ret) = 0;

View File

@ -31,7 +31,7 @@
#include "ISdBus.h"
#include "IConnection.h"
#include "ScopeGuard.h"
#include <systemd/sd-bus.h>
#include SDBUS_HEADER
#include <cassert>
namespace sdbus {
@ -226,6 +226,13 @@ Message& Message::operator<<(const UnixFd &item)
return *this;
}
Message& Message::appendArray(char type, const void *ptr, size_t size)
{
auto r = sd_bus_message_append_array((sd_bus_message*)msg_, type, ptr, size);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to serialize an array", -r);
return *this;
}
Message& Message::operator>>(bool& item)
{
@ -318,6 +325,17 @@ Message& Message::operator>>(uint64_t& item)
return *this;
}
Message& Message::readArray(char type, const void **ptr, size_t *size)
{
auto r = sd_bus_message_read_array((sd_bus_message*)msg_, type, ptr, size);
if (r == 0)
ok_ = false;
SDBUS_THROW_ERROR_IF(r < 0, "Failed to deserialize an array", -r);
return *this;
}
Message& Message::operator>>(double& item)
{
auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_DOUBLE, &item);
@ -630,6 +648,11 @@ bool Message::isEmpty() const
return sd_bus_message_is_empty((sd_bus_message*)msg_) != 0;
}
bool Message::isAtEnd(bool complete) const
{
return sd_bus_message_at_end((sd_bus_message*)msg_, complete) > 0;
}
pid_t Message::getCredsPid() const
{
uint64_t mask = SD_BUS_CREDS_PID | SD_BUS_CREDS_AUGMENT;
@ -862,15 +885,57 @@ void Signal::setDestination(const std::string& destination)
SDBUS_THROW_ERROR_IF(r < 0, "Failed to set signal destination", -r);
}
namespace {
// Pseudo-connection lifetime handling. In standard cases, we could do with simply function-local static pseudo
// connection instance below. However, it may happen that client's sdbus-c++ objects outlive this static connection
// instance (because they are used in global application objects that were created before this connection, and thus
// are destroyed later). This by itself sounds like a smell in client's application design, but it is downright bad
// in sdbus-c++ because it has no control over when client's dependent statics get destroyed. A "Phoenix" pattern
// (see Modern C++ Design - Generic Programming and Design Patterns Applied, by Andrei Alexandrescu) is applied to fix
// this by re-creating the connection again in such cases and keeping it alive until the next exit handler is invoked.
// 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{};
std::unique_ptr<sdbus::internal::IConnection, void(*)(sdbus::internal::IConnection*)> createPseudoConnection()
{
auto deleter = [](sdbus::internal::IConnection* con)
{
delete con;
pseudoConnectionDestroyed = true;
};
return {internal::createPseudoConnection().release(), std::move(deleter)};
}
sdbus::internal::IConnection& getPseudoConnectionInstance()
{
static auto connection = createPseudoConnection();
if (pseudoConnectionDestroyed)
{
connection = createPseudoConnection(); // Phoenix rising from the ashes
atexit([](){ connection.~unique_ptr(); }); // We have to manually take care of deleting the phoenix
pseudoConnectionDestroyed = false;
}
assert(connection != nullptr);
return *connection;
}
}
PlainMessage createPlainMessage()
{
//static auto connection = internal::createConnection();
// Let's create a pseudo connection -- one that does not really connect to the real bus.
// This is a bit of a hack, but it enables use to work with D-Bus message locally without
// the need of D-Bus daemon. This is especially useful in unit tests of both sdbus-c++ and client code.
// Additionally, it's light-weight and fast solution.
static auto connection = internal::createPseudoConnection();
return connection->createPlainMessage();
auto& connection = getPseudoConnectionInstance();
return connection.createPlainMessage();
}
}

View File

@ -35,7 +35,7 @@
#include "IConnection.h"
#include "Utils.h"
#include "VTableUtils.h"
#include <systemd/sd-bus.h>
#include SDBUS_HEADER
#include <utility>
#include <cassert>
@ -347,16 +347,9 @@ int Object::sdbus_method_callback(sd_bus_message *sdbusMessage, void *userData,
auto& callback = interfaceData->methods[message.getMemberName()].callback;
assert(callback);
try
{
callback(message);
}
catch (const Error& e)
{
sd_bus_error_set(retError, e.getName().c_str(), e.getMessage().c_str());
}
auto ok = invokeHandlerAndCatchErrors([&](){ callback(message); }, retError);
return 1;
return ok ? 1 : -1;
}
int Object::sdbus_property_get_callback( sd_bus */*bus*/
@ -381,16 +374,9 @@ int Object::sdbus_property_get_callback( sd_bus */*bus*/
auto reply = Message::Factory::create<PropertyGetReply>(sdbusReply, &object.connection_.getSdBusInterface());
try
{
callback(reply);
}
catch (const Error& e)
{
sd_bus_error_set(retError, e.getName().c_str(), e.getMessage().c_str());
}
auto ok = invokeHandlerAndCatchErrors([&](){ callback(reply); }, retError);
return 1;
return ok ? 1 : -1;
}
int Object::sdbus_property_set_callback( sd_bus */*bus*/
@ -416,16 +402,9 @@ int Object::sdbus_property_set_callback( sd_bus */*bus*/
object.m_CurrentlyProcessedMessage.store(nullptr, std::memory_order_relaxed);
};
try
{
callback(value);
}
catch (const Error& e)
{
sd_bus_error_set(retError, e.getName().c_str(), e.getMessage().c_str());
}
auto ok = invokeHandlerAndCatchErrors([&](){ callback(value); }, retError);
return 1;
return ok ? 1 : -1;
}
}

View File

@ -29,7 +29,7 @@
#include <sdbus-c++/IObject.h>
#include "IConnection.h"
#include <systemd/sd-bus.h>
#include SDBUS_HEADER
#include <string>
#include <map>
#include <vector>

View File

@ -32,7 +32,7 @@
#include "sdbus-c++/IConnection.h"
#include "sdbus-c++/Error.h"
#include "ScopeGuard.h"
#include <systemd/sd-bus.h>
#include SDBUS_HEADER
#include <cassert>
#include <chrono>
#include <utility>
@ -66,6 +66,21 @@ Proxy::Proxy( std::unique_ptr<sdbus::internal::IConnection>&& connection
connection_->enterEventLoopAsync();
}
Proxy::Proxy( std::unique_ptr<sdbus::internal::IConnection>&& connection
, std::string destination
, std::string objectPath
, dont_run_event_loop_thread_t )
: connection_(std::move(connection))
, destination_(std::move(destination))
, objectPath_(std::move(objectPath))
{
SDBUS_CHECK_SERVICE_NAME(destination_);
SDBUS_CHECK_OBJECT_PATH(objectPath_);
// 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.
}
MethodCall Proxy::createMethodCall(const std::string& interfaceName, const std::string& methodName)
{
return connection_->createMethodCall(destination_, objectPath_, interfaceName, methodName);
@ -105,17 +120,39 @@ 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>(AsyncCalls::CallData{*this, std::move(asyncReplyCallback), {}});
auto callData = std::make_shared<AsyncCalls::CallData>(AsyncCalls::CallData{*this, std::move(asyncReplyCallback), {}, AsyncCalls::CallData::State::RUNNING});
auto weakData = std::weak_ptr<AsyncCalls::CallData>{callData};
callData->slot = message.send(callback, callData.get(), timeout);
auto slotPtr = callData->slot.get();
pendingAsyncCalls_.addCall(slotPtr, std::move(callData));
pendingAsyncCalls_.addCall(std::move(callData));
return {weakData};
}
std::future<MethodReply> Proxy::callMethod(const MethodCall& message, with_future_t)
{
return Proxy::callMethod(message, {}, with_future);
}
std::future<MethodReply> Proxy::callMethod(const MethodCall& message, uint64_t timeout, with_future_t)
{
auto promise = std::make_shared<std::promise<MethodReply>>();
auto future = promise->get_future();
async_reply_handler asyncReplyCallback = [promise = std::move(promise)](MethodReply& reply, const Error* error) noexcept
{
if (error == nullptr)
promise->set_value(reply);
else
promise->set_exception(std::make_exception_ptr(*error));
};
(void)Proxy::callMethod(message, std::move(asyncReplyCallback), timeout);
return future;
}
MethodReply Proxy::sendMethodCallMessageAndWaitForReply(const MethodCall& message, uint64_t timeout)
{
/*thread_local*/ SyncCallReplyData syncCallReplyData;
@ -125,7 +162,7 @@ MethodReply Proxy::sendMethodCallMessageAndWaitForReply(const MethodCall& messag
syncCallReplyData.sendMethodReplyToWaitingThread(reply, error);
};
auto callback = (void*)&Proxy::sdbus_async_reply_handler;
AsyncCalls::CallData callData{*this, std::move(asyncReplyCallback), {}};
AsyncCalls::CallData callData{*this, std::move(asyncReplyCallback), {}, AsyncCalls::CallData::State::NOT_ASYNC};
message.send(callback, &callData, timeout, floating_slot);
@ -232,22 +269,22 @@ const Message* Proxy::getCurrentlyProcessedMessage() const
return m_CurrentlyProcessedMessage.load(std::memory_order_relaxed);
}
int Proxy::sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error */*retError*/)
int Proxy::sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError)
{
auto* asyncCallData = static_cast<AsyncCalls::CallData*>(userData);
assert(asyncCallData != nullptr);
assert(asyncCallData->callback);
auto& proxy = asyncCallData->proxy;
auto slot = asyncCallData->slot.get();
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 slot == nullptr)
if (slot)
proxy.pendingAsyncCalls_.removeCall(slot);
// 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);
};
auto message = Message::Factory::create<MethodReply>(sdbusMessage, &proxy.connection_->getSdBusInterface());
@ -258,7 +295,7 @@ int Proxy::sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userDat
proxy.m_CurrentlyProcessedMessage.store(nullptr, std::memory_order_relaxed);
};
try
auto ok = invokeHandlerAndCatchErrors([&]
{
const auto* error = sd_bus_message_get_error(sdbusMessage);
if (error == nullptr)
@ -270,16 +307,12 @@ int Proxy::sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userDat
Error exception(error->name, error->message);
asyncCallData->callback(message, &exception);
}
}
catch (const Error&)
{
// Intentionally left blank -- sdbus-c++ exceptions shall not bubble up to the underlying C sd-bus library
}
}, retError);
return 0;
return ok ? 0 : -1;
}
int Proxy::sdbus_signal_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error */*retError*/)
int Proxy::sdbus_signal_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError)
{
auto* signalData = static_cast<InterfaceData::SignalData*>(userData);
assert(signalData != nullptr);
@ -293,16 +326,9 @@ int Proxy::sdbus_signal_handler(sd_bus_message *sdbusMessage, void *userData, sd
signalData->proxy.m_CurrentlyProcessedMessage.store(nullptr, std::memory_order_relaxed);
};
try
{
signalData->callback(message);
}
catch (const Error&)
{
// Intentionally left blank -- sdbus-c++ exceptions shall not bubble up to the underlying C sd-bus library
}
auto ok = invokeHandlerAndCatchErrors([&](){ signalData->callback(message); }, retError);
return 0;
return ok ? 0 : -1;
}
}
@ -319,7 +345,7 @@ void PendingAsyncCall::cancel()
if (auto ptr = callData_.lock(); ptr != nullptr)
{
auto* callData = static_cast<internal::Proxy::AsyncCalls::CallData*>(ptr.get());
callData->proxy.pendingAsyncCalls_.removeCall(callData->slot.get());
callData->proxy.pendingAsyncCalls_.removeCall(callData);
// 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
@ -363,6 +389,22 @@ std::unique_ptr<sdbus::IProxy> createProxy( std::unique_ptr<IConnection>&& conne
, std::move(objectPath) );
}
std::unique_ptr<sdbus::IProxy> createProxy( std::unique_ptr<IConnection>&& connection
, std::string destination
, std::string objectPath
, dont_run_event_loop_thread_t )
{
auto* sdbusConnection = dynamic_cast<sdbus::internal::IConnection*>(connection.get());
SDBUS_THROW_ERROR_IF(!sdbusConnection, "Connection is not a real sdbus-c++ connection", EINVAL);
connection.release();
return std::make_unique<sdbus::internal::Proxy>( std::unique_ptr<sdbus::internal::IConnection>(sdbusConnection)
, std::move(destination)
, std::move(objectPath)
, dont_run_event_loop_thread );
}
std::unique_ptr<sdbus::IProxy> createProxy( std::string destination
, std::string objectPath )
{
@ -376,4 +418,19 @@ std::unique_ptr<sdbus::IProxy> createProxy( std::string destination
, std::move(objectPath) );
}
std::unique_ptr<sdbus::IProxy> createProxy( std::string destination
, std::string objectPath
, dont_run_event_loop_thread_t )
{
auto connection = sdbus::createConnection();
auto sdbusConnection = std::unique_ptr<sdbus::internal::IConnection>(dynamic_cast<sdbus::internal::IConnection*>(connection.release()));
assert(sdbusConnection != nullptr);
return std::make_unique<sdbus::internal::Proxy>( std::move(sdbusConnection)
, std::move(destination)
, std::move(objectPath)
, dont_run_event_loop_thread );
}
}

View File

@ -29,11 +29,11 @@
#include <sdbus-c++/IProxy.h>
#include "IConnection.h"
#include <systemd/sd-bus.h>
#include SDBUS_HEADER
#include <string>
#include <memory>
#include <map>
#include <unordered_map>
#include <deque>
#include <mutex>
#include <atomic>
#include <condition_variable>
@ -50,10 +50,16 @@ namespace sdbus::internal {
Proxy( std::unique_ptr<sdbus::internal::IConnection>&& connection
, std::string destination
, std::string objectPath );
Proxy( std::unique_ptr<sdbus::internal::IConnection>&& connection
, std::string destination
, std::string objectPath
, dont_run_event_loop_thread_t );
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<MethodReply> callMethod(const MethodCall& message, with_future_t) override;
std::future<MethodReply> callMethod(const MethodCall& message, uint64_t timeout, with_future_t) override;
void registerSignalHandler( const std::string& interfaceName
, const std::string& signalName
@ -129,9 +135,16 @@ namespace sdbus::internal {
public:
struct CallData
{
enum class State
{ NOT_ASYNC
, RUNNING
, FINISHED
};
Proxy& proxy;
async_reply_handler callback;
Slot slot;
State state;
};
~AsyncCalls()
@ -139,18 +152,20 @@ namespace sdbus::internal {
clear();
}
bool addCall(void* slot, std::shared_ptr<CallData> asyncCallData)
void addCall(std::shared_ptr<CallData> asyncCallData)
{
std::lock_guard lock(mutex_);
return calls_.emplace(slot, std::move(asyncCallData)).second;
if (asyncCallData->state != CallData::State::FINISHED) // The call may have finished in the meantime
calls_.emplace_back(std::move(asyncCallData));
}
void removeCall(void* slot)
void removeCall(CallData* data)
{
std::unique_lock lock(mutex_);
if (auto it = calls_.find(slot); it != calls_.end())
data->state = CallData::State::FINISHED;
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->second);
auto callData = std::move(*it);
calls_.erase(it);
lock.unlock();
@ -176,7 +191,7 @@ namespace sdbus::internal {
private:
std::mutex mutex_;
std::unordered_map<void*, std::shared_ptr<CallData>> calls_;
std::deque<std::shared_ptr<CallData>> calls_;
} pendingAsyncCalls_;
std::atomic<const Message*> m_CurrentlyProcessedMessage{nullptr};

View File

@ -55,78 +55,115 @@
// return; // exiting scope normally
// }
#define SCOPE_EXIT \
auto ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE) \
= ::skybase::utils::detail::ScopeGuardOnExit() + [&]() \
#define SCOPE_EXIT \
auto ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE) \
= ::sdbus::internal::ScopeGuardOnExitTag{} + [&]() \
/**/
#define SCOPE_EXIT_NAMED(NAME) \
auto NAME \
= ::sdbus::internal::ScopeGuardOnExitTag{} + [&]() \
/**/
#define SCOPE_EXIT_SUCCESS \
auto ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE) \
= ::sdbus::internal::ScopeGuardOnExitSuccessTag{} + [&]() \
/**/
#define SCOPE_EXIT_SUCCESS_NAMED(NAME) \
auto NAME \
= ::sdbus::internal::ScopeGuardOnExitSuccessTag{} + [&]() \
/**/
#define SCOPE_EXIT_FAILURE \
auto ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE) \
= ::sdbus::internal::ScopeGuardOnExitFailureTag{} + [&]() \
/**/
#define SCOPE_EXIT_FAILURE_NAMED(NAME) \
auto NAME \
= ::sdbus::internal::ScopeGuardOnExitFailureTag{} + [&]() \
/**/
#define SCOPE_EXIT_NAMED(NAME) \
auto NAME \
= ::skybase::utils::detail::ScopeGuardOnExit() + [&]() \
/**/
namespace sdbus::internal {
namespace skybase {
namespace utils {
struct ScopeGuardOnExitTag
{
static bool holds(int /*originalExceptions*/)
{
return true; // Always holds
}
};
struct ScopeGuardOnExitSuccessTag
{
static bool holds(int originalExceptions)
{
return originalExceptions == std::uncaught_exceptions(); // Only holds when no exception occurred within the scope
}
};
struct ScopeGuardOnExitFailureTag
{
static bool holds(int originalExceptions)
{
return originalExceptions != std::uncaught_exceptions(); // Only holds when an exception occurred within the scope
}
};
template <class _Fun>
template <class _Fun, typename _Tag>
class ScopeGuard
{
_Fun fnc_;
bool active_;
public:
ScopeGuard(_Fun f)
: fnc_(std::move(f))
, active_(true)
ScopeGuard(_Fun f) : fnc_(std::move(f))
{
}
~ScopeGuard()
ScopeGuard() = delete;
ScopeGuard(const ScopeGuard&) = delete;
ScopeGuard& operator=(const ScopeGuard&) = delete;
ScopeGuard(ScopeGuard&& rhs) : fnc_(std::move(rhs.fnc_)), active_(rhs.active_), exceptions_(rhs.exceptions_)
{
if (active_)
fnc_();
rhs.dismiss();
}
void dismiss()
{
active_ = false;
}
ScopeGuard() = delete;
ScopeGuard(const ScopeGuard&) = delete;
ScopeGuard& operator=(const ScopeGuard&) = delete;
ScopeGuard(ScopeGuard&& rhs)
: fnc_(std::move(rhs.fnc_))
, active_(rhs.active_)
~ScopeGuard()
{
rhs.dismiss();
if (active_ && _Tag::holds(exceptions_))
fnc_();
}
private:
_Fun fnc_;
int exceptions_{std::uncaught_exceptions()};
bool active_{true};
};
namespace detail
template <typename _Fun>
ScopeGuard<_Fun, ScopeGuardOnExitTag> operator+(ScopeGuardOnExitTag, _Fun&& fnc)
{
enum class ScopeGuardOnExit
{
};
// Helper function to auto-deduce type of the callable entity
template <typename _Fun>
ScopeGuard<_Fun> operator+(ScopeGuardOnExit, _Fun&& fnc)
{
return ScopeGuard<_Fun>(std::forward<_Fun>(fnc));
}
return ScopeGuard<_Fun, ScopeGuardOnExitTag>(std::forward<_Fun>(fnc));
}
}}
template <typename _Fun>
ScopeGuard<_Fun, ScopeGuardOnExitSuccessTag> operator+(ScopeGuardOnExitSuccessTag, _Fun&& fnc)
{
return ScopeGuard<_Fun, ScopeGuardOnExitSuccessTag>(std::forward<_Fun>(fnc));
}
template <typename _Fun>
ScopeGuard<_Fun, ScopeGuardOnExitFailureTag> operator+(ScopeGuardOnExitFailureTag, _Fun&& fnc)
{
return ScopeGuard<_Fun, ScopeGuardOnExitFailureTag>(std::forward<_Fun>(fnc));
}
}
#define CONCATENATE_IMPL(s1, s2) s1##s2
#define CONCATENATE(s1, s2) CONCATENATE_IMPL(s1, s2)
#ifdef __COUNTER__
#define ANONYMOUS_VARIABLE(str) \
CONCATENATE(str, __COUNTER__) \
/**/
#define ANONYMOUS_VARIABLE(str) CONCATENATE(str, __COUNTER__)
#else
#define ANONYMOUS_VARIABLE(str) \
CONCATENATE(str, __LINE__) \
/**/
#define ANONYMOUS_VARIABLE(str) CONCATENATE(str, __LINE__)
#endif
#endif /* SDBUS_CPP_INTERNAL_SCOPEGUARD_H_ */

View File

@ -48,7 +48,14 @@ int SdBus::sd_bus_send(sd_bus *bus, sd_bus_message *m, uint64_t *cookie)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_send(bus, m, cookie);
auto r = ::sd_bus_send(bus, m, cookie);
if (r < 0)
return r;
// Make sure long messages are not only stored in outgoing queues but also really sent out
::sd_bus_flush(bus != nullptr ? bus : ::sd_bus_message_get_bus(m));
return r;
}
int SdBus::sd_bus_call(sd_bus *bus, sd_bus_message *m, uint64_t usec, sd_bus_error *ret_error, sd_bus_message **reply)
@ -62,7 +69,14 @@ int SdBus::sd_bus_call_async(sd_bus *bus, sd_bus_slot **slot, sd_bus_message *m,
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_call_async(bus, slot, m, callback, userdata, usec);
auto r = ::sd_bus_call_async(bus, slot, m, callback, userdata, usec);
if (r < 0)
return r;
// Make sure long messages are not only stored in outgoing queues but also really sent out
::sd_bus_flush(bus != nullptr ? bus : ::sd_bus_message_get_bus(m));
return r;
}
int SdBus::sd_bus_message_new(sd_bus *bus, sd_bus_message **m, uint8_t type)
@ -180,26 +194,98 @@ int SdBus::sd_bus_open_user_with_address(sd_bus **ret, const char* address)
{
sd_bus* bus = nullptr;
int r = sd_bus_new(&bus);
int r = ::sd_bus_new(&bus);
if (r < 0)
return r;
r = sd_bus_set_address(bus, address);
r = ::sd_bus_set_address(bus, address);
if (r < 0)
return r;
r = sd_bus_set_bus_client(bus, true);
r = ::sd_bus_set_bus_client(bus, true);
if (r < 0)
return r;
// Copying behavior from
// https://github.com/systemd/systemd/blob/fee6441601c979165ebcbb35472036439f8dad5f/src/libsystemd/sd-bus/sd-bus.c#L1381
// Here, we make the bus as trusted
r = sd_bus_set_trusted(bus, true);
r = ::sd_bus_set_trusted(bus, true);
if (r < 0)
return r;
r = sd_bus_start(bus);
r = ::sd_bus_start(bus);
if (r < 0)
return r;
*ret = bus;
return 0;
}
int SdBus::sd_bus_open_direct(sd_bus **ret, const char* address)
{
sd_bus* bus = nullptr;
int r = ::sd_bus_new(&bus);
if (r < 0)
return r;
r = ::sd_bus_set_address(bus, address);
if (r < 0)
return r;
r = ::sd_bus_start(bus);
if (r < 0)
return r;
*ret = bus;
return 0;
}
int SdBus::sd_bus_open_direct(sd_bus **ret, int fd)
{
sd_bus* bus = nullptr;
int r = ::sd_bus_new(&bus);
if (r < 0)
return r;
r = ::sd_bus_set_fd(bus, fd, fd);
if (r < 0)
return r;
r = ::sd_bus_start(bus);
if (r < 0)
return r;
*ret = bus;
return 0;
}
int SdBus::sd_bus_open_server(sd_bus **ret, int fd)
{
sd_bus* bus = nullptr;
int r = ::sd_bus_new(&bus);
if (r < 0)
return r;
r = ::sd_bus_set_fd(bus, fd, fd);
if (r < 0)
return r;
sd_id128_t id;
r = ::sd_id128_randomize(&id);
if (r < 0)
return r;
r = ::sd_bus_set_server(bus, true, id);
if (r < 0)
return r;
r = ::sd_bus_start(bus);
if (r < 0)
return r;
@ -210,7 +296,12 @@ int SdBus::sd_bus_open_user_with_address(sd_bus **ret, const char* address)
int SdBus::sd_bus_open_system_remote(sd_bus **ret, const char *host)
{
#ifdef SDBUS_basu
// https://git.sr.ht/~emersion/basu/commit/01d33b244eb6
return -EOPNOTSUPP;
#else
return ::sd_bus_open_system_remote(ret, host);
#endif
}
int SdBus::sd_bus_request_name(sd_bus *bus, const char *name, uint64_t flags)
@ -251,7 +342,14 @@ int SdBus::sd_bus_add_match(sd_bus *bus, sd_bus_slot **slot, const char *match,
{
std::lock_guard lock(sdbusMutex_);
return :: sd_bus_add_match(bus, slot, match, callback, userdata);
return ::sd_bus_add_match(bus, slot, match, callback, userdata);
}
int SdBus::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)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_add_match_async(bus, slot, match, callback, install_callback, userdata);
}
sd_bus_slot* SdBus::sd_bus_slot_unref(sd_bus_slot *slot)

View File

@ -63,12 +63,16 @@ public:
virtual int sd_bus_open_user(sd_bus **ret) override;
virtual int sd_bus_open_user_with_address(sd_bus **ret, const char* address) override;
virtual int sd_bus_open_system_remote(sd_bus **ret, const char* hsot) override;
virtual int sd_bus_open_direct(sd_bus **ret, const char* address) override;
virtual int sd_bus_open_direct(sd_bus **ret, int fd) override;
virtual int sd_bus_open_server(sd_bus **ret, int fd) override;
virtual int sd_bus_request_name(sd_bus *bus, const char *name, uint64_t flags) override;
virtual int sd_bus_release_name(sd_bus *bus, const char *name) override;
virtual int sd_bus_get_unique_name(sd_bus *bus, const char **name) override;
virtual int sd_bus_add_object_vtable(sd_bus *bus, sd_bus_slot **slot, const char *path, const char *interface, const sd_bus_vtable *vtable, void *userdata) override;
virtual int sd_bus_add_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 sd_bus_slot* sd_bus_slot_unref(sd_bus_slot *slot) override;
virtual int sd_bus_new(sd_bus **ret) override;

View File

@ -27,8 +27,11 @@
#include <sdbus-c++/Types.h>
#include <sdbus-c++/Error.h>
#include "MessageUtils.h"
#include <systemd/sd-bus.h>
#include SDBUS_HEADER
#include <cassert>
#include <cerrno>
#include <system_error>
#include <unistd.h>
namespace sdbus {
@ -64,4 +67,27 @@ bool Variant::isEmpty() const
return msg_.isEmpty();
}
void UnixFd::close()
{
if (fd_ >= 0)
{
::close(fd_);
}
}
int UnixFd::checkedDup(int fd)
{
if (fd < 0)
{
return fd;
}
int ret = ::dup(fd);
if (ret < 0)
{
throw std::system_error(errno, std::generic_category(), "dup failed");
}
return ret;
}
}

View File

@ -28,20 +28,20 @@
#define SDBUS_CXX_INTERNAL_UTILS_H_
#include <sdbus-c++/Error.h>
#include <systemd/sd-bus.h>
#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.c_str()), "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.c_str()), "Invalid interface name '" + _NAME + "' provided", EINVAL) \
/**/
#define SDBUS_CHECK_SERVICE_NAME(_NAME) \
SDBUS_THROW_ERROR_IF(!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.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) \
#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) \
/**/
#else
#define SDBUS_CHECK_OBJECT_PATH(_PATH)
@ -50,4 +50,38 @@
#define SDBUS_CHECK_MEMBER_NAME(_NAME)
#endif
namespace sdbus::internal {
template <typename _Callable>
bool invokeHandlerAndCatchErrors(_Callable callable, sd_bus_error *retError)
{
try
{
callable();
}
catch (const Error& e)
{
sd_bus_error_set(retError, e.getName().c_str(), e.getMessage().c_str());
return false;
}
catch (const std::exception& e)
{
sd_bus_error_set(retError, SDBUSCPP_ERROR_NAME, e.what());
return false;
}
catch (...)
{
sd_bus_error_set(retError, SDBUSCPP_ERROR_NAME, "Unknown error occurred");
return false;
}
return true;
}
// Implementation of the overload pattern for variant visitation
template <class... Ts> struct overload : Ts... { using Ts::operator()...; };
template <class... Ts> overload(Ts...) -> overload<Ts...>;
}
#endif /* SDBUS_CXX_INTERNAL_UTILS_H_ */

View File

@ -25,7 +25,7 @@
*/
#include "VTableUtils.h"
#include <systemd/sd-bus.h>
#include SDBUS_HEADER
sd_bus_vtable createVTableStartItem(uint64_t flags)
{

View File

@ -27,7 +27,7 @@
#ifndef SDBUS_CXX_INTERNAL_VTABLEUTILS_H_
#define SDBUS_CXX_INTERNAL_VTABLEUTILS_H_
#include <systemd/sd-bus.h>
#include SDBUS_HEADER
#include <stdbool.h>
#ifdef __cplusplus

View File

@ -104,7 +104,9 @@ 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})
target_compile_definitions(sdbus-c++-unit-tests PRIVATE
LIBSYSTEMD_VERSION=${LIBSYSTEMD_VERSION}
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})
@ -138,21 +140,26 @@ endif()
# INSTALLATION
#----------------------------------
set(TESTS_INSTALL_PATH "/opt/test/bin" CACHE STRING "Specifies where the test binaries will be installed")
option(INSTALL_TESTS "Install tests (default OFF)" OFF)
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 /etc/dbus-1/system.d COMPONENT test)
if(INSTALL_TESTS)
include(GNUInstallDirs)
set(TESTS_INSTALL_PATH "tests/${PROJECT_NAME}" CACHE STRING "Specifies where the test binaries will be installed")
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 /etc/dbus-1/system.d COMPONENT test)
endif()
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_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 /etc/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)
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)
endif()
endif()
#----------------------------------
@ -162,4 +169,4 @@ endif()
if(NOT CMAKE_CROSSCOMPILING)
add_test(NAME sdbus-c++-unit-tests COMMAND sdbus-c++-unit-tests)
add_test(NAME sdbus-c++-integration-tests COMMAND sdbus-c++-integration-tests)
endif()
endif()

View File

@ -161,6 +161,24 @@ TEST_F(SdbusTestObject, InvokesMethodAsynchronouslyOnClientSide)
ASSERT_THAT(future.get(), Eq(100));
}
TEST_F(SdbusTestObject, InvokesMethodAsynchronouslyOnClientSideWithFuture)
{
auto future = m_proxy->doOperationClientSideAsync(100, sdbus::with_future);
ASSERT_THAT(future.get(), Eq(100));
}
TEST_F(SdbusTestObject, InvokesMethodAsynchronouslyOnClientSideWithFutureOnBasicAPILevel)
{
auto future = m_proxy->doOperationClientSideAsyncOnBasicAPILevel(100);
auto methodReply = future.get();
uint32_t returnValue{};
methodReply >> returnValue;
ASSERT_THAT(returnValue, Eq(100));
}
TEST_F(SdbusTestObject, AnswersThatAsyncCallIsPendingIfItIsInProgress)
{
m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t /*res*/, const sdbus::Error* /*err*/){});
@ -222,7 +240,7 @@ TEST_F(SdbusTestObject, SupportsAsyncCallCopyAssignment)
ASSERT_TRUE(call.isPending());
}
TEST_F(SdbusTestObject, InvokesErroneousMethodAsynchronouslyOnClientSide)
TEST_F(SdbusTestObject, ReturnsNonnullErrorWhenAsynchronousMethodCallFails)
{
std::promise<uint32_t> promise;
auto future = promise.get_future();
@ -238,3 +256,10 @@ TEST_F(SdbusTestObject, InvokesErroneousMethodAsynchronouslyOnClientSide)
ASSERT_THROW(future.get(), sdbus::Error);
}
TEST_F(SdbusTestObject, ThrowsErrorWhenClientSideAsynchronousMethodCallWithFutureFails)
{
auto future = m_proxy->doErroneousOperationClientSideAsync(sdbus::with_future);
ASSERT_THROW(future.get(), sdbus::Error);
}

View File

@ -38,6 +38,7 @@
#include <fstream>
#include <future>
#include <unistd.h>
#include <variant>
using ::testing::ElementsAre;
using ::testing::Eq;
@ -45,6 +46,7 @@ using namespace std::chrono_literals;
using namespace sdbus::test;
using AConnection = TestFixture;
using ADirectConnection = TestFixtureWithDirectConnection;
/*-------------------------------------*/
/* -- TEST CASES -- */
@ -59,6 +61,18 @@ TEST(AdaptorAndProxy, CanBeConstructedSuccesfully)
ASSERT_NO_THROW(TestProxy proxy(BUS_NAME, OBJECT_PATH));
}
TEST(AProxy, SupportsMoveSemantics)
{
static_assert(std::is_move_constructible_v<DummyTestProxy>);
static_assert(std::is_move_assignable_v<DummyTestProxy>);
}
TEST(AnAdaptor, SupportsMoveSemantics)
{
static_assert(std::is_move_constructible_v<DummyTestAdaptor>);
static_assert(std::is_move_assignable_v<DummyTestAdaptor>);
}
TEST_F(AConnection, WillCallCallbackHandlerForIncomingMessageMatchingMatchRule)
{
auto matchRule = "sender='" + BUS_NAME + "',path='" + OBJECT_PATH + "'";
@ -74,6 +88,29 @@ TEST_F(AConnection, WillCallCallbackHandlerForIncomingMessageMatchingMatchRule)
ASSERT_TRUE(waitUntil(matchingMessageReceived));
}
TEST_F(AConnection, CanInstallMatchRuleAsynchronously)
{
auto matchRule = "sender='" + BUS_NAME + "',path='" + OBJECT_PATH + "'";
std::atomic<bool> matchingMessageReceived{false};
std::atomic<bool> matchRuleInstalled{false};
auto slot = 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();
ASSERT_TRUE(waitUntil(matchingMessageReceived));
}
TEST_F(AConnection, WillUnsubscribeMatchRuleWhenClientDestroysTheAssociatedSlot)
{
auto matchRule = "sender='" + BUS_NAME + "',path='" + OBJECT_PATH + "'";
@ -103,7 +140,8 @@ TEST_F(AConnection, CanAddFloatingMatchRule)
};
con->addMatch(matchRule, std::move(callback), sdbus::floating_slot);
m_adaptor->emitSimpleSignal();
assert(waitUntil(matchingMessageReceived, 2s));
[[maybe_unused]] auto gotMessage = waitUntil(matchingMessageReceived, 2s);
assert(gotMessage);
matchingMessageReceived = false;
con.reset();
@ -130,3 +168,14 @@ TEST_F(AConnection, WillNotPassToMatchCallbackMessagesThatDoNotMatchTheRule)
ASSERT_TRUE(waitUntil([&](){ return numberOfMatchingMessages == 2; }));
ASSERT_FALSE(waitUntil([&](){ return numberOfMatchingMessages > 2; }, 1s));
}
// A simple direct connection test similar in nature to https://github.com/systemd/systemd/blob/main/src/libsystemd/sd-bus/test-bus-server.c
TEST_F(ADirectConnection, CanBeUsedBetweenClientAndServer)
{
auto val = m_proxy->sumArrayItems({1, 7}, {2, 3, 4});
m_adaptor->emitSimpleSignal();
// Make sure method call passes and emitted signal is received
ASSERT_THAT(val, Eq(1 + 7 + 2 + 3 + 4));
ASSERT_TRUE(waitUntil(m_proxy->m_gotSimpleSignal));
}

View File

@ -103,7 +103,9 @@ TEST_F(SdbusTestObject, CallsMethodWithStructVariantsAndGetMapSuccesfully)
std::vector<int32_t> x{-2, 0, 2};
sdbus::Struct<sdbus::Variant, sdbus::Variant> y{false, true};
auto mapOfVariants = m_proxy->getMapOfVariants(x, y);
decltype(mapOfVariants) res{{-2, false}, {0, false}, {2, true}};
decltype(mapOfVariants) res{ {sdbus::Variant{-2}, sdbus::Variant{false}}
, {sdbus::Variant{0}, sdbus::Variant{false}}
, {sdbus::Variant{2}, sdbus::Variant{true}}};
ASSERT_THAT(mapOfVariants[-2].get<bool>(), Eq(res[-2].get<bool>()));
ASSERT_THAT(mapOfVariants[0].get<bool>(), Eq(res[0].get<bool>()));
@ -125,8 +127,8 @@ TEST_F(SdbusTestObject, CallsMethodWithTwoStructsSuccesfully)
TEST_F(SdbusTestObject, CallsMethodWithTwoVectorsSuccesfully)
{
auto val = m_proxy->sumVectorItems({1, 7}, {2, 3});
ASSERT_THAT(val, Eq(1 + 7 + 2 + 3));
auto val = m_proxy->sumArrayItems({1, 7}, {2, 3, 4});
ASSERT_THAT(val, Eq(1 + 7 + 2 + 3 + 4));
}
TEST_F(SdbusTestObject, CallsMethodWithSignatureSuccesfully)
@ -178,7 +180,7 @@ TEST_F(SdbusTestObject, ThrowsTimeoutErrorWhenMethodTimesOut)
catch (const sdbus::Error& e)
{
ASSERT_THAT(e.getName(), AnyOf("org.freedesktop.DBus.Error.Timeout", "org.freedesktop.DBus.Error.NoReply"));
ASSERT_THAT(e.getMessage(), AnyOf("Connection timed out", "Method call timed out"));
ASSERT_THAT(e.getMessage(), AnyOf("Connection timed out", "Operation timed out", "Method call timed out"));
auto measuredTimeout = std::chrono::steady_clock::now() - start;
ASSERT_THAT(measuredTimeout, Le(50ms));
}
@ -272,3 +274,16 @@ TEST_F(SdbusTestObject, CannotSetGeneralMethodTimeoutWithLibsystemdVersionLessTh
ASSERT_THROW(s_adaptorConnection->getMethodCallTimeout(), sdbus::Error);
}
#endif
TEST_F(SdbusTestObject, CanCallMethodSynchronouslyWithoutAnEventLoopThread)
{
#if defined(__clang__) && defined(__FreeBSD__)
GTEST_SKIP() << "https://github.com/Kistler-Group/sdbus-cpp/issues/359";
#endif
auto proxy = std::make_unique<TestProxy>(BUS_NAME, OBJECT_PATH, sdbus::dont_run_event_loop_thread);
auto multiplyRes = proxy->multiply(INT64_VALUE, DOUBLE_VALUE);
ASSERT_THAT(multiplyRes, Eq(INT64_VALUE * DOUBLE_VALUE));
}

View File

@ -89,10 +89,22 @@ TEST_F(SdbusTestObject, EmitsSignalWithMapSuccesfully)
ASSERT_THAT(m_proxy->m_mapFromSignal[1], Eq("one"));
}
TEST_F(SdbusTestObject, EmitsSignalWithLargeMapSuccesfully)
{
std::map<int32_t, std::string> 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);
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"));
}
TEST_F(SdbusTestObject, EmitsSignalWithVariantSuccesfully)
{
double d = 3.14;
m_adaptor->emitSignalWithVariant(d);
m_adaptor->emitSignalWithVariant(sdbus::Variant{d});
ASSERT_TRUE(waitUntil(m_proxy->m_gotSignalWithVariant));
ASSERT_THAT(m_proxy->m_variantFromSignal, DoubleEq(d));

View File

@ -61,12 +61,9 @@ TEST_F(SdbusTestObject, PingsViaPeerInterface)
TEST_F(SdbusTestObject, AnswersMachineUuidViaPeerInterface)
{
// If /etc/machine-id does not exist in your system (which is very likely because you have
// a non-systemd Linux), org.freedesktop.DBus.Peer.GetMachineId() will not work. To solve
// this, you can create /etc/machine-id yourself as symlink to /var/lib/dbus/machine-id,
// and then org.freedesktop.DBus.Peer.GetMachineId() will start to work.
if (::access("/etc/machine-id", F_OK) == -1)
GTEST_SKIP() << "/etc/machine-id file does not exist, GetMachineId() will not work";
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());
}
@ -82,15 +79,66 @@ TEST_F(SdbusTestObject, GetsPropertyViaPropertiesInterface)
ASSERT_THAT(m_proxy->Get(INTERFACE_NAME, "state").get<std::string>(), Eq(DEFAULT_STATE_VALUE));
}
TEST_F(SdbusTestObject, GetsPropertyAsynchronouslyViaPropertiesInterface)
{
std::promise<std::string> promise;
auto future = promise.get_future();
m_proxy->GetAsync(INTERFACE_NAME, "state", [&](const sdbus::Error* err, sdbus::Variant value)
{
if (err == nullptr)
promise.set_value(value.get<std::string>());
else
promise.set_exception(std::make_exception_ptr(*err));
});
ASSERT_THAT(future.get(), Eq(DEFAULT_STATE_VALUE));
}
TEST_F(SdbusTestObject, GetsPropertyAsynchronouslyViaPropertiesInterfaceWithFuture)
{
auto future = m_proxy->GetAsync(INTERFACE_NAME, "state", sdbus::with_future);
ASSERT_THAT(future.get().get<std::string>(), Eq(DEFAULT_STATE_VALUE));
}
TEST_F(SdbusTestObject, SetsPropertyViaPropertiesInterface)
{
uint32_t newActionValue = 2345;
m_proxy->Set(INTERFACE_NAME, "action", newActionValue);
m_proxy->Set(INTERFACE_NAME, "action", sdbus::Variant{newActionValue});
ASSERT_THAT(m_proxy->action(), Eq(newActionValue));
}
TEST_F(SdbusTestObject, SetsPropertyAsynchronouslyViaPropertiesInterface)
{
uint32_t newActionValue = 2346;
std::promise<void> promise;
auto future = promise.get_future();
m_proxy->SetAsync(INTERFACE_NAME, "action", sdbus::Variant{newActionValue}, [&](const sdbus::Error* err)
{
if (err == nullptr)
promise.set_value();
else
promise.set_exception(std::make_exception_ptr(*err));
});
ASSERT_NO_THROW(future.get());
ASSERT_THAT(m_proxy->action(), Eq(newActionValue));
}
TEST_F(SdbusTestObject, SetsPropertyAsynchronouslyViaPropertiesInterfaceWithFuture)
{
uint32_t newActionValue = 2347;
auto future = m_proxy->SetAsync(INTERFACE_NAME, "action", sdbus::Variant{newActionValue}, sdbus::with_future);
ASSERT_NO_THROW(future.get());
ASSERT_THAT(m_proxy->action(), Eq(newActionValue));
}
TEST_F(SdbusTestObject, GetsAllPropertiesViaPropertiesInterface)
{
const auto properties = m_proxy->GetAll(INTERFACE_NAME);
@ -101,6 +149,38 @@ TEST_F(SdbusTestObject, GetsAllPropertiesViaPropertiesInterface)
EXPECT_THAT(properties.at("blocking").get<bool>(), Eq(DEFAULT_BLOCKING_VALUE));
}
TEST_F(SdbusTestObject, GetsAllPropertiesAsynchronouslyViaPropertiesInterface)
{
std::promise<std::map<std::string, sdbus::Variant>> promise;
auto future = promise.get_future();
m_proxy->GetAllAsync(INTERFACE_NAME, [&](const sdbus::Error* err, std::map<std::string, sdbus::Variant> value)
{
if (err == nullptr)
promise.set_value(std::move(value));
else
promise.set_exception(std::make_exception_ptr(*err));
});
const auto properties = future.get();
ASSERT_THAT(properties, SizeIs(3));
EXPECT_THAT(properties.at("state").get<std::string>(), Eq(DEFAULT_STATE_VALUE));
EXPECT_THAT(properties.at("action").get<uint32_t>(), Eq(DEFAULT_ACTION_VALUE));
EXPECT_THAT(properties.at("blocking").get<bool>(), Eq(DEFAULT_BLOCKING_VALUE));
}
TEST_F(SdbusTestObject, GetsAllPropertiesAsynchronouslyViaPropertiesInterfaceWithFuture)
{
auto future = m_proxy->GetAllAsync(INTERFACE_NAME, sdbus::with_future);
auto properties = future.get();
ASSERT_THAT(properties, SizeIs(3));
EXPECT_THAT(properties.at("state").get<std::string>(), Eq(DEFAULT_STATE_VALUE));
EXPECT_THAT(properties.at("action").get<uint32_t>(), Eq(DEFAULT_ACTION_VALUE));
EXPECT_THAT(properties.at("blocking").get<bool>(), Eq(DEFAULT_BLOCKING_VALUE));
}
TEST_F(SdbusTestObject, EmitsPropertyChangedSignalForSelectedProperties)
{
std::atomic<bool> signalReceived{false};
@ -201,7 +281,13 @@ TEST_F(SdbusTestObject, EmitsInterfacesAddedSignalForAllObjectInterfaces)
, const std::map<std::string, std::map<std::string, sdbus::Variant>>& interfacesAndProperties )
{
EXPECT_THAT(objectPath, Eq(OBJECT_PATH));
#if LIBSYSTEMD_VERSION<=250
EXPECT_THAT(interfacesAndProperties, SizeIs(5)); // INTERFACE_NAME + 4 standard interfaces
#else
// Since systemd v251, ObjectManager standard interface is not listed among the interfaces
// if the object does not have object manager functionality explicitly enabled.
EXPECT_THAT(interfacesAndProperties, SizeIs(4)); // INTERFACE_NAME + 3 standard interfaces
#endif
#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));
@ -248,7 +334,13 @@ TEST_F(SdbusTestObject, EmitsInterfacesRemovedSignalForAllObjectInterfaces)
, const std::vector<std::string>& interfaces )
{
EXPECT_THAT(objectPath, Eq(OBJECT_PATH));
#if LIBSYSTEMD_VERSION<=250
ASSERT_THAT(interfaces, SizeIs(5)); // INTERFACE_NAME + 4 standard interfaces
#else
// Since systemd v251, ObjectManager standard interface is not listed among the interfaces
// if the object does not have object manager functionality explicitly enabled.
ASSERT_THAT(interfaces, SizeIs(4)); // INTERFACE_NAME + 3 standard interfaces
#endif
signalReceived = true;
};

View File

@ -30,14 +30,17 @@
#include "sdbus-c++/Types.h"
#include <chrono>
#include <ostream>
#include <filesystem>
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 std::string DIRECT_CONNECTION_SOCKET_PATH{std::filesystem::temp_directory_path() / "sdbus-cpp-direct-connection-test"};
constexpr const uint8_t UINT8_VALUE{1};
constexpr const int16_t INT16_VALUE{21};

View File

@ -30,7 +30,7 @@
#include <atomic>
namespace sdbus { namespace test {
TestAdaptor::TestAdaptor(sdbus::IConnection& connection, const std::string& path) :
AdaptorInterfaces(connection, path)
{
@ -77,7 +77,7 @@ std::vector<int16_t> TestAdaptor::getInts16FromStruct(const sdbus::Struct<uint8_
sdbus::Variant TestAdaptor::processVariant(const sdbus::Variant& v)
{
sdbus::Variant res = static_cast<int32_t>(v.get<double>());
sdbus::Variant res{static_cast<int32_t>(v.get<double>())};
return res;
}
@ -93,7 +93,7 @@ std::map<int32_t, sdbus::Variant> TestAdaptor::getMapOfVariants(const std::vecto
sdbus::Struct<std::string, sdbus::Struct<std::map<int32_t, int32_t>>> TestAdaptor::getStructInStruct()
{
return sdbus::make_struct(STRING_VALUE, sdbus::make_struct(std::map<int32_t, int32_t>{{INT32_VALUE, INT32_VALUE}}));
return sdbus::Struct{STRING_VALUE, sdbus::Struct{std::map<int32_t, int32_t>{{INT32_VALUE, INT32_VALUE}}}};
}
int32_t TestAdaptor::sumStructItems(const sdbus::Struct<uint8_t, uint16_t>& a, const sdbus::Struct<int32_t, int64_t>& b)
@ -104,7 +104,7 @@ int32_t TestAdaptor::sumStructItems(const sdbus::Struct<uint8_t, uint16_t>& a, c
return res;
}
uint32_t TestAdaptor::sumVectorItems(const std::vector<uint16_t>& a, const std::vector<uint64_t>& b)
uint32_t TestAdaptor::sumArrayItems(const std::vector<uint16_t>& a, const std::array<uint64_t, 3>& b)
{
uint32_t res{0};
for (auto x : a)
@ -162,9 +162,9 @@ sdbus::UnixFd TestAdaptor::getUnixFd()
return sdbus::UnixFd{UNIX_FD_VALUE};
}
std::map<uint64_t, sdbus::Struct<std::map<uint8_t, std::vector<sdbus::Struct<sdbus::ObjectPath, bool, sdbus::Variant, std::map<int32_t, std::string>>>>, sdbus::Signature, std::string>> TestAdaptor::getComplex()
std::unordered_map<uint64_t, sdbus::Struct<std::map<uint8_t, std::vector<sdbus::Struct<sdbus::ObjectPath, bool, sdbus::Variant, std::map<int32_t, std::string>>>>, sdbus::Signature, std::string>> TestAdaptor::getComplex()
{
return { // map
return { // unordered_map
{
0, // uint_64_t
{ // struct
@ -390,7 +390,7 @@ R"delimiter(
<arg type="(ix)" direction="in"/>
<arg type="i" direction="out"/>
</method>
<method name="sumVectorItems">
<method name="sumArrayItems">
<arg type="aq" direction="in"/>
<arg type="at" direction="in"/>
<arg type="u" direction="out"/>

View File

@ -70,13 +70,13 @@ protected:
std::map<int32_t, sdbus::Variant> getMapOfVariants(const std::vector<int32_t>& x, const sdbus::Struct<sdbus::Variant, sdbus::Variant>& y) override;
sdbus::Struct<std::string, sdbus::Struct<std::map<int32_t, int32_t>>> getStructInStruct() override;
int32_t sumStructItems(const sdbus::Struct<uint8_t, uint16_t>& arg0, const sdbus::Struct<int32_t, int64_t>& arg1) override;
uint32_t sumVectorItems(const std::vector<uint16_t>& arg0, const std::vector<uint64_t>& arg1) override;
uint32_t sumArrayItems(const std::vector<uint16_t>& arg0, const std::array<uint64_t, 3>& arg1) override;
uint32_t doOperation(const uint32_t& arg0) override;
void doOperationAsync(sdbus::Result<uint32_t>&& result, uint32_t arg0) override;
sdbus::Signature getSignature() override;
sdbus::ObjectPath getObjPath() override;
sdbus::UnixFd getUnixFd() override;
std::map<uint64_t, sdbus::Struct<std::map<uint8_t, std::vector<sdbus::Struct<sdbus::ObjectPath, bool, sdbus::Variant, std::map<int32_t, std::string>>>>, sdbus::Signature, std::string>> getComplex() override;
std::unordered_map<uint64_t, sdbus::Struct<std::map<uint8_t, std::vector<sdbus::Struct<sdbus::ObjectPath, bool, sdbus::Variant, std::map<int32_t, std::string>>>>, sdbus::Signature, std::string>> getComplex() override;
void throwError() override;
void throwErrorWithNoReply() override;
void doPrivilegedStuff() override;
@ -109,6 +109,43 @@ public: // for tests
std::string m_propertySetSender;
};
class DummyTestAdaptor final : public sdbus::AdaptorInterfaces< org::sdbuscpp::integrationtests_adaptor
, sdbus::Properties_adaptor
, sdbus::ManagedObject_adaptor >
{
public:
DummyTestAdaptor(sdbus::IConnection& connection, const std::string& path) : AdaptorInterfaces(connection, path) {}
protected:
void noArgNoReturn() override {}
int32_t getInt() override { return {}; }
std::tuple<uint32_t, std::string> getTuple() override { return {}; }
double multiply(const int64_t&, const double&) override { return {}; }
void multiplyWithNoReply(const int64_t&, const double&) override {}
std::vector<int16_t> getInts16FromStruct(const sdbus::Struct<uint8_t, int16_t, double, std::string, std::vector<int16_t>>&) override { return {}; }
sdbus::Variant processVariant(const sdbus::Variant&) override { return {}; }
std::map<int32_t, sdbus::Variant> getMapOfVariants(const std::vector<int32_t>&, const sdbus::Struct<sdbus::Variant, sdbus::Variant>&) override { return {}; }
sdbus::Struct<std::string, sdbus::Struct<std::map<int32_t, int32_t>>> getStructInStruct() override { return {}; }
int32_t sumStructItems(const sdbus::Struct<uint8_t, uint16_t>&, const sdbus::Struct<int32_t, int64_t>&) override { return {}; }
uint32_t sumArrayItems(const std::vector<uint16_t>&, const std::array<uint64_t, 3>&) override { return {}; }
uint32_t doOperation(const uint32_t&) override { return {}; }
void doOperationAsync(sdbus::Result<uint32_t>&&, uint32_t) override {}
sdbus::Signature getSignature() override { return {}; }
sdbus::ObjectPath getObjPath() override { return {}; }
sdbus::UnixFd getUnixFd() override { return {}; }
std::unordered_map<uint64_t, sdbus::Struct<std::map<uint8_t, std::vector<sdbus::Struct<sdbus::ObjectPath, bool, sdbus::Variant, std::map<int32_t, std::string>>>>, sdbus::Signature, std::string>> getComplex() override { return {}; }
void throwError() override {}
void throwErrorWithNoReply() override {}
void doPrivilegedStuff() override {}
void emitTwoSimpleSignals() override {}
uint32_t action() override { return {}; }
void action(const uint32_t&) override {}
bool blocking() override { return {}; }
void blocking(const bool&) override {}
std::string state() override { return {}; }
};
}}
#endif /* INTEGRATIONTESTS_TESTADAPTOR_H_ */

View File

@ -27,7 +27,7 @@
#include "TestFixture.h"
namespace sdbus { namespace test {
std::unique_ptr<sdbus::IConnection> TestFixture::s_adaptorConnection = sdbus::createSystemBusConnection();
std::unique_ptr<sdbus::IConnection> TestFixture::s_proxyConnection = sdbus::createSystemBusConnection();

View File

@ -39,6 +39,11 @@
#include <atomic>
#include <chrono>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
namespace sdbus { namespace test {
class TestFixture : public ::testing::Test
@ -59,28 +64,6 @@ public:
s_proxyConnection->leaveEventLoop();
}
template <typename _Fnc>
static bool waitUntil(_Fnc&& fnc, std::chrono::milliseconds timeout = std::chrono::seconds(5))
{
using namespace std::chrono_literals;
std::chrono::milliseconds elapsed{};
std::chrono::milliseconds step{5ms};
do {
std::this_thread::sleep_for(step);
elapsed += step;
if (elapsed > timeout)
return false;
} while (!fnc());
return true;
}
static bool waitUntil(std::atomic<bool>& flag, std::chrono::milliseconds timeout = std::chrono::seconds(5))
{
return waitUntil([&flag]() -> bool { return flag; }, timeout);
}
private:
void SetUp() override
{
@ -106,6 +89,101 @@ public:
std::unique_ptr<TestProxy> m_proxy;
};
class TestFixtureWithDirectConnection : public ::testing::Test
{
private:
void SetUp() override
{
int sock = openUnixSocket();
createClientAndServerConnections(sock);
createAdaptorAndProxyObjects();
}
void TearDown() override
{
m_proxy.reset();
m_adaptor.reset();
m_proxyConnection->leaveEventLoop();
m_adaptorConnection->leaveEventLoop();
}
static int openUnixSocket()
{
int sock = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
assert(sock >= 0);
sockaddr_un sa;
memset(&sa, 0, sizeof(sa));
sa.sun_family = AF_UNIX;
snprintf(sa.sun_path, sizeof(sa.sun_path), "%s", DIRECT_CONNECTION_SOCKET_PATH.c_str());
unlink(DIRECT_CONNECTION_SOCKET_PATH.c_str());
umask(0000);
[[maybe_unused]] int r = bind(sock, (const sockaddr*) &sa, sizeof(sa.sun_path));
assert(r >= 0);
r = listen(sock, 5);
assert(r >= 0);
return sock;
}
void createClientAndServerConnections(int sock)
{
std::thread t([&]()
{
auto fd = accept4(sock, NULL, NULL, /*SOCK_NONBLOCK|*/SOCK_CLOEXEC);
m_adaptorConnection = sdbus::createServerBus(fd);
// This is necessary so that createDirectBusConnection() below does not block
m_adaptorConnection->enterEventLoopAsync();
});
m_proxyConnection = sdbus::createDirectBusConnection("unix:path=" + DIRECT_CONNECTION_SOCKET_PATH);
m_proxyConnection->enterEventLoopAsync();
t.join();
}
void createAdaptorAndProxyObjects()
{
assert(m_adaptorConnection != nullptr);
assert(m_proxyConnection != nullptr);
m_adaptor = std::make_unique<TestAdaptor>(*m_adaptorConnection, OBJECT_PATH);
// Destination parameter can be empty in case of direct connections
m_proxy = std::make_unique<TestProxy>(*m_proxyConnection, EMPTY_DESTINATION, OBJECT_PATH);
}
public:
std::unique_ptr<sdbus::IConnection> m_adaptorConnection;
std::unique_ptr<sdbus::IConnection> m_proxyConnection;
std::unique_ptr<TestAdaptor> m_adaptor;
std::unique_ptr<TestProxy> m_proxy;
};
template <typename _Fnc>
inline bool waitUntil(_Fnc&& fnc, std::chrono::milliseconds timeout = std::chrono::seconds(5))
{
using namespace std::chrono_literals;
std::chrono::milliseconds elapsed{};
std::chrono::milliseconds step{5ms};
do {
std::this_thread::sleep_for(step);
elapsed += step;
if (elapsed > timeout)
return false;
} while (!fnc());
return true;
}
inline bool waitUntil(std::atomic<bool>& flag, std::chrono::milliseconds timeout = std::chrono::seconds(5))
{
return waitUntil([&flag]() -> bool { return flag; }, timeout);
}
}}
#endif /* SDBUS_CPP_INTEGRATIONTESTS_TESTFIXTURE_H_ */

View File

@ -30,7 +30,7 @@
#include <atomic>
namespace sdbus { namespace test {
TestProxy::TestProxy(std::string destination, std::string objectPath)
: ProxyInterfaces(std::move(destination), std::move(objectPath))
{
@ -39,6 +39,13 @@ TestProxy::TestProxy(std::string destination, std::string objectPath)
registerProxy();
}
TestProxy::TestProxy(std::string destination, std::string 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)
: ProxyInterfaces(connection, std::move(destination), std::move(objectPath))
{
@ -116,6 +123,22 @@ sdbus::PendingAsyncCall TestProxy::doOperationClientSideAsync(uint32_t param)
});
}
std::future<uint32_t> TestProxy::doOperationClientSideAsync(uint32_t param, with_future_t)
{
return getProxy().callMethodAsync("doOperation")
.onInterface(sdbus::test::INTERFACE_NAME)
.withArguments(param)
.getResultAsFuture<uint32_t>();
}
std::future<MethodReply> TestProxy::doOperationClientSideAsyncOnBasicAPILevel(uint32_t param)
{
auto methodCall = getProxy().createMethodCall(sdbus::test::INTERFACE_NAME, "doOperation");
methodCall << param;
return getProxy().callMethod(methodCall, sdbus::with_future);
}
void TestProxy::doErroneousOperationClientSideAsync()
{
getProxy().callMethodAsync("throwError")
@ -126,6 +149,13 @@ void TestProxy::doErroneousOperationClientSideAsync()
});
}
std::future<void> TestProxy::doErroneousOperationClientSideAsync(with_future_t)
{
return getProxy().callMethodAsync("throwError")
.onInterface(sdbus::test::INTERFACE_NAME)
.getResultAsFuture<>();
}
void TestProxy::doOperationClientSideAsyncWithTimeout(const std::chrono::microseconds &timeout, uint32_t param)
{
using namespace std::chrono_literals;

View File

@ -32,6 +32,7 @@
#include <thread>
#include <chrono>
#include <atomic>
#include <future>
namespace sdbus { namespace test {
@ -73,6 +74,7 @@ class TestProxy final : public sdbus::ProxyInterfaces< org::sdbuscpp::integratio
{
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();
@ -93,6 +95,9 @@ public:
void installDoOperationClientSideAsyncReplyHandler(std::function<void(uint32_t res, const sdbus::Error* err)> handler);
uint32_t doOperationWithTimeout(const std::chrono::microseconds &timeout, uint32_t param);
sdbus::PendingAsyncCall doOperationClientSideAsync(uint32_t param);
std::future<uint32_t> doOperationClientSideAsync(uint32_t param, with_future_t);
std::future<MethodReply> doOperationClientSideAsyncOnBasicAPILevel(uint32_t param);
std::future<void> doErroneousOperationClientSideAsync(with_future_t);
void doErroneousOperationClientSideAsync();
void doOperationClientSideAsyncWithTimeout(const std::chrono::microseconds &timeout, uint32_t param);
int32_t callNonexistentMethod();
@ -117,6 +122,29 @@ public: // for tests
std::string m_signalMemberName;
};
class DummyTestProxy final : public sdbus::ProxyInterfaces< org::sdbuscpp::integrationtests_proxy
, sdbus::Peer_proxy
, sdbus::Introspectable_proxy
, sdbus::Properties_proxy >
{
public:
DummyTestProxy(std::string destination, std::string objectPath)
: ProxyInterfaces(destination, objectPath)
{
}
protected:
void onSimpleSignal() override {}
void onSignalWithMap(const std::map<int32_t, std::string>&) override {}
void onSignalWithVariant(const sdbus::Variant&) override {}
void onSignalWithoutRegistration(const sdbus::Struct<std::string, sdbus::Struct<sdbus::Signature>>&) {}
void onDoOperationReply(uint32_t, const sdbus::Error*) {}
// Signals of standard D-Bus interfaces
void onPropertiesChanged( const std::string&, const std::map<std::string, sdbus::Variant>&, const std::vector<std::string>& ) override {}
};
}}
#endif /* SDBUS_CPP_INTEGRATIONTESTS_TESTPROXY_H_ */

View File

@ -20,54 +20,59 @@ public:
protected:
integrationtests_adaptor(sdbus::IObject& object)
: object_(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<uint8_t, int16_t, double, std::string, std::vector<int16_t>>& arg0){ return this->getInts16FromStruct(arg0); });
object_.registerMethod("processVariant").onInterface(INTERFACE_NAME).withInputParamNames("variant").withOutputParamNames("result").implementedAs([this](const sdbus::Variant& variant){ return this->processVariant(variant); });
object_.registerMethod("getMapOfVariants").onInterface(INTERFACE_NAME).withInputParamNames("x", "y").withOutputParamNames("aMapOfVariants").implementedAs([this](const std::vector<int32_t>& x, const sdbus::Struct<sdbus::Variant, sdbus::Variant>& 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<uint8_t, uint16_t>& arg0, const sdbus::Struct<int32_t, int64_t>& arg1){ return this->sumStructItems(arg0, arg1); });
object_.registerMethod("sumVectorItems").onInterface(INTERFACE_NAME).withInputParamNames("arg0", "arg1").withOutputParamNames("arg0").implementedAs([this](const std::vector<uint16_t>& arg0, const std::vector<uint64_t>& arg1){ return this->sumVectorItems(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<uint32_t>&& 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<std::map<int32_t, std::string>>("aMap");
object_.registerSignal("signalWithVariant").onInterface(INTERFACE_NAME).withParameters<sdbus::Variant>("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);
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<uint8_t, int16_t, double, std::string, std::vector<int16_t>>& arg0){ return this->getInts16FromStruct(arg0); });
object_->registerMethod("processVariant").onInterface(INTERFACE_NAME).withInputParamNames("variant").withOutputParamNames("result").implementedAs([this](const sdbus::Variant& variant){ return this->processVariant(variant); });
object_->registerMethod("getMapOfVariants").onInterface(INTERFACE_NAME).withInputParamNames("x", "y").withOutputParamNames("aMapOfVariants").implementedAs([this](const std::vector<int32_t>& x, const sdbus::Struct<sdbus::Variant, sdbus::Variant>& 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<uint8_t, uint16_t>& arg0, const sdbus::Struct<int32_t, int64_t>& arg1){ return this->sumStructItems(arg0, arg1); });
object_->registerMethod("sumArrayItems").onInterface(INTERFACE_NAME).withInputParamNames("arg0", "arg1").withOutputParamNames("arg0").implementedAs([this](const std::vector<uint16_t>& arg0, const std::array<uint64_t, 3>& 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<uint32_t>&& 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<std::map<int32_t, std::string>>("aMap");
object_->registerSignal("signalWithVariant").onInterface(INTERFACE_NAME).withParameters<sdbus::Variant>("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;
integrationtests_adaptor& operator=(const integrationtests_adaptor&) = delete;
integrationtests_adaptor(integrationtests_adaptor&&) = default;
integrationtests_adaptor& operator=(integrationtests_adaptor&&) = default;
~integrationtests_adaptor() = default;
public:
void emitSimpleSignal()
{
object_.emitSignal("simpleSignal").onInterface(INTERFACE_NAME);
object_->emitSignal("simpleSignal").onInterface(INTERFACE_NAME);
}
void emitSignalWithMap(const std::map<int32_t, std::string>& aMap)
{
object_.emitSignal("signalWithMap").onInterface(INTERFACE_NAME).withArguments(aMap);
object_->emitSignal("signalWithMap").onInterface(INTERFACE_NAME).withArguments(aMap);
}
void emitSignalWithVariant(const sdbus::Variant& aVariant)
{
object_.emitSignal("signalWithVariant").onInterface(INTERFACE_NAME).withArguments(aVariant);
object_->emitSignal("signalWithVariant").onInterface(INTERFACE_NAME).withArguments(aVariant);
}
private:
@ -81,13 +86,13 @@ private:
virtual std::map<int32_t, sdbus::Variant> getMapOfVariants(const std::vector<int32_t>& x, const sdbus::Struct<sdbus::Variant, sdbus::Variant>& y) = 0;
virtual sdbus::Struct<std::string, sdbus::Struct<std::map<int32_t, int32_t>>> getStructInStruct() = 0;
virtual int32_t sumStructItems(const sdbus::Struct<uint8_t, uint16_t>& arg0, const sdbus::Struct<int32_t, int64_t>& arg1) = 0;
virtual uint32_t sumVectorItems(const std::vector<uint16_t>& arg0, const std::vector<uint64_t>& arg1) = 0;
virtual uint32_t sumArrayItems(const std::vector<uint16_t>& arg0, const std::array<uint64_t, 3>& arg1) = 0;
virtual uint32_t doOperation(const uint32_t& arg0) = 0;
virtual void doOperationAsync(sdbus::Result<uint32_t>&& result, uint32_t arg0) = 0;
virtual sdbus::Signature getSignature() = 0;
virtual sdbus::ObjectPath getObjPath() = 0;
virtual sdbus::UnixFd getUnixFd() = 0;
virtual std::map<uint64_t, sdbus::Struct<std::map<uint8_t, std::vector<sdbus::Struct<sdbus::ObjectPath, bool, sdbus::Variant, std::map<int32_t, std::string>>>>, sdbus::Signature, std::string>> getComplex() = 0;
virtual std::unordered_map<uint64_t, sdbus::Struct<std::map<uint8_t, std::vector<sdbus::Struct<sdbus::ObjectPath, bool, sdbus::Variant, std::map<int32_t, std::string>>>>, sdbus::Signature, std::string>> getComplex() = 0;
virtual void throwError() = 0;
virtual void throwErrorWithNoReply() = 0;
virtual void doPrivilegedStuff() = 0;
@ -101,7 +106,7 @@ private:
virtual std::string state() = 0;
private:
sdbus::IObject& object_;
sdbus::IObject* object_;
};
}} // namespaces

View File

@ -20,13 +20,18 @@ public:
protected:
integrationtests_proxy(sdbus::IProxy& proxy)
: proxy_(proxy)
: proxy_(&proxy)
{
proxy_.uponSignal("simpleSignal").onInterface(INTERFACE_NAME).call([this](){ this->onSimpleSignal(); });
proxy_.uponSignal("signalWithMap").onInterface(INTERFACE_NAME).call([this](const std::map<int32_t, std::string>& aMap){ this->onSignalWithMap(aMap); });
proxy_.uponSignal("signalWithVariant").onInterface(INTERFACE_NAME).call([this](const sdbus::Variant& aVariant){ this->onSignalWithVariant(aVariant); });
proxy_->uponSignal("simpleSignal").onInterface(INTERFACE_NAME).call([this](){ this->onSimpleSignal(); });
proxy_->uponSignal("signalWithMap").onInterface(INTERFACE_NAME).call([this](const std::map<int32_t, std::string>& 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;
integrationtests_proxy& operator=(const integrationtests_proxy&) = delete;
integrationtests_proxy(integrationtests_proxy&&) = default;
integrationtests_proxy& operator=(integrationtests_proxy&&) = default;
~integrationtests_proxy() = default;
virtual void onSimpleSignal() = 0;
@ -36,178 +41,178 @@ protected:
public:
void noArgNoReturn()
{
proxy_.callMethod("noArgNoReturn").onInterface(INTERFACE_NAME);
proxy_->callMethod("noArgNoReturn").onInterface(INTERFACE_NAME);
}
int32_t getInt()
{
int32_t result;
proxy_.callMethod("getInt").onInterface(INTERFACE_NAME).storeResultsTo(result);
proxy_->callMethod("getInt").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result;
}
std::tuple<uint32_t, std::string> getTuple()
{
std::tuple<uint32_t, std::string> result;
proxy_.callMethod("getTuple").onInterface(INTERFACE_NAME).storeResultsTo(result);
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);
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();
proxy_->callMethod("multiplyWithNoReply").onInterface(INTERFACE_NAME).withArguments(a, b).dontExpectReply();
}
std::vector<int16_t> getInts16FromStruct(const sdbus::Struct<uint8_t, int16_t, double, std::string, std::vector<int16_t>>& arg0)
{
std::vector<int16_t> result;
proxy_.callMethod("getInts16FromStruct").onInterface(INTERFACE_NAME).withArguments(arg0).storeResultsTo(result);
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);
proxy_->callMethod("processVariant").onInterface(INTERFACE_NAME).withArguments(variant).storeResultsTo(result);
return result;
}
std::map<int32_t, sdbus::Variant> getMapOfVariants(const std::vector<int32_t>& x, const sdbus::Struct<sdbus::Variant, sdbus::Variant>& y)
{
std::map<int32_t, sdbus::Variant> result;
proxy_.callMethod("getMapOfVariants").onInterface(INTERFACE_NAME).withArguments(x, y).storeResultsTo(result);
proxy_->callMethod("getMapOfVariants").onInterface(INTERFACE_NAME).withArguments(x, y).storeResultsTo(result);
return result;
}
sdbus::Struct<std::string, sdbus::Struct<std::map<int32_t, int32_t>>> getStructInStruct()
{
sdbus::Struct<std::string, sdbus::Struct<std::map<int32_t, int32_t>>> result;
proxy_.callMethod("getStructInStruct").onInterface(INTERFACE_NAME).storeResultsTo(result);
proxy_->callMethod("getStructInStruct").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result;
}
int32_t sumStructItems(const sdbus::Struct<uint8_t, uint16_t>& arg0, const sdbus::Struct<int32_t, int64_t>& arg1)
{
int32_t result;
proxy_.callMethod("sumStructItems").onInterface(INTERFACE_NAME).withArguments(arg0, arg1).storeResultsTo(result);
proxy_->callMethod("sumStructItems").onInterface(INTERFACE_NAME).withArguments(arg0, arg1).storeResultsTo(result);
return result;
}
uint32_t sumVectorItems(const std::vector<uint16_t>& arg0, const std::vector<uint64_t>& arg1)
uint32_t sumArrayItems(const std::vector<uint16_t>& arg0, const std::array<uint64_t, 3>& arg1)
{
uint32_t result;
proxy_.callMethod("sumVectorItems").onInterface(INTERFACE_NAME).withArguments(arg0, arg1).storeResultsTo(result);
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);
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);
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);
proxy_->callMethod("getSignature").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result;
}
sdbus::ObjectPath getObjPath()
{
sdbus::ObjectPath result;
proxy_.callMethod("getObjPath").onInterface(INTERFACE_NAME).storeResultsTo(result);
proxy_->callMethod("getObjPath").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result;
}
sdbus::UnixFd getUnixFd()
{
sdbus::UnixFd result;
proxy_.callMethod("getUnixFd").onInterface(INTERFACE_NAME).storeResultsTo(result);
proxy_->callMethod("getUnixFd").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result;
}
std::map<uint64_t, sdbus::Struct<std::map<uint8_t, std::vector<sdbus::Struct<sdbus::ObjectPath, bool, sdbus::Variant, std::map<int32_t, std::string>>>>, sdbus::Signature, std::string>> getComplex()
std::map<uint64_t, sdbus::Struct<std::map<uint8_t, std::vector<sdbus::Struct<sdbus::ObjectPath, bool, sdbus::Variant, std::unordered_map<int32_t, std::string>>>>, sdbus::Signature, std::string>> getComplex()
{
std::map<uint64_t, sdbus::Struct<std::map<uint8_t, std::vector<sdbus::Struct<sdbus::ObjectPath, bool, sdbus::Variant, std::map<int32_t, std::string>>>>, sdbus::Signature, std::string>> result;
proxy_.callMethod("getComplex").onInterface(INTERFACE_NAME).storeResultsTo(result);
std::map<uint64_t, sdbus::Struct<std::map<uint8_t, std::vector<sdbus::Struct<sdbus::ObjectPath, bool, sdbus::Variant, std::unordered_map<int32_t, std::string>>>>, sdbus::Signature, std::string>> result;
proxy_->callMethod("getComplex").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result;
}
void throwError()
{
proxy_.callMethod("throwError").onInterface(INTERFACE_NAME);
proxy_->callMethod("throwError").onInterface(INTERFACE_NAME);
}
void throwErrorWithNoReply()
{
proxy_.callMethod("throwErrorWithNoReply").onInterface(INTERFACE_NAME).dontExpectReply();
proxy_->callMethod("throwErrorWithNoReply").onInterface(INTERFACE_NAME).dontExpectReply();
}
void doPrivilegedStuff()
{
proxy_.callMethod("doPrivilegedStuff").onInterface(INTERFACE_NAME);
proxy_->callMethod("doPrivilegedStuff").onInterface(INTERFACE_NAME);
}
void emitTwoSimpleSignals()
{
proxy_.callMethod("emitTwoSimpleSignals").onInterface(INTERFACE_NAME);
proxy_->callMethod("emitTwoSimpleSignals").onInterface(INTERFACE_NAME);
}
void unregisterSimpleSignalHandler()
{
proxy_.muteSignal("simpleSignal").onInterface(INTERFACE_NAME);
proxy_->muteSignal("simpleSignal").onInterface(INTERFACE_NAME);
}
void reRegisterSimpleSignalHandler()
{
proxy_.uponSignal("simpleSignal").onInterface(INTERFACE_NAME).call([this](){ this->onSimpleSignal(); });
proxy_.finishRegistration();
proxy_->uponSignal("simpleSignal").onInterface(INTERFACE_NAME).call([this](){ this->onSimpleSignal(); });
proxy_->finishRegistration();
}
public:
uint32_t action()
{
return proxy_.getProperty("action").onInterface(INTERFACE_NAME);
return proxy_->getProperty("action").onInterface(INTERFACE_NAME);
}
void action(const uint32_t& value)
{
proxy_.setProperty("action").onInterface(INTERFACE_NAME).toValue(value);
proxy_->setProperty("action").onInterface(INTERFACE_NAME).toValue(value);
}
bool blocking()
{
return proxy_.getProperty("blocking").onInterface(INTERFACE_NAME);
return proxy_->getProperty("blocking").onInterface(INTERFACE_NAME);
}
void blocking(const bool& value)
{
proxy_.setProperty("blocking").onInterface(INTERFACE_NAME).toValue(value);
proxy_->setProperty("blocking").onInterface(INTERFACE_NAME).toValue(value);
}
std::string state()
{
return proxy_.getProperty("state").onInterface(INTERFACE_NAME);
return proxy_->getProperty("state").onInterface(INTERFACE_NAME);
}
private:
sdbus::IProxy& proxy_;
sdbus::IProxy* proxy_;
};
}} // namespaces

View File

@ -45,7 +45,7 @@
<arg type="(ix)" direction="in" />
<arg type="i" direction="out" />
</method>
<method name="sumVectorItems">
<method name="sumArrayItems">
<arg type="aq" direction="in" />
<arg type="at" direction="in" />
<arg type="u" direction="out" />
@ -82,7 +82,7 @@
</method>
<method name="emitTwoSimpleSignals">
</method>
<signal name="simpleSignal">
<annotation name="org.freedesktop.DBus.Deprecated" value="true" />
</signal>
@ -102,4 +102,4 @@
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
</property>
</interface>
</node>
</node>

View File

@ -20,19 +20,24 @@ public:
protected:
perftests_adaptor(sdbus::IObject& object)
: object_(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<std::string>("data");
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<std::string>("data");
}
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() = default;
public:
void emitDataSignal(const std::string& data)
{
object_.emitSignal("dataSignal").onInterface(INTERFACE_NAME).withArguments(data);
object_->emitSignal("dataSignal").onInterface(INTERFACE_NAME).withArguments(data);
}
private:
@ -40,7 +45,7 @@ private:
virtual std::string concatenateTwoStrings(const std::string& string1, const std::string& string2) = 0;
private:
sdbus::IObject& object_;
sdbus::IObject* object_;
};
}} // namespaces

View File

@ -20,11 +20,16 @@ public:
protected:
perftests_proxy(sdbus::IProxy& proxy)
: proxy_(proxy)
: proxy_(&proxy)
{
proxy_.uponSignal("dataSignal").onInterface(INTERFACE_NAME).call([this](const std::string& data){ this->onDataSignal(data); });
proxy_->uponSignal("dataSignal").onInterface(INTERFACE_NAME).call([this](const std::string& data){ this->onDataSignal(data); });
}
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() = default;
virtual void onDataSignal(const std::string& data) = 0;
@ -32,18 +37,18 @@ protected:
public:
void sendDataSignals(const uint32_t& numberOfSignals, const uint32_t& signalMsgSize)
{
proxy_.callMethod("sendDataSignals").onInterface(INTERFACE_NAME).withArguments(numberOfSignals, signalMsgSize);
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);
proxy_->callMethod("concatenateTwoStrings").onInterface(INTERFACE_NAME).withArguments(string1, string2).storeResultsTo(result);
return result;
}
private:
sdbus::IProxy& proxy_;
sdbus::IProxy* proxy_;
};
}} // namespaces

View File

@ -22,18 +22,23 @@ public:
protected:
thermometer_adaptor(sdbus::IObject& object)
: object_(object)
: object_(&object)
{
object_.registerMethod("getCurrentTemperature").onInterface(INTERFACE_NAME).withOutputParamNames("result").implementedAs([this](){ return this->getCurrentTemperature(); });
object_->registerMethod("getCurrentTemperature").onInterface(INTERFACE_NAME).withOutputParamNames("result").implementedAs([this](){ return this->getCurrentTemperature(); });
}
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() = default;
private:
virtual uint32_t getCurrentTemperature() = 0;
private:
sdbus::IObject& object_;
sdbus::IObject* object_;
};
}}}} // namespaces

View File

@ -22,22 +22,27 @@ public:
protected:
thermometer_proxy(sdbus::IProxy& proxy)
: proxy_(proxy)
: 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() = default;
public:
uint32_t getCurrentTemperature()
{
uint32_t result;
proxy_.callMethod("getCurrentTemperature").onInterface(INTERFACE_NAME).storeResultsTo(result);
proxy_->callMethod("getCurrentTemperature").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result;
}
private:
sdbus::IProxy& proxy_;
sdbus::IProxy* proxy_;
};
}}}} // namespaces

View File

@ -21,25 +21,30 @@ public:
protected:
concatenator_adaptor(sdbus::IObject& object)
: object_(object)
: object_(&object)
{
object_.registerMethod("concatenate").onInterface(INTERFACE_NAME).withInputParamNames("params").withOutputParamNames("result").implementedAs([this](sdbus::Result<std::string>&& result, std::map<std::string, sdbus::Variant> params){ this->concatenate(std::move(result), std::move(params)); });
object_.registerSignal("concatenatedSignal").onInterface(INTERFACE_NAME).withParameters<std::string>("concatenatedString");
object_->registerMethod("concatenate").onInterface(INTERFACE_NAME).withInputParamNames("params").withOutputParamNames("result").implementedAs([this](sdbus::Result<std::string>&& result, std::map<std::string, sdbus::Variant> params){ this->concatenate(std::move(result), std::move(params)); });
object_->registerSignal("concatenatedSignal").onInterface(INTERFACE_NAME).withParameters<std::string>("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() = default;
public:
void emitConcatenatedSignal(const std::string& concatenatedString)
{
object_.emitSignal("concatenatedSignal").onInterface(INTERFACE_NAME).withArguments(concatenatedString);
object_->emitSignal("concatenatedSignal").onInterface(INTERFACE_NAME).withArguments(concatenatedString);
}
private:
virtual void concatenate(sdbus::Result<std::string>&& result, std::map<std::string, sdbus::Variant> params) = 0;
private:
sdbus::IObject& object_;
sdbus::IObject* object_;
};
}}} // namespaces

View File

@ -21,11 +21,16 @@ public:
protected:
concatenator_proxy(sdbus::IProxy& proxy)
: proxy_(proxy)
: proxy_(&proxy)
{
proxy_.uponSignal("concatenatedSignal").onInterface(INTERFACE_NAME).call([this](const std::string& concatenatedString){ this->onConcatenatedSignal(concatenatedString); });
proxy_->uponSignal("concatenatedSignal").onInterface(INTERFACE_NAME).call([this](const std::string& concatenatedString){ this->onConcatenatedSignal(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() = default;
virtual void onConcatenatedSignal(const std::string& concatenatedString) = 0;
@ -35,11 +40,11 @@ protected:
public:
sdbus::PendingAsyncCall concatenate(const std::map<std::string, sdbus::Variant>& 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](const sdbus::Error* error, const std::string& result){ this->onConcatenateReply(result, error); });
}
private:
sdbus::IProxy& proxy_;
sdbus::IProxy* proxy_;
};
}}} // namespaces

View File

@ -22,18 +22,23 @@ public:
protected:
thermometer_adaptor(sdbus::IObject& object)
: object_(object)
: object_(&object)
{
object_.registerMethod("getCurrentTemperature").onInterface(INTERFACE_NAME).withOutputParamNames("result").implementedAs([this](){ return this->getCurrentTemperature(); });
object_->registerMethod("getCurrentTemperature").onInterface(INTERFACE_NAME).withOutputParamNames("result").implementedAs([this](){ return this->getCurrentTemperature(); });
}
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() = default;
private:
virtual uint32_t getCurrentTemperature() = 0;
private:
sdbus::IObject& object_;
sdbus::IObject* object_;
};
}}}} // namespaces
@ -51,12 +56,17 @@ public:
protected:
factory_adaptor(sdbus::IObject& object)
: object_(object)
: object_(&object)
{
object_.registerMethod("createDelegateObject").onInterface(INTERFACE_NAME).withOutputParamNames("delegate").implementedAs([this](sdbus::Result<sdbus::ObjectPath>&& 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();
object_->registerMethod("createDelegateObject").onInterface(INTERFACE_NAME).withOutputParamNames("delegate").implementedAs([this](sdbus::Result<sdbus::ObjectPath>&& 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;
factory_adaptor& operator=(const factory_adaptor&) = delete;
factory_adaptor(factory_adaptor&&) = default;
factory_adaptor& operator=(factory_adaptor&&) = default;
~factory_adaptor() = default;
private:
@ -64,7 +74,7 @@ private:
virtual void destroyDelegateObject(sdbus::Result<>&& result, sdbus::ObjectPath delegate) = 0;
private:
sdbus::IObject& object_;
sdbus::IObject* object_;
};
}}}}} // namespaces

View File

@ -22,22 +22,27 @@ public:
protected:
thermometer_proxy(sdbus::IProxy& proxy)
: proxy_(proxy)
: 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() = default;
public:
uint32_t getCurrentTemperature()
{
uint32_t result;
proxy_.callMethod("getCurrentTemperature").onInterface(INTERFACE_NAME).storeResultsTo(result);
proxy_->callMethod("getCurrentTemperature").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result;
}
private:
sdbus::IProxy& proxy_;
sdbus::IProxy* proxy_;
};
}}}} // namespaces
@ -55,27 +60,32 @@ public:
protected:
factory_proxy(sdbus::IProxy& proxy)
: proxy_(proxy)
: 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() = default;
public:
sdbus::ObjectPath createDelegateObject()
{
sdbus::ObjectPath result;
proxy_.callMethod("createDelegateObject").onInterface(INTERFACE_NAME).storeResultsTo(result);
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();
proxy_->callMethod("destroyDelegateObject").onInterface(INTERFACE_NAME).withArguments(delegate).dontExpectReply();
}
private:
sdbus::IProxy& proxy_;
sdbus::IProxy* proxy_;
};
}}}}} // namespaces

View File

@ -12,7 +12,7 @@
<allow send_destination="org.sdbuscpp.stresstests.service1"/>
<allow send_interface="org.sdbuscpp.stresstests.service1"/>
</policy>
<policy context="default">
<allow own="org.sdbuscpp.stresstests.service2"/>
<allow send_destination="org.sdbuscpp.stresstests.service2"/>

View File

@ -427,8 +427,8 @@ int main(int argc, char *argv[])
while (!stopClients)
{
std::map<std::string, sdbus::Variant> param;
param["key1"] = "sdbus-c++-stress-tests";
param["key2"] = ++localCounter;
param["key1"] = sdbus::Variant{"sdbus-c++-stress-tests"};
param["key2"] = sdbus::Variant{++localCounter};
concatenator.concatenate(param);
@ -509,6 +509,6 @@ int main(int argc, char *argv[])
exitLogger = true;
loggerThread.join();
return 0;
}

View File

@ -29,6 +29,7 @@
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <cstdint>
#include <list>
using ::testing::Eq;
using ::testing::Gt;
@ -45,6 +46,82 @@ namespace
}
}
namespace sdbus {
template <typename _ElementType>
sdbus::Message& operator<<(sdbus::Message& msg, const std::list<_ElementType>& items)
{
msg.openContainer(sdbus::signature_of<_ElementType>::str());
for (const auto& item : items)
msg << item;
msg.closeContainer();
return msg;
}
template <typename _ElementType>
sdbus::Message& operator>>(sdbus::Message& msg, std::list<_ElementType>& items)
{
if(!msg.enterContainer(sdbus::signature_of<_ElementType>::str()))
return msg;
while (true)
{
_ElementType elem;
if (msg >> elem)
items.emplace_back(std::move(elem));
else
break;
}
msg.clearFlags();
msg.exitContainer();
return msg;
}
}
template <typename _Element, typename _Allocator>
struct sdbus::signature_of<std::list<_Element, _Allocator>>
: sdbus::signature_of<std::vector<_Element, _Allocator>>
{};
namespace my {
struct Struct
{
int i;
std::string s;
std::list<double> l;
};
bool operator==(const Struct& lhs, const Struct& rhs)
{
return lhs.i == rhs.i && lhs.s == rhs.s && lhs.l == rhs.l;
}
sdbus::Message& operator<<(sdbus::Message& msg, const Struct& items)
{
return msg << sdbus::Struct{std::forward_as_tuple(items.i, items.s, items.l)};
}
sdbus::Message& operator>>(sdbus::Message& msg, Struct& items)
{
sdbus::Struct s{std::forward_as_tuple(items.i, items.s, items.l)};
return msg >> s;
}
}
template <>
struct sdbus::signature_of<my::Struct>
: sdbus::signature_of<sdbus::Struct<int, std::string, std::list<double>>>
{};
/*-------------------------------------*/
/* -- TEST CASES -- */
/*-------------------------------------*/
@ -116,7 +193,7 @@ TEST(AMessage, CanCarryASimpleInteger)
{
auto msg = sdbus::createPlainMessage();
int dataWritten = 5;
const int dataWritten = 5;
msg << dataWritten;
msg.seal();
@ -131,7 +208,7 @@ TEST(AMessage, CanCarryAUnixFd)
{
auto msg = sdbus::createPlainMessage();
sdbus::UnixFd dataWritten{0};
const sdbus::UnixFd dataWritten{0};
msg << dataWritten;
msg.seal();
@ -146,7 +223,7 @@ TEST(AMessage, CanCarryAVariant)
{
auto msg = sdbus::createPlainMessage();
auto dataWritten = sdbus::Variant((double)3.14);
const auto dataWritten = sdbus::Variant((double)3.14);
msg << dataWritten;
msg.seal();
@ -161,8 +238,8 @@ TEST(AMessage, CanCarryACollectionOfEmbeddedVariants)
{
auto msg = sdbus::createPlainMessage();
auto value = std::vector<sdbus::Variant>{"hello"s, (double)3.14};
auto dataWritten = sdbus::Variant{value};
std::vector<sdbus::Variant> value{sdbus::Variant{"hello"s}, sdbus::Variant{(double)3.14}};
const auto dataWritten = sdbus::Variant{value};
msg << dataWritten;
msg.seal();
@ -174,11 +251,11 @@ TEST(AMessage, CanCarryACollectionOfEmbeddedVariants)
ASSERT_THAT(dataRead.get<decltype(value)>()[1].get<double>(), Eq(value[1].get<double>()));
}
TEST(AMessage, CanCarryAnArray)
TEST(AMessage, CanCarryDBusArrayOfTrivialTypesGivenAsStdVector)
{
auto msg = sdbus::createPlainMessage();
std::vector<int64_t> dataWritten{3545342, 43643532, 324325};
const std::vector<int64_t> dataWritten{3545342, 43643532, 324325};
msg << dataWritten;
msg.seal();
@ -189,6 +266,116 @@ TEST(AMessage, CanCarryAnArray)
ASSERT_THAT(dataRead, Eq(dataWritten));
}
TEST(AMessage, CanCarryDBusArrayOfNontrivialTypesGivenAsStdVector)
{
auto msg = sdbus::createPlainMessage();
const std::vector<sdbus::Signature> dataWritten{"s", "u", "b"};
msg << dataWritten;
msg.seal();
std::vector<sdbus::Signature> dataRead;
msg >> dataRead;
ASSERT_THAT(dataRead, Eq(dataWritten));
}
TEST(AMessage, CanCarryDBusArrayOfTrivialTypesGivenAsStdArray)
{
auto msg = sdbus::createPlainMessage();
const std::array<int, 3> dataWritten{3545342, 43643532, 324325};
msg << dataWritten;
msg.seal();
std::array<int, 3> dataRead;
msg >> dataRead;
ASSERT_THAT(dataRead, Eq(dataWritten));
}
TEST(AMessage, CanCarryDBusArrayOfNontrivialTypesGivenAsStdArray)
{
auto msg = sdbus::createPlainMessage();
const std::array<sdbus::Signature, 3> dataWritten{"s", "u", "b"};
msg << dataWritten;
msg.seal();
std::array<sdbus::Signature, 3> dataRead;
msg >> dataRead;
ASSERT_THAT(dataRead, Eq(dataWritten));
}
#if __cplusplus >= 202002L
TEST(AMessage, CanCarryDBusArrayOfTrivialTypesGivenAsStdSpan)
{
auto msg = sdbus::createPlainMessage();
const std::array<int, 3> sourceArray{3545342, 43643532, 324325};
const std::span dataWritten{sourceArray};
msg << dataWritten;
msg.seal();
std::array<int, 3> destinationArray;
std::span dataRead{destinationArray};
msg >> dataRead;
ASSERT_THAT(std::vector(dataRead.begin(), dataRead.end()), Eq(std::vector(dataWritten.begin(), dataWritten.end())));
}
TEST(AMessage, CanCarryDBusArrayOfNontrivialTypesGivenAsStdSpan)
{
auto msg = sdbus::createPlainMessage();
const std::array<sdbus::Signature, 3> sourceArray{"s", "u", "b"};
const std::span dataWritten{sourceArray};
msg << dataWritten;
msg.seal();
std::array<sdbus::Signature, 3> destinationArray;
std::span dataRead{destinationArray};
msg >> dataRead;
ASSERT_THAT(std::vector(dataRead.begin(), dataRead.end()), Eq(std::vector(dataWritten.begin(), dataWritten.end())));
}
#endif
TEST(AMessage, ThrowsWhenDestinationStdArrayIsTooSmallDuringDeserialization)
{
auto msg = sdbus::createPlainMessage();
const std::vector<int> dataWritten{3545342, 43643532, 324325, 89789, 15343};
msg << dataWritten;
msg.seal();
std::array<int, 3> dataRead;
ASSERT_THROW(msg >> dataRead, sdbus::Error);
}
#if __cplusplus >= 202002L
TEST(AMessage, ThrowsWhenDestinationStdSpanIsTooSmallDuringDeserialization)
{
auto msg = sdbus::createPlainMessage();
const std::array<int, 3> dataWritten{3545342, 43643532, 324325};
msg << dataWritten;
msg.seal();
std::array<int, 2> destinationArray;
std::span dataRead{destinationArray};
ASSERT_THROW(msg >> dataRead, sdbus::Error);
}
#endif
TEST(AMessage, CanCarryADictionary)
{
auto msg = sdbus::createPlainMessage();
@ -264,3 +451,35 @@ TEST(AMessage, CanPeekContainerContents)
ASSERT_THAT(type, "a");
ASSERT_THAT(contents, "{is}");
}
TEST(AMessage, CanCarryDBusArrayGivenAsCustomType)
{
auto msg = sdbus::createPlainMessage();
const std::list<int64_t> dataWritten{3545342, 43643532, 324325};
//custom::MyType t;
msg << dataWritten;
// msg << t;
msg.seal();
std::list<int64_t> dataRead;
msg >> dataRead;
ASSERT_THAT(dataRead, Eq(dataWritten));
}
TEST(AMessage, CanCarryDBusStructGivenAsCustomType)
{
auto msg = sdbus::createPlainMessage();
const my::Struct dataWritten{3545342, "hello"s, {3.14, 2.4568546}};
msg << dataWritten;
msg.seal();
my::Struct dataRead;
msg >> dataRead;
ASSERT_THAT(dataRead, Eq(dataWritten));
}

View File

@ -77,7 +77,12 @@ namespace
TYPE(sdbus::Struct<bool>)HAS_DBUS_TYPE_SIGNATURE("(b)")
TYPE(sdbus::Struct<uint16_t, double, std::string, sdbus::Variant>)HAS_DBUS_TYPE_SIGNATURE("(qdsv)")
TYPE(std::vector<int16_t>)HAS_DBUS_TYPE_SIGNATURE("an")
TYPE(std::array<int16_t, 3>)HAS_DBUS_TYPE_SIGNATURE("an")
#if __cplusplus >= 202002L
TYPE(std::span<int16_t>)HAS_DBUS_TYPE_SIGNATURE("ao")
#endif
TYPE(std::map<int32_t, int64_t>)HAS_DBUS_TYPE_SIGNATURE("a{ix}")
TYPE(std::unordered_map<int32_t, int64_t>)HAS_DBUS_TYPE_SIGNATURE("a{ix}")
using ComplexType = std::map<
uint64_t,
sdbus::Struct<
@ -86,9 +91,10 @@ namespace
std::vector<
sdbus::Struct<
sdbus::ObjectPath,
std::array<int16_t, 3>,
bool,
sdbus::Variant,
std::map<int, std::string>
std::unordered_map<int, std::string>
>
>
>,
@ -97,7 +103,7 @@ namespace
const char*
>
>;
TYPE(ComplexType)HAS_DBUS_TYPE_SIGNATURE("a{t(a{ya(obva{is})}ghs)}")
TYPE(ComplexType)HAS_DBUS_TYPE_SIGNATURE("a{t(a{ya(oanbva{is})}ghs)}")
typedef ::testing::Types< bool
, uint8_t
@ -117,7 +123,12 @@ namespace
, sdbus::Struct<bool>
, sdbus::Struct<uint16_t, double, std::string, sdbus::Variant>
, std::vector<int16_t>
, std::array<int16_t, 3>
#if __cplusplus >= 202002L
, std::span<int16_t>
#endif
, std::map<int32_t, int64_t>
, std::unordered_map<int32_t, int64_t>
, ComplexType
> DBusSupportedTypes;

View File

@ -67,7 +67,7 @@ TEST(AVariant, CanBeConstructedFromASimpleValue)
TEST(AVariant, CanBeConstructedFromAComplexValue)
{
using ComplexType = std::map<uint64_t, std::vector<sdbus::Struct<std::string, double>>>;
ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{sdbus::make_struct("hello"s, ANY_DOUBLE), sdbus::make_struct("world"s, ANY_DOUBLE)}} };
ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{{"hello"s, ANY_DOUBLE}, {"world"s, ANY_DOUBLE}}} };
ASSERT_NO_THROW(sdbus::Variant{value});
}
@ -103,7 +103,7 @@ TEST(ASimpleVariant, ReturnsTheSimpleValueWhenAsked)
TEST(AComplexVariant, ReturnsTheComplexValueWhenAsked)
{
using ComplexType = std::map<uint64_t, std::vector<sdbus::Struct<std::string, double>>>;
ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{sdbus::make_struct("hello"s, ANY_DOUBLE), sdbus::make_struct("world"s, ANY_DOUBLE)}} };
ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{{"hello"s, ANY_DOUBLE}, {"world"s, ANY_DOUBLE}}} };
sdbus::Variant variant(value);
@ -123,7 +123,7 @@ TEST(AVariant, HasConceptuallyNonmutableGetMethodWhichCanBeCalledXTimes)
TEST(AVariant, ReturnsTrueWhenAskedIfItContainsTheTypeItReallyContains)
{
using ComplexType = std::map<uint64_t, std::vector<sdbus::Struct<std::string, double>>>;
ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{sdbus::make_struct("hello"s, ANY_DOUBLE), sdbus::make_struct("world"s, ANY_DOUBLE)}} };
ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{{"hello"s, ANY_DOUBLE}, {"world"s, ANY_DOUBLE}}} };
sdbus::Variant variant(value);
@ -143,8 +143,8 @@ TEST(AVariant, CanContainOtherEmbeddedVariants)
{
using TypeWithVariants = std::vector<sdbus::Struct<sdbus::Variant, double>>;
TypeWithVariants value;
value.emplace_back(sdbus::make_struct(sdbus::Variant("a string"), ANY_DOUBLE));
value.emplace_back(sdbus::make_struct(sdbus::Variant(ANY_UINT64), ANY_DOUBLE));
value.push_back({sdbus::Variant("a string"), ANY_DOUBLE});
value.push_back({sdbus::Variant(ANY_UINT64), ANY_DOUBLE});
sdbus::Variant variant(value);
@ -172,7 +172,7 @@ TEST(AnEmptyVariant, ThrowsWhenBeingSerializedToAMessage)
TEST(ANonEmptyVariant, SerializesToAndDeserializesFromAMessageSuccessfully)
{
using ComplexType = std::map<uint64_t, std::vector<sdbus::Struct<std::string, double>>>;
ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{sdbus::make_struct("hello"s, ANY_DOUBLE), sdbus::make_struct("world"s, ANY_DOUBLE)}} };
ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{{"hello"s, ANY_DOUBLE}, {"world"s, ANY_DOUBLE}}} };
sdbus::Variant variant(value);
auto msg = sdbus::createPlainMessage();
@ -187,7 +187,7 @@ TEST(ANonEmptyVariant, SerializesToAndDeserializesFromAMessageSuccessfully)
TEST(CopiesOfVariant, SerializeToAndDeserializeFromMessageSuccessfully)
{
using ComplexType = std::map<uint64_t, std::vector<sdbus::Struct<std::string, double>>>;
ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{sdbus::make_struct("hello"s, ANY_DOUBLE), sdbus::make_struct("world"s, ANY_DOUBLE)}} };
ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{{"hello"s, ANY_DOUBLE}, {"world"s, ANY_DOUBLE}}} };
sdbus::Variant variant(value);
auto variantCopy1{variant};
auto variantCopy2 = variant;
@ -207,15 +207,41 @@ TEST(CopiesOfVariant, SerializeToAndDeserializeFromMessageSuccessfully)
ASSERT_THAT(receivedVariant3.get<decltype(value)>(), Eq(value));
}
TEST(AStruct, CreatesStructFromTuple)
TEST(AStruct, CanBeCreatedFromStdTuple)
{
std::tuple<int32_t, std::string> value{1234, "abcd"};
sdbus::Struct<int32_t, std::string> valueStruct{value};
sdbus::Struct valueStruct{value};
ASSERT_THAT(valueStruct.get<0>(), Eq(std::get<0>(value)));
ASSERT_THAT(valueStruct.get<1>(), Eq(std::get<1>(value)));
}
TEST(AStruct, CanProvideItsDataThroughStdGet)
{
std::tuple<int32_t, std::string> value{1234, "abcd"};
sdbus::Struct valueStruct{value};
ASSERT_THAT(std::get<0>(valueStruct), Eq(std::get<0>(value)));
ASSERT_THAT(std::get<1>(valueStruct), Eq(std::get<1>(value)));
}
TEST(AStruct, CanBeUsedLikeStdTupleType)
{
using StructType = sdbus::Struct<int, std::string, bool>;
static_assert(std::tuple_size_v<StructType> == 3);
static_assert(std::is_same_v<std::tuple_element_t<1, StructType>, std::string>);
}
TEST(AStruct, CanBeUsedInStructuredBinding)
{
sdbus::Struct valueStruct(1234, "abcd", true);
auto [first, second, third] = valueStruct;
ASSERT_THAT(std::tie(first, second, third), Eq(std::tuple{1234, "abcd", true}));
}
TEST(AnObjectPath, CanBeConstructedFromCString)
{
const char* aPath = "/some/path";

View File

@ -62,12 +62,16 @@ public:
MOCK_METHOD1(sd_bus_open_user, int(sd_bus **ret));
MOCK_METHOD2(sd_bus_open_user_with_address, int(sd_bus **ret, const char* address));
MOCK_METHOD2(sd_bus_open_system_remote, int(sd_bus **ret, const char *host));
MOCK_METHOD2(sd_bus_open_direct, int(sd_bus **ret, const char* address));
MOCK_METHOD2(sd_bus_open_direct, int(sd_bus **ret, int fd));
MOCK_METHOD2(sd_bus_open_server, int(sd_bus **ret, int fd));
MOCK_METHOD3(sd_bus_request_name, int(sd_bus *bus, const char *name, uint64_t flags));
MOCK_METHOD2(sd_bus_release_name, int(sd_bus *bus, const char *name));
MOCK_METHOD2(sd_bus_get_unique_name, int(sd_bus *bus, const char **name));
MOCK_METHOD6(sd_bus_add_object_vtable, int(sd_bus *bus, sd_bus_slot **slot, const char *path, const char *interface, const sd_bus_vtable *vtable, void *userdata));
MOCK_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_METHOD1(sd_bus_slot_unref, sd_bus_slot*(sd_bus_slot *slot));
MOCK_METHOD1(sd_bus_new, int(sd_bus **ret));

View File

@ -4,7 +4,7 @@
cmake_minimum_required(VERSION 3.5)
project(sdbus-c++-tools VERSION 1.2.0)
project(sdbus-c++-tools VERSION 1.5.0)
include(GNUInstallDirs)

View File

@ -85,7 +85,7 @@ 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;
Nodes methods = interface["method"];
Nodes signals = interface["signal"];
@ -111,7 +111,7 @@ std::string AdaptorGenerator::processInterface(Node& interface) const
if(!annotationRegistration.empty())
{
std::stringstream str;
str << tab << tab << "object_.setInterfaceFlags(INTERFACE_NAME)" << annotationRegistration << ";" << endl;
str << tab << tab << "object_->setInterfaceFlags(INTERFACE_NAME)" << annotationRegistration << ";" << endl;
annotationRegistration = str.str();
}
@ -131,6 +131,12 @@ std::string AdaptorGenerator::processInterface(Node& interface) const
<< propertyRegistration
<< 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;
if (!signalMethods.empty())
@ -149,7 +155,7 @@ std::string AdaptorGenerator::processInterface(Node& interface) const
}
body << "private:" << endl
<< tab << "sdbus::IObject& object_;" << endl
<< tab << "sdbus::IObject* object_;" << endl
<< "};" << endl << endl
<< std::string(namespacesCount, '}') << " // namespaces" << endl << endl;
@ -186,7 +192,7 @@ std::tuple<std::string, std::string> AdaptorGenerator::processMethods(const Node
}
else if (annotationName == "org.freedesktop.DBus.Method.Async")
{
if (annotationValue == "server" || annotationValue == "clientserver")
if (annotationValue == "server" || annotationValue == "clientserver" || annotationValue == "client-server")
async = true;
}
else if (annotationName == "org.freedesktop.systemd1.Privileged")
@ -211,7 +217,7 @@ std::tuple<std::string, std::string> AdaptorGenerator::processMethods(const Node
using namespace std::string_literals;
registrationSS << tab << tab << "object_.registerMethod(\""
registrationSS << tab << tab << "object_->registerMethod(\""
<< methodName << "\")"
<< ".onInterface(INTERFACE_NAME)"
<< (!argStringsStr.empty() ? (".withInputParamNames(" + argStringsStr + ")") : "")
@ -267,7 +273,7 @@ std::tuple<std::string, std::string> AdaptorGenerator::processSignals(const Node
std::tie(argStr, argTypeStr, typeStr, argStringsStr) = argsToNamesAndTypes(args);
signalRegistrationSS << tab << tab
<< "object_.registerSignal(\"" << name << "\")"
<< "object_->registerSignal(\"" << name << "\")"
".onInterface(INTERFACE_NAME)";
if (args.size() > 0)
@ -284,7 +290,7 @@ std::tuple<std::string, std::string> AdaptorGenerator::processSignals(const Node
signalMethodSS << tab << "void emit" << nameWithCapFirstLetter << "(" << argTypeStr << ")" << endl
<< tab << "{" << endl
<< tab << tab << "object_.emitSignal(\"" << name << "\")"
<< tab << tab << "object_->emitSignal(\"" << name << "\")"
".onInterface(INTERFACE_NAME)";
if (!argStr.empty())
@ -333,7 +339,7 @@ std::tuple<std::string, std::string> AdaptorGenerator::processProperties(const N
<< "Option '" << annotationName << "' not allowed or supported in this context! Option ignored..." << std::endl;
}
registrationSS << tab << tab << "object_.registerProperty(\""
registrationSS << tab << tab << "object_->registerProperty(\""
<< propertyName << "\")"
<< ".onInterface(INTERFACE_NAME)";

View File

@ -84,7 +84,7 @@ 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 << ": proxy_(&proxy)" << endl;
Nodes methods = interface["method"];
Nodes signals = interface["signal"];
@ -97,17 +97,29 @@ std::string ProxyGenerator::processInterface(Node& interface) const
<< registration
<< 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;
if (!declaration.empty())
body << declaration << endl;
std::string methodDefinitions, asyncDeclarations;
std::tie(methodDefinitions, asyncDeclarations) = processMethods(methods);
std::string methodDefinitions, asyncDeclarationsMethods;
std::tie(methodDefinitions, asyncDeclarationsMethods) = processMethods(methods);
std::string propertyDefinitions, asyncDeclarationsProperties;
std::tie(propertyDefinitions, asyncDeclarationsProperties) = processProperties(properties);
if (!asyncDeclarations.empty())
if (!asyncDeclarationsMethods.empty())
{
body << asyncDeclarations << endl;
body << asyncDeclarationsMethods << endl;
}
if (!asyncDeclarationsProperties.empty())
{
body << asyncDeclarationsProperties << endl;
}
if (!methodDefinitions.empty())
@ -115,14 +127,13 @@ std::string ProxyGenerator::processInterface(Node& interface) const
body << "public:" << endl << methodDefinitions;
}
std::string propertyDefinitions = processProperties(properties);
if (!propertyDefinitions.empty())
{
body << "public:" << endl << propertyDefinitions;
}
body << "private:" << endl
<< tab << "sdbus::IProxy& proxy_;" << endl
<< tab << "sdbus::IProxy* proxy_;" << endl
<< "};" << endl << endl
<< std::string(namespacesCount, '}') << " // namespaces" << endl << endl;
@ -145,19 +156,30 @@ std::tuple<std::string, std::string> ProxyGenerator::processMethods(const Nodes&
bool dontExpectReply{false};
bool async{false};
bool future{false}; // Async methods implemented by means of either std::future or callbacks
std::string timeoutValue;
std::smatch smTimeout;
Nodes annotations = (*method)["annotation"];
for (const auto& annotation : annotations)
{
if (annotation->get("name") == "org.freedesktop.DBus.Method.NoReply" && annotation->get("value") == "true")
const auto annotationName = annotation->get("name");
const auto annotationValue = annotation->get("value");
if (annotationName == "org.freedesktop.DBus.Method.NoReply" && annotationValue == "true")
dontExpectReply = true;
else if (annotation->get("name") == "org.freedesktop.DBus.Method.Async"
&& (annotation->get("value") == "client" || annotation->get("value") == "clientserver"))
async = true;
if (annotation->get("name") == "org.freedesktop.DBus.Method.Timeout")
timeoutValue = annotation->get("value");
else
{
if (annotationName == "org.freedesktop.DBus.Method.Async"
&& (annotationValue == "client" || annotationValue == "clientserver" || annotationValue == "client-server"))
async = true;
else if (annotationName == "org.freedesktop.DBus.Method.Async.ClientImpl" && annotationValue == "callback")
future = false;
else if (annotationName == "org.freedesktop.DBus.Method.Async.ClientImpl" && (annotationValue == "future" || annotationValue == "std::future"))
future = true;
}
if (annotationName == "org.freedesktop.DBus.Method.Timeout")
timeoutValue = annotationValue;
}
if (dontExpectReply && outArgs.size() > 0)
{
@ -180,12 +202,13 @@ std::tuple<std::string, std::string> ProxyGenerator::processMethods(const Nodes&
}
auto retType = outArgsToType(outArgs);
auto retTypeBare = outArgsToType(outArgs, true);
std::string inArgStr, inArgTypeStr;
std::tie(inArgStr, inArgTypeStr, std::ignore, std::ignore) = argsToNamesAndTypes(inArgs);
std::string outArgStr, outArgTypeStr;
std::tie(outArgStr, outArgTypeStr, std::ignore, std::ignore) = argsToNamesAndTypes(outArgs);
const std::string realRetType = (async && !dontExpectReply ? "sdbus::PendingAsyncCall" : async ? "void" : retType);
const std::string realRetType = (async && !dontExpectReply ? (future ? "std::future<" + retType + ">" : "sdbus::PendingAsyncCall") : async ? "void" : retType);
definitionSS << tab << realRetType << " " << nameSafe << "(" << inArgTypeStr << ")" << endl
<< tab << "{" << endl;
@ -200,7 +223,7 @@ std::tuple<std::string, std::string> ProxyGenerator::processMethods(const Nodes&
}
definitionSS << tab << tab << (async && !dontExpectReply ? "return " : "")
<< "proxy_.callMethod" << (async ? "Async" : "") << "(\"" << name << "\").onInterface(INTERFACE_NAME)";
<< "proxy_->callMethod" << (async ? "Async" : "") << "(\"" << name << "\").onInterface(INTERFACE_NAME)";
if (!timeoutValue.empty())
{
@ -219,11 +242,18 @@ std::tuple<std::string, std::string> ProxyGenerator::processMethods(const Nodes&
auto nameBigFirst = name;
nameBigFirst[0] = islower(nameBigFirst[0]) ? nameBigFirst[0] + 'A' - 'a' : nameBigFirst[0];
definitionSS << ".uponReplyInvoke([this](const sdbus::Error* error" << (outArgTypeStr.empty() ? "" : ", ") << outArgTypeStr << ")"
"{ this->on" << nameBigFirst << "Reply(" << outArgStr << (outArgStr.empty() ? "" : ", ") << "error); })";
if (future) // Async methods implemented through future
{
definitionSS << ".getResultAsFuture<" << retTypeBare << ">()";
}
else // Async methods implemented through callbacks
{
definitionSS << ".uponReplyInvoke([this](const sdbus::Error* error" << (outArgTypeStr.empty() ? "" : ", ") << outArgTypeStr << ")"
"{ this->on" << nameBigFirst << "Reply(" << outArgStr << (outArgStr.empty() ? "" : ", ") << "error); })";
asyncDeclarationSS << tab << "virtual void on" << nameBigFirst << "Reply("
<< outArgTypeStr << (outArgTypeStr.empty() ? "" : ", ") << "const sdbus::Error* error) = 0;" << endl;
asyncDeclarationSS << tab << "virtual void on" << nameBigFirst << "Reply("
<< outArgTypeStr << (outArgTypeStr.empty() ? "" : ", ") << "const sdbus::Error* error) = 0;" << endl;
}
}
else if (outArgs.size() > 0)
{
@ -257,7 +287,7 @@ std::tuple<std::string, std::string> ProxyGenerator::processSignals(const Nodes&
std::tie(argStr, argTypeStr, std::ignore, std::ignore) = argsToNamesAndTypes(args);
registrationSS << tab << tab << "proxy_"
".uponSignal(\"" << name << "\")"
"->uponSignal(\"" << name << "\")"
".onInterface(INTERFACE_NAME)"
".call([this](" << argTypeStr << ")"
"{ this->on" << nameBigFirst << "(" << argStr << "); });" << endl;
@ -268,9 +298,9 @@ std::tuple<std::string, std::string> ProxyGenerator::processSignals(const Nodes&
return std::make_tuple(registrationSS.str(), declarationSS.str());
}
std::string ProxyGenerator::processProperties(const Nodes& properties) const
std::tuple<std::string, std::string> ProxyGenerator::processProperties(const Nodes& properties) const
{
std::ostringstream propertySS;
std::ostringstream propertySS, asyncDeclarationSS;
for (const auto& property : properties)
{
auto propertyName = property->get("name");
@ -282,25 +312,93 @@ std::string ProxyGenerator::processProperties(const Nodes& properties) const
auto propertyArg = std::string("value");
auto propertyTypeArg = std::string("const ") + propertyType + "& " + propertyArg;
bool asyncGet{false};
bool futureGet{false}; // Async property getter implemented by means of either std::future or callbacks
bool asyncSet{false};
bool futureSet{false}; // Async property setter implemented by means of either std::future or callbacks
Nodes annotations = (*property)["annotation"];
for (const auto& annotation : annotations)
{
const auto annotationName = annotation->get("name");
const auto annotationValue = annotation->get("value");
if (annotationName == "org.freedesktop.DBus.Property.Get.Async" && annotationValue == "client") // Server-side not supported (may be in the future)
asyncGet = true;
else if (annotationName == "org.freedesktop.DBus.Property.Get.Async.ClientImpl" && annotationValue == "callback")
futureGet = false;
else if (annotationName == "org.freedesktop.DBus.Property.Get.Async.ClientImpl" && (annotationValue == "future" || annotationValue == "std::future"))
futureGet = true;
else if (annotationName == "org.freedesktop.DBus.Property.Set.Async" && annotationValue == "client") // Server-side not supported (may be in the future)
asyncSet = true;
else if (annotationName == "org.freedesktop.DBus.Property.Set.Async.ClientImpl" && annotationValue == "callback")
futureSet = false;
else if (annotationName == "org.freedesktop.DBus.Property.Set.Async.ClientImpl" && (annotationValue == "future" || annotationValue == "std::future"))
futureSet = true;
}
if (propertyAccess == "read" || propertyAccess == "readwrite")
{
propertySS << tab << propertyType << " " << propertyNameSafe << "()" << endl
const std::string realRetType = (asyncGet ? (futureGet ? "std::future<sdbus::Variant>" : "sdbus::PendingAsyncCall") : propertyType);
propertySS << tab << realRetType << " " << propertyNameSafe << "()" << endl
<< tab << "{" << endl;
propertySS << tab << tab << "return proxy_.getProperty(\"" << propertyName << "\")"
propertySS << tab << tab << "return proxy_->getProperty" << (asyncGet ? "Async" : "") << "(\"" << propertyName << "\")"
".onInterface(INTERFACE_NAME)";
if (asyncGet)
{
auto nameBigFirst = propertyName;
nameBigFirst[0] = islower(nameBigFirst[0]) ? nameBigFirst[0] + 'A' - 'a' : nameBigFirst[0];
if (futureGet) // Async methods implemented through future
{
propertySS << ".getResultAsFuture()";
}
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); })";
asyncDeclarationSS << tab << "virtual void on" << nameBigFirst << "PropertyGetReply("
<< "const " << propertyType << "& value, const sdbus::Error* error) = 0;" << endl;
}
}
propertySS << ";" << endl << tab << "}" << endl << endl;
}
if (propertyAccess == "readwrite" || propertyAccess == "write")
{
propertySS << tab << "void " << propertyNameSafe << "(" << propertyTypeArg << ")" << endl
<< tab << "{" << endl;
propertySS << tab << tab << "proxy_.setProperty(\"" << propertyName << "\")"
const std::string realRetType = (asyncSet ? (futureSet ? "std::future<void>" : "sdbus::PendingAsyncCall") : "void");
propertySS << tab << realRetType << " " << propertyNameSafe << "(" << propertyTypeArg << ")" << endl
<< tab << "{" << endl;
propertySS << tab << tab << (asyncSet ? "return " : "") << "proxy_->setProperty" << (asyncSet ? "Async" : "")
<< "(\"" << propertyName << "\")"
".onInterface(INTERFACE_NAME)"
".toValue(" << propertyArg << ")";
if (asyncSet)
{
auto nameBigFirst = propertyName;
nameBigFirst[0] = islower(nameBigFirst[0]) ? nameBigFirst[0] + 'A' - 'a' : nameBigFirst[0];
if (futureSet) // Async methods implemented through future
{
propertySS << ".getResultAsFuture()";
}
else // Async methods implemented through callbacks
{
propertySS << ".uponReplyInvoke([this](const sdbus::Error* error)"
"{ this->on" << nameBigFirst << "PropertySetReply(error); })";
asyncDeclarationSS << tab << "virtual void on" << nameBigFirst << "PropertySetReply("
<< "const sdbus::Error* error) = 0;" << endl;
}
}
propertySS << ";" << endl << tab << "}" << endl << endl;
}
}
return propertySS.str();
return std::make_tuple(propertySS.str(), asyncDeclarationSS.str());
}

View File

@ -73,7 +73,7 @@ private:
* @param properties
* @return source code
*/
std::string processProperties(const sdbuscpp::xml::Nodes& properties) const;
std::tuple<std::string, std::string> processProperties(const sdbuscpp::xml::Nodes& properties) const;
};

View File

@ -87,7 +87,7 @@ static void _parse_signature(const std::string &signature, std::string &type, un
}
case '\0':
{
std::cerr <<
std::cerr <<
"Invalid array definition. Type is missing after '" << signature
<< "'."
<< std::endl;