Compare commits

...

101 Commits

Author SHA1 Message Date
62a546c9d3 Bump revision up to 0.6.0 2019-04-15 21:05:54 +02:00
5b99658f36 Turn on compiler warnings 2019-04-14 20:56:47 +02:00
d3749741d1 Add preliminary ChangeLog 2019-04-13 21:38:22 +02:00
1d44d8b37f Add loops in stress tests to test adaptor/proxy initialization/deinitialization 2019-04-13 21:28:43 +02:00
e3a74a3ff2 Add unregister function to IObject and IProxy API 2019-04-13 21:17:37 +02:00
99160156fe Fix all gcc warnings 2019-04-12 22:39:14 +02:00
ee30375cfc Use official release of googletest (v1.8.1) rather than master 2019-04-12 21:42:33 +02:00
06ca6539f3 Make sure googletest is always built as a static lib and never installed as part of sdbus-c++ 2019-04-12 21:30:07 +02:00
ed0745c83a Only install doxy docs if they were generated 2019-04-12 20:27:54 +02:00
93b6e5237a Clean up some names (rename classes, methods, files)
* ConvenienceClasses.h/.inl/.cpp -> ConvenienceApiClasses.h/.inl/.cpp
  * IObjectProxy class -> IProxy
  * Interfaces class -> AdaptorInterfaces
  * Interfaces.h -> split into AdaptorInterfaces.h and ProxyInterfaces.h
  * createObjectProxy() method -> createProxy()
2019-04-09 21:28:07 +02:00
e7c78460cf Bump revision up to 0.5.3 2019-04-09 20:56:33 +02:00
f5da0dabcb Fix race condition between worker threads and adaptor destructor in stress tests 2019-04-08 21:16:32 +02:00
c9ef1849cd Add two missing headers in test/CMakeLists.txt 2019-04-07 21:16:47 +02:00
d154022205 Extend stress tests with dynamic object creation and destruction in multiple threads 2019-04-04 20:39:31 +02:00
94fd3c88d8 Add getConnection() method to IObject so we ask Object about its connection 2019-04-04 20:39:03 +02:00
a919058d13 Bump up revision to 0.5.2 2019-04-03 00:17:31 +02:00
08945acbc4 Simplify and unify callback design for both sync and async methods 2019-04-03 00:05:20 +02:00
5673a9bcf2 Update section on async D-Bus methods in sdbus-c++ tutorial 2019-03-30 10:10:55 +01:00
b46fb170ea Bump up revision to 0.5.1 2019-03-29 22:28:58 +01:00
878ce6fa5c Update doxygen documentation as well as tutorial 2019-03-29 22:23:25 +01:00
461ac241c8 Introduce doxygen documentation 2019-03-29 21:50:08 +01:00
a5692c08ea Rename sdbus-c++ stub code generator to more consistent sdbus-c++-xml2cpp 2019-03-28 19:15:48 +01:00
4fd2479b06 Don't build tests by default 2019-03-28 19:11:37 +01:00
581e849534 Update README: Add documentation of sdbus-c++ CMake flags 2019-03-28 19:10:30 +01:00
63637b639f Make CMakeLists.txt cleaner and more flexible 2019-03-28 18:47:49 +01:00
fc60700e1b Rename test executables for consistency 2019-03-28 18:25:31 +01:00
a04ab9f445 Bump revision up to 0.5.0, a big step in maturing sdbus-c++ 2019-03-28 16:08:58 +01:00
1c4abab3e4 Remove executable bit erroneously set on source files 2019-03-27 17:53:31 +01:00
d489eee9c0 Revise and update the tutorial for redesigned sdbus-c++ parts 2019-03-27 17:34:41 +01:00
cbf2218301 Remove unnecessary forward declarations from Message.h 2019-03-27 14:41:30 +01:00
6f79c5bf14 Add stress tests for sdbus-c++ 2019-03-26 08:59:50 +01:00
7c968e78cb Fix missing <algorithm> include for std::generate_n in performance tests 2019-03-25 20:30:37 +01:00
fd7be39dd4 Re-design sdbus-c++ approach to connections (#47)
Fixes #33 , among others
2019-03-25 16:28:31 +01:00
26c6ea8730 Fix gcc 6.3 issue in Connection unit test 2019-03-25 16:08:43 +01:00
663df31398 Introduce support for asynchronous D-Bus method calls on the client side (#42)
Fixes #32
2019-03-25 14:45:48 +01:00
004f158817 Bump up revision to 0.4.3 2019-03-24 22:18:48 +01:00
ab407aa8c8 Fix interface names and object paths in integration tests 2019-03-24 20:18:29 +01:00
bb2bf5811b Add SdBus interface to proper namespace 2019-03-20 18:52:05 +01:00
41a10d644f Make code a bit cleaner and more consistent 2019-03-19 20:11:18 +01:00
b9ce1ca3ce Remove unnecessary copy-construction when making SdBus 2019-03-18 21:28:17 +01:00
850e211dca Merge pull request #39 from ardazishvili/testing
Add separation layer from sd-bus to improve isolation in unit testing
2019-03-18 21:05:07 +01:00
2b83d7ca2d Mock sdbus lib, add unit tests of Connection class.
Introduce mock of sdbus library through extracting its interface. Set up unit tests of Connection class through injection of sdbusMock to constructor. Clients of Connections class should use fabrics instead.
2019-03-17 18:02:47 +03:00
8c76e3ef8b Bump up revision to 0.4.2 2019-03-15 12:09:55 +01:00
a6d0b62ff5 Add sdbus-c++ performance tests (#41)
* Introduce simple method call and signal-based manual performance tests

* Put perftests in proper place

* Remove unnecessary CMakeLists file
2019-03-15 11:34:25 +01:00
757cc44381 Merge pull request #40 from Kistler-Group/generate-config-version-file
cmake: generate config version file
2019-03-15 11:26:37 +01:00
6dbcbbfa98 cmake: generate config version file 2019-03-14 11:08:42 +01:00
b813680192 Allow ObjectPath and Signature be created from std::string 2019-02-19 08:57:24 +01:00
84b15776a3 Add systemd and dbus configuration, e.g. to run samples in 'Using sdbus-c++ library' 2019-02-04 21:22:49 +01:00
8c5c774727 Minor fix 2019-01-25 20:02:58 +01:00
cd1efd66a5 Add essential information to doxy comments of ProxyInterfaces constructors 2019-01-25 19:59:09 +01:00
fad81e7659 Be more clear on the different behavior of proxy factory overloads 2019-01-25 19:50:06 +01:00
1dafd6262c Add essential information to doxy comments of createObjectProxy 2019-01-25 19:24:20 +01:00
0b27f222c4 Fix stub generator C++ standard back to 14 2019-01-16 20:55:17 +01:00
58895d2730 Bump revision up to 0.4.1 2019-01-16 20:01:05 +01:00
d957948274 Transform constexpr member to a getter method because of different odr-usage rules in different compilers 2019-01-16 19:58:26 +01:00
47fad7dd63 Remove obsolete autotools stuff from the tutorial 2019-01-10 13:59:05 +01:00
7378cea833 Bump up project version 2019-01-10 13:54:02 +01:00
2526546432 Remove Eclipse project file 2019-01-10 13:52:35 +01:00
9c0e98c580 Introduce support for some common D-Bus annotations (#30)
* Add ability to declare property behavior on PropertyChanged signal

* Add support for Method.NoReply annotation (WIP)

* Add support for common annotations/flags
2019-01-10 08:47:59 +01:00
2c78e08d19 Remove warnings-related compiler options from CMakeLists (#31) 2019-01-06 22:16:13 +01:00
97372155a6 Update ChangeLog for v0.3.3 2018-12-29 01:02:36 +01:00
1def4e247a Add note on thread-safety of Variant and its const methods 2018-12-29 00:59:31 +01:00
d764afec93 Little fixes in tutorial code samples
Fixes #24
2018-12-24 15:43:01 +01:00
eb58d2fa52 - Rewind Message prior to access for peekValueType.
Fixes #8

(cherry picked from commit f8bed4b0faa2c0a2bc7037f3a55105060d56dbdb)
2018-12-24 15:30:55 +01:00
a6bb8c070e Switch from autotools to CMake build system (#23)
* Switch from autotools to CMake

* CMake: require at least cmake 3.8

* cmake: updates for tests
2018-09-26 09:28:10 +02:00
108c33faac stub-generator: fixed issue when <arg> in <method> does not have "name" property 2018-08-24 16:39:31 +02:00
ec06462713 Add missing constructor overload for ProxyInterfaces 2018-08-06 22:58:36 +02:00
234145cade Clarify proxy and connection stuff in case of ProxyInterfaces 2018-08-06 22:52:38 +02:00
e971f95bad Clarify system/session connection on proxy creation 2018-08-06 22:34:08 +02:00
4f5dfbc301 Updated changelog for v0.3.1 2018-07-24 13:03:53 +02:00
fa878e594c Be explicit on apply function from sdbus namespace (#17) 2018-07-24 12:55:33 +02:00
d3d698f02a Fix CPU hog on async methods: Clear the event descriptor by reading from it (#16) 2018-07-24 12:54:31 +02:00
d8fd053714 Introduce support for asynchronous server-side methods (#12)
* Add preliminary changes for async server methods

* Refactor the Message concept and break it into distinctive types

* Continue working on async server methods (high-level API mainly)

* Continue developing support for async server

* Finishing async server methods

* Finishing async server methods (fixing tests & cleaning up)

* A little code cleaning

* Add unit tests for type traits of free functions

* Support for generating async server methods in stub headers

* Update ChangeLog for v0.3.0

* Update the tutorial with how to use async server-side methods

* Update the TOC in sdbus-c++ tutorial

* Update numbering in TOC

* Remove unnecessary code

* Final cleanups
2018-07-02 11:22:00 +02:00
b041f76bfc Update changelog for v0.2.6 2018-06-24 21:27:58 +02:00
f1ff05cb6f Bump up micro revision number 2018-06-22 12:36:14 +02:00
44be60555d Merge pull request #11 from lukasdurfina/fix-memory-leak
Fix leak in Message due to missing unref of sd_bus_message
2018-06-19 12:18:55 +02:00
dfdc6b153e Message: fix missing release of sd_bus_message 2018-06-19 08:56:28 +02:00
fd3799dbc3 Fix sdbus::Struct initialization problem in newer compilers - use make_struct 2018-06-06 11:49:02 +02:00
24b2f2bda3 Update ChangeLog for v0.2.5 2018-06-05 14:09:35 +00:00
d40fdf1b1c Merge pull request #10 from marek-szanyi/feature/fix_gcc_compiling
Among inherited c-tors, provide explicit Struct c-tor from tuple, since that is needed according to the standard
2018-06-05 16:04:35 +02:00
a395adbecf Change in logic when constructor is available 2018-06-05 15:52:59 +02:00
dafd7a791a Provide compiler specific ctor for Struct 2018-05-29 13:36:49 +02:00
83ae4cf5ae Bump up micro revision number 2018-05-25 20:48:56 +02:00
b535198571 Little code cleanups and refactorings 2018-05-25 20:48:20 +02:00
d68be891ee Revert modification for clang, for now it fails on gcc 2018-03-15 17:16:23 +01:00
10d0da9067 Update ChangeLog for v0.2.4 2018-03-15 16:06:05 +00:00
2564bbfb21 Add object proxy factory overload that takes unique_ptr to connection 2018-03-15 17:03:49 +01:00
e1cf50d2cd Bump up version to 0.2.4 2018-03-15 15:33:34 +00:00
db5e9dc963 Merge pull request #6 from Kistler-Group/bugfix/fix-static-assert-problem-in-x64-google-tests
Make Variant conversion operator only present for true D-Bus types
2018-03-15 16:31:24 +01:00
933e8e204d Make sure that Variant conversion operator is only present for true D-Bus type represntations in C++ 2018-03-15 16:22:06 +01:00
47139527f4 Update using-sdbus-c++.md 2018-02-27 08:45:40 +00:00
b22cac9a63 Try to clarify connection to the systems bus vs. session bus in the tutorial 2018-02-27 08:43:08 +00:00
b81c4b494c Add clang workaround comment 2018-02-27 08:26:42 +00:00
7e61a83d09 Merge pull request #2 from lejcik/master
Fix proposal for clang compilation issue
2018-02-27 08:57:48 +01:00
dc5ec014eb Added constructor for sdbus::Struct 2017-12-18 19:19:27 +01:00
f559fc0663 Added a test case that fails to compile with clang 2017-12-18 19:15:40 +01:00
b5866fe5e9 Fix handling of interrupt when polling 2017-12-14 12:58:50 +01:00
96684ce37f Add design diagram and make additional adjustments in the tutorial 2017-12-14 10:43:41 +01:00
55d8084729 Add class diagram 2017-12-14 10:30:55 +01:00
be754eb991 Merge pull request #1 from granxarixia/master
Close file descriptor of event loop's semaphore on exec
2017-12-06 15:26:57 +01:00
7fbc0e360d Close file descriptor of event loop's semaphore on exec 2017-12-06 13:52:40 +01:00
98 changed files with 9000 additions and 1738 deletions

View File

@ -1,35 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>sdbus-cpp</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.cdt.autotools.core.genmakebuilderV2</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.cdt.managedbuilder.core.genmakebuilder</name>
<triggers>clean,full,incremental,</triggers>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder</name>
<triggers>full,incremental,</triggers>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.cdt.core.cnature</nature>
<nature>org.eclipse.cdt.core.ccnature</nature>
<nature>org.eclipse.cdt.managedbuilder.core.managedBuildNature</nature>
<nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature>
<nature>org.yocto.sdk.ide.YoctoSDKNature</nature>
<nature>org.eclipse.cdt.autotools.core.autotoolsNatureV2</nature>
<nature>org.yocto.sdk.ide.YoctoSDKAutotoolsNature</nature>
</natures>
</projectDescription>

159
CMakeLists.txt Normal file
View File

@ -0,0 +1,159 @@
#-------------------------------
# PROJECT INFORMATION
#-------------------------------
cmake_minimum_required(VERSION 3.8)
project(sdbus-c++ VERSION 0.6.0 LANGUAGES C CXX)
include(GNUInstallDirs) # Installation directories for `install` command and pkgconfig file
#-------------------------------
# PERFORMING CHECKS
#-------------------------------
find_package(PkgConfig REQUIRED)
pkg_check_modules(SYSTEMD REQUIRED libsystemd>=236)
#-------------------------------
# SOURCE FILES CONFIGURATION
#-------------------------------
set(SDBUSCPP_SOURCE_DIR ${CMAKE_SOURCE_DIR}/src)
set(SDBUSCPP_INCLUDE_SUBDIR sdbus-c++)
set(SDBUSCPP_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/include/${SDBUSCPP_INCLUDE_SUBDIR})
set(SDBUSCPP_CPP_SRCS
${SDBUSCPP_SOURCE_DIR}/Connection.cpp
${SDBUSCPP_SOURCE_DIR}/ConvenienceApiClasses.cpp
${SDBUSCPP_SOURCE_DIR}/Error.cpp
${SDBUSCPP_SOURCE_DIR}/Message.cpp
${SDBUSCPP_SOURCE_DIR}/Object.cpp
${SDBUSCPP_SOURCE_DIR}/Proxy.cpp
${SDBUSCPP_SOURCE_DIR}/Types.cpp
${SDBUSCPP_SOURCE_DIR}/Flags.cpp
${SDBUSCPP_SOURCE_DIR}/VTableUtils.c
${SDBUSCPP_SOURCE_DIR}/SdBus.cpp)
set(SDBUSCPP_HDR_SRCS
${SDBUSCPP_SOURCE_DIR}/Connection.h
${SDBUSCPP_SOURCE_DIR}/IConnection.h
${SDBUSCPP_SOURCE_DIR}/MessageUtils.h
${SDBUSCPP_SOURCE_DIR}/Object.h
${SDBUSCPP_SOURCE_DIR}/Proxy.h
${SDBUSCPP_SOURCE_DIR}/ScopeGuard.h
${SDBUSCPP_SOURCE_DIR}/VTableUtils.h
${SDBUSCPP_SOURCE_DIR}/SdBus.h
${SDBUSCPP_SOURCE_DIR}/ISdBus.h)
set(SDBUSCPP_PUBLIC_HDRS
${SDBUSCPP_INCLUDE_DIR}/ConvenienceApiClasses.h
${SDBUSCPP_INCLUDE_DIR}/ConvenienceApiClasses.inl
${SDBUSCPP_INCLUDE_DIR}/Error.h
${SDBUSCPP_INCLUDE_DIR}/IConnection.h
${SDBUSCPP_INCLUDE_DIR}/AdaptorInterfaces.h
${SDBUSCPP_INCLUDE_DIR}/ProxyInterfaces.h
${SDBUSCPP_INCLUDE_DIR}/Introspection.h
${SDBUSCPP_INCLUDE_DIR}/IObject.h
${SDBUSCPP_INCLUDE_DIR}/IProxy.h
${SDBUSCPP_INCLUDE_DIR}/Message.h
${SDBUSCPP_INCLUDE_DIR}/MethodResult.h
${SDBUSCPP_INCLUDE_DIR}/Types.h
${SDBUSCPP_INCLUDE_DIR}/TypeTraits.h
${SDBUSCPP_INCLUDE_DIR}/Flags.h
${SDBUSCPP_INCLUDE_DIR}/sdbus-c++.h)
set(SDBUSCPP_SRCS ${SDBUSCPP_CPP_SRCS} ${SDBUSCPP_HDR_SRCS} ${SDBUSCPP_PUBLIC_HDRS})
#-------------------------------
# GENERAL COMPILER CONFIGURATION
#-------------------------------
set(CMAKE_CXX_STANDARD 17)
add_compile_options(-W -Wextra -Wall -pedantic)
include_directories("${CMAKE_SOURCE_DIR}/include")
include_directories("${CMAKE_SOURCE_DIR}/src")
#----------------------------------
# LIBRARY BUILD INFORMATION
#----------------------------------
set(SDBUSCPP_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}")
set(SDBUSCPP_VERSION "${PROJECT_VERSION}")
option(BUILD_SHARED_LIBS "Build shared libraries (.so) instead of static ones (.a)" ON)
add_library(sdbus-cpp OBJECT ${SDBUSCPP_SRCS})
target_include_directories(sdbus-cpp PUBLIC ${SYSTEMD_INCLUDE_DIRS})
target_compile_definitions(sdbus-cpp PRIVATE BUILDLIB=1)
if(BUILD_SHARED_LIBS)
set_target_properties(sdbus-cpp PROPERTIES POSITION_INDEPENDENT_CODE ON)
endif()
add_library(sdbus-c++ $<TARGET_OBJECTS:sdbus-cpp>)
set_target_properties(sdbus-c++
PROPERTIES
PUBLIC_HEADER "${SDBUSCPP_PUBLIC_HDRS}"
VERSION "${SDBUSCPP_VERSION}"
SOVERSION "${SDBUSCPP_VERSION_MAJOR}"
OUTPUT_NAME "sdbus-c++")
target_link_libraries(sdbus-c++ ${SYSTEMD_LIBRARIES})
#----------------------------------
# INSTALLATION
#----------------------------------
install(TARGETS sdbus-c++
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT libraries
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT libraries
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT static_libraries
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${SDBUSCPP_INCLUDE_SUBDIR} COMPONENT dev)
#----------------------------------
# TESTS
#----------------------------------
option(BUILD_TESTS "Build and install tests (default OFF)" OFF)
if(BUILD_TESTS)
enable_testing()
add_subdirectory("${CMAKE_SOURCE_DIR}/test")
endif()
#----------------------------------
# UTILS
#----------------------------------
option(BUILD_CODE_GEN "Build and install interface stub code generator (default OFF)" OFF)
if(BUILD_CODE_GEN)
add_subdirectory("${CMAKE_SOURCE_DIR}/stub-generator")
endif()
#----------------------------------
# DOCUMENTATION
#----------------------------------
option(BUILD_DOC "Build doxygen documentation for sdbus-c++ API" ON)
if(BUILD_DOC)
add_subdirectory("${CMAKE_SOURCE_DIR}/doc")
endif()
#----------------------------------
# CMAKE CONFIG & PACKAGE CONFIG
#----------------------------------
include(CMakePackageConfigHelpers)
set(SDBUSCPP_CONFIG_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/sdbus-c++)
configure_file(sdbus-c++-config.cmake.in sdbus-c++-config.cmake @ONLY)
install(FILES ${CMAKE_BINARY_DIR}/sdbus-c++-config.cmake DESTINATION ${SDBUSCPP_CONFIG_INSTALL_DIR} COMPONENT dev)
write_basic_package_version_file(sdbus-c++-config-version.cmake
VERSION ${PROJECT_VERSION}
COMPATIBILITY AnyNewerVersion)
install(FILES ${CMAKE_BINARY_DIR}/sdbus-c++-config-version.cmake DESTINATION ${SDBUSCPP_CONFIG_INSTALL_DIR} COMPONENT dev)
configure_file(sdbus-c++.pc.in sdbus-c++.pc @ONLY)
install(FILES ${CMAKE_BINARY_DIR}/sdbus-c++.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig COMPONENT dev)

View File

@ -0,0 +1,91 @@
v0.2.3
- Initially published version
v0.2.4
- Fixed closing of file descriptor of event loop's semaphore on exec
- Fixed interrupt handling when polling
- Improved tutorial
- Fixed issue with googlemock
- Added object proxy factory overload that takes unique_ptr to a connection
- Workaround: Clang compilation error when compiling sdbus::Struct (seems like an issue of Clang)
v0.2.5
- Real fix for issue with sdbus::Struct inherited constructors
- Little code refactorings and improvements
v0.2.6
- Fixed memory leak in Message copy operations
v0.3.0
- Introduced support for asynchronous server-side methods
- Refactored the concept of Message into distinctive concrete message types
- As this release comes with breaking API changes:
* if you're using lower-level API, please rename 'Message' to whatever concrete message type (MethodCall, MethodReply, Signal) is needed there,
* if you're using higher-level API, please re-compile and re-link your application against sdbus-c++,
* if you're using generated stub headers, please re-compile and re-link your application against sdbus-c++.
v0.3.1
- Fixed hogging the CPU by server with async methods (issue #15)
v0.3.2
- Switched from autotools to CMake build system
v0.3.3
- Minor fixes in tutorial examples
- Add comment on threading traits of Variant and its const methods
- Fix broken invariant of const Variant::peekValueType() method
v0.4.0
- Introduce support and implementation of common D-Bus annotations
- Remove hard-coded warning-related compiler options from the build system
v0.4.1
- Change constexpr member into a getter method to be compatible across odr-usage rules of various compiler versions
v0.4.2
- Improve documentation
- Minor code improvements
- Introduce sdbus-c++ performance tests with measurements
v0.4.3
- Add another abstraction layer for sd-bus to improve isolation for unit testing
- Minor code improvements for consistency and clarity
- Not-existing interface name and object path fixes in integration tests
v0.5.0
- Redesign of the Connection model for scalability, thread-safety, simplicity and clarity
- [[Breaking ABI change]] Introduce support asynchronous invocation of D-Bus methods on client-side
- Revise and extend sdbus-c++ tutorial
- Introduce stress tests that involve a D-Bus server and client interacting in dense communication in both sync and async ways
- Compilation error fixes for older gcc versions
v0.5.1
- Cleanups of CMakeLists.txt
- Rename sdbus-c++ test executables for consistency
- Rename sdbus-c++ stub code generator executable
- Introduce generation of doxygen documentation
- Improve documentation
v0.5.2
- Simplify and unify basic API level callbacks for both sync and async methods
v0.5.3
- Extend stress tests with dynamic object and proxy creation and destruction from multiple threads
- [[Breaking ABI change]] Add getConnection() method to IObject
- A few minor fixes
v0.6.0
- This release comes with a number of API and ABI breaking changes (that hopefully lead to a more mature and stable API for near future):
- Some constructs have been renamed (see below for details), you'll have to adapt your sources whether you're using the basic or the convenience sdbus-c++ API.
- You'll also have to re-generate adaptor/proxy stubs with the new stub code generator if you're using them.
- And you'll have to take care of manual registeration/deregistration in the constructors/destructors of your final adaptor and proxy classes. See updated using sdbus-c++ tutorial.
- Cleaning up some names (of class, methods, files):
* ConvenienceClasses.h/.inl/.cpp -> ConvenienceApiClasses.h/.inl/.cpp
* IObjectProxy class -> IProxy
* Interfaces class -> AdaptorInterfaces
* Interfaces.h -> split into AdaptorInterfaces.h and ProxyInterfaces.h
* createObjectProxy() method -> createProxy()
- Add new unregister() virtual function to IObject and IProxy to allow for safe construction and destructions of D-Bus ojects and proxies hooked to live D-Bus connections.
- Extend stress tests to allow testing safe initialization/deinitialization of objects and proxies
- Fix gcc warnings
- Use release v1.8.1 of googletest for tests

View File

@ -1,5 +1,7 @@
Building:
$ ./autogen.sh ${CONFIGURE_FLAGS}
$ mkdir build
$ cd build
$ cmake .. ${CONFIGURE_FLAGS_IF_NECESSARY}
$ make
Installing:

View File

@ -1,22 +0,0 @@
EXTRA_DIST = autogen.sh
SUBDIRS = include src test
DISTCHECK_CONFIGURE_FLAGS=
pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = sdbus-c++.pc
CLEANFILES = *~ *.lo *.la
MOSTLYCLEANFILES = *.o
DISTCLEANFILES = \
*libtool* \
aclocal.m4 \
compile config.* configure \
depcomp install-sh \
ltmain.sh \
Makefile Makefile.in \
missing \
stamp-h1 \
sdbus-c++.pc

View File

@ -6,20 +6,48 @@ sdbus-c++ is a C++ API library for D-Bus IPC, based on sd-bus implementation.
Building and installing the library
-----------------------------------
The library is built using CMake:
```bash
$ ./autogen.sh ${CONFIGURE_FLAGS}
$ mkdir build
$ cd build
$ cmake .. ${CONFIGURE_FLAGS_IF_NECESSARY}
$ make
$ sudo make install
```
Use `--disable-tests` flag when configuring to disable building unit and integration tests for the library.
### CMake configuration flags
* `BUILD_CODE_GEN` [boolean]
Option for building the stub code generator `sdbus-c++-xml2cpp` for generating the adaptor and proxy interfaces out of the D-Bus IDL XML description. Default value: `OFF`. Use `-DBUILD_CODE_GEN=ON` flag to turn on building the code gen.
* `BUILD_DOC` [boolean]
Option for building Doxygen documentation of sdbus-c++ API. If `BUILD_DOC` is enabled, the documentation must still be built explicitly through `make doc`. Default value: `ON`. Use `-DBUILD_DOC=OFF` to disable searching for Doxygen and building Doxygen documentation of sdbus-c++ API.
* `BUILD_TESTS` [boolean]
Option for building sdbus-c++ unit and integration tests, invokable by `make test`. That incorporates downloading and building static libraries of Google Test. Default value: `OFF`. Use `-DBUILD_TESTS=ON` to enable building the tests. With this option turned on, you may also enable/disable the following options:
* `BUILD_PERF_TESTS` [boolean]
Option for building sdbus-c++ performance tests. Default value: `OFF`.
* `BUILD_STRESS_TESTS` [boolean]
Option for building sdbus-c++ stress tests. Default value: `OFF`.
* `TESTS_INSTALL_PATH` [string]
Path where the test binaries shall get installed. Default value: `/opt/test/bin`.
Dependencies
------------
* `C++17` - the library uses C++17 `std::uncaught_exceptions()` feature. When building sdbus-c++ manually, make sure you use a compiler that supports that feature.
* `libsystemd` - systemd library containing sd-bus implementation. Systemd v236 at least is needed for sdbus-c++ to compile.
* `googletest` - google unit testing framework, only necessary when building tests
* `googletest` - google unit testing framework, only necessary when building tests, will be downloaded and built automatically
Licensing
---------
@ -32,13 +60,14 @@ References/documentation
* [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)
* [Tutorial: Using sdbus-c++](doc/using-sdbus-c++.md)
* [Systemd and dbus configuration](doc/systemd-dbus-config.md)
Contributing
------------
Contributions that increase the library quality, functionality, or fix issues are very welcome. To introduce a change, please submit a merge request with a description.
Contributions that increase the library quality, functionality, or fix issues are very welcome. To introduce a change, please submit a pull request with a description.
Contact
-------
stanislav.angelovic[at]kistler.com
https://github.com/Kistler-Group/sdbus-cpp

View File

@ -1,10 +0,0 @@
#! /bin/sh
[ -e config.cache ] && rm -f config.cache
libtoolize --automake
aclocal ${OECORE_ACLOCAL_OPTS}
autoconf
autoheader
automake -a
./configure $@
exit

View File

@ -1,62 +0,0 @@
AC_PREREQ(2.61)
# package version number (not shared library version)
# odd micro numbers indicate in-progress development
# even micro numbers indicate released versions
m4_define(sdbus_cpp_version_major, 0)
m4_define(sdbus_cpp_version_minor, 2)
m4_define(sdbus_cpp_version_micro, 3)
m4_define([sdbus_cpp_version],
[sdbus_cpp_version_major.sdbus_cpp_version_minor.sdbus_cpp_version_micro])
m4_define([sdbus_cpp_api_version],
[sdbus_cpp_version_major.sdbus_cpp_version_minor])
AC_INIT(libsdbus-c++, sdbus_cpp_version)
AM_INIT_AUTOMAKE
AC_CONFIG_HEADERS(config.h)
# Checks for programs.
AC_PROG_LIBTOOL
AC_PROG_CXX
AC_PROG_CC
AC_PROG_INSTALL
# enable pkg-config
PKG_PROG_PKG_CONFIG
PKG_CHECK_MODULES(SYSTEMD, [libsystemd >= 0.10.1],,
AC_MSG_ERROR([You need the libsystemd library (version 0.10.1 or better)]
[https://www.freedesktop.org/wiki/Software/systemd/])
)
# Checks for library functions.
#AC_CHECK_FUNCS([memset])
AC_SUBST(libsdbus_cpp_CFLAGS)
AC_SUBST(libsdbus_cpp_LIBS)
AC_ARG_ENABLE([tests],
[AS_HELP_STRING([--enable-tests],
[build and install tests @<:@default=yes@:>@])],
[],
[enable_tests=yes])
AM_CONDITIONAL([ENABLE_TESTS], [test x$enable_tests = xyes])
#icondir=${datadir}/icons/hicolor/32x32/apps
#AC_SUBST(icondir)
AC_OUTPUT([
Makefile
include/Makefile
src/Makefile
test/Makefile
sdbus-c++.pc
])
echo ""
echo " sdbus-cpp $VERSION"
echo " ====================="
echo ""
echo " To build the project, run \"make\""
echo ""

17
doc/CMakeLists.txt Normal file
View File

@ -0,0 +1,17 @@
# Building doxygen documentation
find_package(Doxygen)
if(DOXYGEN_FOUND)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile @ONLY)
add_custom_target(doc
COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMENT "Generating API documentation with Doxygen"
VERBATIM)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html DESTINATION ${CMAKE_INSTALL_DOCDIR} OPTIONAL)
else()
message(WARNING "Documentation enabled, but Doxygen cannot be found")
endif()

2494
doc/Doxyfile.in Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -0,0 +1,63 @@
@startuml
package "Public API" <<frame>> #DDDDDD {
interface IConnection {
+requestName()
+enterProcessLoop()
+leaveProcessLoop()
}
interface IObject {
+registerMethod()
+emitSignal()
}
interface IProxy {
+callMethod()
+subscribeToSignal()
+get/setProperty()
}
class Message {
+serialize(...)
+deserialize(...)
+send()
Type msgType
}
}
interface IConnectionInternal {
+addObjectVTable()
+createMethodCall()
+createSignal()
}
class Connection {
}
class Object {
IConnectionInternal& connection
string objectPath
List interfaces
List methods
List signals
List properties
}
class Proxy {
IConnectionInternal& connection
string destination
string objectPath
List signalHandlers
}
IConnection <|-- Connection
IConnectionInternal <|- Connection
IObject <|-- Object
IProxy <|-- Proxy
Connection <-- Object : "use"
Connection <-- Proxy : "use"
Message <.. Object : "send/receive"
Message <.. Proxy : "send/receive"
@enduml

View File

@ -0,0 +1,54 @@
Systemd and dbus configuration
=======================
**Table of contents**
1. [Introduction](#introduction)
2. [Systemd configuration](#systemd-configuration)
3. [Dbus configuration](#dbus-configuration)
Introduction
------------
To run executable as a systemd service you may need some additional setup. For example, you may need explicitly allow
the usage of your service. Following chapters contain template configurations.
Systemd configuration
---------------------------------------
Filename should use `.service` extension. It also must be placed in configuration directory (/etc/systemd/system in
Ubuntu 18.04.1 LTS)
```
[Unit]
Description=nameOfService
[Service]
ExecStart=/path/to/executable
[Install]
WantedBy=multi-user.target
```
Dbus configuration
------------------
Typical default D-Bus configuration does not allow to register services except explicitly allowed. Filename should
contain name of your service, e.g `/etc/dbus-1/system.d/org.sdbuscpp.concatenator.conf`. So, here is template
configuration to use dbus interface under root:
```
<!DOCTYPE busconfig PUBLIC
"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<policy user="root">
<allow own="org.sdbuscpp.concatenator"/>
<allow send_destination="org.sdbuscpp"/>
<allow send_interface="org.sdbuscpp.concatenator"/>
</policy>
</busconfig>
```
If you need access from other user `root` should be substituted by desired username. For more refer to `man dbus-daemon`.

View File

@ -7,42 +7,40 @@ Using sdbus-c++ library
2. [Integrating sdbus-c++ into your project](#integrating-sdbus-c-into-your-project)
3. [Header files and namespaces](#header-files-and-namespaces)
4. [Error signalling and propagation](#error-signalling-and-propagation)
5. [Multiple layers of sdbus-c++ API](#multiple-layers-of-sdbus-c-api)
6. [An example: Number concatenator](#an-example-number-concatenator)
7. [Implementing the Concatenator example using basic sdbus-c++ API layer](#implementing-the-concatenator-example-using-basic-sdbus-c-api-layer)
8. [Implementing the Concatenator example using convenience sdbus-c++ API layer](#implementing-the-concatenator-example-using-convenience-sdbus-c-api-layer)
9. [Implementing the Concatenator example using sdbus-c++-generated stubs](#implementing-the-concatenator-example-using-sdbus-c-generated-stubs)
10. [Using D-Bus properties](#using-d-bus-properties)
11. [Conclusion](#conclusion)
5. [Design of sdbus-c++](#design-of-sdbus-c)
6. [Multiple layers of sdbus-c++ API](#multiple-layers-of-sdbus-c-api)
7. [An example: Number concatenator](#an-example-number-concatenator)
8. [Implementing the Concatenator example using basic sdbus-c++ API layer](#implementing-the-concatenator-example-using-basic-sdbus-c-api-layer)
9. [Implementing the Concatenator example using convenience sdbus-c++ API layer](#implementing-the-concatenator-example-using-convenience-sdbus-c-api-layer)
10. [Implementing the Concatenator example using sdbus-c++-generated stubs](#implementing-the-concatenator-example-using-sdbus-c-generated-stubs)
11. [Asynchronous server-side methods](#asynchronous-server-side-methods)
12. [Asynchronous client-side methods](#asynchronous-client-side-methods)
13. [Using D-Bus properties](#using-d-bus-properties)
14. [Conclusion](#conclusion)
Introduction
------------
sdbus-c++ is a C++ wrapper library built on top of [sd-bus](http://0pointer.net/blog/the-new-sd-bus-api-of-systemd.html), a lightweight D-Bus client
library implemented within systemd project. It provides D-Bus functionality on a higher level of abstraction, trying to employ C++ type system
to shift as much work as possible from the developer to the compiler.
sdbus-c++ is a C++ wrapper library built on top of [sd-bus](http://0pointer.net/blog/the-new-sd-bus-api-of-systemd.html), a lightweight D-Bus client library implemented within systemd project. It provides D-Bus functionality on a higher level of abstraction, trying to employ C++ type system to shift as much work as possible from the developer to the compiler.
sdbus-c++ does not cover the entire sd-bus API, but provides tools for implementing the most common functionality - RPC
method calls, signals and properties. There is room for additions and improvements, as needed and when needed.
sdbus-c++ does not cover the entire sd-bus API, but provides tools for implementing the most common functionality - RPC method calls, signals and properties. There is room for additions and improvements, as needed and when needed.
Integrating sdbus-c++ into your project
---------------------------------------
The library build system is based on Autotools. The library supports `pkg-config`, so integrating it into your autotools project
is a two step process.
The library build system is based on CMake. The library provides a config file, so integrating it into your CMake project is rather straight-forward:
1. Add `PKG_CHECK_MODULES` macro into your `configure.ac`:
```bash
PKG_CHECK_MODULES(SDBUSCPP, [sdbus-c++ >= 0.1],,
AC_MSG_ERROR([You need the sdbus-c++ library (version 0.1 or better)]
[http://www.kistler.com/])
)
find_package(sdbus-c++ REQUIRED)
```
2. Update `*_CFLAGS` and `*_LDFLAGS` in Makefiles of modules that use *sdbus-c++*, for example like this:
The library also supports `pkg-config`, so it easily be integrated into e.g. an Autotools project:
```bash
AM_CXXFLAGS = -std=c++17 -pedantic -W -Wall @SDBUSCPP_CFLAGS@ ...
AM_LDFLAGS = @SDBUSCPP_LIBS@ ...
PKG_CHECK_MODULES(SDBUSCPP, [sdbus-c++ >= 0.4],,
AC_MSG_ERROR([You need the sdbus-c++ library (version 0.4 or newer)]
[http://www.kistler.com/])
)
```
Note: sdbus-c++ library depends on C++17, since it uses C++17 `std::uncaught_exceptions()` feature. When building sdbus-c++ manually, make sure you use a compiler that supports that feature. To use the library, make sure you have a C++ standard library that supports the feature. The feature is supported by e.g. gcc >= 6, and clang >= 3.7.
@ -50,12 +48,11 @@ Note: sdbus-c++ library depends on C++17, since it uses C++17 `std::uncaught_exc
Header files and namespaces
---------------------------
All sdbus-c++ header files reside in the `sdbus-c++` subdirectory within the standard include directory. Users can either include
individual header files, like so:
All sdbus-c++ header files reside in the `sdbus-c++` subdirectory within the standard include directory. Users can either include individual header files, like so:
```cpp
#include <sdbus-c++/IConnection.h>
#include <sdbus-c++/IObjectProxy.h>
#include <sdbus-c++/IProxy.h>
```
or just include the global header file that pulls in everything:
@ -70,42 +67,76 @@ Error signalling and propagation
--------------------------------
`sdbus::Error` exception is used to signal errors in sdbus-c++. There are two types of errors:
* D-Bus related errors, like call timeouts, failed socket allocation, etc.
* user errors, i.e. errors signalled and propagated from remote methods back to the caller.
The exception object carries the error name and error message with it.
* D-Bus related errors, like call timeouts, failed socket allocation, etc. These are raised by the D-Bus library or D-Bus daemon itself.
* user-defined errors, i.e. errors signalled and propagated from remote methods back to the caller. So these are issued by sdbus-c++ users.
`sdbus::Error` is a carrier for both types of errors, carrying the error name and error message with it.
Design of sdbus-c++
-------------------
The following diagram illustrates the major entities in sdbus-c++.
![class](sdbus-c++-class-diagram.png)
`IConnection` represents the concept of a D-Bus connection. You can connect to either the system bus or a session bus. Services can assign unique service names to those connections. A processing loop should be run on the connection.
`IObject` represents the concept of an object that exposes its methods, signals and properties. Its responsibilities are:
* registering (possibly multiple) interfaces and methods, signals, properties on those interfaces,
* emitting signals.
`IProxy` represents the concept of the proxy, which is a view of the `Object` from the client side. Its responsibilities are:
* invoking remote methods of the corresponding object, in both synchronous and asynchronous way,
* registering handlers for signals,
`Message` class represents a message, which is the fundamental DBus concept. There are three distinctive types of message that derive from the `Message` class:
* `MethodCall` (with serialized parameters),
* `MethodReply` (with serialized return values),
* or a `Signal` (with serialized parameters).
### Thread safety in sdbus-c++
sdbus-c++ is thread-aware by design. But, in general, it's not thread-safe. At least not in all places. There are situations where sdbus-c++ provides and guarantees API-level thread safety by design. It is safe to do these operations from multiple threads at the same time:
* Making and destroying `Object`s and `Proxy`s, even on a shared connection that is already running an event loop. Under *making* here is meant a complete atomic sequence of construction, registration of method/signal/property callbacks and export of the `Object`/`Proxy` so it is ready to issue/receive messages. This sequence must be done in one thread.
* Creating and sending asynchronous method replies on an `Object` instance.
* Creating and emitting signals on an `Object` instance.
* Creating and sending method calls (both synchronously and asynchronously) on an `Proxy` instance. (But it's generally better that our threads use their own exclusive instances of proxy, to minimize shared state and contention.)
Multiple layers of sdbus-c++ API
-------------------------------
sdbus-c++ API comes in two layers:
* the basic layer, which is almost pure wrapper layer on top of sd-bus, using mechanisms that are native to C++,
* the convenience layer, building on top of the basic layer, which aims at providing shorter, safer, and more expressive way of writing the
client 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 proxy part.
Hierarchically, these stubs provide yet another layer of convenience (the "stubs layer"), making it possible for D-Bus RPC calls to look like
native C++ calls on a local object.
* [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.
An example: Number concatenator
-------------------------------
Let's have an object `/org/sdbuscpp/concatenator` that implements the `org.sdbuscpp.concatenator` interface. The interface exposes the following:
* a `concatenate` method that takes a collection of integers and a separator string and returns a string that is the concatenation of all
integers from the collection using given separator,
* a `concatenated` signal that is emitted at the end of each successful concatenation.
* a `concatenate` method that takes a collection of integers and a separator string and returns a string that is the concatenation of all integers from the collection using given separator,
* a `concatenated` signal that is emitted at the end of each successful concatenation.
In the following sections, we will elaborate on the ways of implementing such an object on both the server and the client side.
Implementing the Concatenator example using basic sdbus-c++ API layer
---------------------------------------------------------------------
In the basic API layer, we already have abstractions for D-Bus connections, objects and object proxies, with which we can interact via
interfaces. However, we still work with the concept of messages. To issue a method call for example, we have to go through several steps:
we have to create a method call message first, serialize method arguments into the message, and send the message at last. We get the reply
message (if applicable) in return, so we have to deserialize the return values from it manually.
In the basic API layer, we already have abstractions for D-Bus connections, objects and object proxies, with which we can interact via their interface classes (`IConnection`, `IObject`, `IProxy`), but, analogously to the underlying sd-bus C library, we still work on the level of D-Bus messages. We need to
Overloaded versions of C++ insertion/extraction operators are used for serialization/deserialization. That makes the client code much simpler.
* create them,
* serialize/deserialize arguments to/from them (thanks to many overloads of C++ insertion/extraction operators, this is very simple),
* send them over to the other side.
This is how a simple Concatenator service implemented upon the basic sdbus-c++ API could look like:
### Server side
@ -119,15 +150,15 @@ Overloaded versions of C++ insertion/extraction operators are used for serializa
// to emit signals.
sdbus::IObject* g_concatenator{};
void concatenate(sdbus::Message& msg, sdbus::Message& reply)
void concatenate(sdbus::MethodCall call)
{
// Deserialize the collection of numbers from the message
std::vector<int> numbers;
msg >> numbers;
call >> numbers;
// Deserialize separator from the message
std::string separator;
msg >> separator;
call >> separator;
// Return error if there are no numbers in the collection
if (numbers.empty())
@ -139,21 +170,23 @@ void concatenate(sdbus::Message& msg, sdbus::Message& reply)
result += (result.empty() ? std::string() : separator) + std::to_string(number);
}
// Serialize resulting string to the reply
// Serialize resulting string to the reply and send the reply to the caller
auto reply = call.createReply();
reply << result;
reply.send();
// Emit 'concatenated' signal
const char* interfaceName = "org.sdbuscpp.Concatenator";
auto signalMsg = g_concatenator->createSignal(interfaceName, "concatenated");
signalMsg << result;
g_concatenator->emitSignal(signalMsg);
auto signal = g_concatenator->createSignal(interfaceName, "concatenated");
signal << result;
g_concatenator->emitSignal(signal);
}
int main(int argc, char *argv[])
{
// Create D-Bus connection and requests name on it.
// Create D-Bus connection to the system bus and requests name on it.
const char* serviceName = "org.sdbuscpp.concatenator";
auto connection = sdbus::createConnection(serviceName);
auto connection = sdbus::createSystemBusConnection(serviceName);
// Create concatenator D-Bus object.
const char* objectPath = "/org/sdbuscpp/concatenator";
@ -172,6 +205,12 @@ int main(int argc, char *argv[])
}
```
We establish a D-Bus sytem connection and request `org.sdbuscpp.concatenator` D-Bus name on it. This name will be used by D-Bus clients to find the service. We then create an object with path `/org/sdbuscpp/concatenator` on this connection. We register interfaces, its methods, signals that the object provides, and, through `finishRegistration()`, export the object (i.e., make it visible to clients) on the bus. Then we need to make sure to run the event loop upon the connection, which handles all incoming, outgoing and other requests.
The callback for any D-Bus object method on this level is any callable of signature `void(sdbus::MethodCall call)`. The `call` parameter is the incoming method call message. We need to deserialize our method input arguments from it. Then we can invoke the logic of the method and get the results. Then for the given `call`, we create a `reply` message, pack results into it and send it back to the caller through `send()`. (If we had a void-returning method, we'd just send an empty `reply` back.) We also fire a signal with the results. To do this, we need to create a signal message via object's `createSignal()`, serialize the results into it, and then send it out to subscribers by invoking object's `emitSignal()`.
Please note that we can create and destroy D-Bus objects on a connection dynamically, at any time during runtime, even while there is an active event loop upon the connection. So managing D-Bus objects' lifecycle (creating, exporting and destroying D-Bus objects) is completely thread-safe.
### Client side
```c++
@ -181,20 +220,22 @@ int main(int argc, char *argv[])
#include <iostream>
#include <unistd.h>
void onConcatenated(sdbus::Message& signalMsg)
void onConcatenated(sdbus::Signal& signal)
{
std::string concatenatedString;
msg >> concatenatedString;
signal >> concatenatedString;
std::cout << "Received signal with concatenated string " << concatenatedString << std::endl;
}
int main(int argc, char *argv[])
{
// Create proxy object for the concatenator object on the server side
// Create proxy object for the concatenator object on the server side. Since here
// we are creating the proxy instance without passing connection to it, the proxy
// will create its own connection automatically, and it will be system bus connection.
const char* destinationName = "org.sdbuscpp.concatenator";
const char* objectPath = "/org/sdbuscpp/concatenator";
auto concatenatorProxy = sdbus::createObjectProxy(destinationName, objectPath);
auto concatenatorProxy = sdbus::createProxy(destinationName, objectPath);
// Let's subscribe for the 'concatenated' signals
const char* interfaceName = "org.sdbuscpp.Concatenator";
@ -236,27 +277,57 @@ int main(int argc, char *argv[])
}
```
The object proxy is created without explicitly providing a D-Bus connection as an argument in its factory function. In that case, the proxy
will create its own connection and listen to signals on it in a separate thread. That means the `onConcatenated` method is invoked always
in the context of a thread different from the main thread.
In simple cases, we don't need to create D-Bus connection explicitly for our proxies. Unless a connection is provided to a proxy object explicitly via factory parameter, the proxy will create a connection of his own, and it will be a system bus connection. This is the case in the example above. (This approach is not scalable and resource-saving if we have plenty of proxies; see section [Working with D-Bus connections](#working-with-d-bus-connections-in-sdbus-c) for elaboration.) So, in the example, we create a proxy for object `/org/sdbuscpp/concatenator` publicly available at bus `org.sdbuscpp.concatenator`. We register signal handlers, if any, and finish the registration, making the proxy ready for use.
The callback for a D-Bus signal handler on this level is any callable of signature `void(sdbus::Signal& signal)`. The one and only parameter `signal` is the incoming signal message. We need to deserialize arguments from it, and then we can do our business logic with it.
Subsequently, we invoke two RPC calls to object's `concatenate()` method. We create a method call message by invoking proxy's `createMethodCall()`. We serialize method input arguments into it, and make a synchronous call via proxy's `callMethod()`. As a return value we get the reply message as soon as it arrives. We deserialize return values from that message, and further use it in our program. The second `concatenate()` RPC call is done with invalid arguments, so we get a D-Bus error reply from the service, which as we can see is manifested via `sdbus::Error` exception being thrown.
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.
### Working with D-Bus connections in sdbus-c++
The design of D-Bus connections in sdbus-c++ allows for certain flexibility and enables users to choose simplicity over scalability or scalability (at a finer granularity of user's choice) at the cost of slightly decreased simplicity.
How shall we use connections in relation to D-Bus objects and object proxies?
A D-Bus connection is represented by a `IConnection` instance. Each connection needs an event loop being run upon it. So it needs a thread handling the event loop. This thread serves all incoming and outgoing messages and all communication towards D-Bus daemon.
One process can have multiple D-Bus connections, with assigned unique bus names or without, as long as those with assigned bus names do not share a common bus name.
A D-Bus connection can be created for and used exclusively by one D-Bus object (represented by one `IObject` instance) or one D-Bus proxy (represented by one `IProxy` instance), but can very well be used by and shared across multiple objects, multiple proxies or even both multiple objects and proxies at the same time. When shared, one must bear in mind that the access to the connection is mutually exclusive and is serialized. This means, for example, that if an object's callback is going to be invoked for an incoming remote method call and in another thread we use a proxy to call remote method in another process, the threads are contending and only one can go on while the other must wait and can only proceed after the first one has finished, because both are using a shared resource -- the connection.
The former case (1:1) is one extreme; it's usually simple, has zero resource contention, but hurts scalability (for example, 50 proxies in our program need 50 D-Bus connections and 50 event loop threads upon them). The latter case (1:N) is the other extreme -- all D-Bus objects and proxies share one single connection. This is the most scalable solution (since, for example, 5 or 200 objects/proxies use always one single connection), but may increase contention and hurt concurrency (since only one of all those objects/proxies can work with the connection at a time). And then there are limitless options between the two (for example, we can use one connection for all objects, and another connection for all proxies in our service...). sdbus-c++ gives its users freedom to choose whatever approach is more suitable to them in their application at fine granularity.
How can we use connections from the server and the client perspective?
* On the *server* side, we generally need to create D-Bus objects and publish their APIs. For that we first need a connection with a unique bus name. We need to create the D-Bus connection manually ourselves, request bus name on it, and manually launch its event loop (in a blocking way, through `enterProcessingLoop()`, or non-blocking async way, through `enterProcessingLoopAsync()`). At any time before or after running the event loop on the connection, we can create and "hook", as well as remove, objects and proxies upon that connection.
* On the *client* side, for our D-Bus object proxies, we have more options (corresponding to three overloads of the `createProxy()` factory):
* We don't bother about any connection when creating a proxy. For each proxy instance sdbus-c++ also creates an internal connection instance to be used just by this proxy, and it will be a *system bus* connection. Additionally, an event loop thread for that connection is created and run internally.
This hurts scalability (see discussion above), but our code is simpler, and since each proxy has its own connection, there is zero contention.
* We create a connection explicitly by ourselves and `std::move` it to the proxy object factory. The proxy becomes an owner of this connection, and will run the event loop on that connection. This is the same as in the above bullet point, but with a flexibility that we can choose the bus type (system, session bus).
* We are always full owners of the connection. We create the connection, and a proxy only takes and keeps a reference to it. We take care of the event loop upon that connection (and we must ensure the connection exists as long as all its users exist).
Implementing the Concatenator example using convenience sdbus-c++ API layer
---------------------------------------------------------------------------
One of the major sdbus-c++ design goals is to make the sdbus-c++ API easy to use correctly, and hard to use incorrectly.
The convenience API layer abstracts the concept of underlying D-Bus messages away completely. It abstracts away D-Bus signatures. And it tries
to provide an interface that uses small, focused functions, with one or zero parameters, to form a chained function statement that reads like
a sentence to a human reading the code. To achieve that, sdbus-c++ utilizes the power of the C++ type system, so many issues are resolved at
compile time, and the run-time performance cost compared to the basic layer is close to zero.
The convenience API layer abstracts the concept of underlying D-Bus messages away completely. It abstracts away D-Bus signatures. The interface uses small, focused functions, with a few parameters only, to form a chained function statement that reads like a human language sentence. To achieve that, sdbus-c++ utilizes the power of the C++ type system, which deduces and resolves a lot of things at compile time, and the run-time performance cost compared to the basic layer is close to zero.
Thus, in the end of the day, the code written using the convenience API is:
- more expressive,
- closer to the abstraction level of the problem being solved,
- shorter,
- almost as fast (if not equally fast) as one written using the basic API layer.
Rather than *how*, the code written using this layer expresses *what* it does. Let's look at code samples to see if you agree :)
- more expressive,
- closer to the abstraction level of the problem being solved,
- shorter,
- almost as fast as one written using the basic API layer.
The code written using this layer expresses *what* it does, rather then *how*. Let's look at code samples.
### Server side
@ -291,9 +362,9 @@ std::string concatenate(const std::vector<int> numbers, const std::string& separ
int main(int argc, char *argv[])
{
// Create D-Bus connection and requests name on it.
// Create D-Bus connection to the system bus and requests name on it.
const char* serviceName = "org.sdbuscpp.concatenator";
auto connection = sdbus::createConnection(serviceName);
auto connection = sdbus::createSystemBusConnection(serviceName);
// Create concatenator D-Bus object.
const char* objectPath = "/org/sdbuscpp/concatenator";
@ -331,11 +402,11 @@ int main(int argc, char *argv[])
// Create proxy object for the concatenator object on the server side
const char* destinationName = "org.sdbuscpp.concatenator";
const char* objectPath = "/org/sdbuscpp/concatenator";
auto concatenatorProxy = sdbus::createObjectProxy(destinationName, objectPath);
auto concatenatorProxy = sdbus::createProxy(destinationName, objectPath);
// Let's subscribe for the 'concatenated' signals
const char* interfaceName = "org.sdbuscpp.Concatenator";
concatenatorProxy->uponSignal("concatenated").onInterface(interfaceName).call([this](const std::string& str){ onConcatenated(str); });
concatenatorProxy->uponSignal("concatenated").onInterface(interfaceName).call([](const std::string& str){ onConcatenated(str); });
concatenatorProxy->finishRegistration();
std::vector<int> numbers = {1, 2, 3};
@ -368,24 +439,22 @@ int main(int argc, char *argv[])
}
```
Several lines of code have shrunk into one-liners when registering/calling methods or signals. D-Bus signatures and the serialization/deserialization
of arguments from the messages is generated at compile time, by introspecting signatures of provided callbacks or deducing types of provided arguments.
When registering methods, calling methods or emitting signals, multiple lines of code have shrunk into simple one-liners. Signatures of provided callbacks are introspected and types of provided arguments are deduced at compile time, so the D-Bus signatures as well as serialization and deserialization of arguments to and from D-Bus messages are generated for us completely by the compiler.
sdbus-c++ users shall prefer the convenience API to the lower level, basic API. When feasible, using generated adaptor and proxy stubs is even better. These stubs provide yet another, higher API level built on top of the convenience API. They are described in the following section.
Implementing the Concatenator example using sdbus-c++-generated stubs
---------------------------------------------------------------------
sdbus-c++ ships with the native stub generator tool called sdbuscpp-xml2cpp. The tool is very similar to dbusxx-xml2cpp tool that comes from
dbus-c++ project.
sdbus-c++ ships with the native stub generator tool called `sdbus-c++-xml2cpp`. The tool is very similar to `dbusxx-xml2cpp` tool that comes with the dbus-c++ project.
The generator tool takes D-Bus XML IDL description of D-Bus interfaces on its input, and can be instructed to generate one or both of these:
an adaptor header file for use at server side, and a proxy header file for use at client side. Like this:
The generator tool takes D-Bus XML IDL description of D-Bus interfaces on its input, and can be instructed to generate one or both of these: an adaptor header file for use on the server side, and a proxy header file for use on the client side. Like this:
```bash
sdbuscpp-xml2cpp database-bindings.xml --adaptor=database-server-glue.h --proxy=database-client-glue.h
sdbus-c++-xml2cpp database-bindings.xml --adaptor=database-server-glue.h --proxy=database-client-glue.h
```
The adaptor header file contains classes that can be used to implement described interfaces. The proxy header file contains classes that can be used
to make calls to remote objects.
The adaptor header file contains classes that can be used to implement 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).
### XML description of the Concatenator interface
@ -412,13 +481,11 @@ After running this through the stubs generator, we get the stub code that is des
### concatenator-server-glue.h
There is specific class for each interface in the XML IDL file. The class is de facto an interface which shall be implemented by inheriting from it.
The class' constructor takes care of registering all methods, signals and properties. For each D-Bus method there is a pure virtual member function.
These pure virtual functions must be implemented in the child class. For each signal, there is a public function member that emits this signal.
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 inheriting from it. The class' constructor takes care of registering all methods, signals and properties. For each D-Bus method there is a pure virtual member function. These pure virtual functions must be implemented in the child class. For each signal, there is a public function member that emits this signal.
```cpp
/*
* This file was automatically generated by sdbuscpp-xml2cpp; DO NOT EDIT!
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__concatenator_server_glue_h__adaptor__H__
@ -464,13 +531,11 @@ private:
### concatenator-client-glue.h
Analogously to the adaptor classes described above, there is specific class for each interface in the XML IDL file. The class is de facto a proxy
to the concrete interface of a remote object. For each D-Bus signal there is a pure virtual member function whose body must be provided in a child
class. For each method, there is a public function member that calls the method remotely.
Analogously to the adaptor classes described above, there is 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.
```cpp
/*
* This file was automatically generated by sdbuscpp-xml2cpp; DO NOT EDIT!
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__concatenator_client_glue_h__proxy__H__
@ -489,10 +554,10 @@ public:
static constexpr const char* interfaceName = "org.sdbuscpp.Concatenator";
protected:
Concatenator_proxy(sdbus::IObjectProxy& object)
: object_(object)
Concatenator_proxy(sdbus::IProxy& proxy)
: proxy_(proxy)
{
object_.uponSignal("concatenated").onInterface(interfaceName).call([this](const std::string& concatenatedString){ this->onConcatenated(concatenatedString); });
proxy_.uponSignal("concatenated").onInterface(interfaceName).call([this](const std::string& concatenatedString){ this->onConcatenated(concatenatedString); });
}
virtual void onConcatenated(const std::string& concatenatedString) = 0;
@ -501,12 +566,12 @@ public:
std::string concatenate(const std::vector<int32_t>& numbers, const std::string& separator)
{
std::string result;
object_.callMethod("concatenate").onInterface(interfaceName).withArguments(numbers, separator).storeResultsTo(result);
proxy_.callMethod("concatenate").onInterface(interfaceName).withArguments(numbers, separator).storeResultsTo(result);
return result;
}
private:
sdbus::IObjectProxy& object_;
sdbus::IProxy& proxy_;
};
}} // namespaces
@ -516,22 +581,34 @@ private:
### Providing server implementation based on generated adaptors
To implement a D-Bus object that implements all its D-Bus interfaces, we shall create a class representing the object that inherits from all
corresponding `*_adaptor` classes and implements all pure virtual member functions. Specifically, the object class shall inherit from the
`Interfaces` template class, the template arguments of which are individual adaptor classes. The `Interfaces` is just a convenience class that
hides a few boiler-plate details. For example, in its constructor, it creates an `Object` instance, it takes care of proper initialization of
all adaptor superclasses, and exports the object finally.
To implement a D-Bus object that implements all its D-Bus interfaces, we now need to create a class representing the D-Bus object. This class must inherit from all corresponding `*_adaptor` classes (a-ka object interfaces, because these classes are as-if interfaces) and implement all pure virtual member functions.
How do we do that technically? Simply, our object class just needs to inherit from `AdaptorInterfaces` variadic template class. We fill its template arguments with a list of all generated interface classes. The `AdaptorInterfaces` is a convenience class that hides a few boiler-plate details. For example, in its constructor, it creates an `Object` instance, and it takes care of proper initialization of all adaptor superclasses.
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.
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.
```cpp
#include <sdbus-c++/sdbus-c++.h>
#include "concatenator-server-glue.h"
class Concatenator : public sdbus::Interfaces<org::sdbuscpp::Concatenator_adaptor /*, more adaptor classes if there are more interfaces*/>
class Concatenator : public sdbus::AdaptorInterfaces<org::sdbuscpp::Concatenator_adaptor /*, more adaptor classes if there are more interfaces*/>
{
public:
Concatenator(sdbus::IConnection& connection, std::string objectPath)
: sdbus::Interfaces<org::sdbuscpp::Concatenator_adaptor>(connection, std::move(objectPath))
: sdbus::AdaptorInterfaces(connection, std::move(objectPath))
{
registerAdaptor();
}
~Concatenator()
{
unregisterAdaptor();
}
protected:
@ -557,17 +634,16 @@ protected:
};
```
That's it. We now have an implementation of a D-Bus object implementing `org.sdbuscpp.Concatenator` interface. Let's now create a service
publishing the object.
That's it. We now have an implementation of a D-Bus object implementing `org.sdbuscpp.Concatenator` interface. Let's now create a service publishing the object.
```cpp
#include "Concatenator.h"
int main(int argc, char *argv[])
{
// Create D-Bus connection and requests name on it.
// Create D-Bus connection to the system bus and requests name on it.
const char* serviceName = "org.sdbuscpp.concatenator";
auto connection = sdbus::createConnection(serviceName);
auto connection = sdbus::createSystemBusConnection(serviceName);
// Create concatenator D-Bus object.
const char* objectPath = "/org/sdbuscpp/concatenator";
@ -578,15 +654,21 @@ int main(int argc, char *argv[])
}
```
It's that simple!
Now we have a service with a unique bus name and a D-Bus object available on it. Let's write a client.
### Providing client implementation based on generated proxies
To implement a proxy for a remote D-Bus object, we shall create a class representing the object proxy that inherits from all corresponding
`*_proxy` classes and -- if applicable -- implements all pure virtual member functions. Specifically, the object proxy class shall inherit
from the `ProxyInterfaces` template class. As its template arguments we shall provide all proxy classes. The `ProxyInterfaces` is just a
convenience class that hides a few boiler-plate details. For example, in its constructor, it creates an `ObjectProxy` instance, and it takes
care of proper initialization of all proxy superclasses.
To implement a proxy for a remote D-Bus object, we shall create a class representing the proxy object. This class must inherit from all corresponding `*_proxy` classes (a-ka remote object interfaces, because these classes are as-if interfaces) and -- if applicable -- implement all pure virtual member functions.
How do we do that technically? Simply, our proxy class just needs to inherit from `ProxyInterfaces` variadic template class. We fill its template arguments with a list of all generated interface classes. The `ProxyInterfaces` is a convenience class that hides a few boiler-plate details. For example, in its constructor, it can create a `Proxy` instance for us, and it takes care of proper initialization of all generated interface superclasses.
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.
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.
```cpp
#include <sdbus-c++/sdbus-c++.h>
@ -596,8 +678,14 @@ class ConcatenatorProxy : public sdbus::ProxyInterfaces<org::sdbuscpp::Concatena
{
public:
ConcatenatorProxy(std::string destination, std::string objectPath)
: sdbus::ProxyInterfaces<org::sdbuscpp::Concatenator_proxy>(std::move(destination), std::move(objectPath))
: sdbus::ProxyInterfaces(std::move(destination), std::move(objectPath))
{
registerProxy();
}
~Concatenator()
{
unregisterProxy();
}
protected:
@ -608,10 +696,15 @@ protected:
};
```
In the above example, a proxy is created that creates and maintains its own system bus connection. However, there are `ProxyInterfaces` class template constructor overloads that also take the connection from the user as the first parameter, and pass that connection over to the underlying proxy. The connection instance is used by all interfaces listed in the `ProxyInterfaces` template parameter list.
Note however that there are multiple `ProxyInterfaces` constructor overloads, and they differ in how the proxy behaves towards the D-Bus connection. These overloads precisely map the `sdbus::createProxy` overloads, as they are actually implemented on top of them. See [Proxy and D-Bus connection](#Proxy-and-D-Bus-connection) for more info. We can even create a `IProxy` instance on our own, and inject it into our proxy class -- there is a constructor overload for it in `ProxyInterfaces`. This can help if we need to provide mocked implementations in our unit tests.
Now let's use this proxy to make remote calls and listen to signals in a real application.
```cpp
#include "ConcatenatorProxy.h"
#include <unistd.h>
int main(int argc, char *argv[])
{
@ -645,6 +738,247 @@ int main(int argc, char *argv[])
}
```
Asynchronous server-side methods
--------------------------------
So far in our tutorial, we have only considered simple server methods that are executed in a synchronous way. Sometimes the method call may take longer, however, and we don't want to block (potentially starve) other clients (whose requests may take relative short time). The solution is to execute the D-Bus methods asynchronously, and return the control quickly back to the D-Bus dispatching thread. sdbus-c++ provides API supporting async methods, and gives users the freedom to come up with their own concrete implementation mechanics (one worker thread? thread pool? ...).
### Using basic sdbus-c++ API
This is how the concatenate method would look like if wrote it as an asynchronous D-Bus method using the basic, lower-level API of sdbus-c++:
```c++
void concatenate(sdbus::MethodCall call)
{
// Deserialize the collection of numbers from the message
std::vector<int> numbers;
call >> numbers;
// Deserialize separator from the message
std::string separator;
call >> separator;
// Launch a thread for async execution...
std::thread([numbers = std::move(numbers), separator = std::move(separator), call = std::move(call)]()
{
// Return error if there are no numbers in the collection
if (numbers.empty())
{
// Let's send the error reply message back to the client
auto reply = call.createErrorReply({"org.sdbuscpp.Concatenator.Error", "No numbers provided"});
reply.send();
return;
}
std::string result;
for (auto number : numbers)
{
result += (result.empty() ? std::string() : separator) + std::to_string(number);
}
// Let's send the reply message back to the client
auto reply = call.createReply();
reply << result;
reply.send();
// Emit 'concatenated' signal (creating and emitting signals is thread-safe)
const char* interfaceName = "org.sdbuscpp.Concatenator";
auto signal = g_concatenator->createSignal(interfaceName, "concatenated");
signal << result;
g_concatenator->emitSignal(signal);
}).detach();
}
```
There are a few slight differences compared to the synchronous version. Notice that we `std::move` the `call` message to the worker thread (btw we might also do input arguments deserialization in the worker thread, we don't have to do it in the current thread and then move input arguments to the worker thread...). We need the `call` message there to create the reply message once we have the (normal or error) result. Creating and sending replies, as well as creating and emitting signals is thread-safe by design. Also notice that, unlike in sync methods, sending back errors cannot be done by throwing `Error`, since we are now in the context of the worker thread, not that of the D-Bus dispatcher thread. Instead, we pass the `Error` object to the `createErrorReply()` method of the call message (this way of sending back errors, in addition to throwing, we can actually use also in classic synchronous D-Bus methods).
Method callback signature is the same in sync and async version. That means sdbus-c++ doesn't care how we execute our D-Bus method. We might very well in run-time decide whether we execute it synchronously, or whether (perhaps in case of longer, more complex calculations) we move the execution to a worker thread.
### Convenience API
Callbacks of async methods based on convenience sdbus-c++ API have slightly different signature. They take a result object parameter in addition to other input parameters. The requirements are:
* The result holder is of type `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.
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)`:
```c++
void concatenate(sdbus::Result<std::string>&& result, std::vector<int32_t> numbers, std::string separator) override
{
// Launch a thread for async execution...
std::thread([this, methodResult = std::move(result), numbers = std::move(numbers), separator = std::move(separator)]()
{
// Return error if there are no numbers in the collection
if (numbers.empty())
{
// Let's send the error reply message back to the client
methodResult.returnError({"org.sdbuscpp.Concatenator.Error", "No numbers provided"});
return;
}
std::string result;
for (auto number : numbers)
{
result += (result.empty() ? std::string() : separator) + std::to_string(number);
}
// Let's send the reply message back to the client
methodResult.returnReply(result);
// Emit the 'concatenated' signal with the resulting string
this->concatenated(result);
}).detach();
}
```
The `Result` is a convenience class that represents a future method result, and it is where we write the results (`returnReply()`) or an error (`returnError()`) which we want to send back to the client.
Registraion (`implementedAs()`) doesn't change. Nothing else needs to change.
### Marking server-side async methods in the IDL
sdbus-c++ stub generator can generate stub code for server-side async methods. We just need to annotate the method with the `annotate` element having the "org.freedesktop.DBus.Method.Async" name. The element value must be either "server" (async method on server-side only) or "clientserver" (async method on both client- and server-side):
```xml
<?xml version="1.0" encoding="UTF-8"?>
<node name="/org/sdbuscpp/concatenator">
<interface name="org.sdbuscpp.Concatenator">
<method name="concatenate">
<annotation name="org.freedesktop.DBus.Method.Async" value="server" />
<arg type="ai" name="numbers" direction="in" />
<arg type="s" name="separator" direction="in" />
<arg type="s" name="concatenatedString" direction="out" />
</method>
<signal name="concatenated">
<arg type="s" name="concatenatedString" />
</signal>
</interface>
</node>
```
For a real example of a server-side asynchronous D-Bus method, please look at sdbus-c++ [stress tests](/test/stresstests).
Asynchronous client-side methods
--------------------------------
sdbus-c++ also supports asynchronous approach at the client (the proxy) side. With this approach, we can issue a D-Bus method call without blocking current thread's execution while waiting for the reply. We go on doing other things, and when the reply comes, a given callback is invoked within the context of the D-Bus dispatcher thread.
### Lower-level API
Considering the Concatenator example based on lower-level API, if we wanted to call `concatenate` in an async way, we'd have to pass a callback to the proxy when issuing the call, and that callback gets invoked when the reply arrives:
```c++
int main(int argc, char *argv[])
{
/* ... */
auto callback = [](MethodReply& reply, const sdbus::Error* error)
{
if (error == nullptr) // No error
{
std::string result;
reply >> result;
std::cout << "Got concatenate result: " << result << std::endl;
}
else // We got a D-Bus error...
{
std::cerr << "Got concatenate error " << error->getName() << " with message " << error->getMessage() << std::endl;
}
}
// Invoke concatenate on given interface of the object
{
auto method = concatenatorProxy->createMethodCall(interfaceName, "concatenate");
method << numbers << separator;
concatenatorProxy->callMethod(method, callback);
// When the reply comes, we shall get "Got concatenate result 1:2:3" on the standard output
}
// Invoke concatenate again, this time with no numbers and we shall get an error
{
auto method = concatenatorProxy->createMethodCall(interfaceName, "concatenate");
method << std::vector<int>() << separator;
concatenatorProxy->callMethod(method, callback);
// When the reply comes, we shall get concatenation error message on the standard error output
}
/* ... */
return 0;
}
```
The callback is a void-returning function taking two arguments: a reference to the reply message, and a pointer to the prospective `sdbus::Error` instance. Zero `Error` pointer means that no D-Bus error occurred while making the call, and the reply message contains valid reply. Non-zero `Error` pointer, however, points to the valid `Error` instance, meaning that an error occurred. Error name and message can then be read out by the client from that instance.
### Convenience API
On the convenience API level, the call statement starts with `callMethodAsync()`, and ends with `uponReplyInvoke()` that takes a callback handler. The callback is a void-returning function that takes at least one argument: pointer to the `sdbus::Error` instance. All subsequent arguments shall exactly reflect the D-Bus method output arguments. A concatenator example:
```c++
int main(int argc, char *argv[])
{
/* ... */
auto callback = [](const sdbus::Error* error, const std::string& concatenatedString)
{
if (error == nullptr) // No error
std::cout << "Got concatenate result: " << concatenatedString << std::endl;
else // We got a D-Bus error...
std::cerr << "Got concatenate error " << error->getName() << " with message " << error->getMessage() << std::endl;
}
// Invoke concatenate on given interface of the object
{
concatenatorProxy->callMethodAsync("concatenate").onInterface(interfaceName).withArguments(numbers, separator).uponReplyInvoke(callback);
// When the reply comes, we shall get "Got concatenate result 1:2:3" on the standard output
}
// Invoke concatenate again, this time with no numbers and we shall get an error
{
concatenatorProxy->callMethodAsync("concatenate").onInterface(interfaceName).withArguments(std::vector<int>{}, separator).uponReplyInvoke(callback);
// When the reply comes, we shall get concatenation error message on the standard error output
}
/* ... */
return 0;
}
```
When the `Error` pointer is zero, it means that no D-Bus error occurred while making the call, and subsequent arguments are valid D-Bus method return values. Non-zero `Error` pointer, however, points to the valid `Error` instance, meaning that an error occurred during the call (and subsequent arguments are simply default-constructed). Error name and message can then be read out by the client from `Error` instance.
### Marking client-side async methods in the IDL
sdbus-c++ stub generator can generate stub code for client-side async methods. We just need to annotate the method with the `annotate` element having the "org.freedesktop.DBus.Method.Async" name. The element value must be either "client" (async on the client-side only) or "clientserver" (async method on both client- and server-side):
```xml
<?xml version="1.0" encoding="UTF-8"?>
<node name="/org/sdbuscpp/concatenator">
<interface name="org.sdbuscpp.Concatenator">
<method name="concatenate">
<annotation name="org.freedesktop.DBus.Method.Async" value="client" />
<arg type="ai" name="numbers" direction="in" />
<arg type="s" name="separator" direction="in" />
<arg type="s" name="concatenatedString" direction="out" />
</method>
<signal name="concatenated">
<arg type="s" name="concatenatedString" />
</signal>
</interface>
</node>
```
For each client-side async method, a corresponding `on<MethodName>Reply` pure virtual function, where <MethodName> is capitalized D-Bus method name, is generated in the generated proxy class. This function is the callback invoked when the D-Bus method reply arrives, and must be provided a body by overriding it in the implementation class.
So in the specific example above, the stub generator will generate a `Concatenator_proxy` class similar to one shown in a [dedicated section above](#concatenator-client-glueh), with the difference that it will also generate an additional `virtual void onConcatenateReply(const sdbus::Error* error, const std::string& concatenatedString);` method, which we shall override in derived `ConcatenatorProxy`.
For a real example of a client-side asynchronous D-Bus method, please look at sdbus-c++ [stress tests](/test/stresstests).
Using D-Bus properties
----------------------

View File

@ -1,23 +0,0 @@
# Distribution
# Headers will be installed to $(includedir)/sdbus-c++ directory
HEADER_DIR = sdbus-c++
libsdbuscppdir = $(includedir)/$(HEADER_DIR)
libsdbuscpp_HEADERS = \
$(HEADER_DIR)/ConvenienceClasses.h \
$(HEADER_DIR)/ConvenienceClasses.inl \
$(HEADER_DIR)/Error.h \
$(HEADER_DIR)/IConnection.h \
$(HEADER_DIR)/Interfaces.h \
$(HEADER_DIR)/Introspection.h \
$(HEADER_DIR)/IObject.h \
$(HEADER_DIR)/IObjectProxy.h \
$(HEADER_DIR)/Message.h \
$(HEADER_DIR)/Types.h \
$(HEADER_DIR)/TypeTraits.h \
$(HEADER_DIR)/sdbus-c++.h
EXTRA_DIST = ($libsdbuscpp_HEADERS)
DISTCLEANFILES = Makefile Makefile.in

View File

@ -0,0 +1,139 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file AdaptorInterfaces.h
*
* Created on: Nov 8, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_ADAPTORINTERFACES_H_
#define SDBUS_CXX_ADAPTORINTERFACES_H_
#include <sdbus-c++/IObject.h>
#include <cassert>
#include <string>
#include <memory>
// Forward declarations
namespace sdbus {
class IConnection;
}
namespace sdbus {
/********************************************//**
* @class ObjectHolder
*
* ObjectHolder is a helper that simply owns and provides
* access to an object to other classes in the inheritance
* hierarchy of an object based on generated interface classes.
*
***********************************************/
class ObjectHolder
{
protected:
ObjectHolder(std::unique_ptr<IObject>&& object)
: object_(std::move(object))
{
}
const IObject& getObject() const
{
assert(object_ != nullptr);
return *object_;
}
IObject& getObject()
{
assert(object_ != nullptr);
return *object_;
}
private:
std::unique_ptr<IObject> object_;
};
/********************************************//**
* @class AdaptorInterfaces
*
* AdaptorInterfaces is a helper template class that joins all interface classes of a remote
* D-Bus object generated by sdbus-c++-xml2cpp to be used on the server (the adaptor) side,
* including some auxiliary classes. AdaptorInterfaces is the class that native-like object
* implementation classes written by users should inherit from and implement all pure virtual
* methods. So the _Interfaces template parameter is a list of sdbus-c++-xml2cpp-generated
* adaptor-side interface classes representing interfaces (with methods, signals and properties)
* of the D-Bus object.
*
* In the final adaptor class inherited from AdaptorInterfaces, it is necessary to finish
* adaptor registration in class constructor (finishRegistration();`), and, conversely,
* unregister the adaptor in class destructor (`unregister();`).
*
***********************************************/
template <typename... _Interfaces>
class AdaptorInterfaces
: protected ObjectHolder
, public _Interfaces...
{
public:
/*!
* @brief Creates object instance
*
* @param[in] connection D-Bus connection where the object will publish itself
* @param[in] objectPath Path of the D-Bus object
*
* For more information, consult @ref createObject(sdbus::IConnection&,std::string)
*/
AdaptorInterfaces(IConnection& connection, std::string objectPath)
: ObjectHolder(createObject(connection, std::move(objectPath)))
, _Interfaces(getObject())...
{
}
/*!
* @brief Finishes adaptor API registration and publishes the adaptor on the bus
*
* This function must be called in the constructor of the final adaptor class that implements AdaptorInterfaces.
*
* For more information, see underlying @ref IObject::finishRegistration()
*/
void registerAdaptor()
{
getObject().finishRegistration();
}
/*!
* @brief Unregisters adaptors's API and removes it from the bus
*
* This function must be called in the destructor of the final adaptor class that implements AdaptorInterfaces.
*
* For more information, see underlying @ref IObject::unregister()
*/
void unregisterAdaptor()
{
getObject().unregister();
}
protected:
using base_type = AdaptorInterfaces;
};
}
#endif /* SDBUS_CXX_ADAPTORINTERFACES_H_ */

View File

@ -1,7 +1,7 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file ConvenienceClasses.h
* @file ConvenienceApiClasses.h
*
* Created on: Jan 19, 2017
* Project: sdbus-c++
@ -23,17 +23,21 @@
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_CONVENIENCECLASSES_H_
#define SDBUS_CXX_CONVENIENCECLASSES_H_
#ifndef SDBUS_CXX_CONVENIENCEAPICLASSES_H_
#define SDBUS_CXX_CONVENIENCEAPICLASSES_H_
#include <sdbus-c++/Message.h>
#include <sdbus-c++/TypeTraits.h>
#include <sdbus-c++/Flags.h>
#include <string>
#include <type_traits>
// Forward declarations
namespace sdbus {
class IObject;
class IObjectProxy;
class IProxy;
class Variant;
class Error;
}
namespace sdbus {
@ -42,13 +46,28 @@ namespace sdbus {
{
public:
MethodRegistrator(IObject& object, const std::string& methodName);
MethodRegistrator(MethodRegistrator&& other) = default;
MethodRegistrator& operator=(MethodRegistrator&& other) = default;
~MethodRegistrator() noexcept(false);
MethodRegistrator& onInterface(const std::string& interfaceName);
template <typename _Function> void implementedAs(_Function&& callback);
template <typename _Function>
std::enable_if_t<!is_async_method_v<_Function>, MethodRegistrator&> implementedAs(_Function&& callback);
template <typename _Function>
std::enable_if_t<is_async_method_v<_Function>, MethodRegistrator&> implementedAs(_Function&& callback);
MethodRegistrator& markAsDeprecated();
MethodRegistrator& markAsPrivileged();
MethodRegistrator& withNoReply();
private:
IObject& object_;
const std::string& methodName_;
std::string interfaceName_;
std::string inputSignature_;
std::string outputSignature_;
method_callback methodCallback_;
Flags flags_;
int exceptions_{}; // Number of active exceptions when SignalRegistrator is constructed
};
class SignalRegistrator
@ -58,14 +77,17 @@ namespace sdbus {
SignalRegistrator(SignalRegistrator&& other) = default;
SignalRegistrator& operator=(SignalRegistrator&& other) = default;
~SignalRegistrator() noexcept(false);
SignalRegistrator& onInterface(std::string interfaceName);
template <typename... _Args> void withParameters();
template <typename... _Args> SignalRegistrator& withParameters();
SignalRegistrator& markAsDeprecated();
private:
IObject& object_;
const std::string& signalName_;
std::string interfaceName_;
std::string signalSignature_;
Flags flags_;
int exceptions_{}; // Number of active exceptions when SignalRegistrator is constructed
};
@ -76,9 +98,13 @@ namespace sdbus {
PropertyRegistrator(PropertyRegistrator&& other) = default;
PropertyRegistrator& operator=(PropertyRegistrator&& other) = default;
~PropertyRegistrator() noexcept(false);
PropertyRegistrator& onInterface(const std::string& interfaceName);
template <typename _Function> PropertyRegistrator& withGetter(_Function&& callback);
template <typename _Function> PropertyRegistrator& withSetter(_Function&& callback);
PropertyRegistrator& markAsDeprecated();
PropertyRegistrator& markAsPrivileged();
PropertyRegistrator& withUpdateBehavior(Flags::PropertyUpdateBehaviorFlags behavior);
private:
IObject& object_;
@ -87,9 +113,30 @@ namespace sdbus {
std::string propertySignature_;
property_get_callback getter_;
property_set_callback setter_;
Flags flags_;
int exceptions_{}; // Number of active exceptions when PropertyRegistrator is constructed
};
class InterfaceFlagsSetter
{
public:
InterfaceFlagsSetter(IObject& object, const std::string& interfaceName);
InterfaceFlagsSetter(InterfaceFlagsSetter&& other) = default;
InterfaceFlagsSetter& operator=(InterfaceFlagsSetter&& other) = default;
~InterfaceFlagsSetter() noexcept(false);
InterfaceFlagsSetter& markAsDeprecated();
InterfaceFlagsSetter& markAsPrivileged();
InterfaceFlagsSetter& withNoReplyMethods();
InterfaceFlagsSetter& withPropertyUpdateBehavior(Flags::PropertyUpdateBehaviorFlags behavior);
private:
IObject& object_;
const std::string& interfaceName_;
Flags flags_;
int exceptions_{}; // Number of active exceptions when InterfaceFlagsSetter is constructed
};
class SignalEmitter
{
public:
@ -103,14 +150,14 @@ namespace sdbus {
private:
IObject& object_;
const std::string& signalName_;
Message signal_;
Signal signal_;
int exceptions_{}; // Number of active exceptions when SignalEmitter is constructed
};
class MethodInvoker
{
public:
MethodInvoker(IObjectProxy& objectProxy, const std::string& methodName);
MethodInvoker(IProxy& proxy, const std::string& methodName);
MethodInvoker(MethodInvoker&& other) = default;
MethodInvoker& operator=(MethodInvoker&& other) = default;
~MethodInvoker() noexcept(false);
@ -119,23 +166,39 @@ namespace sdbus {
template <typename... _Args> MethodInvoker& withArguments(_Args&&... args);
template <typename... _Args> void storeResultsTo(_Args&... args);
void dontExpectReply();
private:
IObjectProxy& objectProxy_;
IProxy& proxy_;
const std::string& methodName_;
Message method_;
MethodCall method_;
int exceptions_{}; // Number of active exceptions when MethodInvoker is constructed
bool methodCalled_{};
};
class AsyncMethodInvoker
{
public:
AsyncMethodInvoker(IProxy& proxy, const std::string& methodName);
AsyncMethodInvoker& onInterface(const std::string& interfaceName);
template <typename... _Args> AsyncMethodInvoker& withArguments(_Args&&... args);
template <typename _Function> void uponReplyInvoke(_Function&& callback);
private:
IProxy& proxy_;
const std::string& methodName_;
AsyncMethodCall method_;
};
class SignalSubscriber
{
public:
SignalSubscriber(IObjectProxy& objectProxy, const std::string& signalName);
SignalSubscriber(IProxy& proxy, const std::string& signalName);
SignalSubscriber& onInterface(const std::string& interfaceName);
template <typename _Function> void call(_Function&& callback);
private:
IObjectProxy& objectProxy_;
IProxy& proxy_;
std::string signalName_;
std::string interfaceName_;
};
@ -143,27 +206,27 @@ namespace sdbus {
class PropertyGetter
{
public:
PropertyGetter(IObjectProxy& objectProxy, const std::string& propertyName);
PropertyGetter(IProxy& proxy, const std::string& propertyName);
sdbus::Variant onInterface(const std::string& interfaceName);
private:
IObjectProxy& objectProxy_;
IProxy& proxy_;
std::string propertyName_;
};
class PropertySetter
{
public:
PropertySetter(IObjectProxy& objectProxy, const std::string& propertyName);
PropertySetter(IProxy& proxy, const std::string& propertyName);
PropertySetter& onInterface(const std::string& interfaceName);
template <typename _Value> void toValue(const _Value& value);
private:
IObjectProxy& objectProxy_;
IProxy& proxy_;
const std::string& propertyName_;
std::string interfaceName_;
};
}
#endif /* SDBUS_CXX_CONVENIENCECLASSES_H_ */
#endif /* SDBUS_CXX_CONVENIENCEAPICLASSES_H_ */

View File

@ -1,7 +1,7 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file ConvenienceClasses.inl
* @file ConvenienceApiClasses.inl
*
* Created on: Dec 19, 2016
* Project: sdbus-c++
@ -23,12 +23,13 @@
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CPP_CONVENIENCECLASSES_INL_
#define SDBUS_CPP_CONVENIENCECLASSES_INL_
#ifndef SDBUS_CPP_CONVENIENCEAPICLASSES_INL_
#define SDBUS_CPP_CONVENIENCEAPICLASSES_INL_
#include <sdbus-c++/IObject.h>
#include <sdbus-c++/IObjectProxy.h>
#include <sdbus-c++/IProxy.h>
#include <sdbus-c++/Message.h>
#include <sdbus-c++/MethodResult.h>
#include <sdbus-c++/Types.h>
#include <sdbus-c++/TypeTraits.h>
#include <sdbus-c++/Error.h>
@ -38,12 +39,37 @@
namespace sdbus {
// Moved into the library to isolate from C++17 dependency
/*
inline MethodRegistrator::MethodRegistrator(IObject& object, const std::string& methodName)
: object_(object)
, methodName_(methodName)
, exceptions_(std::uncaught_exceptions()) // Needs C++17
{
}
inline MethodRegistrator::~MethodRegistrator() noexcept(false) // since C++11, destructors must
{ // explicitly be allowed to throw
// Don't register the method if MethodRegistrator threw an exception in one of its methods
if (std::uncaught_exceptions() != exceptions_)
return;
SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when registering a DBus method", EINVAL);
SDBUS_THROW_ERROR_IF(!methodCallback_, "Method handler not specified when registering a DBus method", EINVAL);
// registerMethod() can throw. But as the MethodRegistrator shall always be used as an unnamed,
// temporary object, i.e. not as a stack-allocated object, the double-exception situation
// shall never happen. I.e. it should not happen that this destructor is directly called
// in the stack-unwinding process of another flying exception (which would lead to immediate
// termination). It can be called indirectly in the destructor of another object, but that's
// fine and safe provided that the caller catches exceptions thrown from here.
// Therefore, we can allow registerMethod() to throw even if we are in the destructor.
// Bottomline is, to be on the safe side, the caller must take care of catching and reacting
// to the exception thrown from here if the caller is a destructor itself.
object_.registerMethod(interfaceName_, methodName_, inputSignature_, outputSignature_, std::move(methodCallback_), flags_);
}
*/
inline MethodRegistrator& MethodRegistrator::onInterface(const std::string& interfaceName)
{
interfaceName_ = interfaceName;
@ -52,32 +78,74 @@ namespace sdbus {
}
template <typename _Function>
inline void MethodRegistrator::implementedAs(_Function&& callback)
inline std::enable_if_t<!is_async_method_v<_Function>, MethodRegistrator&> MethodRegistrator::implementedAs(_Function&& callback)
{
SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when registering a DBus method", EINVAL);
object_.registerMethod( interfaceName_
, methodName_
, signature_of_function_input_arguments<_Function>::str()
, signature_of_function_output_arguments<_Function>::str()
, [callback = std::forward<_Function>(callback)](Message& msg, Message& reply)
inputSignature_ = signature_of_function_input_arguments<_Function>::str();
outputSignature_ = signature_of_function_output_arguments<_Function>::str();
methodCallback_ = [callback = std::forward<_Function>(callback)](MethodCall call)
{
// Create a tuple of callback input arguments' types, which will be used
// as a storage for the argument values deserialized from the message.
tuple_of_function_input_arg_types_t<_Function> inputArgs;
// Deserialize input arguments from the message into the tuple
msg >> inputArgs;
call >> inputArgs;
// Invoke callback with input arguments from the tuple.
// For callbacks returning a non-void value, `apply' also returns that value.
// For callbacks returning void, `apply' returns an empty tuple.
auto ret = apply(callback, inputArgs); // We don't yet have C++17's std::apply :-(
auto ret = sdbus::apply(callback, inputArgs); // We don't yet have C++17's std::apply :-(
// The return value is stored to the reply message.
// In case of void functions, ret is an empty tuple and thus nothing is stored.
auto reply = call.createReply();
reply << ret;
});
reply.send();
};
return *this;
}
template <typename _Function>
inline std::enable_if_t<is_async_method_v<_Function>, MethodRegistrator&> MethodRegistrator::implementedAs(_Function&& callback)
{
inputSignature_ = signature_of_function_input_arguments<_Function>::str();
outputSignature_ = signature_of_function_output_arguments<_Function>::str();
methodCallback_ = [callback = std::forward<_Function>(callback)](MethodCall call)
{
// Create a tuple of callback input arguments' types, which will be used
// as a storage for the argument values deserialized from the message.
tuple_of_function_input_arg_types_t<_Function> inputArgs;
// Deserialize input arguments from the message into the tuple.
call >> inputArgs;
// Invoke callback with input arguments from the tuple.
sdbus::apply(callback, typename function_traits<_Function>::async_result_t{std::move(call)}, std::move(inputArgs));
};
return *this;
}
inline MethodRegistrator& MethodRegistrator::markAsDeprecated()
{
flags_.set(Flags::DEPRECATED);
return *this;
}
inline MethodRegistrator& MethodRegistrator::markAsPrivileged()
{
flags_.set(Flags::PRIVILEGED);
return *this;
}
inline MethodRegistrator& MethodRegistrator::withNoReply()
{
flags_.set(Flags::METHOD_NO_REPLY);
return *this;
}
@ -120,9 +188,18 @@ namespace sdbus {
}
template <typename... _Args>
inline void SignalRegistrator::withParameters()
inline SignalRegistrator& SignalRegistrator::withParameters()
{
signalSignature_ = signature_of_function_input_arguments<void(_Args...)>::str();
return *this;
}
inline SignalRegistrator& SignalRegistrator::markAsDeprecated()
{
flags_.set(Flags::DEPRECATED);
return *this;
}
@ -210,6 +287,87 @@ namespace sdbus {
return *this;
}
inline PropertyRegistrator& PropertyRegistrator::markAsDeprecated()
{
flags_.set(Flags::DEPRECATED);
return *this;
}
inline PropertyRegistrator& PropertyRegistrator::markAsPrivileged()
{
flags_.set(Flags::PRIVILEGED);
return *this;
}
inline PropertyRegistrator& PropertyRegistrator::withUpdateBehavior(Flags::PropertyUpdateBehaviorFlags behavior)
{
flags_.set(behavior);
return *this;
}
// Moved into the library to isolate from C++17 dependency
/*
inline InterfaceFlagsSetter::InterfaceFlagsSetter(IObject& object, const std::string& interfaceName)
: object_(object)
, interfaceName_(interfaceName)
, exceptions_(std::uncaught_exceptions())
{
}
inline InterfaceFlagsSetter::~InterfaceFlagsSetter() noexcept(false) // since C++11, destructors must
{ // explicitly be allowed to throw
// Don't set any flags if InterfaceFlagsSetter threw an exception in one of its methods
if (std::uncaught_exceptions() != exceptions_)
return;
SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when setting its flags", EINVAL);
// setInterfaceFlags() can throw. But as the InterfaceFlagsSetter shall always be used as an unnamed,
// temporary object, i.e. not as a stack-allocated object, the double-exception situation
// shall never happen. I.e. it should not happen that this destructor is directly called
// in the stack-unwinding process of another flying exception (which would lead to immediate
// termination). It can be called indirectly in the destructor of another object, but that's
// fine and safe provided that the caller catches exceptions thrown from here.
// Therefore, we can allow setInterfaceFlags() to throw even if we are in the destructor.
// Bottomline is, to be on the safe side, the caller must take care of catching and reacting
// to the exception thrown from here if the caller is a destructor itself.
object_.setInterfaceFlags( std::move(interfaceName_)
, std::move(flags_) );
}
*/
inline InterfaceFlagsSetter& InterfaceFlagsSetter::markAsDeprecated()
{
flags_.set(Flags::DEPRECATED);
return *this;
}
inline InterfaceFlagsSetter& InterfaceFlagsSetter::markAsPrivileged()
{
flags_.set(Flags::PRIVILEGED);
return *this;
}
inline InterfaceFlagsSetter& InterfaceFlagsSetter::withNoReplyMethods()
{
flags_.set(Flags::METHOD_NO_REPLY);
return *this;
}
inline InterfaceFlagsSetter& InterfaceFlagsSetter::withPropertyUpdateBehavior(Flags::PropertyUpdateBehaviorFlags behavior)
{
flags_.set(behavior);
return *this;
}
// Moved into the library to isolate from C++17 dependency
/*
@ -260,8 +418,8 @@ namespace sdbus {
// Moved into the library to isolate from C++17 dependency
/*
inline MethodInvoker::MethodInvoker(IObjectProxy& objectProxy, const std::string& methodName)
: objectProxy_(objectProxy)
inline MethodInvoker::MethodInvoker(IProxy& proxyObject, const std::string& methodName)
: proxy_(proxy)
, methodName_(methodName)
, exceptions_(std::uncaught_exceptions())
{
@ -286,13 +444,13 @@ namespace sdbus {
// Therefore, we can allow callMethod() to throw even if we are in the destructor.
// Bottomline is, to be on the safe side, the caller must take care of catching and reacting
// to the exception thrown from here if the caller is a destructor itself.
objectProxy_.callMethod(method_);
proxy_.callMethod(method_);
}
*/
inline MethodInvoker& MethodInvoker::onInterface(const std::string& interfaceName)
{
method_ = objectProxy_.createMethodCall(interfaceName, methodName_);
method_ = proxy_.createMethodCall(interfaceName, methodName_);
return *this;
}
@ -312,15 +470,66 @@ namespace sdbus {
{
SDBUS_THROW_ERROR_IF(!method_.isValid(), "DBus interface not specified when calling a DBus method", EINVAL);
auto reply = objectProxy_.callMethod(method_);
auto reply = proxy_.callMethod(method_);
methodCalled_ = true;
detail::deserialize_pack(reply, args...);
}
inline void MethodInvoker::dontExpectReply()
{
SDBUS_THROW_ERROR_IF(!method_.isValid(), "DBus interface not specified when calling a DBus method", EINVAL);
inline SignalSubscriber::SignalSubscriber(IObjectProxy& objectProxy, const std::string& signalName)
: objectProxy_(objectProxy)
method_.dontExpectReply();
}
inline AsyncMethodInvoker::AsyncMethodInvoker(IProxy& proxy, const std::string& methodName)
: proxy_(proxy)
, methodName_(methodName)
{
}
inline AsyncMethodInvoker& AsyncMethodInvoker::onInterface(const std::string& interfaceName)
{
method_ = proxy_.createAsyncMethodCall(interfaceName, methodName_);
return *this;
}
template <typename... _Args>
inline AsyncMethodInvoker& AsyncMethodInvoker::withArguments(_Args&&... args)
{
SDBUS_THROW_ERROR_IF(!method_.isValid(), "DBus interface not specified when calling a DBus method", EINVAL);
detail::serialize_pack(method_, std::forward<_Args>(args)...);
return *this;
}
template <typename _Function>
void AsyncMethodInvoker::uponReplyInvoke(_Function&& callback)
{
SDBUS_THROW_ERROR_IF(!method_.isValid(), "DBus interface not specified when calling a DBus method", EINVAL);
proxy_.callMethod(method_, [callback = std::forward<_Function>(callback)](MethodReply& reply, const Error* error)
{
// Create a tuple of callback input arguments' types, which will be used
// as a storage for the argument values deserialized from the message.
tuple_of_function_input_arg_types_t<_Function> args;
// Deserialize input arguments from the message into the tuple (if no error occurred).
if (error == nullptr)
reply >> args;
// Invoke callback with input arguments from the tuple.
sdbus::apply(callback, error, args); // TODO: Use std::apply when switching to full C++17 support
});
}
inline SignalSubscriber::SignalSubscriber(IProxy& proxy, const std::string& signalName)
: proxy_(proxy)
, signalName_(signalName)
{
}
@ -337,9 +546,9 @@ namespace sdbus {
{
SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when subscribing to a signal", EINVAL);
objectProxy_.registerSignalHandler( interfaceName_
, signalName_
, [callback = std::forward<_Function>(callback)](Message& signal)
proxy_.registerSignalHandler( interfaceName_
, signalName_
, [callback = std::forward<_Function>(callback)](Signal& signal)
{
// Create a tuple of callback input arguments' types, which will be used
// as a storage for the argument values deserialized from the signal message.
@ -349,13 +558,13 @@ namespace sdbus {
signal >> signalArgs;
// Invoke callback with input arguments from the tuple.
apply(callback, signalArgs); // We don't yet have C++17's std::apply :-(
sdbus::apply(callback, signalArgs); // We don't yet have C++17's std::apply :-(
});
}
inline PropertyGetter::PropertyGetter(IObjectProxy& objectProxy, const std::string& propertyName)
: objectProxy_(objectProxy)
inline PropertyGetter::PropertyGetter(IProxy& proxy, const std::string& propertyName)
: proxy_(proxy)
, propertyName_(propertyName)
{
}
@ -363,7 +572,7 @@ namespace sdbus {
inline sdbus::Variant PropertyGetter::onInterface(const std::string& interfaceName)
{
sdbus::Variant var;
objectProxy_
proxy_
.callMethod("Get")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(interfaceName, propertyName_)
@ -372,8 +581,8 @@ namespace sdbus {
}
inline PropertySetter::PropertySetter(IObjectProxy& objectProxy, const std::string& propertyName)
: objectProxy_(objectProxy)
inline PropertySetter::PropertySetter(IProxy& proxy, const std::string& propertyName)
: proxy_(proxy)
, propertyName_(propertyName)
{
}
@ -390,7 +599,7 @@ namespace sdbus {
{
SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when setting a property", EINVAL);
objectProxy_
proxy_
.callMethod("Set")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(interfaceName_, propertyName_, sdbus::Variant{value});
@ -398,4 +607,4 @@ namespace sdbus {
}
#endif /* SDBUS_CPP_CONVENIENCECLASSES_INL_ */
#endif /* SDBUS_CPP_CONVENIENCEAPICLASSES_INL_ */

15
include/sdbus-c++/Error.h Executable file → Normal file
View File

@ -1,7 +1,7 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file ConvenienceClasses.h
* @file Error.h
*
* Created on: Nov 8, 2016
* Project: sdbus-c++
@ -57,6 +57,11 @@ namespace sdbus {
return message_;
}
bool isValid() const
{
return !getName().empty();
}
private:
std::string name_;
std::string message_;
@ -65,12 +70,12 @@ namespace sdbus {
sdbus::Error createError(int errNo, const std::string& customMsg);
}
#define SDBUS_THROW_ERROR(_MSG, _ERRNO) \
throw sdbus::createError((_ERRNO), (_MSG)) \
#define SDBUS_THROW_ERROR(_MSG, _ERRNO) \
throw sdbus::createError((_ERRNO), (_MSG)) \
/**/
#define SDBUS_THROW_ERROR_IF(_COND, _MSG, _ERRNO) \
if (_COND) SDBUS_THROW_ERROR((_MSG), (_ERRNO)) \
#define SDBUS_THROW_ERROR_IF(_COND, _MSG, _ERRNO) \
if (!(_COND)) ; else SDBUS_THROW_ERROR((_MSG), (_ERRNO)) \
/**/
#endif /* SDBUS_CXX_ERROR_H_ */

98
include/sdbus-c++/Flags.h Normal file
View File

@ -0,0 +1,98 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file Flags.h
*
* Created on: Dec 31, 2018
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_FLAGS_H_
#define SDBUS_CXX_FLAGS_H_
#include <bitset>
#include <cstdint>
namespace sdbus {
// D-Bus interface, method, signal or property flags
class Flags
{
public:
enum GeneralFlags : uint8_t
{ DEPRECATED = 0
, METHOD_NO_REPLY = 1
, PRIVILEGED = 2
};
enum PropertyUpdateBehaviorFlags : uint8_t
{ EMITS_CHANGE_SIGNAL = 3
, EMITS_INVALIDATION_SIGNAL = 4
, EMITS_NO_SIGNAL = 5
, CONST_PROPERTY_VALUE = 6
};
enum : uint8_t
{ FLAG_COUNT = 7
};
Flags()
{
// EMITS_CHANGE_SIGNAL is on by default
flags_.set(EMITS_CHANGE_SIGNAL, true);
}
void set(GeneralFlags flag, bool value = true)
{
flags_.set(flag, value);
}
void set(PropertyUpdateBehaviorFlags flag, bool value = true)
{
flags_.set(EMITS_CHANGE_SIGNAL, false);
flags_.set(EMITS_INVALIDATION_SIGNAL, false);
flags_.set(EMITS_NO_SIGNAL, false);
flags_.set(CONST_PROPERTY_VALUE, false);
flags_.set(flag, value);
}
bool test(GeneralFlags flag) const
{
return flags_.test(flag);
}
bool test(PropertyUpdateBehaviorFlags flag) const
{
return flags_.test(flag);
}
uint64_t toSdBusInterfaceFlags() const;
uint64_t toSdBusMethodFlags() const;
uint64_t toSdBusSignalFlags() const;
uint64_t toSdBusPropertyFlags() const;
uint64_t toSdBusWritablePropertyFlags() const;
private:
std::bitset<FLAG_COUNT> flags_;
};
}
#endif /* SDBUS_CXX_FLAGS_H_ */

View File

@ -38,8 +38,8 @@ namespace sdbus {
* An interface to D-Bus bus connection. Incorporates implementation
* of both synchronous and asynchronous processing loop.
*
* All methods throw @sdbus::Error in case of failure. The class is
* thread-aware, but not thread-safe.
* All methods throw sdbus::Error in case of failure. All methods in
* this class are thread-aware, but not thread-safe.
*
***********************************************/
class IConnection
@ -67,7 +67,7 @@ namespace sdbus {
* @brief Enters the D-Bus processing loop
*
* The incoming D-Bus messages are processed in the loop. The method
* blocks indefinitely, until unblocked via @leaveProcessingLoop.
* blocks indefinitely, until unblocked via leaveProcessingLoop.
*
* @throws sdbus::Error in case of failure
*/
@ -76,7 +76,7 @@ namespace sdbus {
/*!
* @brief Enters the D-Bus processing loop in a separate thread
*
* The same as @enterProcessingLoop, except that it doesn't block
* The same as enterProcessingLoop, except that it doesn't block
* because it runs the loop in a separate thread managed internally.
*/
virtual void enterProcessingLoopAsync() = 0;

96
include/sdbus-c++/IObject.h Executable file → Normal file
View File

@ -26,15 +26,16 @@
#ifndef SDBUS_CXX_IOBJECT_H_
#define SDBUS_CXX_IOBJECT_H_
#include <sdbus-c++/ConvenienceClasses.h>
#include <sdbus-c++/ConvenienceApiClasses.h>
#include <sdbus-c++/TypeTraits.h>
#include <sdbus-c++/Flags.h>
#include <functional>
#include <string>
#include <memory>
// Forward declarations
namespace sdbus {
class Message;
class Signal;
class IConnection;
}
@ -43,11 +44,14 @@ namespace sdbus {
/********************************************//**
* @class IObject
*
* An interface to D-Bus object. Provides API for registration
* of methods, signals, properties, and for emitting signals.
* IObject class represents a D-Bus object instance identified by a specific object path.
* D-Bus object provides its interfaces, methods, signals and properties on a bus
* identified by a specific bus name.
*
* All methods throw @c sdbus::Error in case of failure. The class is
* thread-aware, but not thread-safe.
* All IObject member methods throw @c sdbus::Error in case of D-Bus or sdbus-c++ error.
* The IObject class has been designed as thread-aware. However, the operation of
* creating and sending asynchronous method replies, as well as creating and emitting
* signals, is thread-safe by design.
*
***********************************************/
class IObject
@ -61,6 +65,7 @@ namespace sdbus {
* @param[in] inputSignature D-Bus signature of method input parameters
* @param[in] outputSignature D-Bus signature of method output parameters
* @param[in] methodCallback Callback that implements the body of the method
* @param[in] flags D-Bus method flags (privileged, deprecated, or no reply)
*
* @throws sdbus::Error in case of failure
*/
@ -68,7 +73,8 @@ namespace sdbus {
, const std::string& methodName
, const std::string& inputSignature
, const std::string& outputSignature
, method_callback methodCallback ) = 0;
, method_callback methodCallback
, Flags flags = {} ) = 0;
/*!
* @brief Registers signal that the object will emit on D-Bus
@ -76,12 +82,14 @@ namespace sdbus {
* @param[in] interfaceName Name of an interface that the signal will fall under
* @param[in] signalName Name of the signal
* @param[in] signature D-Bus signature of signal parameters
* @param[in] flags D-Bus signal flags (deprecated)
*
* @throws sdbus::Error in case of failure
*/
virtual void registerSignal( const std::string& interfaceName
, const std::string& signalName
, const std::string& signature ) = 0;
, const std::string& signature
, Flags flags = {} ) = 0;
/*!
* @brief Registers read-only property that the object will provide on D-Bus
@ -90,13 +98,15 @@ namespace sdbus {
* @param[in] propertyName Name of the property
* @param[in] signature D-Bus signature of property parameters
* @param[in] getCallback Callback that implements the body of the property getter
* @param[in] flags D-Bus property flags (deprecated, property update behavior)
*
* @throws sdbus::Error in case of failure
*/
virtual void registerProperty( const std::string& interfaceName
, const std::string& propertyName
, const std::string& signature
, property_get_callback getCallback ) = 0;
, property_get_callback getCallback
, Flags flags = {} ) = 0;
/*!
* @brief Registers read/write property that the object will provide on D-Bus
@ -106,6 +116,7 @@ namespace sdbus {
* @param[in] signature D-Bus signature of property parameters
* @param[in] getCallback Callback that implements the body of the property getter
* @param[in] setCallback Callback that implements the body of the property setter
* @param[in] flags D-Bus property flags (deprecated, property update behavior)
*
* @throws sdbus::Error in case of failure
*/
@ -113,18 +124,41 @@ namespace sdbus {
, const std::string& propertyName
, const std::string& signature
, property_get_callback getCallback
, property_set_callback setCallback ) = 0;
, property_set_callback setCallback
, Flags flags = {} ) = 0;
/*!
* @brief Finishes the registration and exports object API on D-Bus
* @brief Sets flags for a given interface
*
* @param[in] interfaceName Name of an interface whose flags will be set
* @param[in] flags Flags to be set
*
* @throws sdbus::Error in case of failure
*/
virtual void setInterfaceFlags(const std::string& interfaceName, Flags flags) = 0;
/*!
* @brief Finishes object API registration and publishes the object on the bus
*
* The method exports all up to now registered methods, signals and properties on D-Bus.
* Must be called only once, after all methods, signals and properties have been registered.
* Must be called after all methods, signals and properties have been registered.
*
* @throws sdbus::Error in case of failure
*/
virtual void finishRegistration() = 0;
/*!
* @brief Unregisters object's API and removes object from the bus
*
* This method unregisters the object, its interfaces, methods, signals and properties
* from the bus. Unregistration is done automatically also in object's destructor. This
* method makes sense if, in the process of object removal, we need to make sure that
* callbacks are unregistered explicitly before the final destruction of the object instance.
*
* @throws sdbus::Error in case of failure
*/
virtual void unregister() = 0;
/*!
* @brief Creates a signal message
*
@ -138,7 +172,7 @@ namespace sdbus {
*
* @throws sdbus::Error in case of failure
*/
virtual Message createSignal(const std::string& interfaceName, const std::string& signalName) = 0;
virtual Signal createSignal(const std::string& interfaceName, const std::string& signalName) = 0;
/*!
* @brief Emits signal on D-Bus
@ -149,7 +183,14 @@ namespace sdbus {
*
* @throws sdbus::Error in case of failure
*/
virtual void emitSignal(const sdbus::Message& message) = 0;
virtual void emitSignal(const sdbus::Signal& message) = 0;
/*!
* @brief Provides D-Bus connection used by the object
*
* @return Reference to the D-Bus connection
*/
virtual sdbus::IConnection& getConnection() const = 0;
/*!
* @brief Registers method that the object will provide on D-Bus
@ -209,6 +250,23 @@ namespace sdbus {
*/
PropertyRegistrator registerProperty(const std::string& propertyName);
/*!
* @brief Sets flags (annotations) for a given interface
*
* @param[in] interfaceName Name of an interface whose flags will be set
* @return A helper object for convenient setting of Interface flags
*
* This is a high-level, convenience alternative to the other setInterfaceFlags overload.
*
* Example of use:
* @code
* object_.setInterfaceFlags("com.kistler.foo").markAsDeprecated().withPropertyUpdateBehavior(sdbus::Flags::EMITS_NO_SIGNAL);
* @endcode
*
* @throws sdbus::Error in case of failure
*/
InterfaceFlagsSetter setInterfaceFlags(const std::string& interfaceName);
/*!
* @brief Emits signal on D-Bus
*
@ -248,6 +306,11 @@ namespace sdbus {
return PropertyRegistrator(*this, std::move(propertyName));
}
inline InterfaceFlagsSetter IObject::setInterfaceFlags(const std::string& interfaceName)
{
return InterfaceFlagsSetter(*this, std::move(interfaceName));
}
inline SignalEmitter IObject::emitSignal(const std::string& signalName)
{
return SignalEmitter(*this, signalName);
@ -265,6 +328,9 @@ namespace sdbus {
* The provided connection will be used by the object to export methods,
* issue signals and provide properties.
*
* Creating a D-Bus object instance is (thread-)safe even upon the connection
* which is already running its processing loop.
*
* Code example:
* @code
* auto proxy = sdbus::createObject(connection, "/com/kistler/foo");
@ -274,6 +340,6 @@ namespace sdbus {
}
#include <sdbus-c++/ConvenienceClasses.inl>
#include <sdbus-c++/ConvenienceApiClasses.inl>
#endif /* SDBUS_CXX_IOBJECT_H_ */

View File

@ -1,249 +0,0 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file IObjectProxy.h
*
* Created on: Nov 8, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_IOBJECTPROXY_H_
#define SDBUS_CXX_IOBJECTPROXY_H_
#include <sdbus-c++/ConvenienceClasses.h>
#include <string>
#include <memory>
#include <functional>
// Forward declarations
namespace sdbus {
class Message;
class IConnection;
}
namespace sdbus {
/********************************************//**
* @class IObjectProxy
*
* An interface to D-Bus object proxy. Provides API for calling
* methods, getting/setting properties, and for registering to signals.
*
* All methods throw @c sdbus::Error in case of failure. The class is
* thread-aware, but not thread-safe.
*
***********************************************/
class IObjectProxy
{
public:
/*!
* @brief Creates a method call message
*
* @param[in] interfaceName Name of an interface that the method is defined under
* @param[in] methodName Name of the method
* @return A method call message message
*
* Serialize method arguments into the returned message and invoke the method by passing
* the message with serialized arguments to the @c callMethod function.
* Alternatively, use higher-level API @c callMethod(const std::string& methodName) defined below.
*
* @throws sdbus::Error in case of failure
*/
virtual Message createMethodCall(const std::string& interfaceName, const std::string& methodName) = 0;
/*!
* @brief Calls method on the proxied D-Bus object
*
* @param[in] message Message representing a method call
*
* Note: To avoid messing with messages, use higher-level API defined below.
*
* @throws sdbus::Error in case of failure
*/
virtual Message callMethod(const sdbus::Message& message) = 0;
/*!
* @brief Registers a handler for the desired signal emitted by the proxied D-Bus object
*
* @param[in] interfaceName Name of an interface that the signal belongs to
* @param[in] signalName Name of the signal
* @param[in] signalHandler Callback that implements the body of the signal handler
*
* @throws sdbus::Error in case of failure
*/
virtual void registerSignalHandler( const std::string& interfaceName
, const std::string& signalName
, signal_handler signalHandler ) = 0;
/*!
* @brief Finishes the registration of signal handlers
*
* The method physically subscribes to the desired signals.
* Must be called only once, after all signals have been registered already.
*
* @throws sdbus::Error in case of failure
*/
virtual void finishRegistration() = 0;
/*!
* @brief Calls method on the proxied D-Bus object
*
* @param[in] methodName Name of the method
* @return A helper object for convenient invocation of the method
*
* This is a high-level, convenience way of calling D-Bus methods that abstracts
* from the D-Bus message concept. Method arguments/return value are automatically (de)serialized
* in a message and D-Bus signatures automatically deduced from the provided native arguments
* and return values.
*
* Example of use:
* @code
* int result, a = ..., b = ...;
* object_.callMethod("multiply").onInterface(INTERFACE_NAME).withArguments(a, b).storeResultsTo(result);
* @endcode
*
* @throws sdbus::Error in case of failure
*/
MethodInvoker callMethod(const std::string& methodName);
/*!
* @brief Registers signal handler for a given signal of the proxied D-Bus object
*
* @param[in] signalName Name of the signal
* @return A helper object for convenient registration of the signal handler
*
* This is a high-level, convenience way of registering to D-Bus signals that abstracts
* from the D-Bus message concept. Signal arguments are automatically serialized
* in a message and D-Bus signatures automatically deduced from the parameters
* of the provided native signal callback.
*
* Example of use:
* @code
* object_.uponSignal("fooSignal").onInterface("com.kistler.foo").call([this](int arg1, double arg2){ this->onFooSignal(arg1, arg2); });
* @endcode
*
* @throws sdbus::Error in case of failure
*/
SignalSubscriber uponSignal(const std::string& signalName);
/*!
* @brief Gets value of a property of the proxied D-Bus object
*
* @param[in] propertyName Name of the property
* @return A helper object for convenient 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. sdbus::Variant is returned which shall then be converted
* to the real property type (implicit conversion is supported).
*
* Example of use:
* @code
* int state = object.getProperty("state").onInterface("com.kistler.foo");
* @endcode
*
* @throws sdbus::Error in case of failure
*/
PropertyGetter getProperty(const std::string& propertyName);
/*!
* @brief Sets value of a property of the proxied 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.
*
* Example of use:
* @code
* int state = ...;
* object_.setProperty("state").onInterface("com.kistler.foo").toValue(state);
* @endcode
*
* @throws sdbus::Error in case of failure
*/
PropertySetter setProperty(const std::string& propertyName);
virtual ~IObjectProxy() = 0;
};
inline MethodInvoker IObjectProxy::callMethod(const std::string& methodName)
{
return MethodInvoker(*this, methodName);
}
inline SignalSubscriber IObjectProxy::uponSignal(const std::string& signalName)
{
return SignalSubscriber(*this, signalName);
}
inline PropertyGetter IObjectProxy::getProperty(const std::string& propertyName)
{
return PropertyGetter(*this, propertyName);
}
inline PropertySetter IObjectProxy::setProperty(const std::string& propertyName)
{
return PropertySetter(*this, propertyName);
}
inline IObjectProxy::~IObjectProxy() {}
/*!
* @brief Creates object proxy 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
* @return Pointer to the object proxy instance
*
* The provided connection will be used by the proxy to issue calls against the object,
* and signals, if any, will be subscribed to on this connection.
*
* Code example:
* @code
* auto proxy = sdbus::createObjectProxy(connection, "com.kistler.foo", "/com/kistler/foo");
* @endcode
*/
std::unique_ptr<sdbus::IObjectProxy> createObjectProxy( sdbus::IConnection& connection
, std::string destination
, std::string objectPath );
/*!
* @brief Creates object proxy instance that uses its own D-Bus connection
*
* @param[in] destination Bus name that provides a D-Bus object
* @param[in] objectPath Path of the D-Bus object
* @return Pointer to the object proxy instance
*
* This factory overload creates a proxy that manages its own D-Bus connection(s).
*
* Code example:
* @code
* auto proxy = sdbus::createObjectProxy(connection, "com.kistler.foo", "/com/kistler/foo");
* @endcode
*/
std::unique_ptr<sdbus::IObjectProxy> createObjectProxy( std::string destination
, std::string objectPath );
}
#include <sdbus-c++/ConvenienceClasses.inl>
#endif /* SDBUS_CXX_IOBJECTPROXY_H_ */

361
include/sdbus-c++/IProxy.h Normal file
View File

@ -0,0 +1,361 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file IProxy.h
*
* Created on: Nov 8, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_IPROXY_H_
#define SDBUS_CXX_IPROXY_H_
#include <sdbus-c++/ConvenienceApiClasses.h>
#include <string>
#include <memory>
#include <functional>
// Forward declarations
namespace sdbus {
class MethodCall;
class AsyncMethodCall;
class MethodReply;
class IConnection;
}
namespace sdbus {
/********************************************//**
* @class IProxy
*
* IProxy class represents a proxy object, which is a convenient local object created
* to represent a remote D-Bus object in another process.
* The proxy enables calling methods on remote objects, receiving signals from remote
* objects, and getting/setting properties of remote objects.
*
* All IProxy member methods throw @c sdbus::Error in case of D-Bus or sdbus-c++ error.
* The IProxy class has been designed as thread-aware. However, the operation of
* creating and sending method calls (both synchronously and asynchronously) is
* thread-safe by design.
*
***********************************************/
class IProxy
{
public:
/*!
* @brief Creates a method call message
*
* @param[in] interfaceName Name of an interface that provides a given method
* @param[in] methodName Name of the method
* @return A method call message
*
* Serialize method arguments into the returned message and invoke the method by passing
* the message with serialized arguments to the @c callMethod function.
* Alternatively, use higher-level API @c callMethod(const std::string& methodName) defined below.
*
* @throws sdbus::Error in case of failure
*/
virtual MethodCall createMethodCall(const std::string& interfaceName, const std::string& methodName) = 0;
/*!
* @brief Creates an asynchronous method call message
*
* @param[in] interfaceName Name of an interface that provides a given method
* @param[in] methodName Name of the method
* @return A method call message
*
* Serialize method arguments into the returned message and invoke the method by passing
* the message with serialized arguments to the @c callMethod function.
* Alternatively, use higher-level API @c callMethodAsync(const std::string& methodName) defined below.
*
* @throws sdbus::Error in case of failure
*/
virtual AsyncMethodCall createAsyncMethodCall(const std::string& interfaceName, const std::string& methodName) = 0;
/*!
* @brief Calls method on the proxied D-Bus object
*
* @param[in] message Message representing a method call
* @return A method reply message
*
* Normally, the call is blocking, i.e. it waits for the remote method to finish with either
* a return value or an error.
*
* If the method call argument is set to not expect reply, the call will not wait for the remote
* method to finish, i.e. the call will be non-blocking, and the function will return an empty,
* invalid MethodReply object (representing void).
*
* Note: To avoid messing with messages, use higher-level API defined below.
*
* @throws sdbus::Error in case of failure
*/
virtual MethodReply callMethod(const MethodCall& message) = 0;
/*!
* @brief Calls method on the proxied D-Bus object asynchronously
*
* @param[in] message Message representing an async method call
* @param[in] asyncReplyCallback Handler for the async reply
*
* The call is non-blocking. It doesn't wait for the reply. Once the reply arrives,
* the provided async reply handler will get invoked from the context of the connection
* event loop processing thread.
*
* Note: To avoid messing with messages, use higher-level API defined below.
*
* @throws sdbus::Error in case of failure
*/
virtual void callMethod(const AsyncMethodCall& message, async_reply_handler asyncReplyCallback) = 0;
/*!
* @brief Registers a handler for the desired signal emitted by the proxied D-Bus object
*
* @param[in] interfaceName Name of an interface that the signal belongs to
* @param[in] signalName Name of the signal
* @param[in] signalHandler Callback that implements the body of the signal handler
*
* @throws sdbus::Error in case of failure
*/
virtual void registerSignalHandler( const std::string& interfaceName
, const std::string& signalName
, signal_handler signalHandler ) = 0;
/*!
* @brief Finishes the registration of signal handlers
*
* The method physically subscribes to the desired signals.
* Must be called only once, after all signals have been registered already.
*
* @throws sdbus::Error in case of failure
*/
virtual void finishRegistration() = 0;
/*!
* @brief Unregisters proxy's signal handlers and stops receving replies to pending async calls
*
* Unregistration is done automatically also in proxy's destructor. This method makes
* sense if, in the process of proxy removal, we need to make sure that callbacks
* are unregistered explicitly before the final destruction of the proxy instance.
*
* @throws sdbus::Error in case of failure
*/
virtual void unregister() = 0;
/*!
* @brief Calls method on the proxied D-Bus object
*
* @param[in] methodName Name of the method
* @return A helper object for convenient invocation of the method
*
* This is a high-level, convenience way of calling D-Bus methods that abstracts
* from the D-Bus message concept. Method arguments/return value are automatically (de)serialized
* in a message and D-Bus signatures automatically deduced from the provided native arguments
* and return values.
*
* Example of use:
* @code
* int result, a = ..., b = ...;
* object_.callMethod("multiply").onInterface(INTERFACE_NAME).withArguments(a, b).storeResultsTo(result);
* @endcode
*
* @throws sdbus::Error in case of failure
*/
MethodInvoker callMethod(const std::string& methodName);
/*!
* @brief Calls method on the proxied D-Bus object asynchronously
*
* @param[in] methodName Name of the method
* @return A helper object for convenient asynchronous invocation of the method
*
* This is a high-level, convenience way of calling D-Bus methods that abstracts
* from the D-Bus message concept. Method arguments/return value are automatically (de)serialized
* in a message and D-Bus signatures automatically deduced from the provided native arguments
* and return values.
*
* Example of use:
* @code
* int a = ..., b = ...;
* object_.callMethodAsync("multiply").onInterface(INTERFACE_NAME).withArguments(a, b).uponReplyInvoke([](int result)
* {
* std::cout << "Got result of multiplying " << a << " and " << b << ": " << result << std::endl;
* });
* @endcode
*
* @throws sdbus::Error in case of failure
*/
AsyncMethodInvoker callMethodAsync(const std::string& methodName);
/*!
* @brief Registers signal handler for a given signal of the proxied D-Bus object
*
* @param[in] signalName Name of the signal
* @return A helper object for convenient registration of the signal handler
*
* This is a high-level, convenience way of registering to D-Bus signals that abstracts
* from the D-Bus message concept. Signal arguments are automatically serialized
* in a message and D-Bus signatures automatically deduced from the parameters
* of the provided native signal callback.
*
* Example of use:
* @code
* object_.uponSignal("fooSignal").onInterface("com.kistler.foo").call([this](int arg1, double arg2){ this->onFooSignal(arg1, arg2); });
* @endcode
*
* @throws sdbus::Error in case of failure
*/
SignalSubscriber uponSignal(const std::string& signalName);
/*!
* @brief Gets value of a property of the proxied D-Bus object
*
* @param[in] propertyName Name of the property
* @return A helper object for convenient 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. sdbus::Variant is returned which shall then be converted
* to the real property type (implicit conversion is supported).
*
* Example of use:
* @code
* int state = object.getProperty("state").onInterface("com.kistler.foo");
* @endcode
*
* @throws sdbus::Error in case of failure
*/
PropertyGetter getProperty(const std::string& propertyName);
/*!
* @brief Sets value of a property of the proxied 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.
*
* Example of use:
* @code
* int state = ...;
* object_.setProperty("state").onInterface("com.kistler.foo").toValue(state);
* @endcode
*
* @throws sdbus::Error in case of failure
*/
PropertySetter setProperty(const std::string& propertyName);
virtual ~IProxy() = default;
};
inline MethodInvoker IProxy::callMethod(const std::string& methodName)
{
return MethodInvoker(*this, methodName);
}
inline AsyncMethodInvoker IProxy::callMethodAsync(const std::string& methodName)
{
return AsyncMethodInvoker(*this, methodName);
}
inline SignalSubscriber IProxy::uponSignal(const std::string& signalName)
{
return SignalSubscriber(*this, signalName);
}
inline PropertyGetter IProxy::getProperty(const std::string& propertyName)
{
return PropertyGetter(*this, propertyName);
}
inline PropertySetter IProxy::setProperty(const std::string& propertyName)
{
return PropertySetter(*this, propertyName);
}
/*!
* @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 proxy object instance
*
* The provided connection will be used by the proxy to issue calls against the object,
* and signals, if any, will be subscribed to on this connection. The caller still
* remains the owner of the connection (the proxy just keeps a reference to it), and
* should make sure that a processing loop is running on that connection, so the proxy
* may receive incoming signals and asynchronous method replies.
*
* Code example:
* @code
* auto proxy = sdbus::createProxy(connection, "com.kistler.foo", "/com/kistler/foo");
* @endcode
*/
std::unique_ptr<sdbus::IProxy> createProxy( sdbus::IConnection& connection
, 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,
* and signals, if any, will be subscribed to on this connection. The Object proxy becomes
* an exclusive owner of this connection, and will automatically start a procesing loop
* upon that connection in a separate internal thread. Handlers for incoming signals and
* asynchronous method replies will be executed in the context of that thread.
*
* Code example:
* @code
* auto proxy = sdbus::createProxy(std::move(connection), "com.kistler.foo", "/com/kistler/foo");
* @endcode
*/
std::unique_ptr<sdbus::IProxy> createProxy( std::unique_ptr<sdbus::IConnection>&& connection
, 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, and will automatically start a procesing loop upon that connection
* in a separate internal thread. Handlers for incoming signals and asynchronous
* method replies will be executed in the context of that thread.
*
* Code example:
* @code
* auto proxy = sdbus::createProxy("com.kistler.foo", "/com/kistler/foo");
* @endcode
*/
std::unique_ptr<sdbus::IProxy> createProxy( std::string destination
, std::string objectPath );
}
#include <sdbus-c++/ConvenienceApiClasses.inl>
#endif /* SDBUS_CXX_IPROXY_H_ */

View File

@ -1,120 +0,0 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file Interfaces.h
*
* Created on: Nov 8, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_INTERFACES_H_
#define SDBUS_CXX_INTERFACES_H_
#include <sdbus-c++/IObject.h>
#include <sdbus-c++/IObjectProxy.h>
#include <cassert>
#include <string>
#include <memory>
// Forward declarations
namespace sdbus {
class IConnection;
}
namespace sdbus {
template <typename _Object>
class ObjectHolder
{
protected:
ObjectHolder(std::unique_ptr<_Object>&& object)
: object_(std::move(object))
{
}
const _Object& getObject() const
{
assert(object_ != nullptr);
return *object_;
}
_Object& getObject()
{
assert(object_ != nullptr);
return *object_;
}
private:
std::unique_ptr<_Object> object_;
};
/********************************************//**
* @class Interfaces
*
* A helper template class that a user class representing a D-Bus object
* should inherit from, providing as template arguments the adaptor
* classes representing D-Bus interfaces that the object implements.
*
***********************************************/
template <typename... _Interfaces>
class Interfaces
: private ObjectHolder<IObject>
, public _Interfaces...
{
public:
Interfaces(IConnection& connection, std::string objectPath)
: ObjectHolder<IObject>(createObject(connection, std::move(objectPath)))
, _Interfaces(getObject())...
{
getObject().finishRegistration();
}
};
/********************************************//**
* @class Interfaces
*
* A helper template class that a user class representing a proxy of a
* D-Bus object should inherit from, providing as template arguments the proxy
* classes representing object D-Bus interfaces that the object implements.
*
***********************************************/
template <typename... _Interfaces>
class ProxyInterfaces
: private ObjectHolder<IObjectProxy>
, public _Interfaces...
{
public:
ProxyInterfaces(std::string destination, std::string objectPath)
: ObjectHolder<IObjectProxy>(createObjectProxy(std::move(destination), std::move(objectPath)))
, _Interfaces(getObject())...
{
getObject().finishRegistration();
}
ProxyInterfaces(IConnection& connection, std::string destination, std::string objectPath)
: ObjectHolder<IObjectProxy>(createObjectProxy(connection, std::move(destination), std::move(objectPath)))
, _Interfaces(getObject())...
{
getObject().finishRegistration();
}
};
}
#endif /* SDBUS_CXX_INTERFACES_H_ */

View File

@ -27,7 +27,7 @@
#define SDBUS_CXX_INTROSPECTION_H_
#include <sdbus-c++/IObject.h>
#include <sdbus-c++/IObjectProxy.h>
#include <sdbus-c++/IProxy.h>
#include <string>
namespace sdbus {
@ -38,7 +38,7 @@ namespace sdbus {
static constexpr const char* interfaceName = "org.freedesktop.DBus.Introspectable";
protected:
introspectable_proxy(sdbus::IObjectProxy& object)
introspectable_proxy(sdbus::IProxy& object)
: object_(object)
{
}
@ -52,7 +52,7 @@ namespace sdbus {
}
private:
sdbus::IObjectProxy& object_;
sdbus::IProxy& object_;
};
// Adaptor is not necessary if we want to rely on sdbus-provided introspection

View File

@ -35,8 +35,7 @@
#include <utility>
#include <cstdint>
#include <cassert>
#include <iostream>
#include <functional>
// Forward declarations
namespace sdbus {
@ -44,10 +43,22 @@ namespace sdbus {
class ObjectPath;
class Signature;
template <typename... _ValueTypes> class Struct;
class MethodReply;
namespace internal {
class ISdBus;
}
}
namespace sdbus {
// Assume the caller has already obtained message ownership
struct adopt_message_t { explicit adopt_message_t() = default; };
#ifdef __cpp_inline_variables
inline constexpr adopt_message_t adopt_message{};
#else
constexpr adopt_message_t adopt_message{};
#endif
/********************************************//**
* @class Message
*
@ -59,22 +70,16 @@ namespace sdbus {
* by D-Bus.
*
* You don't need to work with this class directly if you use high-level APIs
* of @c IObject and @c IObjectProxy.
* of @c IObject and @c IProxy.
*
***********************************************/
class Message
{
public:
enum class Type
{
ePlainMessage
, eMethodCall
, eMethodReply
, eSignal
};
Message() = default;
Message(void *msg, Type type = Type::ePlainMessage) noexcept;
Message(internal::ISdBus* sdbus) noexcept;
Message(void *msg, internal::ISdBus* sdbus) noexcept;
Message(void *msg, internal::ISdBus* sdbus, adopt_message_t) noexcept;
Message(const Message&) noexcept;
Message& operator=(const Message&) noexcept;
Message(Message&& other) noexcept;
@ -137,19 +142,55 @@ namespace sdbus {
void peekType(std::string& type, std::string& contents) const;
bool isValid() const;
bool isEmpty() const;
Type getType() const;
void copyTo(Message& destination, bool complete) const;
void seal();
void rewind(bool complete);
Message createReply() const;
Message send() const;
protected:
void* msg_{};
internal::ISdBus* sdbus_{};
mutable bool ok_{true};
};
class MethodCall : public Message
{
public:
using Message::Message;
MethodReply send() const;
MethodReply createReply() const;
MethodReply createErrorReply(const sdbus::Error& error) const;
void dontExpectReply();
bool doesntExpectReply() const;
private:
void* msg_{};
Type type_{Type::ePlainMessage};
mutable bool ok_{true};
MethodReply sendWithReply() const;
MethodReply sendWithNoReply() const;
};
class AsyncMethodCall : public Message
{
public:
using Slot = std::unique_ptr<void, std::function<void(void*)>>;
using Message::Message;
AsyncMethodCall() = default; // Fixes gcc 6.3 error (default c-tor is not imported in above using declaration)
AsyncMethodCall(MethodCall&& call) noexcept;
Slot send(void* callback, void* userData) const;
};
class MethodReply : public Message
{
public:
using Message::Message;
void send() const;
};
class Signal : public Message
{
public:
using Message::Message;
void send() const;
};
template <typename _Element>

View File

@ -0,0 +1,96 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file MethodResult.h
*
* Created on: Nov 8, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_METHODRESULT_H_
#define SDBUS_CXX_METHODRESULT_H_
#include <sdbus-c++/Message.h>
#include <cassert>
// Forward declaration
namespace sdbus {
class Error;
}
namespace sdbus {
/********************************************//**
* @class Result
*
* Represents result of an asynchronous server-side method.
* An instance is provided to the method and shall be set
* by the method to either method return value or an error.
*
***********************************************/
template <typename... _Results>
class Result
{
public:
Result() = default;
Result(MethodCall call);
Result(const Result&) = delete;
Result& operator=(const Result&) = delete;
Result(Result&& other) = default;
Result& operator=(Result&& other) = default;
void returnResults(const _Results&... results) const;
void returnError(const Error& error) const;
private:
MethodCall call_;
};
template <typename... _Results>
inline Result<_Results...>::Result(MethodCall call)
: call_(std::move(call))
{
}
template <typename... _Results>
inline void Result<_Results...>::returnResults(const _Results&... results) const
{
assert(call_.isValid());
auto reply = call_.createReply();
#ifdef __cpp_fold_expressions
(reply << ... << results);
#else
using _ = std::initializer_list<int>;
(void)_{(void(reply << results), 0)...};
#endif
reply.send();
}
template <typename... _Results>
inline void Result<_Results...>::returnError(const Error& error) const
{
auto reply = call_.createErrorReply(error);
reply.send();
}
}
#endif /* SDBUS_CXX_METHODRESULT_H_ */

View File

@ -0,0 +1,173 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file ProxyInterfaces.h
*
* Created on: Apr 8, 2019
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_PROXYINTERFACES_H_
#define SDBUS_CXX_PROXYINTERFACES_H_
#include <sdbus-c++/IProxy.h>
#include <cassert>
#include <string>
#include <memory>
// Forward declarations
namespace sdbus {
class IConnection;
}
namespace sdbus {
/********************************************//**
* @class ProxyObjectHolder
*
* ProxyObjectHolder is a helper that simply owns and provides
* access to a proxy object to other classes in the inheritance
* hierarchy of a native-like proxy object based on generated
* interface classes.
*
***********************************************/
class ProxyObjectHolder
{
protected:
ProxyObjectHolder(std::unique_ptr<IProxy>&& proxy)
: proxy_(std::move(proxy))
{
assert(proxy_ != nullptr);
}
const IProxy& getProxy() const
{
assert(proxy_ != nullptr);
return *proxy_;
}
IProxy& getProxy()
{
assert(proxy_ != nullptr);
return *proxy_;
}
private:
std::unique_ptr<IProxy> proxy_;
};
/********************************************//**
* @class ProxyInterfaces
*
* ProxyInterfaces is a helper template class that joins all interface classes of a remote
* D-Bus object generated by sdbus-c++-xml2cpp to be used on the client (the proxy) side,
* including some auxiliary classes. ProxyInterfaces is the class that native-like proxy
* implementation classes written by users should inherit from and implement all pure virtual
* methods. So the _Interfaces template parameter is a list of sdbus-c++-xml2cpp-generated
* proxy-side interface classes representing interfaces of the corresponding remote D-Bus object.
*
* In the final proxy class inherited from ProxyInterfaces, it is necessary to finish proxy
* registration in class constructor (`finishRegistration();`), and, conversely, unregister
* the proxy in class destructor (`unregister();`).
*
***********************************************/
template <typename... _Interfaces>
class ProxyInterfaces
: protected ProxyObjectHolder
, public _Interfaces...
{
public:
/*!
* @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)
*/
ProxyInterfaces(std::string destination, std::string objectPath)
: ProxyObjectHolder(createProxy(std::move(destination), std::move(objectPath)))
, _Interfaces(getProxy())...
{
}
/*!
* @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 just references a D-Bus connection owned and managed by the user.
* For more information on its behavior, consult @ref createProxy(IConnection&,std::string,std::string)
*/
ProxyInterfaces(IConnection& connection, std::string destination, std::string objectPath)
: ProxyObjectHolder(createProxy(connection, std::move(destination), std::move(objectPath)))
, _Interfaces(getProxy())...
{
}
/*!
* @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)
*/
ProxyInterfaces(std::unique_ptr<sdbus::IConnection>&& connection, std::string destination, std::string objectPath)
: ProxyObjectHolder(createProxy(std::move(connection), std::move(destination), std::move(objectPath)))
, _Interfaces(getProxy())...
{
}
/*!
* @brief Finishes proxy registration and makes the proxy ready for use
*
* This function must be called in the constructor of the final proxy class that implements ProxyInterfaces.
*
* For more information, see underlying @ref IProxy::finishRegistration()
*/
void registerProxy()
{
getProxy().finishRegistration();
}
/*!
* @brief Unregisters the proxy so it no more receives signals and async call replies
*
* This function must be called in the destructor of the final proxy class that implements ProxyInterfaces.
*
* See underlying @ref IProxy::unregister()
*/
void unregisterProxy()
{
getProxy().unregister();
}
protected:
using base_type = ProxyInterfaces;
};
}
#endif /* SDBUS_CXX_INTERFACES_H_ */

View File

@ -26,6 +26,7 @@
#ifndef SDBUS_CXX_TYPETRAITS_H_
#define SDBUS_CXX_TYPETRAITS_H_
#include <type_traits>
#include <string>
#include <vector>
#include <map>
@ -40,19 +41,26 @@ namespace sdbus {
class ObjectPath;
class Signature;
class Message;
class MethodCall;
class MethodReply;
class Signal;
template <typename... _Results> class Result;
class Error;
}
namespace sdbus {
using method_callback = std::function<void(Message& msg, Message& reply)>;
using signal_handler = std::function<void(Message& signal)>;
using method_callback = std::function<void(MethodCall msg)>;
using async_reply_handler = std::function<void(MethodReply& reply, const Error* error)>;
using signal_handler = std::function<void(Signal& signal)>;
using property_set_callback = std::function<void(Message& msg)>;
using property_get_callback = std::function<void(Message& reply)>;
// Primary template
template <typename _T>
struct signature_of
{
static constexpr bool is_valid = false;
static const std::string str()
{
// sizeof(_T) < 0 is here to make compiler not being able to figure out
@ -65,6 +73,8 @@ namespace sdbus {
template <>
struct signature_of<void>
{
static constexpr bool is_valid = true;
static const std::string str()
{
return "";
@ -74,6 +84,8 @@ namespace sdbus {
template <>
struct signature_of<bool>
{
static constexpr bool is_valid = true;
static const std::string str()
{
return "b";
@ -83,6 +95,8 @@ namespace sdbus {
template <>
struct signature_of<uint8_t>
{
static constexpr bool is_valid = true;
static const std::string str()
{
return "y";
@ -92,6 +106,8 @@ namespace sdbus {
template <>
struct signature_of<int16_t>
{
static constexpr bool is_valid = true;
static const std::string str()
{
return "n";
@ -101,6 +117,8 @@ namespace sdbus {
template <>
struct signature_of<uint16_t>
{
static constexpr bool is_valid = true;
static const std::string str()
{
return "q";
@ -110,6 +128,8 @@ namespace sdbus {
template <>
struct signature_of<int32_t>
{
static constexpr bool is_valid = true;
static const std::string str()
{
return "i";
@ -119,6 +139,8 @@ namespace sdbus {
template <>
struct signature_of<uint32_t>
{
static constexpr bool is_valid = true;
static const std::string str()
{
return "u";
@ -128,6 +150,8 @@ namespace sdbus {
template <>
struct signature_of<int64_t>
{
static constexpr bool is_valid = true;
static const std::string str()
{
return "x";
@ -137,6 +161,8 @@ namespace sdbus {
template <>
struct signature_of<uint64_t>
{
static constexpr bool is_valid = true;
static const std::string str()
{
return "t";
@ -146,6 +172,8 @@ namespace sdbus {
template <>
struct signature_of<double>
{
static constexpr bool is_valid = true;
static const std::string str()
{
return "d";
@ -155,6 +183,8 @@ namespace sdbus {
template <>
struct signature_of<char*>
{
static constexpr bool is_valid = true;
static const std::string str()
{
return "s";
@ -164,6 +194,8 @@ namespace sdbus {
template <>
struct signature_of<const char*>
{
static constexpr bool is_valid = true;
static const std::string str()
{
return "s";
@ -173,6 +205,8 @@ namespace sdbus {
template <std::size_t _N>
struct signature_of<char[_N]>
{
static constexpr bool is_valid = true;
static const std::string str()
{
return "s";
@ -182,6 +216,8 @@ namespace sdbus {
template <std::size_t _N>
struct signature_of<const char[_N]>
{
static constexpr bool is_valid = true;
static const std::string str()
{
return "s";
@ -191,6 +227,8 @@ namespace sdbus {
template <>
struct signature_of<std::string>
{
static constexpr bool is_valid = true;
static const std::string str()
{
return "s";
@ -200,6 +238,8 @@ namespace sdbus {
template <typename... _ValueTypes>
struct signature_of<Struct<_ValueTypes...>>
{
static constexpr bool is_valid = true;
static const std::string str()
{
std::initializer_list<std::string> signatures{signature_of<_ValueTypes>::str()...};
@ -215,6 +255,8 @@ namespace sdbus {
template <>
struct signature_of<Variant>
{
static constexpr bool is_valid = true;
static const std::string str()
{
return "v";
@ -224,6 +266,8 @@ namespace sdbus {
template <>
struct signature_of<ObjectPath>
{
static constexpr bool is_valid = true;
static const std::string str()
{
return "o";
@ -233,6 +277,8 @@ namespace sdbus {
template <>
struct signature_of<Signature>
{
static constexpr bool is_valid = true;
static const std::string str()
{
return "g";
@ -242,6 +288,8 @@ namespace sdbus {
template <typename _Element>
struct signature_of<std::vector<_Element>>
{
static constexpr bool is_valid = true;
static const std::string str()
{
return "a" + signature_of<_Element>::str();
@ -251,12 +299,17 @@ namespace sdbus {
template <typename _Key, typename _Value>
struct signature_of<std::map<_Key, _Value>>
{
static constexpr bool is_valid = true;
static const std::string str()
{
return "a{" + signature_of<_Key>::str() + signature_of<_Value>::str() + "}";
}
};
// Function traits implementation inspired by (c) kennytm,
// https://github.com/kennytm/utils/blob/master/traits.hpp
template <typename _Type>
struct function_traits
: public function_traits<decltype(&_Type::operator())>
@ -272,18 +325,32 @@ namespace sdbus {
: public function_traits<_Type>
{};
// Function traits implementation inspired by (c) kennytm,
// https://github.com/kennytm/utils/blob/master/traits.hpp
template <typename _ReturnType, typename... _Args>
struct function_traits<_ReturnType(_Args...)>
struct function_traits_base
{
typedef _ReturnType result_type;
typedef std::tuple<_Args...> arguments_type;
typedef std::tuple<std::decay_t<_Args>...> decayed_arguments_type;
typedef _ReturnType function_type(_Args...);
static constexpr std::size_t arity = sizeof...(_Args);
// template <size_t _Idx, typename _Enabled = void>
// struct arg;
//
// template <size_t _Idx>
// struct arg<_Idx, std::enable_if_t<(_Idx < arity)>>
// {
// typedef std::tuple_element_t<_Idx, arguments_type> type;
// };
//
// template <size_t _Idx>
// struct arg<_Idx, std::enable_if_t<!(_Idx < arity)>>
// {
// typedef void type;
// };
template <size_t _Idx>
struct arg
{
@ -294,6 +361,35 @@ namespace sdbus {
using arg_t = typename arg<_Idx>::type;
};
template <typename _ReturnType, typename... _Args>
struct function_traits<_ReturnType(_Args...)>
: public function_traits_base<_ReturnType, _Args...>
{
static constexpr bool is_async = false;
};
template <typename... _Args>
struct function_traits<void(const Error*, _Args...)>
: public function_traits_base<void, _Args...>
{
};
template <typename... _Args, typename... _Results>
struct function_traits<void(Result<_Results...>, _Args...)>
: public function_traits_base<std::tuple<_Results...>, _Args...>
{
static constexpr bool is_async = true;
using async_result_t = Result<_Results...>;
};
template <typename... _Args, typename... _Results>
struct function_traits<void(Result<_Results...>&&, _Args...)>
: public function_traits_base<std::tuple<_Results...>, _Args...>
{
static constexpr bool is_async = true;
using async_result_t = Result<_Results...>;
};
template <typename _ReturnType, typename... _Args>
struct function_traits<_ReturnType(*)(_Args...)>
: public function_traits<_ReturnType(_Args...)>
@ -332,12 +428,39 @@ namespace sdbus {
: public function_traits<FunctionType>
{};
template <class _Function>
constexpr auto is_async_method_v = function_traits<_Function>::is_async;
template <typename _FunctionType>
using function_arguments_t = typename function_traits<_FunctionType>::arguments_type;
template <typename _FunctionType, size_t _Idx>
using function_argument_t = typename function_traits<_FunctionType>::template arg_t<_Idx>;
template <typename _FunctionType>
constexpr auto function_argument_count_v = function_traits<_FunctionType>::arity;
template <typename _FunctionType>
using function_result_t = typename function_traits<_FunctionType>::result_type;
template <typename _Function>
struct tuple_of_function_input_arg_types
{
typedef typename function_traits<_Function>::decayed_arguments_type type;
};
template <typename _Function>
using tuple_of_function_input_arg_types_t = typename tuple_of_function_input_arg_types<_Function>::type;
template <typename _Function>
struct tuple_of_function_output_arg_types
{
typedef typename function_traits<_Function>::result_type type;
};
template <typename _Function>
using tuple_of_function_output_arg_types_t = typename tuple_of_function_output_arg_types<_Function>::type;
template <typename _Type>
struct aggregate_signature
{
@ -352,6 +475,7 @@ namespace sdbus {
{
static const std::string str()
{
// TODO: This could be a fold expression in C++17...
std::initializer_list<std::string> signatures{signature_of<std::decay_t<_Types>>::str()...};
std::string signature;
for (const auto& item : signatures)
@ -360,28 +484,6 @@ namespace sdbus {
}
};
// Get a tuple of function input argument types from function signature.
// But first, convert provided function signature to the standardized form `out(in...)'.
template <typename _Function>
struct tuple_of_function_input_arg_types
: public tuple_of_function_input_arg_types<typename function_traits<_Function>::function_type>
{};
// Get a tuple of function input argument types from function signature.
// Function signature is expected in the standardized form `out(in...)'.
template <typename _ReturnType, typename... _Args>
struct tuple_of_function_input_arg_types<_ReturnType(_Args...)>
{
// Arguments may be cv-qualified and may be references, so we have to strip cv and references
// with decay_t in order to get real 'naked' types.
// Example: for a function with signature void(const int i, const std::vector<float> v, double d)
// the `type' will be `std::tuple<int, std::vector<float>, double>'.
typedef std::tuple<std::decay_t<_Args>...> type;
};
template <typename _Function>
using tuple_of_function_input_arg_types_t = typename tuple_of_function_input_arg_types<_Function>::type;
template <typename _Function>
struct signature_of_function_input_arguments
{
@ -396,12 +498,30 @@ namespace sdbus {
{
static const std::string str()
{
return aggregate_signature<function_result_t<_Function>>::str();
return aggregate_signature<tuple_of_function_output_arg_types_t<_Function>>::str();
}
};
namespace detail
{
template <class _Function, class _Tuple, typename... _Args, std::size_t... _I>
constexpr decltype(auto) apply_impl( _Function&& f
, Result<_Args...>&& r
, _Tuple&& t
, std::index_sequence<_I...> )
{
return std::forward<_Function>(f)(std::move(r), std::get<_I>(std::forward<_Tuple>(t))...);
}
template <class _Function, class _Tuple, std::size_t... _I>
constexpr decltype(auto) apply_impl( _Function&& f
, const Error* e
, _Tuple&& t
, std::index_sequence<_I...> )
{
return std::forward<_Function>(f)(e, std::get<_I>(std::forward<_Tuple>(t))...);
}
// Version of apply_impl for functions returning non-void values.
// In this case just forward function return value.
template <class _Function, class _Tuple, std::size_t... _I>
@ -436,6 +556,27 @@ namespace sdbus {
, std::make_index_sequence<std::tuple_size<std::decay_t<_Tuple>>::value>{} );
}
// Convert tuple `t' of values into a list of arguments
// and invoke function `f' with those arguments.
template <class _Function, class _Tuple, typename... _Args>
constexpr decltype(auto) apply(_Function&& f, Result<_Args...>&& r, _Tuple&& t)
{
return detail::apply_impl( std::forward<_Function>(f)
, std::move(r)
, std::forward<_Tuple>(t)
, std::make_index_sequence<std::tuple_size<std::decay_t<_Tuple>>::value>{} );
}
// Convert tuple `t' of values into a list of arguments
// and invoke function `f' with those arguments.
template <class _Function, class _Tuple>
constexpr decltype(auto) apply(_Function&& f, const Error* e, _Tuple&& t)
{
return detail::apply_impl( std::forward<_Function>(f)
, e
, std::forward<_Tuple>(t)
, std::make_index_sequence<std::tuple_size<std::decay_t<_Tuple>>::value>{} );
}
}
#endif /* SDBUS_CXX_TYPETRAITS_H_ */

View File

@ -40,6 +40,12 @@ 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
* should process a single Variant object in a single thread at a time.
* Otherwise they need to take care of synchronization by themselves.
*
***********************************************/
class Variant
@ -68,7 +74,8 @@ namespace sdbus {
return val;
}
template <typename _ValueType>
// Only allow conversion operator for true D-Bus type representations in C++
template <typename _ValueType, typename = std::enable_if_t<signature_of<_ValueType>::is_valid>>
operator _ValueType() const
{
return get<_ValueType>();
@ -97,6 +104,16 @@ namespace sdbus {
public:
using std::tuple<_ValueTypes...>::tuple;
// Disable constructor if an older then 7.1.0 version of GCC is used
#if !((defined(__GNUC__) || defined(__GNUG__)) && !defined(__clang__) && !(__GNUC__ > 7 || (__GNUC__ == 7 && (__GNUC_MINOR__ > 1 || (__GNUC_MINOR__ == 1 && __GNUC_PATCHLEVEL__ > 0)))))
Struct() = default;
explicit Struct(const std::tuple<_ValueTypes...>& t)
: std::tuple<_ValueTypes...>(t)
{
}
#endif
template <std::size_t _I>
auto& get()
{
@ -122,6 +139,10 @@ namespace sdbus {
{
public:
using std::string::string;
ObjectPath() = default; // Fixes gcc 6.3 error (default c-tor is not imported in above using declaration)
ObjectPath(std::string path)
: std::string(std::move(path))
{}
using std::string::operator=;
};
@ -129,6 +150,10 @@ namespace sdbus {
{
public:
using std::string::string;
Signature() = default; // Fixes gcc 6.3 error (default c-tor is not imported in above using declaration)
Signature(std::string path)
: std::string(std::move(path))
{}
using std::string::operator=;
};

View File

@ -25,9 +25,11 @@
#include <sdbus-c++/IConnection.h>
#include <sdbus-c++/IObject.h>
#include <sdbus-c++/IObjectProxy.h>
#include <sdbus-c++/Interfaces.h>
#include <sdbus-c++/IProxy.h>
#include <sdbus-c++/AdaptorInterfaces.h>
#include <sdbus-c++/ProxyInterfaces.h>
#include <sdbus-c++/Message.h>
#include <sdbus-c++/MethodResult.h>
#include <sdbus-c++/Types.h>
#include <sdbus-c++/TypeTraits.h>
#include <sdbus-c++/Introspection.h>

14
sdbus-c++-config.cmake.in Normal file
View File

@ -0,0 +1,14 @@
# Config file for the sdbus-c++ package.
#
# It defines the following variables:
# SDBUSCPP_VERSION - version of sdbus-c++
# SDBUSCPP_FOUND - set to true
# SDBUSCPP_INCLUDE_DIRS - include directories for sdbus-c++
# SDBUSCPP_LIBRARY_DIR - library directories for sdbus-c++
# SDBUSCPP_LIBRARIES - libraries to link against
set(SDBUSCPP_VERSION "@SDBUSCPP_VERSION@")
set(SDBUSCPP_FOUND "TRUE")
set(SDBUSCPP_INCLUDE_DIRS "@CMAKE_INSTALL_FULL_INCLUDEDIR@")
set(SDBUSCPP_LIBRARY_DIR "@CMAKE_INSTALL_FULL_LIBDIR@")
set(SDBUSCPP_LIBRARIES sdbus-c++)

View File

@ -1,11 +1,11 @@
prefix=@prefix@
exec_prefix=@exec_prefix@
libdir=@libdir@
includedir=@includedir@
prefix=@CMAKE_INSTALL_PREFIX@
exec_prefix=@CMAKE_INSTALL_PREFIX@
libdir=@CMAKE_INSTALL_FULL_LIBDIR@
includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@
Name: @PACKAGE@
Description: C++ bindings library for sd-bus
Name: @PROJECT_NAME@
Description: C++ library on top of sd-bus, a systemd D-Bus library
Requires: libsystemd
Version: @VERSION@
Libs: -L${libdir} -lsdbus-c++
Version: @SDBUSCPP_VERSION@
Libs: -L${libdir} -l@PROJECT_NAME@
Cflags: -I${includedir}

349
src/Connection.cpp Executable file → Normal file
View File

@ -24,6 +24,7 @@
*/
#include "Connection.h"
#include "SdBus.h"
#include <sdbus-c++/Message.h>
#include <sdbus-c++/Error.h>
#include "ScopeGuard.h"
@ -32,191 +33,265 @@
#include <poll.h>
#include <sys/eventfd.h>
namespace {
std::map<sdbus::internal::Connection::BusType, int(*)(sd_bus **)> busTypeToFactory
{
{sdbus::internal::Connection::BusType::eSystem, &sd_bus_open_system},
{sdbus::internal::Connection::BusType::eSession, &sd_bus_open_user}
};
}
namespace sdbus { namespace internal {
Connection::Connection(Connection::BusType type)
: busType_(type)
Connection::Connection(Connection::BusType type, std::unique_ptr<ISdBus>&& interface)
: iface_(std::move(interface))
, busType_(type)
{
sd_bus* bus{};
auto r = busTypeToFactory[busType_](&bus);
if (r < 0)
SDBUS_THROW_ERROR("Failed to open system bus", -r);
assert(iface_ != nullptr);
auto bus = openBus(busType_);
bus_.reset(bus);
// Process all requests that are part of the initial handshake,
// like processing the Hello message response, authentication etc.,
// to avoid connection authentication timeout in dbus daemon.
r = sd_bus_flush(bus_.get());
if (r < 0)
SDBUS_THROW_ERROR("Failed to flush system bus on opening", -r);
finishHandshake(bus);
r = eventfd(0, EFD_SEMAPHORE);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to create event object", -errno);
runFd_ = r;
loopExitFd_ = createProcessingLoopExitDescriptor();
}
Connection::~Connection()
{
leaveProcessingLoop();
close(runFd_);
closeProcessingLoopExitDescriptor(loopExitFd_);
}
void Connection::requestName(const std::string& name)
{
auto r = sd_bus_request_name(bus_.get(), name.c_str(), 0);
if (r < 0)
SDBUS_THROW_ERROR("Failed to request bus name", -r);
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);
}
void Connection::releaseName(const std::string& name)
{
auto r = sd_bus_release_name(bus_.get(), name.c_str());
if (r < 0)
SDBUS_THROW_ERROR("Failed to release bus name", -r);
auto r = iface_->sd_bus_release_name(bus_.get(), name.c_str());
SDBUS_THROW_ERROR_IF(r < 0, "Failed to release bus name", -r);
}
void Connection::enterProcessingLoop()
{
int semaphoreFd = runFd_;
short int semaphoreEvents = POLLIN;
while (true)
{
/* Process requests */
int r = sd_bus_process(bus_.get(), nullptr);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to process bus requests", -r);
if (r > 0) /* we processed a request, try to process another one, right-away */
continue;
auto processed = processPendingRequest();
if (processed)
continue; // Process next one
r = sd_bus_get_fd(bus_.get());
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get bus descriptor", -r);
auto sdbusFd = r;
r = sd_bus_get_events(bus_.get());
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get bus descriptor", -r);
short int sdbusEvents = r;
struct pollfd fds[] = {{sdbusFd, sdbusEvents, 0}, {semaphoreFd, semaphoreEvents, 0}};
/* Wait for the next request to process */
uint64_t usec;
sd_bus_get_timeout(bus_.get(), &usec);
auto fdsCount = sizeof(fds)/sizeof(fds[0]);
r = poll(fds, fdsCount, usec == (uint64_t) -1 ? -1 : (usec+999)/1000);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to wait on the bus", -errno);
if (fds[1].revents & POLLIN)
break;
auto success = waitForNextRequest();
if (!success)
break; // Exit processing loop
}
}
void Connection::enterProcessingLoopAsync()
{
asyncLoopThread_ = std::thread([this](){ enterProcessingLoop(); });
if (!asyncLoopThread_.joinable())
asyncLoopThread_ = std::thread([this](){ enterProcessingLoop(); });
}
void Connection::leaveProcessingLoop()
{
assert(runFd_ >= 0);
uint64_t value = 1;
write(runFd_, &value, sizeof(value));
notifyProcessingLoopToExit();
joinWithProcessingLoop();
}
const ISdBus& Connection::getSdBusInterface() const
{
return *iface_.get();
}
ISdBus& Connection::getSdBusInterface()
{
return *iface_.get();
}
sd_bus_slot* Connection::addObjectVTable( const std::string& objectPath
, const std::string& interfaceName
, const sd_bus_vtable* vtable
, void* userData )
{
sd_bus_slot *slot{};
auto r = iface_->sd_bus_add_object_vtable( bus_.get()
, &slot
, objectPath.c_str()
, interfaceName.c_str()
, vtable
, userData );
SDBUS_THROW_ERROR_IF(r < 0, "Failed to register object vtable", -r);
return slot;
}
void Connection::removeObjectVTable(sd_bus_slot* vtableHandle)
{
iface_->sd_bus_slot_unref(vtableHandle);
}
MethodCall Connection::createMethodCall( const std::string& destination
, const std::string& objectPath
, const std::string& interfaceName
, const std::string& methodName ) const
{
sd_bus_message *sdbusMsg{};
auto r = iface_->sd_bus_message_new_method_call( bus_.get()
, &sdbusMsg
, destination.c_str()
, objectPath.c_str()
, interfaceName.c_str()
, methodName.c_str() );
SDBUS_THROW_ERROR_IF(r < 0, "Failed to create method call", -r);
return MethodCall{sdbusMsg, iface_.get(), adopt_message};
}
Signal Connection::createSignal( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName ) const
{
sd_bus_message *sdbusSignal{};
auto r = iface_->sd_bus_message_new_signal( bus_.get()
, &sdbusSignal
, objectPath.c_str()
, interfaceName.c_str()
, signalName.c_str() );
SDBUS_THROW_ERROR_IF(r < 0, "Failed to create signal", -r);
return Signal{sdbusSignal, iface_.get(), adopt_message};
}
sd_bus_slot* Connection::registerSignalHandler( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName
, sd_bus_message_handler_t callback
, void* userData )
{
sd_bus_slot *slot{};
auto filter = composeSignalMatchFilter(objectPath, interfaceName, signalName);
auto r = iface_->sd_bus_add_match(bus_.get(), &slot, filter.c_str(), callback, userData);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to register signal handler", -r);
return slot;
}
void Connection::unregisterSignalHandler(sd_bus_slot* handlerCookie)
{
iface_->sd_bus_slot_unref(handlerCookie);
}
sd_bus* Connection::openBus(Connection::BusType type)
{
sd_bus* bus{};
int r = 0;
if (type == BusType::eSystem)
r = iface_->sd_bus_open_system(&bus);
else if (type == BusType::eSession)
r = iface_->sd_bus_open_user(&bus);
else
assert(false);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to open bus", -r);
assert(bus != nullptr);
return bus;
}
void Connection::finishHandshake(sd_bus* bus)
{
// Process all requests that are part of the initial handshake,
// like processing the Hello message response, authentication etc.,
// to avoid connection authentication timeout in dbus daemon.
assert(bus != nullptr);
auto r = iface_->sd_bus_flush(bus);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to flush bus on opening", -r);
}
int Connection::createProcessingLoopExitDescriptor()
{
auto r = eventfd(0, EFD_SEMAPHORE | EFD_CLOEXEC | EFD_NONBLOCK);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to create event object", -errno);
return r;
}
void Connection::closeProcessingLoopExitDescriptor(int fd)
{
close(fd);
}
void Connection::notifyProcessingLoopToExit()
{
assert(loopExitFd_ >= 0);
uint64_t value = 1;
auto r = write(loopExitFd_, &value, sizeof(value));
SDBUS_THROW_ERROR_IF(r < 0, "Failed to notify processing loop", -errno);
}
void Connection::clearExitNotification()
{
uint64_t value{};
auto r = read(loopExitFd_, &value, sizeof(value));
SDBUS_THROW_ERROR_IF(r < 0, "Failed to read from the event descriptor", -errno);
}
void Connection::joinWithProcessingLoop()
{
if (asyncLoopThread_.joinable())
asyncLoopThread_.join();
}
void* Connection::addObjectVTable( const std::string& objectPath
, const std::string& interfaceName
, const void* vtable
, void* userData )
bool Connection::processPendingRequest()
{
sd_bus_slot *slot{};
auto r = sd_bus_add_object_vtable( bus_.get()
, &slot
, objectPath.c_str()
, interfaceName.c_str()
, static_cast<const sd_bus_vtable*>(vtable)
, userData );
if (r < 0)
SDBUS_THROW_ERROR("Failed to register object vtable", -r);
auto bus = bus_.get();
return slot;
assert(bus != nullptr);
int r = iface_->sd_bus_process(bus, nullptr);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to process bus requests", -r);
return r > 0;
}
void Connection::removeObjectVTable(void* vtableHandle)
bool Connection::waitForNextRequest()
{
sd_bus_slot_unref((sd_bus_slot *)vtableHandle);
}
auto bus = bus_.get();
sdbus::Message Connection::createMethodCall( const std::string& destination
, const std::string& objectPath
, const std::string& interfaceName
, const std::string& methodName ) const
{
sd_bus_message *sdbusMsg{};
SCOPE_EXIT{ sd_bus_message_unref(sdbusMsg); }; // Returned message will become an owner of sdbusMsg
auto r = sd_bus_message_new_method_call( bus_.get()
, &sdbusMsg
, destination.c_str()
, objectPath.c_str()
, interfaceName.c_str()
, methodName.c_str() );
if (r < 0)
SDBUS_THROW_ERROR("Failed to create method call", -r);
assert(bus != nullptr);
assert(loopExitFd_ != 0);
return Message(sdbusMsg, Message::Type::eMethodCall);
}
ISdBus::PollData sdbusPollData;
auto r = iface_->sd_bus_get_poll_data(bus, &sdbusPollData);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get bus poll data", -r);
sdbus::Message Connection::createSignal( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName ) const
{
sd_bus_message *sdbusSignal{};
SCOPE_EXIT{ sd_bus_message_unref(sdbusSignal); }; // Returned message will become an owner of sdbusSignal
auto r = sd_bus_message_new_signal( bus_.get()
, &sdbusSignal
, objectPath.c_str()
, interfaceName.c_str()
, signalName.c_str() );
if (r < 0)
SDBUS_THROW_ERROR("Failed to create signal", -r);
struct pollfd fds[] = {{sdbusPollData.fd, sdbusPollData.events, 0}, {loopExitFd_, POLLIN, 0}};
auto fdsCount = sizeof(fds)/sizeof(fds[0]);
return Message(sdbusSignal, Message::Type::eSignal);
}
auto timeout = sdbusPollData.timeout_usec == (uint64_t) -1 ? (uint64_t)-1 : (sdbusPollData.timeout_usec+999)/1000;
r = poll(fds, fdsCount, timeout);
void* Connection::registerSignalHandler( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName
, sd_bus_message_handler_t callback
, void* userData )
{
sd_bus_slot *slot{};
auto filter = composeSignalMatchFilter(objectPath, interfaceName, signalName);
auto r = sd_bus_add_match(bus_.get(), &slot, filter.c_str(), callback, userData);
if (r < 0)
SDBUS_THROW_ERROR("Failed to register signal handler", -r);
if (r < 0 && errno == EINTR)
return true; // Try again
return slot;
}
SDBUS_THROW_ERROR_IF(r < 0, "Failed to wait on the bus", -errno);
void Connection::unregisterSignalHandler(void* handlerCookie)
{
sd_bus_slot_unref((sd_bus_slot *)handlerCookie);
}
if (fds[1].revents & POLLIN)
{
clearExitNotification();
return false;
}
std::unique_ptr<sdbus::internal::IConnection> Connection::clone() const
{
return std::make_unique<sdbus::internal::Connection>(busType_);
return true;
}
std::string Connection::composeSignalMatchFilter( const std::string& objectPath
@ -224,10 +299,12 @@ std::string Connection::composeSignalMatchFilter( const std::string& objectPath
, const std::string& signalName )
{
std::string filter;
filter += "type='signal',";
filter += "interface='" + interfaceName + "',";
filter += "member='" + signalName + "',";
filter += "path='" + objectPath + "'";
return filter;
}
@ -247,7 +324,10 @@ std::unique_ptr<sdbus::IConnection> createConnection(const std::string& name)
std::unique_ptr<sdbus::IConnection> createSystemBusConnection()
{
return std::make_unique<sdbus::internal::Connection>(sdbus::internal::Connection::BusType::eSystem);
auto interface = std::make_unique<sdbus::internal::SdBus>();
assert(interface != nullptr);
return std::make_unique<sdbus::internal::Connection>( sdbus::internal::Connection::BusType::eSystem
, std::move(interface));
}
std::unique_ptr<sdbus::IConnection> createSystemBusConnection(const std::string& name)
@ -259,7 +339,10 @@ std::unique_ptr<sdbus::IConnection> createSystemBusConnection(const std::string&
std::unique_ptr<sdbus::IConnection> createSessionBusConnection()
{
return std::make_unique<sdbus::internal::Connection>(sdbus::internal::Connection::BusType::eSession);
auto interface = std::make_unique<sdbus::internal::SdBus>();
assert(interface != nullptr);
return std::make_unique<sdbus::internal::Connection>( sdbus::internal::Connection::BusType::eSession
, std::move(interface));
}
std::unique_ptr<sdbus::IConnection> createSessionBusConnection(const std::string& name)

70
src/Connection.h Executable file → Normal file
View File

@ -27,13 +27,14 @@
#define SDBUS_CXX_INTERNAL_CONNECTION_H_
#include <sdbus-c++/IConnection.h>
#include <sdbus-c++/Message.h>
#include "IConnection.h"
#include "ScopeGuard.h"
#include "ISdBus.h"
#include <systemd/sd-bus.h>
#include <memory>
#include <atomic>
#include <thread>
#include "IConnection.h"
namespace sdbus { namespace internal {
class Connection
@ -47,8 +48,8 @@ namespace sdbus { namespace internal {
eSession
};
Connection(BusType type);
~Connection();
Connection(BusType type, std::unique_ptr<ISdBus>&& interface);
~Connection() override;
void requestName(const std::string& name) override;
void releaseName(const std::string& name) override;
@ -56,41 +57,54 @@ namespace sdbus { namespace internal {
void enterProcessingLoopAsync() override;
void leaveProcessingLoop() override;
void* addObjectVTable( const std::string& objectPath
, const std::string& interfaceName
, const void* vtable
, void* userData ) override;
void removeObjectVTable(void* vtableHandle) override;
const ISdBus& getSdBusInterface() const override;
ISdBus& getSdBusInterface() override;
sdbus::Message createMethodCall( const std::string& destination
, const std::string& objectPath
, const std::string& interfaceName
, const std::string& methodName ) const override;
sdbus::Message createSignal( const std::string& objectPath
sd_bus_slot* addObjectVTable( const std::string& objectPath
, const std::string& interfaceName
, const sd_bus_vtable* vtable
, void* userData ) override;
void removeObjectVTable(sd_bus_slot* vtableHandle) override;
MethodCall createMethodCall( const std::string& destination
, const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName ) const override;
, const std::string& methodName ) const override;
Signal createSignal( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName ) const override;
void* registerSignalHandler( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName
, sd_bus_message_handler_t callback
, void* userData ) override;
void unregisterSignalHandler(void* handlerCookie) override;
std::unique_ptr<sdbus::internal::IConnection> clone() const override;
sd_bus_slot* registerSignalHandler( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName
, sd_bus_message_handler_t callback
, void* userData ) override;
void unregisterSignalHandler(sd_bus_slot* handlerCookie) override;
private:
sd_bus* openBus(Connection::BusType type);
void finishHandshake(sd_bus* bus);
static int createProcessingLoopExitDescriptor();
static void closeProcessingLoopExitDescriptor(int fd);
bool processPendingRequest();
bool waitForNextRequest();
static std::string composeSignalMatchFilter( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName );
void notifyProcessingLoopToExit();
void clearExitNotification();
void joinWithProcessingLoop();
private:
std::unique_ptr<sd_bus, decltype(&sd_bus_flush_close_unref)> bus_{nullptr, &sd_bus_flush_close_unref};
std::thread asyncLoopThread_;
std::atomic<int> runFd_{-1};
std::unique_ptr<ISdBus> iface_;
std::unique_ptr<sd_bus, std::function<sd_bus*(sd_bus*)>> bus_ {nullptr, [this](sd_bus* bus)
{
return iface_->sd_bus_flush_close_unref(bus);
}};
BusType busType_;
static constexpr const uint64_t POLL_TIMEOUT_USEC = 500000;
std::thread asyncLoopThread_;
int loopExitFd_{-1};
};
}}

View File

@ -1,7 +1,7 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file ConvenienceClasses.cpp
* @file ConvenienceApiClasses.cpp
*
* Created on: Jan 19, 2017
* Project: sdbus-c++
@ -23,14 +23,42 @@
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include <sdbus-c++/ConvenienceClasses.h>
#include <sdbus-c++/IObject.h>
#include <sdbus-c++/IObjectProxy.h>
#include "sdbus-c++/ConvenienceApiClasses.h"
#include "sdbus-c++/IObject.h"
#include "sdbus-c++/IProxy.h"
#include <string>
#include <exception>
namespace sdbus {
MethodRegistrator::MethodRegistrator(IObject& object, const std::string& methodName)
: object_(object)
, methodName_(methodName)
, exceptions_(std::uncaught_exceptions()) // Needs C++17
{
}
MethodRegistrator::~MethodRegistrator() noexcept(false) // since C++11, destructors must
{ // explicitly be allowed to throw
// Don't register the method if MethodRegistrator threw an exception in one of its methods
if (std::uncaught_exceptions() != exceptions_)
return;
SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when registering a DBus method", EINVAL);
SDBUS_THROW_ERROR_IF(!methodCallback_, "Method handler not specified when registering a DBus method", EINVAL);
// registerMethod() can throw. But as the MethodRegistrator shall always be used as an unnamed,
// temporary object, i.e. not as a stack-allocated object, the double-exception situation
// shall never happen. I.e. it should not happen that this destructor is directly called
// in the stack-unwinding process of another flying exception (which would lead to immediate
// termination). It can be called indirectly in the destructor of another object, but that's
// fine and safe provided that the caller catches exceptions thrown from here.
// Therefore, we can allow registerMethod() to throw even if we are in the destructor.
// Bottomline is, to be on the safe side, the caller must take care of catching and reacting
// to the exception thrown from here if the caller is a destructor itself.
object_.registerMethod(interfaceName_, methodName_, inputSignature_, outputSignature_, std::move(methodCallback_), flags_);
}
SignalRegistrator::SignalRegistrator(IObject& object, const std::string& signalName)
: object_(object)
, signalName_(signalName)
@ -55,7 +83,7 @@ SignalRegistrator::~SignalRegistrator() noexcept(false) // since C++11, destruct
// Therefore, we can allow registerSignal() to throw even if we are in the destructor.
// Bottomline is, to be on the safe side, the caller must take care of catching and reacting
// to the exception thrown from here if the caller is a destructor itself.
object_.registerSignal(interfaceName_, signalName_, signalSignature_);
object_.registerSignal(interfaceName_, signalName_, signalSignature_, flags_);
}
@ -87,7 +115,37 @@ PropertyRegistrator::~PropertyRegistrator() noexcept(false) // since C++11, dest
, std::move(propertyName_)
, std::move(propertySignature_)
, std::move(getter_)
, std::move(setter_) );
, std::move(setter_)
, flags_ );
}
InterfaceFlagsSetter::InterfaceFlagsSetter(IObject& object, const std::string& interfaceName)
: object_(object)
, interfaceName_(interfaceName)
, exceptions_(std::uncaught_exceptions())
{
}
InterfaceFlagsSetter::~InterfaceFlagsSetter() noexcept(false) // since C++11, destructors must
{ // explicitly be allowed to throw
// Don't set any flags if InterfaceFlagsSetter threw an exception in one of its methods
if (std::uncaught_exceptions() != exceptions_)
return;
SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when setting its flags", EINVAL);
// setInterfaceFlags() can throw. But as the InterfaceFlagsSetter shall always be used as an unnamed,
// temporary object, i.e. not as a stack-allocated object, the double-exception situation
// shall never happen. I.e. it should not happen that this destructor is directly called
// in the stack-unwinding process of another flying exception (which would lead to immediate
// termination). It can be called indirectly in the destructor of another object, but that's
// fine and safe provided that the caller catches exceptions thrown from here.
// Therefore, we can allow setInterfaceFlags() to throw even if we are in the destructor.
// Bottomline is, to be on the safe side, the caller must take care of catching and reacting
// to the exception thrown from here if the caller is a destructor itself.
object_.setInterfaceFlags( std::move(interfaceName_)
, std::move(flags_) );
}
@ -119,8 +177,8 @@ SignalEmitter::~SignalEmitter() noexcept(false) // since C++11, destructors must
}
MethodInvoker::MethodInvoker(IObjectProxy& objectProxy, const std::string& methodName)
: objectProxy_(objectProxy)
MethodInvoker::MethodInvoker(IProxy& proxy, const std::string& methodName)
: proxy_(proxy)
, methodName_(methodName)
, exceptions_(std::uncaught_exceptions()) // Needs C++17
{
@ -144,7 +202,7 @@ MethodInvoker::~MethodInvoker() noexcept(false) // since C++11, destructors must
// Therefore, we can allow callMethod() to throw even if we are in the destructor.
// Bottomline is, to be on the safe side, the caller must take care of catching and reacting
// to the exception thrown from here if the caller is a destructor itself.
objectProxy_.callMethod(method_);
proxy_.callMethod(method_);
}
}

View File

@ -25,6 +25,7 @@
#include <sdbus-c++/Error.h>
#include <systemd/sd-bus.h>
#include "ScopeGuard.h"
namespace sdbus
{
@ -32,9 +33,10 @@ namespace sdbus
{
sd_bus_error sdbusError = SD_BUS_ERROR_NULL;
sd_bus_error_set_errno(&sdbusError, errNo);
SCOPE_EXIT{ sd_bus_error_free(&sdbusError); };
std::string name(sdbusError.name);
std::string message(customMsg + " (" + sdbusError.message + ")");
sd_bus_error_free(&sdbusError);
return sdbus::Error(name, message);
}
}

111
src/Flags.cpp Normal file
View File

@ -0,0 +1,111 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file Flags.cpp
*
* Created on: Dec 31, 2018
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include <sdbus-c++/Flags.h>
#include <systemd/sd-bus.h>
namespace sdbus
{
uint64_t Flags::toSdBusInterfaceFlags() const
{
uint64_t sdbusFlags{};
using namespace sdbus;
if (flags_.test(Flags::DEPRECATED))
sdbusFlags |= SD_BUS_VTABLE_DEPRECATED;
if (!flags_.test(Flags::PRIVILEGED))
sdbusFlags |= SD_BUS_VTABLE_UNPRIVILEGED;
if (flags_.test(Flags::EMITS_CHANGE_SIGNAL))
sdbusFlags |= SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE;
else if (flags_.test(Flags::EMITS_INVALIDATION_SIGNAL))
sdbusFlags |= SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION;
else if (flags_.test(Flags::CONST_PROPERTY_VALUE))
sdbusFlags |= SD_BUS_VTABLE_PROPERTY_CONST;
else if (flags_.test(Flags::EMITS_NO_SIGNAL))
sdbusFlags |= 0;
return sdbusFlags;
}
uint64_t Flags::toSdBusMethodFlags() const
{
uint64_t sdbusFlags{};
using namespace sdbus;
if (flags_.test(Flags::DEPRECATED))
sdbusFlags |= SD_BUS_VTABLE_DEPRECATED;
if (!flags_.test(Flags::PRIVILEGED))
sdbusFlags |= SD_BUS_VTABLE_UNPRIVILEGED;
if (flags_.test(Flags::METHOD_NO_REPLY))
sdbusFlags |= SD_BUS_VTABLE_METHOD_NO_REPLY;
return sdbusFlags;
}
uint64_t Flags::toSdBusSignalFlags() const
{
uint64_t sdbusFlags{};
using namespace sdbus;
if (flags_.test(Flags::DEPRECATED))
sdbusFlags |= SD_BUS_VTABLE_DEPRECATED;
return sdbusFlags;
}
uint64_t Flags::toSdBusPropertyFlags() const
{
uint64_t sdbusFlags{};
using namespace sdbus;
if (flags_.test(Flags::DEPRECATED))
sdbusFlags |= SD_BUS_VTABLE_DEPRECATED;
//if (!flags_.test(Flags::PRIVILEGED))
// sdbusFlags |= SD_BUS_VTABLE_UNPRIVILEGED;
if (flags_.test(Flags::EMITS_CHANGE_SIGNAL))
sdbusFlags |= SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE;
else if (flags_.test(Flags::EMITS_INVALIDATION_SIGNAL))
sdbusFlags |= SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION;
else if (flags_.test(Flags::CONST_PROPERTY_VALUE))
sdbusFlags |= SD_BUS_VTABLE_PROPERTY_CONST;
else if (flags_.test(Flags::EMITS_NO_SIGNAL))
sdbusFlags |= 0;
return sdbusFlags;
}
uint64_t Flags::toSdBusWritablePropertyFlags() const
{
auto sdbusFlags = toSdBusPropertyFlags();
using namespace sdbus;
if (!flags_.test(Flags::PRIVILEGED))
sdbusFlags |= SD_BUS_VTABLE_UNPRIVILEGED;
return sdbusFlags;
}
}

47
src/IConnection.h Executable file → Normal file
View File

@ -32,7 +32,13 @@
// Forward declaration
namespace sdbus {
class Message;
class MethodCall;
class AsyncMethodCall;
class MethodReply;
class Signal;
namespace internal {
class ISdBus;
}
}
namespace sdbus {
@ -41,33 +47,34 @@ namespace internal {
class IConnection
{
public:
virtual void* addObjectVTable( const std::string& objectPath
, const std::string& interfaceName
, const void* vtable
, void* userData ) = 0;
virtual void removeObjectVTable(void* vtableHandle) = 0;
virtual const ISdBus& getSdBusInterface() const = 0;
virtual ISdBus& getSdBusInterface() = 0;
virtual sdbus::Message createMethodCall( const std::string& destination
, const std::string& objectPath
, const std::string& interfaceName
, const std::string& methodName ) const = 0;
virtual sd_bus_slot* addObjectVTable( const std::string& objectPath
, const std::string& interfaceName
, const sd_bus_vtable* vtable
, void* userData ) = 0;
virtual void removeObjectVTable(sd_bus_slot* vtableHandle) = 0;
virtual sdbus::Message createSignal( const std::string& objectPath
virtual MethodCall createMethodCall( const std::string& destination
, const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName ) const = 0;
, const std::string& methodName ) const = 0;
virtual void* registerSignalHandler( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName
, sd_bus_message_handler_t callback
, void* userData ) = 0;
virtual void unregisterSignalHandler(void* handlerCookie) = 0;
virtual Signal createSignal( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName ) const = 0;
virtual sd_bus_slot* registerSignalHandler( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName
, sd_bus_message_handler_t callback
, void* userData ) = 0;
virtual void unregisterSignalHandler(sd_bus_slot* handlerCookie) = 0;
virtual void enterProcessingLoopAsync() = 0;
virtual void leaveProcessingLoop() = 0;
virtual std::unique_ptr<sdbus::internal::IConnection> clone() const = 0;
virtual ~IConnection() = default;
};

75
src/ISdBus.h Normal file
View File

@ -0,0 +1,75 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file ISdBus.h
* @author Ardazishvili Roman (ardazishvili.roman@yandex.ru)
*
* Created on: Mar 12, 2019
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_ISDBUS_H
#define SDBUS_CXX_ISDBUS_H
#include <systemd/sd-bus.h>
namespace sdbus { namespace internal {
class ISdBus
{
public:
struct PollData
{
int fd;
short int events;
uint64_t timeout_usec;
};
virtual sd_bus_message* sd_bus_message_ref(sd_bus_message *m) = 0;
virtual sd_bus_message* sd_bus_message_unref(sd_bus_message *m) = 0;
virtual int sd_bus_send(sd_bus *bus, sd_bus_message *m, uint64_t *cookie) = 0;
virtual int sd_bus_call(sd_bus *bus, sd_bus_message *m, uint64_t usec, sd_bus_error *ret_error, sd_bus_message **reply) = 0;
virtual int sd_bus_call_async(sd_bus *bus, sd_bus_slot **slot, sd_bus_message *m, sd_bus_message_handler_t callback, void *userdata, uint64_t usec) = 0;
virtual int sd_bus_message_new_method_call(sd_bus *bus, sd_bus_message **m, const char *destination, const char *path, const char *interface, const char *member) = 0;
virtual int sd_bus_message_new_signal(sd_bus *bus, sd_bus_message **m, const char *path, const char *interface, const char *member) = 0;
virtual int sd_bus_message_new_method_return(sd_bus_message *call, sd_bus_message **m) = 0;
virtual int sd_bus_message_new_method_error(sd_bus_message *call, sd_bus_message **m, const sd_bus_error *e) = 0;
virtual int sd_bus_open_user(sd_bus **ret) = 0;
virtual int sd_bus_open_system(sd_bus **ret) = 0;
virtual int sd_bus_request_name(sd_bus *bus, const char *name, uint64_t flags) = 0;
virtual int sd_bus_release_name(sd_bus *bus, const char *name) = 0;
virtual int sd_bus_add_object_vtable(sd_bus *bus, sd_bus_slot **slot, const char *path, const char *interface, const sd_bus_vtable *vtable, void *userdata) = 0;
virtual 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 sd_bus_slot* sd_bus_slot_unref(sd_bus_slot *slot) = 0;
virtual int sd_bus_process(sd_bus *bus, sd_bus_message **r) = 0;
virtual int sd_bus_get_poll_data(sd_bus *bus, PollData* data) = 0;
virtual int sd_bus_flush(sd_bus *bus) = 0;
virtual sd_bus *sd_bus_flush_close_unref(sd_bus *bus) = 0;
virtual ~ISdBus() = default;
};
}}
#endif //SDBUS_CXX_ISDBUS_H

View File

@ -1,26 +0,0 @@
lib_LTLIBRARIES = libsdbus-c++.la
libsdbus_c___la_SOURCES = \
Connection.cpp \
ConvenienceClasses.cpp \
Message.cpp \
Object.cpp \
ObjectProxy.cpp \
Types.cpp \
Error.cpp \
VTableUtils.c
libsdbus_c___la_LIBADD = @SYSTEMD_LIBS@
# Setting per-file flags
AM_CPPFLAGS = -I$(top_srcdir)/include
AM_CXXFLAGS = @libsdbus_cpp_CFLAGS@ @SYSTEMD_CFLAGS@ -std=c++17 -pipe -pedantic -W -Wall
AM_LDFLAGS = @libsdbus_cpp_LIBS@ @SYSTEMD_LIBS@
libsdbus_c___la_LDFLAGS = -version-info 0:0:0
# Cleaning
CLEANFILES = *~ *.lo *.la
MOSTLYCLEANFILES = *.o
DISTCLEANFILES = Makefile Makefile.in

364
src/Message.cpp Executable file → Normal file
View File

@ -27,18 +27,34 @@
#include <sdbus-c++/Types.h>
#include <sdbus-c++/Error.h>
#include "MessageUtils.h"
#include "SdBus.h"
#include "ScopeGuard.h"
#include <systemd/sd-bus.h>
#include <cassert>
namespace sdbus { /*namespace internal {*/
namespace sdbus {
Message::Message(void *msg, Type type) noexcept
Message::Message(internal::ISdBus* sdbus) noexcept
: sdbus_(sdbus)
{
assert(sdbus_ != nullptr);
}
Message::Message(void *msg, internal::ISdBus* sdbus) noexcept
: msg_(msg)
, type_(type)
, sdbus_(sdbus)
{
assert(msg_ != nullptr);
sd_bus_message_ref((sd_bus_message*)msg_);
assert(sdbus_ != nullptr);
sdbus_->sd_bus_message_ref((sd_bus_message*)msg_);
}
Message::Message(void *msg, internal::ISdBus* sdbus, adopt_message_t) noexcept
: msg_(msg)
, sdbus_(sdbus)
{
assert(msg_ != nullptr);
assert(sdbus_ != nullptr);
}
Message::Message(const Message& other) noexcept
@ -48,11 +64,14 @@ Message::Message(const Message& other) noexcept
Message& Message::operator=(const Message& other) noexcept
{
if (msg_)
sdbus_->sd_bus_message_unref((sd_bus_message*)msg_);
msg_ = other.msg_;
type_ = other.type_;
sdbus_ = other.sdbus_;
ok_ = other.ok_;
sd_bus_message_ref((sd_bus_message*)msg_);
sdbus_->sd_bus_message_ref((sd_bus_message*)msg_);
return *this;
}
@ -64,10 +83,13 @@ Message::Message(Message&& other) noexcept
Message& Message::operator=(Message&& other) noexcept
{
if (msg_)
sdbus_->sd_bus_message_unref((sd_bus_message*)msg_);
msg_ = other.msg_;
other.msg_ = nullptr;
type_ = other.type_;
other.type_ = {};
sdbus_ = other.sdbus_;
other.sdbus_ = nullptr;
ok_ = other.ok_;
other.ok_ = true;
@ -77,7 +99,7 @@ Message& Message::operator=(Message&& other) noexcept
Message::~Message()
{
if (msg_)
sd_bus_message_unref((sd_bus_message*)msg_);
sdbus_->sd_bus_message_unref((sd_bus_message*)msg_);
}
Message& Message::operator<<(bool item)
@ -85,8 +107,7 @@ Message& Message::operator<<(bool item)
int intItem = item;
auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_BOOLEAN, &intItem);
if (r < 0)
SDBUS_THROW_ERROR("Failed to serialize a bool value", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to serialize a bool value", -r);
return *this;
}
@ -94,8 +115,7 @@ Message& Message::operator<<(bool item)
Message& Message::operator<<(int16_t item)
{
auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_INT16, &item);
if (r < 0)
SDBUS_THROW_ERROR("Failed to serialize a int16_t value", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to serialize a int16_t value", -r);
return *this;
}
@ -103,8 +123,7 @@ Message& Message::operator<<(int16_t item)
Message& Message::operator<<(int32_t item)
{
auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_INT32, &item);
if (r < 0)
SDBUS_THROW_ERROR("Failed to serialize a int32_t value", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to serialize a int32_t value", -r);
return *this;
}
@ -112,8 +131,7 @@ Message& Message::operator<<(int32_t item)
Message& Message::operator<<(int64_t item)
{
auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_INT64, &item);
if (r < 0)
SDBUS_THROW_ERROR("Failed to serialize a int64_t value", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to serialize a int64_t value", -r);
return *this;
}
@ -121,8 +139,7 @@ Message& Message::operator<<(int64_t item)
Message& Message::operator<<(uint8_t item)
{
auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_BYTE, &item);
if (r < 0)
SDBUS_THROW_ERROR("Failed to serialize a byte value", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to serialize a byte value", -r);
return *this;
}
@ -130,8 +147,7 @@ Message& Message::operator<<(uint8_t item)
Message& Message::operator<<(uint16_t item)
{
auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_UINT16, &item);
if (r < 0)
SDBUS_THROW_ERROR("Failed to serialize a uint16_t value", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to serialize a uint16_t value", -r);
return *this;
}
@ -139,8 +155,7 @@ Message& Message::operator<<(uint16_t item)
Message& Message::operator<<(uint32_t item)
{
auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_UINT32, &item);
if (r < 0)
SDBUS_THROW_ERROR("Failed to serialize a uint32_t value", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to serialize a uint32_t value", -r);
return *this;
}
@ -148,8 +163,7 @@ Message& Message::operator<<(uint32_t item)
Message& Message::operator<<(uint64_t item)
{
auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_UINT64, &item);
if (r < 0)
SDBUS_THROW_ERROR("Failed to serialize a uint64_t value", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to serialize a uint64_t value", -r);
return *this;
}
@ -157,8 +171,7 @@ Message& Message::operator<<(uint64_t item)
Message& Message::operator<<(double item)
{
auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_DOUBLE, &item);
if (r < 0)
SDBUS_THROW_ERROR("Failed to serialize a double value", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to serialize a double value", -r);
return *this;
}
@ -166,8 +179,7 @@ Message& Message::operator<<(double item)
Message& Message::operator<<(const char* item)
{
auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_STRING, item);
if (r < 0)
SDBUS_THROW_ERROR("Failed to serialize a C-string value", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to serialize a C-string value", -r);
return *this;
}
@ -175,8 +187,7 @@ Message& Message::operator<<(const char* item)
Message& Message::operator<<(const std::string& item)
{
auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_STRING, item.c_str());
if (r < 0)
SDBUS_THROW_ERROR("Failed to serialize a string value", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to serialize a string value", -r);
return *this;
}
@ -191,8 +202,7 @@ Message& Message::operator<<(const Variant &item)
Message& Message::operator<<(const ObjectPath &item)
{
auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_OBJECT_PATH, item.c_str());
if (r < 0)
SDBUS_THROW_ERROR("Failed to serialize an ObjectPath value", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to serialize an ObjectPath value", -r);
return *this;
}
@ -200,8 +210,7 @@ Message& Message::operator<<(const ObjectPath &item)
Message& Message::operator<<(const Signature &item)
{
auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_SIGNATURE, item.c_str());
if (r < 0)
SDBUS_THROW_ERROR("Failed to serialize an Signature value", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to serialize an Signature value", -r);
return *this;
}
@ -213,8 +222,8 @@ Message& Message::operator>>(bool& item)
auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_BOOLEAN, &intItem);
if (r == 0)
ok_ = false;
else if (r < 0)
SDBUS_THROW_ERROR("Failed to deserialize a bool value", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to deserialize a bool value", -r);
item = static_cast<bool>(intItem);
@ -226,8 +235,8 @@ Message& Message::operator>>(int16_t& item)
auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_INT16, &item);
if (r == 0)
ok_ = false;
else if (r < 0)
SDBUS_THROW_ERROR("Failed to deserialize a int16_t value", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to deserialize a int16_t value", -r);
return *this;
}
@ -237,8 +246,8 @@ Message& Message::operator>>(int32_t& item)
auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_INT32, &item);
if (r == 0)
ok_ = false;
else if (r < 0)
SDBUS_THROW_ERROR("Failed to deserialize a int32_t value", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to deserialize a int32_t value", -r);
return *this;
}
@ -248,8 +257,8 @@ Message& Message::operator>>(int64_t& item)
auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_INT64, &item);
if (r == 0)
ok_ = false;
else if (r < 0)
SDBUS_THROW_ERROR("Failed to deserialize a bool value", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to deserialize a bool value", -r);
return *this;
}
@ -259,8 +268,8 @@ Message& Message::operator>>(uint8_t& item)
auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_BYTE, &item);
if (r == 0)
ok_ = false;
else if (r < 0)
SDBUS_THROW_ERROR("Failed to deserialize a byte value", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to deserialize a byte value", -r);
return *this;
}
@ -270,8 +279,8 @@ Message& Message::operator>>(uint16_t& item)
auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_UINT16, &item);
if (r == 0)
ok_ = false;
else if (r < 0)
SDBUS_THROW_ERROR("Failed to deserialize a uint16_t value", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to deserialize a uint16_t value", -r);
return *this;
}
@ -281,8 +290,8 @@ Message& Message::operator>>(uint32_t& item)
auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_UINT32, &item);
if (r == 0)
ok_ = false;
else if (r < 0)
SDBUS_THROW_ERROR("Failed to deserialize a uint32_t value", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to deserialize a uint32_t value", -r);
return *this;
}
@ -292,8 +301,8 @@ Message& Message::operator>>(uint64_t& item)
auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_UINT64, &item);
if (r == 0)
ok_ = false;
else if (r < 0)
SDBUS_THROW_ERROR("Failed to deserialize a uint64_t value", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to deserialize a uint64_t value", -r);
return *this;
}
@ -303,8 +312,8 @@ Message& Message::operator>>(double& item)
auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_DOUBLE, &item);
if (r == 0)
ok_ = false;
else if (r < 0)
SDBUS_THROW_ERROR("Failed to deserialize a double value", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to deserialize a double value", -r);
return *this;
}
@ -314,8 +323,8 @@ Message& Message::operator>>(char*& item)
auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_STRING, &item);
if (r == 0)
ok_ = false;
else if (r < 0)
SDBUS_THROW_ERROR("Failed to deserialize a string value", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to deserialize a string value", -r);
return *this;
}
@ -350,8 +359,8 @@ Message& Message::operator>>(ObjectPath &item)
auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_OBJECT_PATH, &str);
if (r == 0)
ok_ = false;
else if (r < 0)
SDBUS_THROW_ERROR("Failed to deserialize an ObjectPath value", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to deserialize an ObjectPath value", -r);
if (str != nullptr)
item = str;
@ -365,8 +374,8 @@ Message& Message::operator>>(Signature &item)
auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_SIGNATURE, &str);
if (r == 0)
ok_ = false;
else if (r < 0)
SDBUS_THROW_ERROR("Failed to deserialize a Signature value", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to deserialize a Signature value", -r);
if (str != nullptr)
item = str;
@ -378,8 +387,7 @@ Message& Message::operator>>(Signature &item)
Message& Message::openContainer(const std::string& signature)
{
auto r = sd_bus_message_open_container((sd_bus_message*)msg_, SD_BUS_TYPE_ARRAY, signature.c_str());
if (r < 0)
SDBUS_THROW_ERROR("Failed to open a container", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to open a container", -r);
return *this;
}
@ -387,8 +395,7 @@ Message& Message::openContainer(const std::string& signature)
Message& Message::closeContainer()
{
auto r = sd_bus_message_close_container((sd_bus_message*)msg_);
if (r < 0)
SDBUS_THROW_ERROR("Failed to close a container", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to close a container", -r);
return *this;
}
@ -396,8 +403,7 @@ Message& Message::closeContainer()
Message& Message::openDictEntry(const std::string& signature)
{
auto r = sd_bus_message_open_container((sd_bus_message*)msg_, SD_BUS_TYPE_DICT_ENTRY, signature.c_str());
if (r < 0)
SDBUS_THROW_ERROR("Failed to open a dictionary entry", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to open a dictionary entry", -r);
return *this;
}
@ -405,8 +411,7 @@ Message& Message::openDictEntry(const std::string& signature)
Message& Message::closeDictEntry()
{
auto r = sd_bus_message_close_container((sd_bus_message*)msg_);
if (r < 0)
SDBUS_THROW_ERROR("Failed to close a dictionary entry", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to close a dictionary entry", -r);
return *this;
}
@ -414,8 +419,7 @@ Message& Message::closeDictEntry()
Message& Message::openVariant(const std::string& signature)
{
auto r = sd_bus_message_open_container((sd_bus_message*)msg_, SD_BUS_TYPE_VARIANT, signature.c_str());
if (r < 0)
SDBUS_THROW_ERROR("Failed to open a variant", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to open a variant", -r);
return *this;
}
@ -423,8 +427,7 @@ Message& Message::openVariant(const std::string& signature)
Message& Message::closeVariant()
{
auto r = sd_bus_message_close_container((sd_bus_message*)msg_);
if (r < 0)
SDBUS_THROW_ERROR("Failed to close a variant", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to close a variant", -r);
return *this;
}
@ -432,8 +435,7 @@ Message& Message::closeVariant()
Message& Message::openStruct(const std::string& signature)
{
auto r = sd_bus_message_open_container((sd_bus_message*)msg_, SD_BUS_TYPE_STRUCT, signature.c_str());
if (r < 0)
SDBUS_THROW_ERROR("Failed to open a struct", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to open a struct", -r);
return *this;
}
@ -441,8 +443,7 @@ Message& Message::openStruct(const std::string& signature)
Message& Message::closeStruct()
{
auto r = sd_bus_message_close_container((sd_bus_message*)msg_);
if (r < 0)
SDBUS_THROW_ERROR("Failed to close a struct", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to close a struct", -r);
return *this;
}
@ -453,8 +454,8 @@ Message& Message::enterContainer(const std::string& signature)
auto r = sd_bus_message_enter_container((sd_bus_message*)msg_, SD_BUS_TYPE_ARRAY, signature.c_str());
if (r == 0)
ok_ = false;
else if (r < 0)
SDBUS_THROW_ERROR("Failed to enter a container", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to enter a container", -r);
return *this;
}
@ -462,8 +463,7 @@ Message& Message::enterContainer(const std::string& signature)
Message& Message::exitContainer()
{
auto r = sd_bus_message_exit_container((sd_bus_message*)msg_);
if (r < 0)
SDBUS_THROW_ERROR("Failed to exit a container", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to exit a container", -r);
return *this;
}
@ -473,8 +473,8 @@ Message& Message::enterDictEntry(const std::string& signature)
auto r = sd_bus_message_enter_container((sd_bus_message*)msg_, SD_BUS_TYPE_DICT_ENTRY, signature.c_str());
if (r == 0)
ok_ = false;
else if (r < 0)
SDBUS_THROW_ERROR("Failed to enter a dictionary entry", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to enter a dictionary entry", -r);
return *this;
}
@ -482,8 +482,7 @@ Message& Message::enterDictEntry(const std::string& signature)
Message& Message::exitDictEntry()
{
auto r = sd_bus_message_exit_container((sd_bus_message*)msg_);
if (r < 0)
SDBUS_THROW_ERROR("Failed to exit a dictionary entry", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to exit a dictionary entry", -r);
return *this;
}
@ -493,8 +492,8 @@ Message& Message::enterVariant(const std::string& signature)
auto r = sd_bus_message_enter_container((sd_bus_message*)msg_, SD_BUS_TYPE_VARIANT, signature.c_str());
if (r == 0)
ok_ = false;
else if (r < 0)
SDBUS_THROW_ERROR("Failed to enter a variant", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to enter a variant", -r);
return *this;
}
@ -502,8 +501,7 @@ Message& Message::enterVariant(const std::string& signature)
Message& Message::exitVariant()
{
auto r = sd_bus_message_exit_container((sd_bus_message*)msg_);
if (r < 0)
SDBUS_THROW_ERROR("Failed to exit a variant", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to exit a variant", -r);
return *this;
}
@ -513,8 +511,8 @@ Message& Message::enterStruct(const std::string& signature)
auto r = sd_bus_message_enter_container((sd_bus_message*)msg_, SD_BUS_TYPE_STRUCT, signature.c_str());
if (r == 0)
ok_ = false;
else if (r < 0)
SDBUS_THROW_ERROR("Failed to enter a struct", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to enter a struct", -r);
return *this;
}
@ -522,8 +520,7 @@ Message& Message::enterStruct(const std::string& signature)
Message& Message::exitStruct()
{
auto r = sd_bus_message_exit_container((sd_bus_message*)msg_);
if (r < 0)
SDBUS_THROW_ERROR("Failed to exit a struct", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to exit a struct", -r);
return *this;
}
@ -542,8 +539,7 @@ void Message::clearFlags()
void Message::copyTo(Message& destination, bool complete) const
{
auto r = sd_bus_message_copy((sd_bus_message*)destination.msg_, (sd_bus_message*)msg_, complete);
if (r < 0)
SDBUS_THROW_ERROR("Failed to copy the message", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to copy the message", -r);
}
void Message::seal()
@ -551,71 +547,13 @@ void Message::seal()
const auto messageCookie = 1;
const auto sealTimeout = 0;
auto r = sd_bus_message_seal((sd_bus_message*)msg_, messageCookie, sealTimeout);
if (r < 0)
SDBUS_THROW_ERROR("Failed to seal the message", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to seal the message", -r);
}
void Message::rewind(bool complete)
{
auto r = sd_bus_message_rewind((sd_bus_message*)msg_, complete);
if (r < 0)
SDBUS_THROW_ERROR("Failed to rewind the message", -r);
}
Message Message::send() const
{
if (type_ == Type::eMethodCall)
{
sd_bus_message* sdbusReply{};
SCOPE_EXIT{ sd_bus_message_unref(sdbusReply); }; // Returned message will become an owner of sdbusReply
sd_bus_error sdbusError = SD_BUS_ERROR_NULL;
SCOPE_EXIT{ sd_bus_error_free(&sdbusError); };
auto r = sd_bus_call(nullptr, (sd_bus_message*)msg_, 0, &sdbusError, &sdbusReply);
if (sd_bus_error_is_set(&sdbusError))
{
throw sdbus::Error(sdbusError.name, sdbusError.message);
}
SDBUS_THROW_ERROR_IF(r < 0, "Failed to call method", -r);
return Message(sdbusReply);
}
else if (type_ == Type::eMethodReply)
{
auto r = sd_bus_send(nullptr, (sd_bus_message*)msg_, nullptr);
if (r < 0)
SDBUS_THROW_ERROR("Failed to send reply", -r);
return Message();
}
else if (type_ == Type::eSignal)
{
auto r = sd_bus_send(nullptr, (sd_bus_message*)msg_, nullptr);
if (r < 0)
SDBUS_THROW_ERROR("Failed to emit signal", -r);
return Message();
}
else
{
assert(false);
return Message();
}
}
Message Message::createReply() const
{
sd_bus_message *sdbusReply{};
SCOPE_EXIT{ sd_bus_message_unref(sdbusReply); }; // Returned message will become an owner of sdbusReply
auto r = sd_bus_message_new_method_return((sd_bus_message*)msg_, &sdbusReply);
if (r < 0)
SDBUS_THROW_ERROR("Failed to create method reply", -r);
assert(sdbusReply != nullptr);
return Message(sdbusReply, Type::eMethodReply);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to rewind the message", -r);
}
std::string Message::getInterfaceName() const
@ -633,15 +571,14 @@ void Message::peekType(std::string& type, std::string& contents) const
char typeSig;
const char* contentsSig;
auto r = sd_bus_message_peek_type((sd_bus_message*)msg_, &typeSig, &contentsSig);
if (r < 0)
SDBUS_THROW_ERROR("Failed to peek message type", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to peek message type", -r);
type = typeSig;
contents = contentsSig;
}
bool Message::isValid() const
{
return msg_ != nullptr;
return msg_ != nullptr && sdbus_ != nullptr;
}
bool Message::isEmpty() const
@ -649,9 +586,98 @@ bool Message::isEmpty() const
return sd_bus_message_is_empty((sd_bus_message*)msg_);
}
Message::Type Message::getType() const
void MethodCall::dontExpectReply()
{
return type_;
auto r = sd_bus_message_set_expect_reply((sd_bus_message*)msg_, 0);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to set the dont-expect-reply flag", -r);
}
bool MethodCall::doesntExpectReply() const
{
auto r = sd_bus_message_get_expect_reply((sd_bus_message*)msg_);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get the dont-expect-reply flag", -r);
return r > 0 ? false : true;
}
MethodReply MethodCall::send() const
{
if (!doesntExpectReply())
return sendWithReply();
else
return sendWithNoReply();
}
MethodReply MethodCall::sendWithReply() const
{
sd_bus_error sdbusError = SD_BUS_ERROR_NULL;
SCOPE_EXIT{ sd_bus_error_free(&sdbusError); };
sd_bus_message* sdbusReply{};
auto r = sdbus_->sd_bus_call(nullptr, (sd_bus_message*)msg_, 0, &sdbusError, &sdbusReply);
if (sd_bus_error_is_set(&sdbusError))
throw sdbus::Error(sdbusError.name, sdbusError.message);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to call method", -r);
return MethodReply{sdbusReply, sdbus_, adopt_message};
}
MethodReply MethodCall::sendWithNoReply() const
{
auto r = sdbus_->sd_bus_send(nullptr, (sd_bus_message*)msg_, nullptr);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to call method with no reply", -r);
return MethodReply{}; // No reply
}
MethodReply MethodCall::createReply() const
{
sd_bus_message* sdbusReply{};
auto r = sdbus_->sd_bus_message_new_method_return((sd_bus_message*)msg_, &sdbusReply);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to create method reply", -r);
return MethodReply{sdbusReply, sdbus_, adopt_message};
}
MethodReply MethodCall::createErrorReply(const Error& error) const
{
sd_bus_error sdbusError = SD_BUS_ERROR_NULL;
SCOPE_EXIT{ sd_bus_error_free(&sdbusError); };
sd_bus_error_set(&sdbusError, error.getName().c_str(), error.getMessage().c_str());
sd_bus_message* sdbusErrorReply{};
auto r = sdbus_->sd_bus_message_new_method_error((sd_bus_message*)msg_, &sdbusErrorReply, &sdbusError);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to create method error reply", -r);
return MethodReply{sdbusErrorReply, sdbus_, adopt_message};
}
AsyncMethodCall::AsyncMethodCall(MethodCall&& call) noexcept
: Message(std::move(call))
{
}
AsyncMethodCall::Slot AsyncMethodCall::send(void* callback, void* userData) const
{
sd_bus_slot* slot;
auto r = sdbus_->sd_bus_call_async(nullptr, &slot, (sd_bus_message*)msg_, (sd_bus_message_handler_t)callback, userData, 0);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to call method asynchronously", -r);
return Slot{slot, [sdbus_ = sdbus_](void *slot){ sdbus_->sd_bus_slot_unref((sd_bus_slot*)slot); }};
}
void MethodReply::send() const
{
auto r = sdbus_->sd_bus_send(nullptr, (sd_bus_message*)msg_, nullptr);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to send reply", -r);
}
void Signal::send() const
{
auto r = sdbus_->sd_bus_send(nullptr, (sd_bus_message*)msg_, nullptr);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to emit signal", -r);
}
Message createPlainMessage()
@ -659,18 +685,24 @@ Message createPlainMessage()
int r;
sd_bus* bus{};
SCOPE_EXIT{ sd_bus_unref(bus); }; // sdbusMsg will hold reference to the bus
SCOPE_EXIT{ sd_bus_unref(bus); };
r = sd_bus_default_system(&bus);
if (r < 0)
SDBUS_THROW_ERROR("Failed to get default system bus", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get default system bus", -r);
thread_local struct BusReferenceKeeper
{
BusReferenceKeeper(sd_bus* bus) : bus_(bus) {}
~BusReferenceKeeper() { sd_bus_unref(bus_); }
sd_bus* bus_{};
} busReferenceKeeper{bus};
sd_bus_message* sdbusMsg{};
SCOPE_EXIT{ sd_bus_message_unref(sdbusMsg); }; // Returned message will become an owner of sdbusMsg
r = sd_bus_message_new(bus, &sdbusMsg, _SD_BUS_MESSAGE_TYPE_INVALID);
if (r < 0)
SDBUS_THROW_ERROR("Failed to create a new message", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to create a new message", -r);
return Message(sdbusMsg, Message::Type::ePlainMessage);
thread_local internal::SdBus sdbus;
return Message{sdbusMsg, &sdbus, adopt_message};
}
/*}*/}
}

189
src/Object.cpp Executable file → Normal file
View File

@ -27,6 +27,8 @@
#include <sdbus-c++/IConnection.h>
#include <sdbus-c++/Message.h>
#include <sdbus-c++/Error.h>
#include <sdbus-c++/MethodResult.h>
#include <sdbus-c++/Flags.h>
#include "IConnection.h"
#include "VTableUtils.h"
#include <systemd/sd-bus.h>
@ -44,13 +46,13 @@ void Object::registerMethod( const std::string& interfaceName
, const std::string& methodName
, const std::string& inputSignature
, const std::string& outputSignature
, method_callback methodCallback )
, method_callback methodCallback
, Flags flags )
{
SDBUS_THROW_ERROR_IF(!methodCallback, "Invalid method callback provided", EINVAL);
auto& interface = interfaces_[interfaceName];
InterfaceData::MethodData methodData{inputSignature, outputSignature, std::move(methodCallback)};
InterfaceData::MethodData methodData{inputSignature, outputSignature, std::move(methodCallback), flags};
auto inserted = interface.methods_.emplace(methodName, std::move(methodData)).second;
SDBUS_THROW_ERROR_IF(!inserted, "Failed to register method: method already exists", EINVAL);
@ -58,11 +60,12 @@ void Object::registerMethod( const std::string& interfaceName
void Object::registerSignal( const std::string& interfaceName
, const std::string& signalName
, const std::string& signature )
, const std::string& signature
, Flags flags )
{
auto& interface = interfaces_[interfaceName];
InterfaceData::SignalData signalData{signature};
InterfaceData::SignalData signalData{signature, flags};
auto inserted = interface.signals_.emplace(signalName, std::move(signalData)).second;
SDBUS_THROW_ERROR_IF(!inserted, "Failed to register signal: signal already exists", EINVAL);
@ -71,31 +74,40 @@ void Object::registerSignal( const std::string& interfaceName
void Object::registerProperty( const std::string& interfaceName
, const std::string& propertyName
, const std::string& signature
, property_get_callback getCallback )
, property_get_callback getCallback
, Flags flags )
{
registerProperty( interfaceName
, propertyName
, signature
, getCallback
, property_set_callback{} );
, property_set_callback{}
, flags );
}
void Object::registerProperty( const std::string& interfaceName
, const std::string& propertyName
, const std::string& signature
, property_get_callback getCallback
, property_set_callback setCallback )
, property_set_callback setCallback
, Flags flags )
{
SDBUS_THROW_ERROR_IF(!getCallback && !setCallback, "Invalid property callbacks provided", EINVAL);
auto& interface = interfaces_[interfaceName];
InterfaceData::PropertyData propertyData{signature, std::move(getCallback), std::move(setCallback)};
InterfaceData::PropertyData propertyData{signature, std::move(getCallback), std::move(setCallback), flags};
auto inserted = interface.properties_.emplace(propertyName, std::move(propertyData)).second;
SDBUS_THROW_ERROR_IF(!inserted, "Failed to register property: property already exists", EINVAL);
}
void Object::setInterfaceFlags(const std::string& interfaceName, Flags flags)
{
auto& interface = interfaces_[interfaceName];
interface.flags_ = flags;
}
void Object::finishRegistration()
{
for (auto& item : interfaces_)
@ -103,86 +115,124 @@ void Object::finishRegistration()
const auto& interfaceName = item.first;
auto& interfaceData = item.second;
auto& vtable = interfaceData.vtable_;
assert(vtable.empty());
vtable.push_back(createVTableStartItem());
for (const auto& item : interfaceData.methods_)
{
const auto& methodName = item.first;
const auto& methodData = item.second;
vtable.push_back(createVTableMethodItem( methodName.c_str()
, methodData.inputArgs_.c_str()
, methodData.outputArgs_.c_str()
, &Object::sdbus_method_callback ));
}
for (const auto& item : interfaceData.signals_)
{
const auto& signalName = item.first;
const auto& signalData = item.second;
vtable.push_back(createVTableSignalItem( signalName.c_str()
, signalData.signature_.c_str() ));
}
for (const auto& item : interfaceData.properties_)
{
const auto& propertyName = item.first;
const auto& propertyData = item.second;
if (!propertyData.setCallback_)
vtable.push_back(createVTablePropertyItem( propertyName.c_str()
, propertyData.signature_.c_str()
, &Object::sdbus_property_get_callback ));
else
vtable.push_back(createVTableWritablePropertyItem( propertyName.c_str()
, propertyData.signature_.c_str()
, &Object::sdbus_property_get_callback
, &Object::sdbus_property_set_callback ));
}
vtable.push_back(createVTableEndItem());
// Tell, don't ask
auto slot = (sd_bus_slot*) connection_.addObjectVTable(objectPath_, interfaceName, &vtable[0], this);
interfaceData.slot_.reset(slot);
interfaceData.slot_.get_deleter() = [this](void *slot){ connection_.removeObjectVTable(slot); };
const auto& vtable = createInterfaceVTable(interfaceData);
activateInterfaceVTable(interfaceName, interfaceData, vtable);
}
}
sdbus::Message Object::createSignal(const std::string& interfaceName, const std::string& signalName)
void Object::unregister()
{
interfaces_.clear();
}
sdbus::Signal Object::createSignal(const std::string& interfaceName, const std::string& signalName)
{
// Tell, don't ask
return connection_.createSignal(objectPath_, interfaceName, signalName);
}
void Object::emitSignal(const sdbus::Message& message)
void Object::emitSignal(const sdbus::Signal& message)
{
message.send();
}
sdbus::IConnection& Object::getConnection() const
{
return dynamic_cast<sdbus::IConnection&>(connection_);
}
const std::vector<sd_bus_vtable>& Object::createInterfaceVTable(InterfaceData& interfaceData)
{
auto& vtable = interfaceData.vtable_;
assert(vtable.empty());
vtable.push_back(createVTableStartItem(interfaceData.flags_.toSdBusInterfaceFlags()));
registerMethodsToVTable(interfaceData, vtable);
registerSignalsToVTable(interfaceData, vtable);
registerPropertiesToVTable(interfaceData, vtable);
vtable.push_back(createVTableEndItem());
return vtable;
}
void Object::registerMethodsToVTable(const InterfaceData& interfaceData, std::vector<sd_bus_vtable>& vtable)
{
for (const auto& item : interfaceData.methods_)
{
const auto& methodName = item.first;
const auto& methodData = item.second;
vtable.push_back(createVTableMethodItem( methodName.c_str()
, methodData.inputArgs_.c_str()
, methodData.outputArgs_.c_str()
, &Object::sdbus_method_callback
, methodData.flags_.toSdBusMethodFlags() ));
}
}
void Object::registerSignalsToVTable(const InterfaceData& interfaceData, std::vector<sd_bus_vtable>& vtable)
{
for (const auto& item : interfaceData.signals_)
{
const auto& signalName = item.first;
const auto& signalData = item.second;
vtable.push_back(createVTableSignalItem( signalName.c_str()
, signalData.signature_.c_str()
, signalData.flags_.toSdBusSignalFlags() ));
}
}
void Object::registerPropertiesToVTable(const InterfaceData& interfaceData, std::vector<sd_bus_vtable>& vtable)
{
for (const auto& item : interfaceData.properties_)
{
const auto& propertyName = item.first;
const auto& propertyData = item.second;
if (!propertyData.setCallback_)
vtable.push_back(createVTablePropertyItem( propertyName.c_str()
, propertyData.signature_.c_str()
, &Object::sdbus_property_get_callback
, propertyData.flags_.toSdBusPropertyFlags() ));
else
vtable.push_back(createVTableWritablePropertyItem( propertyName.c_str()
, propertyData.signature_.c_str()
, &Object::sdbus_property_get_callback
, &Object::sdbus_property_set_callback
, propertyData.flags_.toSdBusWritablePropertyFlags() ));
}
}
void Object::activateInterfaceVTable( const std::string& interfaceName
, InterfaceData& interfaceData
, const std::vector<sd_bus_vtable>& vtable )
{
// Tell, don't ask
auto slot = connection_.addObjectVTable(objectPath_, interfaceName, &vtable[0], this);
interfaceData.slot_.reset(slot);
interfaceData.slot_.get_deleter() = [this](sd_bus_slot *slot){ connection_.removeObjectVTable(slot); };
}
int Object::sdbus_method_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError)
{
Message message(sdbusMessage, Message::Type::eMethodCall);
auto* object = static_cast<Object*>(userData);
assert(object != nullptr);
MethodCall message{sdbusMessage, &object->connection_.getSdBusInterface()};
// Note: The lookup can be optimized by using sorted vectors instead of associative containers
auto& callback = object->interfaces_[message.getInterfaceName()].methods_[message.getMemberName()].callback_;
assert(callback);
auto reply = message.createReply();
try
{
callback(message, reply);
callback(std::move(message));
}
catch (const sdbus::Error& e)
{
sd_bus_error_set(retError, e.getName().c_str(), e.getMessage().c_str());
return 1;
}
reply.send();
return 1;
}
@ -194,9 +244,9 @@ int Object::sdbus_property_get_callback( sd_bus */*bus*/
, void *userData
, sd_bus_error *retError )
{
Message reply(sdbusReply, Message::Type::ePlainMessage);
auto* object = static_cast<Object*>(userData);
assert(object != nullptr);
// Note: The lookup can be optimized by using sorted vectors instead of associative containers
auto& callback = object->interfaces_[interface].properties_[property].getCallback_;
// Getter can be empty - the case of "write-only" property
@ -206,6 +256,8 @@ int Object::sdbus_property_get_callback( sd_bus */*bus*/
return 1;
}
Message reply{sdbusReply, &object->connection_.getSdBusInterface()};
try
{
callback(reply);
@ -226,13 +278,15 @@ int Object::sdbus_property_set_callback( sd_bus */*bus*/
, void *userData
, sd_bus_error *retError )
{
Message value(sdbusValue, Message::Type::ePlainMessage);
auto* object = static_cast<Object*>(userData);
assert(object != nullptr);
// Note: The lookup can be optimized by using sorted vectors instead of associative containers
auto& callback = object->interfaces_[interface].properties_[property].setCallback_;
assert(callback);
Message value{sdbusValue, &object->connection_.getSdBusInterface()};
try
{
callback(value);
@ -240,7 +294,6 @@ int Object::sdbus_property_set_callback( sd_bus */*bus*/
catch (const sdbus::Error& e)
{
sd_bus_error_set(retError, e.getName().c_str(), e.getMessage().c_str());
return 1;
}
return 1;

View File

@ -32,6 +32,7 @@
#include <string>
#include <map>
#include <vector>
#include <functional>
#include <memory>
#include <cassert>
@ -48,29 +49,80 @@ namespace internal {
, const std::string& methodName
, const std::string& inputSignature
, const std::string& outputSignature
, method_callback methodCallback ) override;
, method_callback methodCallback
, Flags flags ) override;
void registerSignal( const std::string& interfaceName
, const std::string& signalName
, const std::string& signature ) override;
void registerProperty( const std::string& interfaceName
, const std::string& propertyName
, const std::string& signature
, property_get_callback getCallback ) override;
, const std::string& signature
, Flags flags ) override;
void registerProperty( const std::string& interfaceName
, const std::string& propertyName
, const std::string& signature
, property_get_callback getCallback
, property_set_callback setCallback ) override;
, Flags flags ) override;
void registerProperty( const std::string& interfaceName
, const std::string& propertyName
, const std::string& signature
, property_get_callback getCallback
, property_set_callback setCallback
, Flags flags ) override;
void setInterfaceFlags(const std::string& interfaceName, Flags flags) override;
void finishRegistration() override;
void unregister() override;
sdbus::Message createSignal(const std::string& interfaceName, const std::string& signalName) override;
void emitSignal(const sdbus::Message& message) override;
sdbus::Signal createSignal(const std::string& interfaceName, const std::string& signalName) override;
void emitSignal(const sdbus::Signal& message) override;
sdbus::IConnection& getConnection() const override;
private:
using InterfaceName = std::string;
struct InterfaceData
{
using MethodName = std::string;
struct MethodData
{
std::string inputArgs_;
std::string outputArgs_;
method_callback callback_;
Flags flags_;
};
std::map<MethodName, MethodData> methods_;
using SignalName = std::string;
struct SignalData
{
std::string signature_;
Flags flags_;
};
std::map<SignalName, SignalData> signals_;
using PropertyName = std::string;
struct PropertyData
{
std::string signature_;
property_get_callback getCallback_;
property_set_callback setCallback_;
Flags flags_;
};
std::map<PropertyName, PropertyData> properties_;
std::vector<sd_bus_vtable> vtable_;
Flags flags_;
std::unique_ptr<sd_bus_slot, std::function<void(sd_bus_slot*)>> slot_;
};
static const std::vector<sd_bus_vtable>& createInterfaceVTable(InterfaceData& interfaceData);
static void registerMethodsToVTable(const InterfaceData& interfaceData, std::vector<sd_bus_vtable>& vtable);
static void registerSignalsToVTable(const InterfaceData& interfaceData, std::vector<sd_bus_vtable>& vtable);
static void registerPropertiesToVTable(const InterfaceData& interfaceData, std::vector<sd_bus_vtable>& vtable);
void activateInterfaceVTable( const std::string& interfaceName
, InterfaceData& interfaceData
, const std::vector<sd_bus_vtable>& vtable );
static int sdbus_method_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError);
static int sdbus_property_get_callback( sd_bus *bus
, const char *objectPath
@ -90,36 +142,6 @@ namespace internal {
private:
sdbus::internal::IConnection& connection_;
std::string objectPath_;
using InterfaceName = std::string;
struct InterfaceData
{
using MethodName = std::string;
struct MethodData
{
std::string inputArgs_;
std::string outputArgs_;
method_callback callback_;
};
std::map<MethodName, MethodData> methods_;
using SignalName = std::string;
struct SignalData
{
std::string signature_;
};
std::map<SignalName, SignalData> signals_;
using PropertyName = std::string;
struct PropertyData
{
std::string signature_;
property_get_callback getCallback_;
property_set_callback setCallback_;
};
std::map<PropertyName, PropertyData> properties_;
std::vector<sd_bus_vtable> vtable_;
std::unique_ptr<void, std::function<void(void*)>> slot_;
};
std::map<InterfaceName, InterfaceData> interfaces_;
};

View File

@ -1,182 +0,0 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file ObjectProxy.cpp
*
* Created on: Nov 8, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ObjectProxy.h"
#include <sdbus-c++/Message.h>
#include <sdbus-c++/IConnection.h>
#include <sdbus-c++/Error.h>
#include "IConnection.h"
#include <systemd/sd-bus.h>
#include <cassert>
namespace sdbus { namespace internal {
ObjectProxy::ObjectProxy(sdbus::internal::IConnection& connection, std::string destination, std::string objectPath)
: connection_(&connection, [](sdbus::internal::IConnection *){ /* Intentionally left empty */ })
, ownConnection_(false)
, destination_(std::move(destination))
, objectPath_(std::move(objectPath))
{
}
ObjectProxy::ObjectProxy( std::unique_ptr<sdbus::internal::IConnection>&& connection
, std::string destination
, std::string objectPath )
: connection_(std::move(connection))
, ownConnection_(true)
, destination_(std::move(destination))
, objectPath_(std::move(objectPath))
{
}
ObjectProxy::~ObjectProxy()
{
// If the dedicated connection for signals is used, we have to stop the processing loop
// upon this connection prior to unregistering signal slots in the interfaces_ container,
// otherwise we might have a race condition of two threads working upon one connection.
if (signalConnection_ != nullptr)
signalConnection_->leaveProcessingLoop();
}
Message ObjectProxy::createMethodCall(const std::string& interfaceName, const std::string& methodName)
{
// Tell, don't ask
return connection_->createMethodCall(destination_, objectPath_, interfaceName, methodName);
}
Message ObjectProxy::callMethod(const Message& message)
{
return message.send();
}
void ObjectProxy::registerSignalHandler( const std::string& interfaceName
, const std::string& signalName
, signal_handler signalHandler )
{
SDBUS_THROW_ERROR_IF(!signalHandler, "Invalid signal handler provided", EINVAL);
auto& interface = interfaces_[interfaceName];
InterfaceData::SignalData signalData{std::move(signalHandler), nullptr};
auto insertionResult = interface.signals_.emplace(signalName, std::move(signalData));
auto inserted = insertionResult.second;
SDBUS_THROW_ERROR_IF(!inserted, "Failed to register signal handler: handler already exists", EINVAL);
}
void ObjectProxy::finishRegistration()
{
bool hasSignals = listensToSignals();
if (hasSignals && ownConnection_)
{
// Let's use dedicated signalConnection_ for signals,
// which will then be used by the processing loop thread.
signalConnection_ = connection_->clone();
registerSignalHandlers(*signalConnection_);
signalConnection_->enterProcessingLoopAsync();
}
else if (hasSignals)
{
// Let's used connection provided from the outside.
registerSignalHandlers(*connection_);
}
}
bool ObjectProxy::listensToSignals() const
{
for (auto& interfaceItem : interfaces_)
if (!interfaceItem.second.signals_.empty())
return true;
return false;
}
void ObjectProxy::registerSignalHandlers(sdbus::internal::IConnection& connection)
{
for (auto& interfaceItem : interfaces_)
{
const auto& interfaceName = interfaceItem.first;
auto& signalsOnInterface = interfaceItem.second.signals_;
for (auto& signalItem : signalsOnInterface)
{
const auto& signalName = signalItem.first;
auto& slot = signalItem.second.slot_;
auto* rawSlotPtr = connection.registerSignalHandler( objectPath_
, interfaceName
, signalName
, &ObjectProxy::sdbus_signal_callback
, this );
slot.reset(rawSlotPtr);
slot.get_deleter() = [&connection](void *slot){ connection.unregisterSignalHandler(slot); };
}
}
}
int ObjectProxy::sdbus_signal_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error */*retError*/)
{
Message message(sdbusMessage, Message::Type::eSignal);
auto* object = static_cast<ObjectProxy*>(userData);
// Note: The lookup can be optimized by using sorted vectors instead of associative containers
auto& callback = object->interfaces_[message.getInterfaceName()].signals_[message.getMemberName()].callback_;
assert(callback);
callback(message);
return 1;
}
}}
namespace sdbus {
std::unique_ptr<sdbus::IObjectProxy> createObjectProxy( IConnection& connection
, std::string destination
, std::string objectPath )
{
auto* sdbusConnection = dynamic_cast<sdbus::internal::IConnection*>(&connection);
SDBUS_THROW_ERROR_IF(!sdbusConnection, "Connection is not a real sdbus-c++ connection", EINVAL);
return std::make_unique<sdbus::internal::ObjectProxy>( *sdbusConnection
, std::move(destination)
, std::move(objectPath) );
}
std::unique_ptr<sdbus::IObjectProxy> createObjectProxy( std::string destination
, std::string objectPath )
{
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::ObjectProxy>( std::move(sdbusConnection)
, std::move(destination)
, std::move(objectPath) );
}
}

View File

@ -1,93 +0,0 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file ObjectProxy.h
*
* Created on: Nov 8, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_INTERNAL_OBJECTPROXY_H_
#define SDBUS_CXX_INTERNAL_OBJECTPROXY_H_
#include <sdbus-c++/IObjectProxy.h>
#include <systemd/sd-bus.h>
#include <string>
#include <memory>
#include <map>
// Forward declarations
namespace sdbus { namespace internal {
class IConnection;
}}
namespace sdbus {
namespace internal {
class ObjectProxy
: public IObjectProxy
{
public:
ObjectProxy( sdbus::internal::IConnection& connection
, std::string destination
, std::string objectPath );
ObjectProxy( std::unique_ptr<sdbus::internal::IConnection>&& connection
, std::string destination
, std::string objectPath );
~ObjectProxy();
Message createMethodCall(const std::string& interfaceName, const std::string& methodName) override;
Message callMethod(const Message& message) override;
void registerSignalHandler( const std::string& interfaceName
, const std::string& signalName
, signal_handler signalHandler ) override;
void finishRegistration() override;
private:
bool listensToSignals() const;
void registerSignalHandlers(sdbus::internal::IConnection& connection);
static int sdbus_signal_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError);
private:
std::unique_ptr< sdbus::internal::IConnection
, std::function<void(sdbus::internal::IConnection*)>
> connection_;
bool ownConnection_{};
std::unique_ptr<sdbus::internal::IConnection> signalConnection_;
std::string destination_;
std::string objectPath_;
using InterfaceName = std::string;
struct InterfaceData
{
using SignalName = std::string;
struct SignalData
{
signal_handler callback_;
std::unique_ptr<void, std::function<void(void*)>> slot_;
};
std::map<SignalName, SignalData> signals_;
};
std::map<InterfaceName, InterfaceData> interfaces_;
};
}}
#endif /* SDBUS_CXX_INTERNAL_OBJECTPROXY_H_ */

217
src/Proxy.cpp Normal file
View File

@ -0,0 +1,217 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file Proxy.cpp
*
* Created on: Nov 8, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Proxy.h"
#include "IConnection.h"
#include "sdbus-c++/Message.h"
#include "sdbus-c++/IConnection.h"
#include "sdbus-c++/Error.h"
#include "ScopeGuard.h"
#include <systemd/sd-bus.h>
#include <cassert>
#include <chrono>
#include <thread>
namespace sdbus { namespace internal {
Proxy::Proxy(sdbus::internal::IConnection& connection, std::string destination, std::string objectPath)
: connection_(&connection, [](sdbus::internal::IConnection *){ /* Intentionally left empty */ })
, destination_(std::move(destination))
, objectPath_(std::move(objectPath))
{
// The connection is not ours only, it is owned and managed by the user and we just reference
// it here, so we expect the client to manage the event loop upon this connection themselves.
}
Proxy::Proxy( std::unique_ptr<sdbus::internal::IConnection>&& connection
, std::string destination
, std::string objectPath )
: connection_(std::move(connection))
, destination_(std::move(destination))
, objectPath_(std::move(objectPath))
{
// The connection is ours only, i.e. it's us who has to manage the event loop upon this connection,
// in order that we get and process signals, async call replies, and other messages from D-Bus.
connection_->enterProcessingLoopAsync();
}
MethodCall Proxy::createMethodCall(const std::string& interfaceName, const std::string& methodName)
{
return connection_->createMethodCall(destination_, objectPath_, interfaceName, methodName);
}
AsyncMethodCall Proxy::createAsyncMethodCall(const std::string& interfaceName, const std::string& methodName)
{
return AsyncMethodCall{Proxy::createMethodCall(interfaceName, methodName)};
}
MethodReply Proxy::callMethod(const MethodCall& message)
{
return message.send();
}
void Proxy::callMethod(const AsyncMethodCall& message, async_reply_handler asyncReplyCallback)
{
auto callback = (void*)&Proxy::sdbus_async_reply_handler;
auto callData = std::make_unique<AsyncCalls::CallData>(AsyncCalls::CallData{*this, std::move(asyncReplyCallback), {}});
callData->slot = message.send(callback, callData.get());
pendingAsyncCalls_.addCall(callData->slot.get(), std::move(callData));
}
void Proxy::registerSignalHandler( const std::string& interfaceName
, const std::string& signalName
, signal_handler signalHandler )
{
SDBUS_THROW_ERROR_IF(!signalHandler, "Invalid signal handler provided", EINVAL);
auto& interface = interfaces_[interfaceName];
InterfaceData::SignalData signalData{std::move(signalHandler), nullptr};
auto insertionResult = interface.signals_.emplace(signalName, std::move(signalData));
auto inserted = insertionResult.second;
SDBUS_THROW_ERROR_IF(!inserted, "Failed to register signal handler: handler already exists", EINVAL);
}
void Proxy::finishRegistration()
{
registerSignalHandlers(*connection_);
}
void Proxy::registerSignalHandlers(sdbus::internal::IConnection& connection)
{
for (auto& interfaceItem : interfaces_)
{
const auto& interfaceName = interfaceItem.first;
auto& signalsOnInterface = interfaceItem.second.signals_;
for (auto& signalItem : signalsOnInterface)
{
const auto& signalName = signalItem.first;
auto& slot = signalItem.second.slot_;
auto* rawSlotPtr = connection.registerSignalHandler( objectPath_
, interfaceName
, signalName
, &Proxy::sdbus_signal_callback
, this );
slot.reset(rawSlotPtr);
slot.get_deleter() = [&connection](sd_bus_slot *slot){ connection.unregisterSignalHandler(slot); };
}
}
}
void Proxy::unregister()
{
pendingAsyncCalls_.clear();
interfaces_.clear();
}
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;
SCOPE_EXIT{ proxy.pendingAsyncCalls_.removeCall(asyncCallData->slot.get()); };
MethodReply message{sdbusMessage, &proxy.connection_->getSdBusInterface()};
const auto* error = sd_bus_message_get_error(sdbusMessage);
if (error == nullptr)
{
asyncCallData->callback(message, nullptr);
}
else
{
sdbus::Error exception(error->name, error->message);
asyncCallData->callback(message, &exception);
}
return 1;
}
int Proxy::sdbus_signal_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error */*retError*/)
{
auto* proxy = static_cast<Proxy*>(userData);
assert(proxy != nullptr);
Signal message{sdbusMessage, &proxy->connection_->getSdBusInterface()};
// Note: The lookup can be optimized by using sorted vectors instead of associative containers
auto& callback = proxy->interfaces_[message.getInterfaceName()].signals_[message.getMemberName()].callback_;
assert(callback);
callback(message);
return 1;
}
}}
namespace sdbus {
std::unique_ptr<sdbus::IProxy> createProxy( IConnection& connection
, std::string destination
, std::string objectPath )
{
auto* sdbusConnection = dynamic_cast<sdbus::internal::IConnection*>(&connection);
SDBUS_THROW_ERROR_IF(!sdbusConnection, "Connection is not a real sdbus-c++ connection", EINVAL);
return std::make_unique<sdbus::internal::Proxy>( *sdbusConnection
, std::move(destination)
, std::move(objectPath) );
}
std::unique_ptr<sdbus::IProxy> createProxy( std::unique_ptr<IConnection>&& connection
, std::string destination
, std::string objectPath )
{
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) );
}
std::unique_ptr<sdbus::IProxy> createProxy( std::string destination
, std::string objectPath )
{
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) );
}
}

140
src/Proxy.h Normal file
View File

@ -0,0 +1,140 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file Proxy.h
*
* Created on: Nov 8, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_INTERNAL_PROXY_H_
#define SDBUS_CXX_INTERNAL_PROXY_H_
#include <sdbus-c++/IProxy.h>
#include <systemd/sd-bus.h>
#include <string>
#include <memory>
#include <map>
#include <unordered_map>
#include <mutex>
// Forward declarations
namespace sdbus { namespace internal {
class IConnection;
}}
namespace sdbus {
namespace internal {
class Proxy
: public IProxy
{
public:
Proxy( sdbus::internal::IConnection& connection
, std::string destination
, std::string objectPath );
Proxy( std::unique_ptr<sdbus::internal::IConnection>&& connection
, std::string destination
, std::string objectPath );
MethodCall createMethodCall(const std::string& interfaceName, const std::string& methodName) override;
AsyncMethodCall createAsyncMethodCall(const std::string& interfaceName, const std::string& methodName) override;
MethodReply callMethod(const MethodCall& message) override;
void callMethod(const AsyncMethodCall& message, async_reply_handler asyncReplyCallback) override;
void registerSignalHandler( const std::string& interfaceName
, const std::string& signalName
, signal_handler signalHandler ) override;
void finishRegistration() override;
void unregister() override;
private:
void registerSignalHandlers(sdbus::internal::IConnection& connection);
static int sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError);
static int sdbus_signal_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError);
private:
std::unique_ptr< sdbus::internal::IConnection
, std::function<void(sdbus::internal::IConnection*)>
> connection_;
std::string destination_;
std::string objectPath_;
using InterfaceName = std::string;
struct InterfaceData
{
using SignalName = std::string;
struct SignalData
{
signal_handler callback_;
std::unique_ptr<sd_bus_slot, std::function<void(sd_bus_slot*)>> slot_;
};
std::map<SignalName, SignalData> signals_;
};
std::map<InterfaceName, InterfaceData> interfaces_;
// We need to keep track of pending async calls. When the proxy is being destructed, we must
// remove all slots of these pending calls, otherwise in case when the connection outlives
// the proxy, we might get async reply handlers invoked for pending async calls after the proxy
// has been destroyed, which is a free ticket into the realm of undefined behavior.
class AsyncCalls
{
public:
struct CallData
{
Proxy& proxy;
async_reply_handler callback;
AsyncMethodCall::Slot slot;
};
~AsyncCalls()
{
clear();
}
bool addCall(void* slot, std::unique_ptr<CallData>&& asyncCallData)
{
std::lock_guard<std::mutex> lock(mutex_);
return calls_.emplace(slot, std::move(asyncCallData)).second;
}
bool removeCall(void* slot)
{
std::lock_guard<std::mutex> lock(mutex_);
return calls_.erase(slot) > 0;
}
void clear()
{
std::unique_lock<std::mutex> lock(mutex_);
auto asyncCallSlots = std::move(calls_);
// Perform releasing of sd-bus slots outside of the calls_ critical section which avoids
// double mutex dead lock when the async reply handler is invoked at the same time.
lock.unlock();
}
private:
std::unordered_map<void*, std::unique_ptr<CallData>> calls_;
std::mutex mutex_;
} pendingAsyncCalls_;
};
}}
#endif /* SDBUS_CXX_INTERNAL_PROXY_H_ */

175
src/SdBus.cpp Normal file
View File

@ -0,0 +1,175 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file SdBus.cpp
* @author Ardazishvili Roman (ardazishvili.roman@yandex.ru)
*
* Created on: Mar 3, 2019
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include "SdBus.h"
namespace sdbus { namespace internal {
sd_bus_message* SdBus::sd_bus_message_ref(sd_bus_message *m)
{
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
return ::sd_bus_message_ref(m);
}
sd_bus_message* SdBus::sd_bus_message_unref(sd_bus_message *m)
{
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
return ::sd_bus_message_unref(m);
}
int SdBus::sd_bus_send(sd_bus *bus, sd_bus_message *m, uint64_t *cookie)
{
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
return ::sd_bus_send(bus, m, cookie);
}
int SdBus::sd_bus_call(sd_bus *bus, sd_bus_message *m, uint64_t usec, sd_bus_error *ret_error, sd_bus_message **reply)
{
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
return ::sd_bus_call(bus, m, usec, ret_error, reply);
}
int SdBus::sd_bus_call_async(sd_bus *bus, sd_bus_slot **slot, sd_bus_message *m, sd_bus_message_handler_t callback, void *userdata, uint64_t usec)
{
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
return ::sd_bus_call_async(bus, slot, m, callback, userdata, usec);
}
int SdBus::sd_bus_message_new_method_call(sd_bus *bus, sd_bus_message **m, const char *destination, const char *path, const char *interface, const char *member)
{
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
return ::sd_bus_message_new_method_call(bus, m, destination, path, interface, member);
}
int SdBus::sd_bus_message_new_signal(sd_bus *bus, sd_bus_message **m, const char *path, const char *interface, const char *member)
{
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
return ::sd_bus_message_new_signal(bus, m, path, interface, member);
}
int SdBus::sd_bus_message_new_method_return(sd_bus_message *call, sd_bus_message **m)
{
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
return ::sd_bus_message_new_method_return(call, m);
}
int SdBus::sd_bus_message_new_method_error(sd_bus_message *call, sd_bus_message **m, const sd_bus_error *e)
{
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
return ::sd_bus_message_new_method_error(call, m, e);
}
int SdBus::sd_bus_open_user(sd_bus **ret)
{
return ::sd_bus_open_user(ret);
}
int SdBus::sd_bus_open_system(sd_bus **ret)
{
return ::sd_bus_open_system(ret);
}
int SdBus::sd_bus_request_name(sd_bus *bus, const char *name, uint64_t flags)
{
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
return ::sd_bus_request_name(bus, name, flags);
}
int SdBus::sd_bus_release_name(sd_bus *bus, const char *name)
{
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
return ::sd_bus_release_name(bus, name);
}
int SdBus::sd_bus_add_object_vtable(sd_bus *bus, sd_bus_slot **slot, const char *path, const char *interface, const sd_bus_vtable *vtable, void *userdata)
{
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
return ::sd_bus_add_object_vtable(bus, slot, path, interface, vtable, userdata);
}
int SdBus::sd_bus_add_match(sd_bus *bus, sd_bus_slot **slot, const char *match, sd_bus_message_handler_t callback, void *userdata)
{
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
return :: sd_bus_add_match(bus, slot, match, callback, userdata);
}
sd_bus_slot* SdBus::sd_bus_slot_unref(sd_bus_slot *slot)
{
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
return ::sd_bus_slot_unref(slot);
}
int SdBus::sd_bus_process(sd_bus *bus, sd_bus_message **r)
{
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
return ::sd_bus_process(bus, r);
}
int SdBus::sd_bus_get_poll_data(sd_bus *bus, PollData* data)
{
std::unique_lock<std::recursive_mutex> lock(sdbusMutex_);
auto r = ::sd_bus_get_fd(bus);
if (r < 0)
return r;
data->fd = r;
r = ::sd_bus_get_events(bus);
if (r < 0)
return r;
data->events = static_cast<short int>(r);
r = ::sd_bus_get_timeout(bus, &data->timeout_usec);
return r;
}
int SdBus::sd_bus_flush(sd_bus *bus)
{
return ::sd_bus_flush(bus);
}
sd_bus* SdBus::sd_bus_flush_close_unref(sd_bus *bus)
{
return ::sd_bus_flush_close_unref(bus);
}
}}

70
src/SdBus.h Normal file
View File

@ -0,0 +1,70 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file SdBus.h
* @author Ardazishvili Roman (ardazishvili.roman@yandex.ru)
*
* Created on: Mar 3, 2019
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_SDBUS_H
#define SDBUS_CXX_SDBUS_H
#include "ISdBus.h"
#include <mutex>
namespace sdbus { namespace internal {
class SdBus final : public ISdBus
{
public:
virtual sd_bus_message* sd_bus_message_ref(sd_bus_message *m) override;
virtual sd_bus_message* sd_bus_message_unref(sd_bus_message *m) override;
virtual int sd_bus_send(sd_bus *bus, sd_bus_message *m, uint64_t *cookie) override;
virtual int sd_bus_call(sd_bus *bus, sd_bus_message *m, uint64_t usec, sd_bus_error *ret_error, sd_bus_message **reply) override;
virtual int sd_bus_call_async(sd_bus *bus, sd_bus_slot **slot, sd_bus_message *m, sd_bus_message_handler_t callback, void *userdata, uint64_t usec) override;
virtual int sd_bus_message_new_method_call(sd_bus *bus, sd_bus_message **m, const char *destination, const char *path, const char *interface, const char *member) override;
virtual int sd_bus_message_new_signal(sd_bus *bus, sd_bus_message **m, const char *path, const char *interface, const char *member) override;
virtual int sd_bus_message_new_method_return(sd_bus_message *call, sd_bus_message **m) override;
virtual int sd_bus_message_new_method_error(sd_bus_message *call, sd_bus_message **m, const sd_bus_error *e) override;
virtual int sd_bus_open_user(sd_bus **ret) override;
virtual int sd_bus_open_system(sd_bus **ret) override;
virtual int sd_bus_request_name(sd_bus *bus, const char *name, uint64_t flags) override;
virtual int sd_bus_release_name(sd_bus *bus, const char *name) override;
virtual int sd_bus_add_object_vtable(sd_bus *bus, sd_bus_slot **slot, const char *path, const char *interface, const sd_bus_vtable *vtable, void *userdata) override;
virtual int sd_bus_add_match(sd_bus *bus, sd_bus_slot **slot, const char *match, sd_bus_message_handler_t callback, void *userdata) override;
virtual sd_bus_slot* sd_bus_slot_unref(sd_bus_slot *slot) override;
virtual int sd_bus_process(sd_bus *bus, sd_bus_message **r) override;
virtual int sd_bus_get_poll_data(sd_bus *bus, PollData* data) override;
virtual int sd_bus_flush(sd_bus *bus) override;
virtual sd_bus *sd_bus_flush_close_unref(sd_bus *bus) override;
private:
std::recursive_mutex sdbusMutex_;
};
}}
#endif //SDBUS_C_SDBUS_H

View File

@ -29,7 +29,7 @@
#include <systemd/sd-bus.h>
#include <cassert>
namespace sdbus { /*namespace internal {*/
namespace sdbus {
Variant::Variant()
: msg_(createPlainMessage())
@ -51,6 +51,7 @@ void Variant::deserializeFrom(Message& msg)
std::string Variant::peekValueType() const
{
msg_.rewind(false);
std::string type;
std::string contents;
msg_.peekType(type, contents);

View File

@ -26,42 +26,46 @@
#include "VTableUtils.h"
#include <systemd/sd-bus.h>
sd_bus_vtable createVTableStartItem()
sd_bus_vtable createVTableStartItem(uint64_t flags)
{
struct sd_bus_vtable vtableStart = SD_BUS_VTABLE_START(0);
struct sd_bus_vtable vtableStart = SD_BUS_VTABLE_START(flags);
return vtableStart;
}
sd_bus_vtable createVTableMethodItem( const char *member
, const char *signature
, const char *result
, sd_bus_message_handler_t handler )
, sd_bus_message_handler_t handler
, uint64_t flags )
{
struct sd_bus_vtable vtableItem = SD_BUS_METHOD(member, signature, result, handler, SD_BUS_VTABLE_UNPRIVILEGED);
struct sd_bus_vtable vtableItem = SD_BUS_METHOD(member, signature, result, handler, flags);
return vtableItem;
}
sd_bus_vtable createVTableSignalItem( const char *member
, const char *signature )
, const char *signature
, uint64_t flags )
{
struct sd_bus_vtable vtableItem = SD_BUS_SIGNAL(member, signature, 0);
struct sd_bus_vtable vtableItem = SD_BUS_SIGNAL(member, signature, flags);
return vtableItem;
}
sd_bus_vtable createVTablePropertyItem( const char *member
, const char *signature
, sd_bus_property_get_t getter )
, sd_bus_property_get_t getter
, uint64_t flags )
{
struct sd_bus_vtable vtableItem = SD_BUS_PROPERTY(member, signature, getter, 0, 0);
struct sd_bus_vtable vtableItem = SD_BUS_PROPERTY(member, signature, getter, 0, flags);
return vtableItem;
}
sd_bus_vtable createVTableWritablePropertyItem( const char *member
, const char *signature
, sd_bus_property_get_t getter
, sd_bus_property_set_t setter )
, sd_bus_property_set_t setter
, uint64_t flags )
{
struct sd_bus_vtable vtableItem = SD_BUS_WRITABLE_PROPERTY(member, signature, getter, setter, 0, 0);
struct sd_bus_vtable vtableItem = SD_BUS_WRITABLE_PROPERTY(member, signature, getter, setter, 0, flags);
return vtableItem;
}

View File

@ -27,25 +27,30 @@
#define SDBUS_CXX_INTERNAL_VTABLEUTILS_H_
#include <systemd/sd-bus.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
sd_bus_vtable createVTableStartItem();
sd_bus_vtable createVTableStartItem(uint64_t flags);
sd_bus_vtable createVTableMethodItem( const char *member
, const char *signature
, const char *result
, sd_bus_message_handler_t handler );
, sd_bus_message_handler_t handler
, uint64_t flags );
sd_bus_vtable createVTableSignalItem( const char *member
, const char *signature );
, const char *signature
, uint64_t flags );
sd_bus_vtable createVTablePropertyItem( const char *member
, const char *signature
, sd_bus_property_get_t getter );
, sd_bus_property_get_t getter
, uint64_t flags );
sd_bus_vtable createVTableWritablePropertyItem( const char *member
, const char *signature
, sd_bus_property_get_t getter
, sd_bus_property_set_t setter );
, sd_bus_property_set_t setter
, uint64_t flags );
sd_bus_vtable createVTableEndItem();
#ifdef __cplusplus

View File

@ -89,6 +89,30 @@ std::string AdaptorGenerator::processInterface(Node& interface) const
Nodes signals = interface["signal"];
Nodes properties = interface["property"];
auto annotations = getAnnotations(interface);
std::string annotationRegistration;
for (const auto& annotation : annotations)
{
const auto& annotationName = annotation.first;
const auto& annotationValue = annotation.second;
if (annotationName == "org.freedesktop.DBus.Deprecated" && annotationValue == "true")
annotationRegistration += ".markAsDeprecated()";
else if (annotationName == "org.freedesktop.systemd1.Privileged" && annotationValue == "true")
annotationRegistration += ".markAsPrivileged()";
else if (annotationName == "org.freedesktop.DBus.Property.EmitsChangedSignal")
annotationRegistration += ".withPropertyUpdateBehavior(" + propertyAnnotationToFlag(annotationValue) + ")";
else
std::cerr << "Node: " << ifaceName << ": "
<< "Option '" << annotationName << "' not allowed or supported in this context! Option ignored..." << std::endl;
}
if(!annotationRegistration.empty())
{
std::stringstream str;
str << tab << tab << "object_.setInterfaceFlags(interfaceName)" << annotationRegistration << ";" << endl;
annotationRegistration = str.str();
}
std::string methodRegistration, methodDeclaration;
std::tie(methodRegistration, methodDeclaration) = processMethods(methods);
@ -99,10 +123,11 @@ std::string AdaptorGenerator::processInterface(Node& interface) const
std::tie(propertyRegistration, propertyAccessorDeclaration) = processProperties(properties);
body << tab << "{" << endl
<< methodRegistration
<< signalRegistration
<< propertyRegistration
<< tab << "}" << endl << endl;
<< annotationRegistration
<< methodRegistration
<< signalRegistration
<< propertyRegistration
<< tab << "}" << endl << endl;
if (!signalMethods.empty())
{
@ -136,25 +161,70 @@ std::tuple<std::string, std::string> AdaptorGenerator::processMethods(const Node
{
auto methodName = method->get("name");
auto annotations = getAnnotations(*method);
bool async{false};
std::string annotationRegistration;
for (const auto& annotation : annotations)
{
const auto& annotationName = annotation.first;
const auto& annotationValue = annotation.second;
if (annotationName == "org.freedesktop.DBus.Deprecated")
{
if (annotationValue == "true")
annotationRegistration += ".markAsDeprecated()";
}
else if (annotationName == "org.freedesktop.DBus.Method.NoReply")
{
if (annotationValue == "true")
annotationRegistration += ".withNoReply()";
}
else if (annotationName == "org.freedesktop.DBus.Method.Async")
{
if (annotationValue == "server" || annotationValue == "clientserver")
async = true;
}
else if (annotationName == "org.freedesktop.systemd1.Privileged")
{
if (annotationValue == "true")
annotationRegistration += ".markAsPrivileged()";
}
else
{
std::cerr << "Node: " << methodName << ": "
<< "Option '" << annotationName << "' not allowed or supported in this context! Option ignored..." << std::endl;
}
}
Nodes args = (*method)["arg"];
Nodes inArgs = args.select("direction" , "in");
Nodes outArgs = args.select("direction" , "out");
std::string argStr, argTypeStr;
std::tie(argStr, argTypeStr, std::ignore) = argsToNamesAndTypes(inArgs);
std::tie(argStr, argTypeStr, std::ignore) = argsToNamesAndTypes(inArgs, async);
using namespace std::string_literals;
registrationSS << tab << tab << "object_.registerMethod(\""
<< methodName << "\")"
<< ".onInterface(interfaceName)"
<< ".implementedAs("
<< "[this]("
<< (async ? "sdbus::Result<" + outArgsToType(outArgs, true) + ">&& result" + (argTypeStr.empty() ? "" : ", ") : "")
<< argTypeStr
<< "){ return this->" << methodName << "("
<< argStr << "); });" << endl;
<< "){ " << (async ? "" : "return ") << "this->" << methodName << "("
<< (async ? "std::move(result)"s + (argTypeStr.empty() ? "" : ", ") : "")
<< argStr << "); })"
<< annotationRegistration << ";" << endl;
declarationSS << tab
<< "virtual " << outArgsToType(outArgs) << " " << methodName
<< "(" << argTypeStr << ") = 0;" << endl;
<< "virtual "
<< (async ? "void" : outArgsToType(outArgs))
<< " " << methodName
<< "("
<< (async ? "sdbus::Result<" + outArgsToType(outArgs, true) + ">&& result" + (argTypeStr.empty() ? "" : ", ") : "")
<< argTypeStr
<< ") = 0;" << endl;
}
return std::make_tuple(registrationSS.str(), declarationSS.str());
@ -168,6 +238,21 @@ std::tuple<std::string, std::string> AdaptorGenerator::processSignals(const Node
for (const auto& signal : signals)
{
auto name = signal->get("name");
auto annotations = getAnnotations(*signal);
std::string annotationRegistration;
for (const auto& annotation : annotations)
{
const auto& annotationName = annotation.first;
const auto& annotationValue = annotation.second;
if (annotationName == "org.freedesktop.DBus.Deprecated" && annotationValue == "true")
annotationRegistration += ".markAsDeprecated()";
else
std::cerr << "Node: " << name << ": "
<< "Option '" << annotationName << "' not allowed or supported in this context! Option ignored..." << std::endl;
}
Nodes args = (*signal)["arg"];
std::string argStr, argTypeStr, typeStr;;
@ -182,6 +267,7 @@ std::tuple<std::string, std::string> AdaptorGenerator::processSignals(const Node
signalRegistrationSS << ".withParameters<" << typeStr << ">()";
}
signalRegistrationSS << annotationRegistration;
signalRegistrationSS << ";" << endl;
signalMethodSS << tab << "void " << name << "(" << argTypeStr << ")" << endl
@ -216,6 +302,24 @@ std::tuple<std::string, std::string> AdaptorGenerator::processProperties(const N
auto propertyArg = std::string("value");
auto propertyTypeArg = std::string("const ") + propertyType + "& " + propertyArg;
auto annotations = getAnnotations(*property);
std::string annotationRegistration;
for (const auto& annotation : annotations)
{
const auto& annotationName = annotation.first;
const auto& annotationValue = annotation.second;
if (annotationName == "org.freedesktop.DBus.Deprecated" && annotationValue == "true")
annotationRegistration += ".markAsDeprecated()";
else if (annotationName == "org.freedesktop.DBus.Property.EmitsChangedSignal")
annotationRegistration += ".withUpdateBehavior(" + propertyAnnotationToFlag(annotationValue) + ")";
else if (annotationName == "org.freedesktop.systemd1.Privileged" && annotationValue == "true")
annotationRegistration += ".markAsPrivileged()";
else
std::cerr << "Node: " << propertyName << ": "
<< "Option '" << annotationName << "' not allowed or supported in this context! Option ignored..." << std::endl;
}
registrationSS << tab << tab << "object_.registerProperty(\""
<< propertyName << "\")"
<< ".onInterface(interfaceName)";
@ -232,6 +336,7 @@ std::tuple<std::string, std::string> AdaptorGenerator::processProperties(const N
"{ this->" << propertyName << "(" << propertyArg << "); })";
}
registrationSS << annotationRegistration;
registrationSS << ";" << endl;
if (propertyAccess == "read" || propertyAccess == "readwrite")
@ -242,3 +347,25 @@ std::tuple<std::string, std::string> AdaptorGenerator::processProperties(const N
return std::make_tuple(registrationSS.str(), declarationSS.str());
}
std::map<std::string, std::string> AdaptorGenerator::getAnnotations( sdbuscpp::xml::Node& node) const
{
std::map<std::string, std::string> result;
Nodes annotations = (node)["annotation"];
for (const auto& annotation : annotations)
{
result[annotation->get("name")] = annotation->get("value");
}
return result;
}
std::string AdaptorGenerator::propertyAnnotationToFlag(const std::string& annotationValue) const
{
return annotationValue == "true" ? "sdbus::Flags::EMITS_CHANGE_SIGNAL"
: annotationValue == "invalidates" ? "sdbus::Flags::EMITS_INVALIDATION_SIGNAL"
: annotationValue == "const" ? "sdbus::Flags::CONST_PROPERTY_VALUE"
: annotationValue == "false" ? "sdbus::Flags::EMITS_NO_SIGNAL"
: "EMITS_CHANGE_SIGNAL"; // Default
}

View File

@ -32,6 +32,7 @@
// STL
#include <tuple>
#include <set>
class AdaptorGenerator : public BaseGenerator
{
@ -73,6 +74,19 @@ private:
*/
std::tuple<std::string, std::string> processProperties(const sdbuscpp::xml::Nodes& properties) const;
/**
* Get annotations listed for a given node
* @param node
* @return map of annotation names to their values
*/
std::map<std::string, std::string> getAnnotations(sdbuscpp::xml::Node& node) const;
/**
* Get flag for property update behavior annotation value
* @param annotationValue
* @return flag
*/
std::string propertyAnnotationToFlag(const std::string& annotationValue) const;
};

View File

@ -104,19 +104,36 @@ std::tuple<unsigned, std::string> BaseGenerator::generateNamespaces(const std::s
}
std::tuple<std::string, std::string, std::string> BaseGenerator::argsToNamesAndTypes(const Nodes& args) const
std::tuple<std::string, std::string, std::string> BaseGenerator::argsToNamesAndTypes(const Nodes& args, bool async) const
{
std::ostringstream argSS, argTypeSS, typeSS;
bool firstArg{true};
for (const auto& arg : args)
for (size_t i = 0; i < args.size(); ++i)
{
if (firstArg) firstArg = false; else { argSS << ", "; argTypeSS << ", "; typeSS << ", "; }
auto arg = args.at(i);
if (i > 0)
{
argSS << ", ";
argTypeSS << ", ";
typeSS << ", ";
}
auto argName = arg->get("name");
if (argName.empty())
{
argName = "arg" + std::to_string(i);
}
auto type = signature_to_type(arg->get("type"));
argSS << argName;
argTypeSS << "const " << type << "& " << argName;
if (!async)
{
argSS << argName;
argTypeSS << "const " << type << "& " << argName;
}
else
{
argSS << "std::move(" << argName << ")";
argTypeSS << type << " " << argName;
}
typeSS << type;
}
@ -126,12 +143,15 @@ std::tuple<std::string, std::string, std::string> BaseGenerator::argsToNamesAndT
/**
*
*/
std::string BaseGenerator::outArgsToType(const Nodes& args) const
std::string BaseGenerator::outArgsToType(const Nodes& args, bool bareList) const
{
std::ostringstream retTypeSS;
if (args.size() == 0)
{
if (bareList)
return "";
retTypeSS << "void";
}
else if (args.size() == 1)
@ -141,7 +161,8 @@ std::string BaseGenerator::outArgsToType(const Nodes& args) const
}
else if (args.size() >= 2)
{
retTypeSS << "std::tuple<";
if (!bareList)
retTypeSS << "std::tuple<";
bool firstArg = true;
for (const auto& arg : args)
@ -150,7 +171,8 @@ std::string BaseGenerator::outArgsToType(const Nodes& args) const
retTypeSS << signature_to_type(arg->get("type"));
}
retTypeSS << ">";
if (!bareList)
retTypeSS << ">";
}
return retTypeSS.str();

View File

@ -87,14 +87,14 @@ protected:
* @param args
* @return tuple: argument names, argument types and names, argument types
*/
std::tuple<std::string, std::string, std::string> argsToNamesAndTypes(const sdbuscpp::xml::Nodes& args) const;
std::tuple<std::string, std::string, std::string> argsToNamesAndTypes(const sdbuscpp::xml::Nodes& args, bool async = false) const;
/**
* Output arguments to return type
* @param args
* @return return type
*/
std::string outArgsToType(const sdbuscpp::xml::Nodes& args) const;
std::string outArgsToType(const sdbuscpp::xml::Nodes& args, bool bareList = false) const;
};

View File

@ -1,13 +1,52 @@
cmake_minimum_required (VERSION 3.3)
#-------------------------------
# PROJECT INFORMATION
#-------------------------------
add_definitions(-std=c++14)
cmake_minimum_required(VERSION 3.5)
project (sdbuscpp-xml2cpp)
project(sdbus-c++-xml2cpp)
add_executable(${PROJECT_NAME} xml2cpp.cpp xml.cpp generator_utils.cpp BaseGenerator.cpp AdaptorGenerator.cpp ProxyGenerator.cpp)
include(GNUInstallDirs)
find_package (EXPAT REQUIRED)
#-------------------------------
# PERFORMING CHECKS
#-------------------------------
find_package(EXPAT REQUIRED)
#-------------------------------
# SOURCE FILES CONFIGURATION
#-------------------------------
set(SDBUSCPP_XML2CPP_SRCS
xml2cpp.cpp
xml.h
xml.cpp
generator_utils.h
generator_utils.cpp
BaseGenerator.h
BaseGenerator.cpp
AdaptorGenerator.h
AdaptorGenerator.cpp
ProxyGenerator.h
ProxyGenerator.cpp)
#-------------------------------
# GENERAL COMPILER CONFIGURATION
#-------------------------------
set(CMAKE_CXX_STANDARD 14)
#----------------------------------
# EXECUTABLE BUILD INFORMATION
#----------------------------------
add_executable(${PROJECT_NAME} ${SDBUSCPP_XML2CPP_SRCS})
target_link_libraries (${PROJECT_NAME} ${EXPAT_LIBRARIES})
install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME} DESTINATION bin)
#----------------------------------
# INSTALLATION
#----------------------------------
install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})

View File

@ -82,8 +82,8 @@ std::string ProxyGenerator::processInterface(Node& interface) const
<< "public:" << endl
<< tab << "static constexpr const char* interfaceName = \"" << ifaceName << "\";" << endl << endl
<< "protected:" << endl
<< tab << className << "(sdbus::IObjectProxy& object)" << endl
<< tab << tab << ": object_(object)" << endl;
<< tab << className << "(sdbus::IProxy& proxy)" << endl
<< tab << tab << ": proxy_(proxy)" << endl;
Nodes methods = interface["method"];
Nodes signals = interface["signal"];
@ -94,10 +94,18 @@ std::string ProxyGenerator::processInterface(Node& interface) const
body << tab << "{" << endl
<< registration
<< tab << "}" << endl << endl
<< declaration << endl;
<< tab << "}" << endl << endl;
if (!declaration.empty())
body << declaration << endl;
std::string methodDefinitions, asyncDeclarations;
std::tie(methodDefinitions, asyncDeclarations) = processMethods(methods);
if (!asyncDeclarations.empty())
{
body << asyncDeclarations << endl;
}
std::string methodDefinitions = processMethods(methods);
if (!methodDefinitions.empty())
{
body << "public:" << endl << methodDefinitions;
@ -110,16 +118,17 @@ std::string ProxyGenerator::processInterface(Node& interface) const
}
body << "private:" << endl
<< tab << "sdbus::IObjectProxy& object_;" << endl
<< tab << "sdbus::IProxy& proxy_;" << endl
<< "};" << endl << endl
<< std::string(namespacesCount, '}') << " // namespaces" << endl << endl;
return body.str();
}
std::string ProxyGenerator::processMethods(const Nodes& methods) const
std::tuple<std::string, std::string> ProxyGenerator::processMethods(const Nodes& methods) const
{
std::ostringstream methodSS;
std::ostringstream definitionSS, asyncDeclarationSS;
for (const auto& method : methods)
{
auto name = method->get("name");
@ -127,39 +136,73 @@ std::string ProxyGenerator::processMethods(const Nodes& methods) const
Nodes inArgs = args.select("direction" , "in");
Nodes outArgs = args.select("direction" , "out");
auto retType = outArgsToType(outArgs);
std::string argStr, argTypeStr;
std::tie(argStr, argTypeStr, std::ignore) = argsToNamesAndTypes(inArgs);
methodSS << tab << retType << " " << name << "(" << argTypeStr << ")" << endl
<< tab << "{" << endl;
if (outArgs.size() > 0)
bool dontExpectReply{false};
bool async{false};
Nodes annotations = (*method)["annotation"];
for (const auto& annotation : annotations)
{
methodSS << tab << tab << retType << " result;" << endl;
if (annotation->get("name") == "org.freedesktop.DBus.Method.NoReply" && annotation->get("value") == "true")
dontExpectReply = true;
else if (annotation->get("name") == "org.freedesktop.DBus.Method.Async"
&& (annotation->get("value") == "client" || annotation->get("value") == "clientserver"))
async = true;
}
if (dontExpectReply && outArgs.size() > 0)
{
std::cerr << "Function: " << name << ": ";
std::cerr << "Option 'org.freedesktop.DBus.Method.NoReply' not allowed for methods with 'out' variables! Option ignored..." << std::endl;
dontExpectReply = false;
}
methodSS << tab << tab << "object_.callMethod(\"" << name << "\")"
auto retType = outArgsToType(outArgs);
std::string inArgStr, inArgTypeStr;
std::tie(inArgStr, inArgTypeStr, std::ignore) = argsToNamesAndTypes(inArgs);
std::string outArgStr, outArgTypeStr;
std::tie(outArgStr, outArgTypeStr, std::ignore) = argsToNamesAndTypes(outArgs);
definitionSS << tab << (async ? "void" : retType) << " " << name << "(" << inArgTypeStr << ")" << endl
<< tab << "{" << endl;
if (outArgs.size() > 0 && !async)
{
definitionSS << tab << tab << retType << " result;" << endl;
}
definitionSS << tab << tab << "proxy_.callMethod" << (async ? "Async" : "") << "(\"" << name << "\")"
".onInterface(interfaceName)";
if (inArgs.size() > 0)
{
methodSS << ".withArguments(" << argStr << ")";
definitionSS << ".withArguments(" << inArgStr << ")";
}
if (outArgs.size() > 0)
if (async && !dontExpectReply)
{
methodSS << ".storeResultsTo(result);" << endl
<< tab << tab << "return result";
auto nameBigFirst = name;
nameBigFirst[0] = islower(nameBigFirst[0]) ? nameBigFirst[0] + 'A' - 'a' : nameBigFirst[0];
definitionSS << ".uponReplyInvoke([this](const sdbus::Error* error" << (outArgTypeStr.empty() ? "" : ", ") << outArgTypeStr << ")"
"{ this->on" << nameBigFirst << "Reply(" << outArgStr << (outArgStr.empty() ? "" : ", ") << "error); })";
asyncDeclarationSS << tab << "virtual void on" << nameBigFirst << "Reply("
<< outArgTypeStr << (outArgTypeStr.empty() ? "" : ", ") << "const sdbus::Error* error) = 0;" << endl;
}
else if (outArgs.size() > 0)
{
definitionSS << ".storeResultsTo(result);" << endl
<< tab << tab << "return result";
}
else if (dontExpectReply)
{
definitionSS << ".dontExpectReply()";
}
methodSS << ";" << endl << tab << "}" << endl << endl;
definitionSS << ";" << endl << tab << "}" << endl << endl;
}
return methodSS.str();
return std::make_tuple(definitionSS.str(), asyncDeclarationSS.str());
}
std::tuple<std::string, std::string> ProxyGenerator::processSignals(const Nodes& signals) const
{
std::ostringstream registrationSS, declarationSS;
@ -175,7 +218,7 @@ std::tuple<std::string, std::string> ProxyGenerator::processSignals(const Nodes&
std::string argStr, argTypeStr;
std::tie(argStr, argTypeStr, std::ignore) = argsToNamesAndTypes(args);
registrationSS << tab << tab << "object_"
registrationSS << tab << tab << "proxy_"
".uponSignal(\"" << name << "\")"
".onInterface(interfaceName)"
".call([this](" << argTypeStr << ")"
@ -204,7 +247,7 @@ std::string ProxyGenerator::processProperties(const Nodes& properties) const
{
propertySS << tab << propertyType << " " << propertyName << "()" << endl
<< tab << "{" << endl;
propertySS << tab << tab << "return object_.getProperty(\"" << propertyName << "\")"
propertySS << tab << tab << "return proxy_.getProperty(\"" << propertyName << "\")"
".onInterface(interfaceName)";
propertySS << ";" << endl << tab << "}" << endl << endl;
}
@ -213,7 +256,7 @@ std::string ProxyGenerator::processProperties(const Nodes& properties) const
{
propertySS << tab << "void " << propertyName << "(" << propertyTypeArg << ")" << endl
<< tab << "{" << endl;
propertySS << tab << tab << "object_.setProperty(\"" << propertyName << "\")"
propertySS << tab << tab << "proxy_.setProperty(\"" << propertyName << "\")"
".onInterface(interfaceName)"
".toValue(" << propertyArg << ")";
propertySS << ";" << endl << tab << "}" << endl << endl;

View File

@ -56,9 +56,9 @@ private:
/**
* Generate method calls
* @param methods
* @return source code
* @return tuple: definition of methods, declaration of virtual async reply handlers
*/
std::string processMethods(const sdbuscpp::xml::Nodes& methods) const;
std::tuple<std::string, std::string> processMethods(const sdbuscpp::xml::Nodes& methods) const;
/**
* Generate code for handling signals

View File

@ -17,6 +17,6 @@ std::string signature_to_type(const std::string& signature);
std::string underscorize(const std::string& str);
constexpr const char* getHeaderComment() noexcept { return "\n/*\n * This file was automatically generated by sdbuscpp-xml2cpp; DO NOT EDIT!\n */\n\n"; }
constexpr const char* getHeaderComment() noexcept { return "\n/*\n * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!\n */\n\n"; }
#endif //__SDBUSCPP_TOOLS_GENERATOR_UTILS_H

View File

@ -247,15 +247,15 @@ std::string Document::to_xml() const
}
void Document::Expat::start_doctype_decl_handler(
void* data,
const XML_Char* name,
const XML_Char* sysid,
const XML_Char *pubid,
int has_internal_subset)
void* /*data*/,
const XML_Char* /*name*/,
const XML_Char* /*sysid*/,
const XML_Char */*pubid*/,
int /*has_internal_subset*/)
{
}
void Document::Expat::end_doctype_decl_handler(void* data)
void Document::Expat::end_doctype_decl_handler(void* /*data*/)
{
}
@ -300,7 +300,7 @@ void Document::Expat::character_data_handler(void* data, const XML_Char* chars,
nod->cdata = std::string(chars, x, y + 1);
}
void Document::Expat::end_element_handler(void* data, const XML_Char* name)
void Document::Expat::end_element_handler(void* data, const XML_Char* /*name*/)
{
Document* doc = static_cast<Document*>(data);

142
test/CMakeLists.txt Normal file
View File

@ -0,0 +1,142 @@
#-------------------------------
# DOWNLOAD AND BUILD OF GOOGLETEST
# https://github.com/google/googletest/blob/master/googletest/README.md#incorporating-into-an-existing-cmake-project
#-------------------------------
configure_file(googletest-download/CMakeLists.txt.in googletest-download/CMakeLists.txt)
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" .
RESULT_VARIABLE result
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download)
if(result)
message(FATAL_ERROR "CMake step for googletest failed: ${result}")
endif()
execute_process(COMMAND ${CMAKE_COMMAND} --build .
RESULT_VARIABLE result
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download)
if(result)
message(FATAL_ERROR "Build step for googletest failed: ${result}")
endif()
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
set(BUILD_GMOCK ON CACHE BOOL "" FORCE)
set(INSTALL_GTEST OFF CACHE BOOL "" FORCE)
set(BUILD_SHARED_LIBS_BAK ${BUILD_SHARED_LIBS})
set(BUILD_SHARED_LIBS OFF)
add_subdirectory(${CMAKE_CURRENT_BINARY_DIR}/googletest-src
${CMAKE_CURRENT_BINARY_DIR}/googletest-build
EXCLUDE_FROM_ALL)
set(BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS_BAK})
#-------------------------------
# SOURCE FILES CONFIGURATION
#-------------------------------
set(UNITTESTS_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/unittests)
set(UNITTESTS_SRCS
${UNITTESTS_SOURCE_DIR}/sdbus-c++-unit-tests.cpp
${UNITTESTS_SOURCE_DIR}/Message_test.cpp
${UNITTESTS_SOURCE_DIR}/Types_test.cpp
${UNITTESTS_SOURCE_DIR}/TypeTraits_test.cpp
${UNITTESTS_SOURCE_DIR}/Connection_test.cpp
${UNITTESTS_SOURCE_DIR}/mocks/SdBusMock.h)
set(INTEGRATIONTESTS_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/integrationtests)
set(INTEGRATIONTESTS_SRCS
${INTEGRATIONTESTS_SOURCE_DIR}/AdaptorAndProxy_test.cpp
${INTEGRATIONTESTS_SOURCE_DIR}/Connection_test.cpp
${INTEGRATIONTESTS_SOURCE_DIR}/sdbus-c++-integration-tests.cpp
${INTEGRATIONTESTS_SOURCE_DIR}/adaptor-glue.h
${INTEGRATIONTESTS_SOURCE_DIR}/defs.h
${INTEGRATIONTESTS_SOURCE_DIR}/proxy-glue.h
${INTEGRATIONTESTS_SOURCE_DIR}/TestingAdaptor.h
${INTEGRATIONTESTS_SOURCE_DIR}/TestingProxy.h)
set(PERFTESTS_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/perftests)
set(STRESSTESTS_CLIENT_SRCS
${PERFTESTS_SOURCE_DIR}/client.cpp
${PERFTESTS_SOURCE_DIR}/perftests-proxy.h)
set(STRESSTESTS_SERVER_SRCS
${PERFTESTS_SOURCE_DIR}/server.cpp
${PERFTESTS_SOURCE_DIR}/perftests-adaptor.h)
set(STRESSTESTS_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/stresstests)
set(STRESSTESTS_SRCS
${STRESSTESTS_SOURCE_DIR}/sdbus-c++-stress-tests.cpp
${STRESSTESTS_SOURCE_DIR}/fahrenheit-thermometer-adaptor.h
${STRESSTESTS_SOURCE_DIR}/fahrenheit-thermometer-proxy.h
${STRESSTESTS_SOURCE_DIR}/celsius-thermometer-adaptor.h
${STRESSTESTS_SOURCE_DIR}/celsius-thermometer-proxy.h
${STRESSTESTS_SOURCE_DIR}/concatenator-adaptor.h
${STRESSTESTS_SOURCE_DIR}/concatenator-proxy.h)
#-------------------------------
# GENERAL COMPILER CONFIGURATION
#-------------------------------
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
#----------------------------------
# BUILD INFORMATION
#----------------------------------
add_executable(sdbus-c++-unit-tests ${UNITTESTS_SRCS} $<TARGET_OBJECTS:sdbus-cpp>)
target_link_libraries(sdbus-c++-unit-tests ${SYSTEMD_LIBRARIES} gmock gmock_main)
add_executable(sdbus-c++-integration-tests ${INTEGRATIONTESTS_SRCS})
target_link_libraries(sdbus-c++-integration-tests sdbus-c++ gmock gmock_main)
# Manual performance and stress tests
option(ENABLE_PERF_TESTS "Build and install manual performance tests (default OFF)" OFF)
option(ENABLE_STRESS_TESTS "Build and install manual stress tests (default OFF)" OFF)
if(ENABLE_PERF_TESTS OR ENABLE_STRESS_TESTS)
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
if(ENABLE_PERF_TESTS)
add_executable(sdbus-c++-perf-tests-client ${STRESSTESTS_CLIENT_SRCS})
target_link_libraries(sdbus-c++-perf-tests-client sdbus-c++ Threads::Threads)
add_executable(sdbus-c++-perf-tests-server ${STRESSTESTS_SERVER_SRCS})
target_link_libraries(sdbus-c++-perf-tests-server sdbus-c++ Threads::Threads)
endif()
if(ENABLE_STRESS_TESTS)
add_executable(sdbus-c++-stress-tests ${STRESSTESTS_SRCS})
target_link_libraries(sdbus-c++-stress-tests sdbus-c++ Threads::Threads)
endif()
endif()
#----------------------------------
# INSTALLATION
#----------------------------------
set(TESTS_INSTALL_PATH "/opt/test/bin" CACHE STRING "Specifies where the test binaries will be installed")
install(TARGETS sdbus-c++-unit-tests DESTINATION ${TESTS_INSTALL_PATH})
install(TARGETS sdbus-c++-integration-tests DESTINATION ${TESTS_INSTALL_PATH})
install(FILES ${INTEGRATIONTESTS_SOURCE_DIR}/files/org.sdbuscpp.integrationtests.conf DESTINATION /etc/dbus-1/system.d)
if(ENABLE_PERF_TESTS)
install(TARGETS sdbus-c++-perf-tests-client DESTINATION ${TESTS_INSTALL_PATH})
install(TARGETS sdbus-c++-perf-tests-server DESTINATION ${TESTS_INSTALL_PATH})
install(FILES ${PERFTESTS_SOURCE_DIR}/files/org.sdbuscpp.perftests.conf DESTINATION /etc/dbus-1/system.d)
endif()
if(ENABLE_STRESS_TESTS)
install(TARGETS sdbus-c++-stress-tests DESTINATION ${TESTS_INSTALL_PATH})
install(FILES ${STRESSTESTS_SOURCE_DIR}/files/org.sdbuscpp.stresstests.conf DESTINATION /etc/dbus-1/system.d)
endif()
#----------------------------------
# RUNNING THE TESTS UPON BUILD
#----------------------------------
if(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()

View File

@ -1,68 +0,0 @@
# Defines how to build and install libsdbus-c++ tests
AUTOMAKE_OPTIONS = subdir-objects
# Target dirs for test binaries, scripts and files
testbindir = /opt/test/bin
dbusconfdir = $(sysconfdir)/dbus-1/system.d
# ENABLE_TESTS is defined by configure when user enables tests during configuration
if ENABLE_TESTS
testbin_PROGRAMS = libsdbus-c++_unittests libsdbus-c++_integrationtests
dbusconf_DATA = integrationtests/files/libsdbus-cpp-test.conf
endif
# Setting per-file flags
AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_srcdir)/src
AM_CXXFLAGS = @libsdbus_cpp_CFLAGS@ @SYSTEMD_CFLAGS@ -W -Wall -Werror -pedantic -pipe -std=c++14
AM_LDFLAGS = @libsdbus_cpp_LIBS@ @SYSTEMD_LIBS@
CLEANFILES = *~ *.lo *.la
MOSTLYCLEANFILES = *.o
TESTS =
# Configuration for libsdbus-c++_unittests
libsdbus_c___unittests_SOURCES = \
unittests/libsdbus-c++_unittests.cpp \
unittests/TypeTraits_test.cpp \
unittests/Types_test.cpp \
unittests/Message_test.cpp
libsdbus_c___unittests_LDFLAGS = -L$(top_builddir)/src
libsdbus_c___unittests_LDADD = \
-lsdbus-c++ \
@libsdbus_cpp_LIBS@ \
@SYSTEMD_LIBS@ \
-lgmock
TESTS += libsdbus-c++_unittests
# Configuration for libsdbus-c++_integrationtests
libsdbus_c___integrationtests_SOURCES = \
integrationtests/libsdbus-c++_integrationtests.cpp \
integrationtests/Connection_test.cpp \
integrationtests/AdaptorAndProxy_test.cpp
libsdbus_c___integrationtests_LDFLAGS = -L$(top_builddir)/src
libsdbus_c___integrationtests_LDADD = \
-lsdbus-c++ \
@libsdbus_cpp_LIBS@ \
@SYSTEMD_LIBS@ \
-lgmock \
-lpthread
TESTS += libsdbus-c++_integrationtests
check_PROGRAMS = libsdbus-c++_unittests libsdbus-c++_integrationtests
DISTCLEANFILES = Makefile Makefile.in
# Post-build action: executing tests from the IDE
if ENABLE_TESTS
all-local: libsdbus-c++_unittests libsdbus-c++_integrationtests
if [ "${UNIT_TESTS_RUNNER}" ]; then "${UNIT_TESTS_RUNNER}" --deviceip="${TEST_DEVICE_IP}" --testbin=.libs/libsdbus-c++_unittests; fi; exit 0
if [ "${UNIT_TESTS_RUNNER}" ]; then "${UNIT_TESTS_RUNNER}" --deviceip="${TEST_DEVICE_IP}" --testbin=.libs/libsdbus-c++_integrationtests; fi; exit 0
endif

View File

@ -0,0 +1,17 @@
# Taken from https://github.com/google/googletest/blob/master/googletest/README.md#incorporating-into-an-existing-cmake-project
cmake_minimum_required(VERSION 2.8.2)
project(googletest-download NONE)
include(ExternalProject)
ExternalProject_Add(googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG release-1.8.1
SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-src"
BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-build"
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
TEST_COMMAND "")

View File

@ -25,6 +25,7 @@
// Own
#include "Connection.h"
#include "SdBus.h"
#include "TestingAdaptor.h"
#include "TestingProxy.h"
@ -40,9 +41,14 @@
#include <string>
#include <thread>
#include <tuple>
#include <chrono>
#include <fstream>
#include <future>
using ::testing::Eq;
using ::testing::Gt;
using ::testing::ElementsAre;
using namespace std::chrono_literals;
namespace
{
@ -52,22 +58,22 @@ class AdaptorAndProxyFixture : public ::testing::Test
public:
static void SetUpTestCase()
{
m_connection.requestName(INTERFACE_NAME);
m_connection.enterProcessingLoopAsync();
s_connection->requestName(INTERFACE_NAME);
s_connection->enterProcessingLoopAsync();
}
static void TearDownTestCase()
{
m_connection.leaveProcessingLoop();
m_connection.releaseName(INTERFACE_NAME);
s_connection->leaveProcessingLoop();
s_connection->releaseName(INTERFACE_NAME);
}
private:
void SetUp() override
{
m_adaptor = std::make_unique<TestingAdaptor>(m_connection);
m_adaptor = std::make_unique<TestingAdaptor>(*s_connection);
m_proxy = std::make_unique<TestingProxy>(INTERFACE_NAME, OBJECT_PATH);
usleep(50000); // Give time for the proxy to start listening to signals
std::this_thread::sleep_for(50ms); // Give time for the proxy to start listening to signals
}
void TearDown() override
@ -77,14 +83,13 @@ private:
}
public:
static sdbus::internal::Connection m_connection;
static std::unique_ptr<sdbus::IConnection> s_connection;
std::unique_ptr<TestingAdaptor> m_adaptor;
std::unique_ptr<TestingProxy> m_proxy;
};
sdbus::internal::Connection AdaptorAndProxyFixture::m_connection{sdbus::internal::Connection::BusType::eSystem};
std::unique_ptr<sdbus::IConnection> AdaptorAndProxyFixture::s_connection = sdbus::createSystemBusConnection();
}
/*-------------------------------------*/
@ -197,6 +202,140 @@ TEST_F(SdbusTestObject, CallsMethodWithComplexTypeSuccesfully)
ASSERT_THAT(resComplex.count(0), Eq(1));
}
TEST_F(SdbusTestObject, CallsMultiplyMethodWithNoReplyFlag)
{
m_proxy->multiplyWithNoReply(INT64_VALUE, DOUBLE_VALUE);
for (auto i = 0; i < 100; ++i)
{
if (m_adaptor->wasMultiplyCalled())
break;
std::this_thread::sleep_for(10ms);
}
ASSERT_TRUE(m_adaptor->wasMultiplyCalled());
ASSERT_THAT(m_adaptor->getMultiplyResult(), Eq(INT64_VALUE * DOUBLE_VALUE));
}
TEST_F(SdbusTestObject, CallsMethodThatThrowsError)
{
try
{
m_proxy->throwError();
FAIL() << "Expected sdbus::Error exception";
}
catch (const sdbus::Error& e)
{
ASSERT_THAT(e.getName(), Eq("org.freedesktop.DBus.Error.AccessDenied"));
ASSERT_THAT(e.getMessage(), Eq("A test error occurred (Operation not permitted)"));
}
catch(...)
{
FAIL() << "Expected sdbus::Error exception";
}
}
TEST_F(SdbusTestObject, CallsErrorThrowingMethodWithDontExpectReplySet)
{
ASSERT_NO_THROW(m_proxy->throwErrorWithNoReply());
for (auto i = 0; i < 100; ++i)
{
if (m_adaptor->wasThrowErrorCalled())
break;
std::this_thread::sleep_for(10ms);
}
ASSERT_TRUE(m_adaptor->wasThrowErrorCalled());
}
TEST_F(SdbusTestObject, RunsServerSideAsynchoronousMethodAsynchronously)
{
// Yeah, this is kinda timing-dependent test, but times should be safe...
std::mutex mtx;
std::vector<uint32_t> results;
std::atomic<bool> invoke{};
std::atomic<int> startedCount{};
auto call = [&](uint32_t param)
{
TestingProxy proxy{INTERFACE_NAME, OBJECT_PATH};
++startedCount;
while (!invoke) ;
auto result = proxy.doOperationAsync(param);
std::lock_guard<std::mutex> guard(mtx);
results.push_back(result);
};
std::thread invocations[]{std::thread{call, 1500}, std::thread{call, 1000}, std::thread{call, 500}};
while (startedCount != 3) ;
invoke = true;
std::for_each(std::begin(invocations), std::end(invocations), [](auto& t){ t.join(); });
ASSERT_THAT(results, ElementsAre(500, 1000, 1500));
}
TEST_F(SdbusTestObject, HandlesCorrectlyABulkOfParallelServerSideAsyncMethods)
{
std::atomic<size_t> resultCount{};
std::atomic<bool> invoke{};
std::atomic<int> startedCount{};
auto call = [&]()
{
TestingProxy proxy{INTERFACE_NAME, OBJECT_PATH};
++startedCount;
while (!invoke) ;
size_t localResultCount{};
for (size_t i = 0; i < 500; ++i)
{
auto result = proxy.doOperationAsync(i % 2);
if (result == (i % 2)) // Correct return value?
localResultCount++;
}
resultCount += localResultCount;
};
std::thread invocations[]{std::thread{call}, std::thread{call}, std::thread{call}};
while (startedCount != 3) ;
invoke = true;
std::for_each(std::begin(invocations), std::end(invocations), [](auto& t){ t.join(); });
ASSERT_THAT(resultCount, Eq(1500));
}
TEST_F(SdbusTestObject, InvokesMethodAsynchronouslyOnClientSide)
{
std::promise<uint32_t> promise;
auto future = promise.get_future();
m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t res, const sdbus::Error* err)
{
if (err == nullptr)
promise.set_value(res);
else
promise.set_exception(std::make_exception_ptr(*err));
});
m_proxy->doOperationClientSideAsync(100);
ASSERT_THAT(future.get(), Eq(100));
}
TEST_F(SdbusTestObject, InvokesErroneousMethodAsynchronouslyOnClientSide)
{
std::promise<uint32_t> promise;
auto future = promise.get_future();
m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t res, const sdbus::Error* err)
{
if (err == nullptr)
promise.set_value(res);
else
promise.set_exception(std::make_exception_ptr(*err));
});
m_proxy->doErroneousOperationClientSideAsync();
ASSERT_THROW(future.get(), sdbus::Error);
}
TEST_F(SdbusTestObject, FailsCallingNonexistentMethod)
{
ASSERT_THROW(m_proxy->callNonexistentMethod(), sdbus::Error);
@ -209,13 +348,13 @@ TEST_F(SdbusTestObject, FailsCallingMethodOnNonexistentInterface)
TEST_F(SdbusTestObject, FailsCallingMethodOnNonexistentDestination)
{
TestingProxy proxy("wrongDestination", OBJECT_PATH);
TestingProxy proxy("sdbuscpp.destination.that.does.not.exist", OBJECT_PATH);
ASSERT_THROW(proxy.getInt(), sdbus::Error);
}
TEST_F(SdbusTestObject, FailsCallingMethodOnNonexistentObject)
{
TestingProxy proxy(INTERFACE_NAME, "/wrong/path");
TestingProxy proxy(INTERFACE_NAME, "/sdbuscpp/path/that/does/not/exist");
ASSERT_THROW(proxy.getInt(), sdbus::Error);
}
@ -258,11 +397,6 @@ TEST_F(SdbusTestObject, EmitsSignalWithoutRegistrationSuccesfully)
ASSERT_THAT(signature["platform"], Eq("av"));
}
TEST_F(SdbusTestObject, failsEmitingSignalOnNonexistentInterface)
{
ASSERT_THROW(m_adaptor->emitSignalOnNonexistentInterface(), sdbus::Error);
}
// Properties
TEST_F(SdbusTestObject, ReadsReadPropertySuccesfully)
@ -272,7 +406,7 @@ TEST_F(SdbusTestObject, ReadsReadPropertySuccesfully)
TEST_F(SdbusTestObject, WritesAndReadsReadWritePropertySuccesfully)
{
auto x = 42;
uint32_t x = 42;
ASSERT_NO_THROW(m_proxy->action(x));
ASSERT_THAT(m_proxy->action(), Eq(x));
}
@ -287,3 +421,8 @@ TEST_F(SdbusTestObject, CannotReadFromWriteProperty)
{
ASSERT_THROW(m_proxy->blocking(), sdbus::Error);
}
TEST_F(SdbusTestObject, AnswersXmlApiDescriptionOnIntrospection)
{
ASSERT_THAT(m_proxy->Introspect(), Eq(m_adaptor->getExpectedXmlApiDescription()));
}

View File

@ -60,7 +60,7 @@ TEST(Connection, CanRequestRegisteredDbusName)
TEST(Connection, CannotRequestNonregisteredDbusName)
{
auto connection = sdbus::createConnection();
ASSERT_THROW(connection->requestName("some_random_not_supported_dbus_name"), sdbus::Error);
ASSERT_THROW(connection->requestName("some.random.not.supported.dbus.name"), sdbus::Error);
}
TEST(Connection, CanReleasedRequestedName)
@ -74,7 +74,7 @@ TEST(Connection, CanReleasedRequestedName)
TEST(Connection, CannotReleaseNonrequestedName)
{
auto connection = sdbus::createConnection();
ASSERT_THROW(connection->releaseName("some_random_nonrequested_name"), sdbus::Error);
ASSERT_THROW(connection->releaseName("some.random.nonrequested.name"), sdbus::Error);
}
TEST(Connection, CanEnterAndLeaveProcessingLoop)

View File

@ -27,18 +27,31 @@
#define SDBUS_CPP_INTEGRATIONTESTS_TESTINGADAPTOR_H_
#include "adaptor-glue.h"
#include <thread>
#include <chrono>
#include <atomic>
class TestingAdaptor : public sdbus::Interfaces<testing_adaptor>
class TestingAdaptor : public sdbus::AdaptorInterfaces<testing_adaptor>
{
public:
TestingAdaptor(sdbus::IConnection& connection) :
sdbus::Interfaces<::testing_adaptor>(connection, OBJECT_PATH) { }
AdaptorInterfaces(connection, OBJECT_PATH)
{
registerAdaptor();
}
virtual ~TestingAdaptor() { }
~TestingAdaptor()
{
unregisterAdaptor();
}
bool wasMultiplyCalled() const { return m_multiplyCalled; }
double getMultiplyResult() const { return m_multiplyResult; }
bool wasThrowErrorCalled() const { return m_throwErrorCalled; }
protected:
void noArgNoReturn() const { }
void noArgNoReturn() const {}
int32_t getInt() const { return INT32_VALUE; }
@ -46,6 +59,12 @@ protected:
double multiply(const int64_t& a, const double& b) const { return a * b; }
void multiplyWithNoReply(const int64_t& a, const double& b) const
{
m_multiplyResult = a * b;
m_multiplyCalled = true;
}
std::vector<int16_t> getInts16FromStruct(const sdbus::Struct<uint8_t, int16_t, double, std::string, std::vector<int16_t>>& x) const
{
std::vector<int16_t> res{x.get<1>()};
@ -72,8 +91,7 @@ protected:
sdbus::Struct<std::string, sdbus::Struct<std::map<int32_t, int32_t>>> getStructInStruct() const
{
sdbus::Struct<std::string, sdbus::Struct<std::map<int32_t, int32_t>>> x{STRING_VALUE, {{{INT32_VALUE, INT32_VALUE}}}};
return x;
return sdbus::make_struct(STRING_VALUE, sdbus::make_struct(std::map<int32_t, int32_t>{{INT32_VALUE, INT32_VALUE}}));
}
int32_t sumStructItems(const sdbus::Struct<uint8_t, uint16_t>& a, const sdbus::Struct<int32_t, int64_t>& b)
@ -98,6 +116,30 @@ protected:
return res;
}
uint32_t doOperation(uint32_t param)
{
std::this_thread::sleep_for(std::chrono::milliseconds(param));
return param;
}
void doOperationAsync(uint32_t param, sdbus::Result<uint32_t> result)
{
if (param == 0)
{
// Don't sleep and return the result from this thread
result.returnResults(param);
}
else
{
// Process asynchronously in another thread and return the result from there
std::thread([param, result = std::move(result)]()
{
std::this_thread::sleep_for(std::chrono::milliseconds(param));
result.returnResults(param);
}).detach();
}
}
sdbus::Signature getSignature() const { return SIGNATURE_VALUE; }
sdbus::ObjectPath getObjectPath() const { return OBJECT_PATH_VALUE; }
@ -129,6 +171,12 @@ protected:
};
}
void throwError() const
{
m_throwErrorCalled = true;
throw sdbus::createError(1, "A test error occurred");
}
std::string state() { return STRING_VALUE; }
uint32_t action() { return m_action; }
void action(const uint32_t& value) { m_action = value; }
@ -139,6 +187,10 @@ private:
uint32_t m_action;
bool m_blocking;
// For dont-expect-reply method call verifications
mutable std::atomic<bool> m_multiplyCalled{};
mutable double m_multiplyResult{};
mutable std::atomic<bool> m_throwErrorCalled{};
};

View File

@ -28,16 +28,30 @@
#include "proxy-glue.h"
class TestingProxy : public sdbus::ProxyInterfaces<::testing_proxy>
class TestingProxy : public sdbus::ProxyInterfaces<::testing_proxy, sdbus::introspectable_proxy>
{
public:
using sdbus::ProxyInterfaces<::testing_proxy>::ProxyInterfaces;
TestingProxy(std::string destination, std::string objectPath)
: ProxyInterfaces(std::move(destination), std::move(objectPath))
{
registerProxy();
}
~TestingProxy()
{
unregisterProxy();
}
int getSimpleCallCount() const { return m_simpleCallCounter; }
std::map<int32_t, std::string> getMap() const { return m_map; }
double getVariantValue() const { return m_variantValue; }
std::map<std::string, std::string> getSignatureFromSignal() const { return m_signature; }
void installDoOperationClientSideAsyncReplyHandler(std::function<void(uint32_t res, const sdbus::Error* err)> handler)
{
m_DoOperationClientSideAsyncReplyHandler = handler;
}
protected:
void onSimpleSignal() override { ++m_simpleCallCounter; }
@ -53,12 +67,19 @@ protected:
m_signature[std::get<0>(s)] = static_cast<std::string>(std::get<0>(std::get<1>(s)));
}
void onDoOperationReply(uint32_t returnValue, const sdbus::Error* error) override
{
if (m_DoOperationClientSideAsyncReplyHandler)
m_DoOperationClientSideAsyncReplyHandler(returnValue, error);
}
private:
int m_simpleCallCounter{};
std::map<int32_t, std::string> m_map;
double m_variantValue;
std::map<std::string, std::string> m_signature;
std::function<void(uint32_t res, const sdbus::Error* err)> m_DoOperationClientSideAsyncReplyHandler;
};

View File

@ -57,11 +57,14 @@ protected:
testing_adaptor(sdbus::IObject& object) :
object_(object)
{
object_.setInterfaceFlags(INTERFACE_NAME).markAsDeprecated().withPropertyUpdateBehavior(sdbus::Flags::EMITS_NO_SIGNAL);
object_.registerMethod("noArgNoReturn").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->noArgNoReturn(); });
object_.registerMethod("getInt").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->getInt(); });
object_.registerMethod("getTuple").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->getTuple(); });
object_.registerMethod("multiply").onInterface(INTERFACE_NAME).implementedAs([this](const int64_t& a, const double& b){ return this->multiply(a, b); });
object_.registerMethod("multiplyWithNoReply").onInterface(INTERFACE_NAME).implementedAs([this](const int64_t& a, const double& b){ this->multiplyWithNoReply(a, b); }).markAsDeprecated().withNoReply();
object_.registerMethod("getInts16FromStruct").onInterface(INTERFACE_NAME).implementedAs([this](
const sdbus::Struct<uint8_t, int16_t, double, std::string, std::vector<int16_t>>& x){ return this->getInts16FromStruct(x); });
@ -82,18 +85,33 @@ protected:
return this->sumVectorItems(a, b);
});
object_.registerMethod("doOperation").onInterface(INTERFACE_NAME).implementedAs([this](uint32_t param)
{
return this->doOperation(param);
});
object_.registerMethod("doOperationAsync").onInterface(INTERFACE_NAME).implementedAs([this](sdbus::Result<uint32_t> result, uint32_t param)
{
this->doOperationAsync(param, std::move(result));
});
object_.registerMethod("getSignature").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->getSignature(); });
object_.registerMethod("getObjectPath").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->getObjectPath(); });
object_.registerMethod("getComplex").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->getComplex(); });
object_.registerMethod("getComplex").onInterface(INTERFACE_NAME).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](){ this->throwError(); }).withNoReply();
object_.registerMethod("doPrivilegedStuff").onInterface(INTERFACE_NAME).implementedAs([](){}).markAsPrivileged();
// registration of signals is optional, it is useful because of introspection
object_.registerSignal("simpleSignal").onInterface(INTERFACE_NAME);
object_.registerSignal("simpleSignal").onInterface(INTERFACE_NAME).markAsDeprecated();
object_.registerSignal("signalWithMap").onInterface(INTERFACE_NAME).withParameters<std::map<int32_t, std::string>>();
object_.registerSignal("signalWithVariant").onInterface(INTERFACE_NAME).withParameters<sdbus::Variant>();
object_.registerProperty("state").onInterface(INTERFACE_NAME).withGetter([this](){ return this->state(); });
object_.registerProperty("action").onInterface(INTERFACE_NAME).withGetter([this](){ return this->action(); }).withSetter([this](const uint32_t& value){ this->action(value); });
object_.registerProperty("state").onInterface(INTERFACE_NAME).withGetter([this](){ return this->state(); }).markAsDeprecated().withUpdateBehavior(sdbus::Flags::CONST_PROPERTY_VALUE);
object_.registerProperty("action").onInterface(INTERFACE_NAME).withGetter([this](){ return this->action(); }).withSetter([this](const uint32_t& value){ this->action(value); }).withUpdateBehavior(sdbus::Flags::EMITS_NO_SIGNAL);
object_.registerProperty("blocking").onInterface(INTERFACE_NAME)./*withGetter([this](){ return this->blocking(); }).*/withSetter([this](const bool& value){ this->blocking(value); });
}
@ -121,7 +139,7 @@ public:
void emitSignalOnNonexistentInterface()
{
object_.emitSignal("simpleSignal").onInterface("interfaceThatDoesNotExists");
object_.emitSignal("simpleSignal").onInterface("sdbuscpp.interface.that.does.not.exist");
}
private:
@ -133,15 +151,19 @@ protected:
virtual int32_t getInt() const = 0;
virtual std::tuple<uint32_t, std::string> getTuple() const = 0;
virtual double multiply(const int64_t& a, const double& b) const = 0;
virtual void multiplyWithNoReply(const int64_t& a, const double& b) const = 0;
virtual std::vector<int16_t> getInts16FromStruct(const sdbus::Struct<uint8_t, int16_t, double, std::string, std::vector<int16_t>>& x) const = 0;
virtual sdbus::Variant processVariant(sdbus::Variant& v) = 0;
virtual std::map<int32_t, sdbus::Variant> getMapOfVariants(const std::vector<int32_t>& x, const sdbus::Struct<sdbus::Variant, sdbus::Variant>& y) const = 0;
virtual sdbus::Struct<std::string, sdbus::Struct<std::map<int32_t, int32_t>>> getStructInStruct() const = 0;
virtual int32_t sumStructItems(const sdbus::Struct<uint8_t, uint16_t>& a, const sdbus::Struct<int32_t, int64_t>& b) = 0;
virtual uint32_t sumVectorItems(const std::vector<uint16_t>& a, const std::vector<uint64_t>& b) = 0;
virtual uint32_t doOperation(uint32_t param) = 0;
virtual void doOperationAsync(uint32_t param, sdbus::Result<uint32_t> result) = 0;
virtual sdbus::Signature getSignature() const = 0;
virtual sdbus::ObjectPath getObjectPath() const = 0;
virtual ComplexType getComplex() const = 0;
virtual void throwError() const = 0;
virtual std::string state() = 0;
virtual uint32_t action() = 0;
@ -149,6 +171,142 @@ protected:
virtual bool blocking() = 0;
virtual void blocking(const bool& value) = 0;
public: // For testing purposes
std::string getExpectedXmlApiDescription()
{
return
R"delimiter(<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.freedesktop.DBus.Peer">
<method name="Ping"/>
<method name="GetMachineId">
<arg type="s" name="machine_uuid" direction="out"/>
</method>
</interface>
<interface name="org.freedesktop.DBus.Introspectable">
<method name="Introspect">
<arg name="data" type="s" direction="out"/>
</method>
</interface>
<interface name="org.freedesktop.DBus.Properties">
<method name="Get">
<arg name="interface" direction="in" type="s"/>
<arg name="property" direction="in" type="s"/>
<arg name="value" direction="out" type="v"/>
</method>
<method name="GetAll">
<arg name="interface" direction="in" type="s"/>
<arg name="properties" direction="out" type="a{sv}"/>
</method>
<method name="Set">
<arg name="interface" direction="in" type="s"/>
<arg name="property" direction="in" type="s"/>
<arg name="value" direction="in" type="v"/>
</method>
<signal name="PropertiesChanged">
<arg type="s" name="interface"/>
<arg type="a{sv}" name="changed_properties"/>
<arg type="as" name="invalidated_properties"/>
</signal>
</interface>
<interface name="org.sdbuscpp.integrationtests">
<annotation name="org.freedesktop.DBus.Deprecated" value="true"/>
<method name="doOperation">
<arg type="u" direction="in"/>
<arg type="u" direction="out"/>
</method>
<method name="doOperationAsync">
<arg type="u" direction="in"/>
<arg type="u" direction="out"/>
</method>
<method name="doPrivilegedStuff">
<annotation name="org.freedesktop.systemd1.Privileged" value="true"/>
</method>
<method name="getComplex">
<arg type="a{t(a{ya(obva{is})}gs)}" direction="out"/>
<annotation name="org.freedesktop.DBus.Deprecated" value="true"/>
</method>
<method name="getInt">
<arg type="i" direction="out"/>
</method>
<method name="getInts16FromStruct">
<arg type="(yndsan)" direction="in"/>
<arg type="an" direction="out"/>
</method>
<method name="getMapOfVariants">
<arg type="ai" direction="in"/>
<arg type="(vv)" direction="in"/>
<arg type="a{iv}" direction="out"/>
</method>
<method name="getObjectPath">
<arg type="o" direction="out"/>
</method>
<method name="getSignature">
<arg type="g" direction="out"/>
</method>
<method name="getStructInStruct">
<arg type="(s(a{ii}))" direction="out"/>
</method>
<method name="getTuple">
<arg type="u" direction="out"/>
<arg type="s" direction="out"/>
</method>
<method name="multiply">
<arg type="x" direction="in"/>
<arg type="d" direction="in"/>
<arg type="d" direction="out"/>
</method>
<method name="multiplyWithNoReply">
<arg type="x" direction="in"/>
<arg type="d" direction="in"/>
<annotation name="org.freedesktop.DBus.Deprecated" value="true"/>
<annotation name="org.freedesktop.DBus.Method.NoReply" value="true"/>
</method>
<method name="noArgNoReturn">
</method>
<method name="processVariant">
<arg type="v" direction="in"/>
<arg type="v" direction="out"/>
</method>
<method name="sumStructItems">
<arg type="(yq)" direction="in"/>
<arg type="(ix)" direction="in"/>
<arg type="i" direction="out"/>
</method>
<method name="sumVectorItems">
<arg type="aq" direction="in"/>
<arg type="at" direction="in"/>
<arg type="u" direction="out"/>
</method>
<method name="throwError">
</method>
<method name="throwErrorWithNoReply">
<annotation name="org.freedesktop.DBus.Method.NoReply" value="true"/>
</method>
<signal name="signalWithMap">
<arg type="a{is}"/>
</signal>
<signal name="signalWithVariant">
<arg type="v"/>
</signal>
<signal name="simpleSignal">
<annotation name="org.freedesktop.DBus.Deprecated" value="true"/>
</signal>
<property name="action" type="u" access="readwrite">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
</property>
<property name="blocking" type="b" access="readwrite">
</property>
<property name="state" type="s" access="read">
<annotation name="org.freedesktop.DBus.Deprecated" value="true"/>
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const"/>
</property>
</interface>
</node>
)delimiter";
}
};

View File

@ -28,7 +28,7 @@
#include "sdbus-c++/Types.h"
const std::string INTERFACE_NAME{"com.kistler.testsdbuscpp"};
const std::string INTERFACE_NAME{"org.sdbuscpp.integrationtests"};
const std::string OBJECT_PATH{"/"};
constexpr const uint8_t UINT8_VALUE{1};

View File

@ -0,0 +1,17 @@
<!-- This configuration file specifies the required security policies
for the Kistler DBUS example to run core daemon to work. -->
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<!-- ../system.conf have denied everything, so we just punch some holes -->
<policy context="default">
<allow own="org.sdbuscpp.integrationtests"/>
<allow send_destination="org.sdbuscpp.integrationtests"/>
<allow send_interface="org.sdbuscpp.integrationtests"/>
</policy>
</busconfig>

View File

@ -34,7 +34,7 @@
class testing_proxy
{
protected:
testing_proxy(sdbus::IObjectProxy& object) :
testing_proxy(sdbus::IProxy& object) :
object_(object)
{
object_.uponSignal("simpleSignal").onInterface(INTERFACE_NAME).call([this](){ this->onSimpleSignal(); });
@ -45,12 +45,13 @@ protected:
{ this->onSignalWithoutRegistration(s); });
}
virtual void onSimpleSignal() = 0;
virtual void onSignalWithMap(const std::map<int32_t, std::string>& map) = 0;
virtual void onSignalWithVariant(const sdbus::Variant& v) = 0;
virtual void onSignalWithoutRegistration(const sdbus::Struct<std::string, sdbus::Struct<sdbus::Signature>>& s) = 0;
virtual void onDoOperationReply(uint32_t returnValue, const sdbus::Error* error) = 0;
public:
void noArgNoReturn()
{
@ -78,6 +79,11 @@ public:
return result;
}
void multiplyWithNoReply(const int64_t& a, const double& b)
{
object_.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>>& x)
{
std::vector<int16_t> result;
@ -120,6 +126,41 @@ public:
return result;
}
uint32_t doOperation(uint32_t param)
{
uint32_t result;
object_.callMethod("doOperation").onInterface(INTERFACE_NAME).withArguments(param).storeResultsTo(result);
return result;
}
uint32_t doOperationAsync(uint32_t param)
{
uint32_t result;
object_.callMethod("doOperationAsync").onInterface(INTERFACE_NAME).withArguments(param).storeResultsTo(result);
return result;
}
void doOperationClientSideAsync(uint32_t param)
{
object_.callMethodAsync("doOperation")
.onInterface(INTERFACE_NAME)
.withArguments(param)
.uponReplyInvoke([this](const sdbus::Error* error, uint32_t returnValue)
{
this->onDoOperationReply(returnValue, error);
});
}
void doErroneousOperationClientSideAsync()
{
object_.callMethodAsync("throwError")
.onInterface(INTERFACE_NAME)
.uponReplyInvoke([this](const sdbus::Error* error)
{
this->onDoOperationReply(0, error);
});
}
sdbus::Signature getSignature()
{
sdbus::Signature result;
@ -141,6 +182,16 @@ public:
return result;
}
void throwError()
{
object_.callMethod("throwError").onInterface(INTERFACE_NAME);
}
void throwErrorWithNoReply()
{
object_.callMethod("throwErrorWithNoReply").onInterface(INTERFACE_NAME).dontExpectReply();
}
int32_t callNonexistentMethod()
{
int32_t result;
@ -151,7 +202,7 @@ public:
int32_t callMethodOnNonexistentInterface()
{
int32_t result;
object_.callMethod("someMethod").onInterface("interfaceThatDoesNotExist").storeResultsTo(result);
object_.callMethod("someMethod").onInterface("sdbuscpp.interface.that.does.not.exist").storeResultsTo(result);
return result;
}
@ -182,7 +233,7 @@ public:
private:
sdbus::IObjectProxy& object_;
sdbus::IProxy& object_;
};

View File

@ -1,7 +1,7 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file libsdbus-c++_integrationtests.cpp
* @file sdbus-c++-integration-tests.cpp
*
* Created on: Jan 2, 2017
* Project: sdbus-c++

170
test/perftests/client.cpp Normal file
View File

@ -0,0 +1,170 @@
/**
* (C) 2019 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file client.cpp
*
* Created on: Jan 25, 2019
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include "perftests-proxy.h"
#include <sdbus-c++/sdbus-c++.h>
#include <vector>
#include <string>
#include <iostream>
#include <unistd.h>
#include <thread>
#include <chrono>
#include <cassert>
#include <algorithm>
#include <iostream>
using namespace std::chrono_literals;
class PerftestProxy : public sdbus::ProxyInterfaces<org::sdbuscpp::perftests_proxy>
{
public:
PerftestProxy(std::string destination, std::string objectPath)
: ProxyInterfaces(std::move(destination), std::move(objectPath))
{
registerProxy();
}
~PerftestProxy()
{
unregisterProxy();
}
protected:
virtual void onDataSignal(const std::string& data) override
{
static unsigned int counter = 0;
static std::chrono::time_point<std::chrono::steady_clock> startTime;
assert(data.size() == m_msgSize);
++counter;
if (counter == 1)
startTime = std::chrono::steady_clock::now();
else if (counter == m_msgCount)
{
auto stopTime = std::chrono::steady_clock::now();
std::cout << "Received " << m_msgCount << " signals in: " << std::chrono::duration_cast<std::chrono::milliseconds>(stopTime - startTime).count() << " ms" << std::endl;
counter = 0;
}
}
public:
unsigned int m_msgSize{};
unsigned int m_msgCount{};
};
std::string createRandomString(size_t length)
{
auto randchar = []() -> char
{
const char charset[] =
"0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz";
const size_t max_index = (sizeof(charset) - 1);
return charset[ rand() % max_index ];
};
std::string str(length, 0);
std::generate_n(str.begin(), length, randchar);
return str;
}
//-----------------------------------------
int main(int /*argc*/, char */*argv*/[])
{
const char* destinationName = "org.sdbuscpp.perftests";
const char* objectPath = "/org/sdbuscpp/perftests";
PerftestProxy client(destinationName, objectPath);
const unsigned int repetitions{20};
unsigned int msgCount = 1000;
unsigned int msgSize{};
msgSize = 20;
std::cout << "** Measuring signals of size " << msgSize << " bytes (" << repetitions << " repetitions)..." << std::endl << std::endl;
client.m_msgCount = msgCount; client.m_msgSize = msgSize;
for (unsigned int r = 0; r < repetitions; ++r)
{
client.sendDataSignals(msgCount, msgSize);
std::this_thread::sleep_for(1000ms);
}
msgSize = 1000;
std::cout << std::endl << "** Measuring signals of size " << msgSize << " bytes (" << repetitions << " repetitions)..." << std::endl << std::endl;
client.m_msgCount = msgCount; client.m_msgSize = msgSize;
for (unsigned int r = 0; r < repetitions; ++r)
{
client.sendDataSignals(msgCount, msgSize);
std::this_thread::sleep_for(1000ms);
}
msgSize = 20;
std::cout << std::endl << "** Measuring method calls of size " << msgSize << " bytes (" << repetitions << " repetitions)..." << std::endl << std::endl;
for (unsigned int r = 0; r < repetitions; ++r)
{
auto str1 = createRandomString(msgSize/2);
auto str2 = createRandomString(msgSize/2);
auto startTime = std::chrono::steady_clock::now();
for (unsigned int i = 0; i < msgCount; i++)
{
auto result = client.concatenateTwoStrings(str1, str2);
assert(result.size() == str1.size() + str2.size());
assert(result.size() == msgSize);
}
auto stopTime = std::chrono::steady_clock::now();
std::cout << "Called " << msgCount << " methods in: " << std::chrono::duration_cast<std::chrono::milliseconds>(stopTime - startTime).count() << " ms" << std::endl;
std::this_thread::sleep_for(1000ms);
}
msgSize = 1000;
std::cout << std::endl << "** Measuring method calls of size " << msgSize << " bytes (" << repetitions << " repetitions)..." << std::endl << std::endl;
for (unsigned int r = 0; r < repetitions; ++r)
{
auto str1 = createRandomString(msgSize/2);
auto str2 = createRandomString(msgSize/2);
auto startTime = std::chrono::steady_clock::now();
for (unsigned int i = 0; i < msgCount; i++)
{
auto result = client.concatenateTwoStrings(str1, str2);
assert(result.size() == str1.size() + str2.size());
assert(result.size() == msgSize);
}
auto stopTime = std::chrono::steady_clock::now();
std::cout << "Called " << msgCount << " methods in: " << std::chrono::duration_cast<std::chrono::milliseconds>(stopTime - startTime).count() << " ms" << std::endl;
std::this_thread::sleep_for(1000ms);
}
return 0;
}

View File

@ -8,10 +8,9 @@
<!-- ../system.conf have denied everything, so we just punch some holes -->
<policy context="default">
<allow own="com.kistler.testsdbuscpp"/>
<allow send_destination="com.kistler.testsdbuscpp"/>
<allow send_interface="com.kistler.testsdbuscpp"/>
<allow own="org.sdbuscpp.perftests"/>
<allow send_destination="org.sdbuscpp.perftests"/>
<allow send_interface="org.sdbuscpp.perftests"/>
</policy>
</busconfig>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<node name="/org/sdbuscpp/perftest">
<interface name="org.sdbuscpp.perftests">
<method name="sendDataSignals">
<arg type="u" name="numberOfSignals" direction="in" />
<arg type="u" name="signalMsgSize" direction="in" />
</method>
<method name="concatenateTwoStrings">
<arg type="s" name="string1" direction="in" />
<arg type="s" name="string2" direction="in" />
<arg type="s" name="result" direction="out" />
</method>
<signal name="dataSignal">
<arg type="s" name="data" />
</signal>
</interface>
</node>

View File

@ -0,0 +1,46 @@
/*
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__perftest_adaptor_h__adaptor__H__
#define __sdbuscpp__perftest_adaptor_h__adaptor__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace sdbuscpp {
class perftests_adaptor
{
public:
static constexpr const char* interfaceName = "org.sdbuscpp.perftests";
protected:
perftests_adaptor(sdbus::IObject& object)
: object_(object)
{
object_.registerMethod("sendDataSignals").onInterface(interfaceName).implementedAs([this](const uint32_t& numberOfSignals, const uint32_t& signalMsgSize){ return this->sendDataSignals(numberOfSignals, signalMsgSize); });
object_.registerMethod("concatenateTwoStrings").onInterface(interfaceName).implementedAs([this](const std::string& string1, const std::string& string2){ return this->concatenateTwoStrings(string1, string2); });
object_.registerSignal("dataSignal").onInterface(interfaceName).withParameters<std::string>();
}
public:
void dataSignal(const std::string& data)
{
object_.emitSignal("dataSignal").onInterface(interfaceName).withArguments(data);
}
private:
virtual void sendDataSignals(const uint32_t& numberOfSignals, const uint32_t& signalMsgSize) = 0;
virtual std::string concatenateTwoStrings(const std::string& string1, const std::string& string2) = 0;
private:
sdbus::IObject& object_;
};
}} // namespaces
#endif

View File

@ -0,0 +1,49 @@
/*
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__perftest_proxy_h__proxy__H__
#define __sdbuscpp__perftest_proxy_h__proxy__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace sdbuscpp {
class perftests_proxy
{
public:
static constexpr const char* interfaceName = "org.sdbuscpp.perftests";
protected:
perftests_proxy(sdbus::IProxy& proxy)
: proxy_(proxy)
{
proxy_.uponSignal("dataSignal").onInterface(interfaceName).call([this](const std::string& data){ this->onDataSignal(data); });
}
virtual void onDataSignal(const std::string& data) = 0;
public:
void sendDataSignals(const uint32_t& numberOfSignals, const uint32_t& signalMsgSize)
{
proxy_.callMethod("sendDataSignals").onInterface(interfaceName).withArguments(numberOfSignals, signalMsgSize);
}
std::string concatenateTwoStrings(const std::string& string1, const std::string& string2)
{
std::string result;
proxy_.callMethod("concatenateTwoStrings").onInterface(interfaceName).withArguments(string1, string2).storeResultsTo(result);
return result;
}
private:
sdbus::IProxy& proxy_;
};
}} // namespaces
#endif

101
test/perftests/server.cpp Normal file
View File

@ -0,0 +1,101 @@
/**
* (C) 2019 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file server.cpp
*
* Created on: Jan 25, 2019
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include "perftests-adaptor.h"
#include <sdbus-c++/sdbus-c++.h>
#include <vector>
#include <string>
#include <thread>
#include <chrono>
#include <algorithm>
#include <iostream>
using namespace std::chrono_literals;
std::string createRandomString(size_t length);
class PerftestAdaptor : public sdbus::AdaptorInterfaces<org::sdbuscpp::perftests_adaptor>
{
public:
PerftestAdaptor(sdbus::IConnection& connection, std::string objectPath)
: AdaptorInterfaces(connection, std::move(objectPath))
{
registerAdaptor();
}
~PerftestAdaptor()
{
unregisterAdaptor();
}
protected:
virtual void sendDataSignals(const uint32_t& numberOfSignals, const uint32_t& signalMsgSize) override
{
auto data = createRandomString(signalMsgSize);
auto start_time = std::chrono::steady_clock::now();
for (uint32_t i = 0; i < numberOfSignals; ++i)
{
// Emit signal
dataSignal(data);
}
auto stop_time = std::chrono::steady_clock::now();
std::cout << "Server sent " << numberOfSignals << " signals in: " << std::chrono::duration_cast<std::chrono::milliseconds>(stop_time - start_time).count() << " ms" << std::endl;
}
virtual std::string concatenateTwoStrings(const std::string& string1, const std::string& string2) override
{
return string1 + string2;
}
};
std::string createRandomString(size_t length)
{
auto randchar = []() -> char
{
const char charset[] =
"0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz";
const size_t max_index = (sizeof(charset) - 1);
return charset[ rand() % max_index ];
};
std::string str(length, 0);
std::generate_n(str.begin(), length, randchar);
return str;
}
//-----------------------------------------
int main(int /*argc*/, char */*argv*/[])
{
const char* serviceName = "org.sdbuscpp.perftests";
auto connection = sdbus::createSystemBusConnection(serviceName);
const char* objectPath = "/org/sdbuscpp/perftests";
PerftestAdaptor server(*connection, objectPath);
connection->enterProcessingLoop();
}

View File

@ -0,0 +1,39 @@
/*
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__celsius_thermometer_adaptor_h__adaptor__H__
#define __sdbuscpp__celsius_thermometer_adaptor_h__adaptor__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace sdbuscpp {
namespace stresstests {
namespace celsius {
class thermometer_adaptor
{
public:
static constexpr const char* interfaceName = "org.sdbuscpp.stresstests.celsius.thermometer";
protected:
thermometer_adaptor(sdbus::IObject& object)
: object_(object)
{
object_.registerMethod("getCurrentTemperature").onInterface(interfaceName).implementedAs([this](){ return this->getCurrentTemperature(); });
}
private:
virtual uint32_t getCurrentTemperature() = 0;
private:
sdbus::IObject& object_;
};
}}}} // namespaces
#endif

View File

@ -0,0 +1,43 @@
/*
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__celsius_thermometer_proxy_h__proxy__H__
#define __sdbuscpp__celsius_thermometer_proxy_h__proxy__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace sdbuscpp {
namespace stresstests {
namespace celsius {
class thermometer_proxy
{
public:
static constexpr const char* interfaceName = "org.sdbuscpp.stresstests.celsius.thermometer";
protected:
thermometer_proxy(sdbus::IProxy& proxy)
: proxy_(proxy)
{
}
public:
uint32_t getCurrentTemperature()
{
uint32_t result;
proxy_.callMethod("getCurrentTemperature").onInterface(interfaceName).storeResultsTo(result);
return result;
}
private:
sdbus::IProxy& proxy_;
};
}}}} // namespaces
#endif

View File

@ -0,0 +1,45 @@
/*
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__concatenator_adaptor_h__adaptor__H__
#define __sdbuscpp__concatenator_adaptor_h__adaptor__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace sdbuscpp {
namespace stresstests {
class concatenator_adaptor
{
public:
static constexpr const char* interfaceName = "org.sdbuscpp.stresstests.concatenator";
protected:
concatenator_adaptor(sdbus::IObject& object)
: object_(object)
{
object_.registerMethod("concatenate").onInterface(interfaceName).implementedAs([this](sdbus::Result<std::string>&& result, std::map<std::string, sdbus::Variant> params){ this->concatenate(std::move(result), std::move(params)); });
object_.registerSignal("concatenatedSignal").onInterface(interfaceName).withParameters<std::string>();
}
public:
void concatenatedSignal(const std::string& concatenatedString)
{
object_.emitSignal("concatenatedSignal").onInterface(interfaceName).withArguments(concatenatedString);
}
private:
virtual void concatenate(sdbus::Result<std::string>&& result, std::map<std::string, sdbus::Variant> params) = 0;
private:
sdbus::IObject& object_;
};
}}} // namespaces
#endif

View File

@ -0,0 +1,45 @@
/*
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__concatenator_proxy_h__proxy__H__
#define __sdbuscpp__concatenator_proxy_h__proxy__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace sdbuscpp {
namespace stresstests {
class concatenator_proxy
{
public:
static constexpr const char* interfaceName = "org.sdbuscpp.stresstests.concatenator";
protected:
concatenator_proxy(sdbus::IProxy& proxy)
: proxy_(proxy)
{
proxy_.uponSignal("concatenatedSignal").onInterface(interfaceName).call([this](const std::string& concatenatedString){ this->onConcatenatedSignal(concatenatedString); });
}
virtual void onConcatenatedSignal(const std::string& concatenatedString) = 0;
virtual void onConcatenateReply(const std::string& result, const sdbus::Error* error) = 0;
public:
void concatenate(const std::map<std::string, sdbus::Variant>& params)
{
proxy_.callMethodAsync("concatenate").onInterface(interfaceName).withArguments(params).uponReplyInvoke([this](const sdbus::Error* error, const std::string& result){ this->onConcatenateReply(result, error); });
}
private:
sdbus::IProxy& proxy_;
};
}}} // namespaces
#endif

View File

@ -0,0 +1,68 @@
/*
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__fahrenheit_thermometer_adaptor_h__adaptor__H__
#define __sdbuscpp__fahrenheit_thermometer_adaptor_h__adaptor__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace sdbuscpp {
namespace stresstests {
namespace fahrenheit {
class thermometer_adaptor
{
public:
static constexpr const char* interfaceName = "org.sdbuscpp.stresstests.fahrenheit.thermometer";
protected:
thermometer_adaptor(sdbus::IObject& object)
: object_(object)
{
object_.registerMethod("getCurrentTemperature").onInterface(interfaceName).implementedAs([this](){ return this->getCurrentTemperature(); });
}
private:
virtual uint32_t getCurrentTemperature() = 0;
private:
sdbus::IObject& object_;
};
}}}} // namespaces
namespace org {
namespace sdbuscpp {
namespace stresstests {
namespace fahrenheit {
namespace thermometer {
class factory_adaptor
{
public:
static constexpr const char* interfaceName = "org.sdbuscpp.stresstests.fahrenheit.thermometer.factory";
protected:
factory_adaptor(sdbus::IObject& object)
: object_(object)
{
object_.registerMethod("createDelegateObject").onInterface(interfaceName).implementedAs([this](sdbus::Result<sdbus::ObjectPath>&& result){ this->createDelegateObject(std::move(result)); });
object_.registerMethod("destroyDelegateObject").onInterface(interfaceName).implementedAs([this](sdbus::Result<>&& result, sdbus::ObjectPath delegate){ this->destroyDelegateObject(std::move(result), std::move(delegate)); }).withNoReply();
}
private:
virtual void createDelegateObject(sdbus::Result<sdbus::ObjectPath>&& result) = 0;
virtual void destroyDelegateObject(sdbus::Result<>&& result, sdbus::ObjectPath delegate) = 0;
private:
sdbus::IObject& object_;
};
}}}}} // namespaces
#endif

View File

@ -0,0 +1,79 @@
/*
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__fahrenheit_thermometer_proxy_h__proxy__H__
#define __sdbuscpp__fahrenheit_thermometer_proxy_h__proxy__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace sdbuscpp {
namespace stresstests {
namespace fahrenheit {
class thermometer_proxy
{
public:
static constexpr const char* interfaceName = "org.sdbuscpp.stresstests.fahrenheit.thermometer";
protected:
thermometer_proxy(sdbus::IProxy& proxy)
: proxy_(proxy)
{
}
public:
uint32_t getCurrentTemperature()
{
uint32_t result;
proxy_.callMethod("getCurrentTemperature").onInterface(interfaceName).storeResultsTo(result);
return result;
}
private:
sdbus::IProxy& proxy_;
};
}}}} // namespaces
namespace org {
namespace sdbuscpp {
namespace stresstests {
namespace fahrenheit {
namespace thermometer {
class factory_proxy
{
public:
static constexpr const char* interfaceName = "org.sdbuscpp.stresstests.fahrenheit.thermometer.factory";
protected:
factory_proxy(sdbus::IProxy& proxy)
: proxy_(proxy)
{
}
public:
sdbus::ObjectPath createDelegateObject()
{
sdbus::ObjectPath result;
proxy_.callMethod("createDelegateObject").onInterface(interfaceName).storeResultsTo(result);
return result;
}
void destroyDelegateObject(const sdbus::ObjectPath& delegate)
{
proxy_.callMethod("destroyDelegateObject").onInterface(interfaceName).withArguments(delegate).dontExpectReply();
}
private:
sdbus::IProxy& proxy_;
};
}}}}} // namespaces
#endif

View File

@ -0,0 +1,22 @@
<!-- This configuration file specifies the required security policies
for the Kistler DBUS example to run core daemon to work. -->
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<!-- ../system.conf have denied everything, so we just punch some holes -->
<policy context="default">
<allow own="org.sdbuscpp.stresstests.service1"/>
<allow send_destination="org.sdbuscpp.stresstests.service1"/>
<allow send_interface="org.sdbuscpp.stresstests.service1"/>
</policy>
<policy context="default">
<allow own="org.sdbuscpp.stresstests.service2"/>
<allow send_destination="org.sdbuscpp.stresstests.service2"/>
<allow send_interface="org.sdbuscpp.stresstests.service2"/>
</policy>
</busconfig>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<node name="/org/sdbuscpp/stresstests/celsius/thermometer">
<interface name="org.sdbuscpp.stresstests.celsius.thermometer">
<method name="getCurrentTemperature">
<arg type="u" name="result" direction="out" />
</method>
</interface>
</node>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<node name="/org/sdbuscpp/stresstests/concatenator">
<interface name="org.sdbuscpp.stresstests.concatenator">
<method name="concatenate">
<arg type="a{sv}" name="params" direction="in" />
<arg type="s" name="result" direction="out" />
<annotation name="org.freedesktop.DBus.Method.Async" value="clientserver" />
</method>
<signal name="concatenatedSignal">
<arg type="s" name="concatenatedString" />
</signal>
</interface>
</node>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<node name="/org/sdbuscpp/stresstests/fahrenheit/thermometer">
<interface name="org.sdbuscpp.stresstests.fahrenheit.thermometer">
<method name="getCurrentTemperature">
<arg type="u" name="result" direction="out" />
</method>
</interface>
<interface name="org.sdbuscpp.stresstests.fahrenheit.thermometer.factory">
<method name="createDelegateObject">
<arg type="o" name="delegate" direction="out" />
<annotation name="org.freedesktop.DBus.Method.Async" value="server" />
</method>
<method name="destroyDelegateObject">
<arg type="o" name="delegate" direction="in" />
<annotation name="org.freedesktop.DBus.Method.Async" value="server" />
<annotation name="org.freedesktop.DBus.Method.NoReply" value="true" />
</method>
</interface>
</node>

View File

@ -0,0 +1,514 @@
/**
* (C) 2019 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file sdbus-c++-stress-tests.cpp
*
* Created on: Jan 25, 2019
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include "celsius-thermometer-adaptor.h"
#include "celsius-thermometer-proxy.h"
#include "fahrenheit-thermometer-adaptor.h"
#include "fahrenheit-thermometer-proxy.h"
#include "concatenator-adaptor.h"
#include "concatenator-proxy.h"
#include <sdbus-c++/sdbus-c++.h>
#include <vector>
#include <string>
#include <iostream>
#include <unistd.h>
#include <thread>
#include <chrono>
#include <cassert>
#include <cstdlib>
#include <atomic>
#include <sstream>
#include <mutex>
#include <condition_variable>
#include <queue>
using namespace std::chrono_literals;
using namespace std::string_literals;
#define SERVICE_1_BUS_NAME "org.sdbuscpp.stresstests.service1"s
#define SERVICE_2_BUS_NAME "org.sdbuscpp.stresstests.service2"s
#define CELSIUS_THERMOMETER_OBJECT_PATH "/org/sdbuscpp/stresstests/celsius/thermometer"s
#define FAHRENHEIT_THERMOMETER_OBJECT_PATH "/org/sdbuscpp/stresstests/fahrenheit/thermometer"s
#define CONCATENATOR_OBJECT_PATH "/org/sdbuscpp/stresstests/concatenator"s
class CelsiusThermometerAdaptor : public sdbus::AdaptorInterfaces<org::sdbuscpp::stresstests::celsius::thermometer_adaptor>
{
public:
CelsiusThermometerAdaptor(sdbus::IConnection& connection, std::string objectPath)
: AdaptorInterfaces(connection, std::move(objectPath))
{
registerAdaptor();
}
~CelsiusThermometerAdaptor()
{
unregisterAdaptor();
}
protected:
virtual uint32_t getCurrentTemperature() override
{
return m_currentTemperature++;
}
private:
uint32_t m_currentTemperature{};
};
class CelsiusThermometerProxy : public sdbus::ProxyInterfaces<org::sdbuscpp::stresstests::celsius::thermometer_proxy>
{
public:
CelsiusThermometerProxy(sdbus::IConnection& connection, std::string destination, std::string objectPath)
: ProxyInterfaces(connection, std::move(destination), std::move(objectPath))
{
registerProxy();
}
~CelsiusThermometerProxy()
{
unregisterProxy();
}
};
class FahrenheitThermometerAdaptor : public sdbus::AdaptorInterfaces< org::sdbuscpp::stresstests::fahrenheit::thermometer_adaptor
, org::sdbuscpp::stresstests::fahrenheit::thermometer::factory_adaptor >
{
public:
FahrenheitThermometerAdaptor(sdbus::IConnection& connection, std::string objectPath, bool isDelegate)
: AdaptorInterfaces(connection, std::move(objectPath))
, celsiusProxy_(connection, SERVICE_2_BUS_NAME, CELSIUS_THERMOMETER_OBJECT_PATH)
{
if (!isDelegate)
{
unsigned int workers = std::thread::hardware_concurrency();
if (workers < 4)
workers = 4;
for (unsigned int i = 0; i < workers; ++i)
workers_.emplace_back([this]()
{
//std::cout << "Created FTA worker thread 0x" << std::hex << std::this_thread::get_id() << std::dec << std::endl;
while(!exit_)
{
// Pop a work item from the queue
std::unique_lock<std::mutex> lock(mutex_);
cond_.wait(lock, [this]{return !requests_.empty() || exit_;});
if (exit_)
break;
auto request = std::move(requests_.front());
requests_.pop();
lock.unlock();
// Either create or destroy a delegate object
if (request.delegateObjectPath.empty())
{
// Create new delegate object
auto& connection = getObject().getConnection();
sdbus::ObjectPath newObjectPath = FAHRENHEIT_THERMOMETER_OBJECT_PATH + "/" + std::to_string(request.objectNr);
// Here we are testing dynamic creation of a D-Bus object in an async way
auto adaptor = std::make_unique<FahrenheitThermometerAdaptor>(connection, newObjectPath, true);
std::unique_lock<std::mutex> lock{childrenMutex_};
children_.emplace(newObjectPath, std::move(adaptor));
lock.unlock();
request.result.returnResults(newObjectPath);
}
else
{
// Destroy existing delegate object
// Here we are testing dynamic removal of a D-Bus object in an async way
std::lock_guard<std::mutex> lock{childrenMutex_};
children_.erase(request.delegateObjectPath);
}
}
});
}
registerAdaptor();
}
~FahrenheitThermometerAdaptor()
{
unregisterAdaptor();
exit_ = true;
cond_.notify_all();
for (auto& worker : workers_)
worker.join();
}
protected:
virtual uint32_t getCurrentTemperature() override
{
// In this D-Bus call, make yet another D-Bus call to another service over the same connection
return static_cast<uint32_t>(celsiusProxy_.getCurrentTemperature() * 1.8 + 32.);
}
virtual void createDelegateObject(sdbus::Result<sdbus::ObjectPath>&& result) override
{
static size_t objectCounter{};
objectCounter++;
std::unique_lock<std::mutex> lock(mutex_);
requests_.push(WorkItem{objectCounter, std::string{}, std::move(result)});
lock.unlock();
cond_.notify_one();
}
virtual void destroyDelegateObject(sdbus::Result<>&& /*result*/, sdbus::ObjectPath delegate) override
{
std::unique_lock<std::mutex> lock(mutex_);
requests_.push(WorkItem{0, std::move(delegate), {}});
lock.unlock();
cond_.notify_one();
}
private:
CelsiusThermometerProxy celsiusProxy_;
std::map<std::string, std::unique_ptr<FahrenheitThermometerAdaptor>> children_;
std::mutex childrenMutex_;
struct WorkItem
{
size_t objectNr;
sdbus::ObjectPath delegateObjectPath;
sdbus::Result<sdbus::ObjectPath> result;
};
std::mutex mutex_;
std::condition_variable cond_;
std::queue<WorkItem> requests_;
std::vector<std::thread> workers_;
std::atomic<bool> exit_{};
};
class FahrenheitThermometerProxy : public sdbus::ProxyInterfaces< org::sdbuscpp::stresstests::fahrenheit::thermometer_proxy
, org::sdbuscpp::stresstests::fahrenheit::thermometer::factory_proxy >
{
public:
FahrenheitThermometerProxy(sdbus::IConnection& connection, std::string destination, std::string objectPath)
: ProxyInterfaces(connection, std::move(destination), std::move(objectPath))
{
registerProxy();
}
~FahrenheitThermometerProxy()
{
unregisterProxy();
}
};
class ConcatenatorAdaptor : public sdbus::AdaptorInterfaces<org::sdbuscpp::stresstests::concatenator_adaptor>
{
public:
ConcatenatorAdaptor(sdbus::IConnection& connection, std::string objectPath)
: AdaptorInterfaces(connection, std::move(objectPath))
{
unsigned int workers = std::thread::hardware_concurrency();
if (workers < 4)
workers = 4;
for (unsigned int i = 0; i < workers; ++i)
workers_.emplace_back([this]()
{
//std::cout << "Created CA worker thread 0x" << std::hex << std::this_thread::get_id() << std::dec << std::endl;
while(!exit_)
{
// Pop a work item from the queue
std::unique_lock<std::mutex> lock(mutex_);
cond_.wait(lock, [this]{return !requests_.empty() || exit_;});
if (exit_)
break;
auto request = std::move(requests_.front());
requests_.pop();
lock.unlock();
// Do concatenation work, return results and fire signal
auto aString = request.input.at("key1").get<std::string>();
auto aNumber = request.input.at("key2").get<uint32_t>();
auto resultString = aString + " " + std::to_string(aNumber);
request.result.returnResults(resultString);
concatenatedSignal(resultString);
}
});
registerAdaptor();
}
~ConcatenatorAdaptor()
{
unregisterAdaptor();
exit_ = true;
cond_.notify_all();
for (auto& worker : workers_)
worker.join();
}
protected:
virtual void concatenate(sdbus::Result<std::string>&& result, std::map<std::string, sdbus::Variant> params) override
{
std::unique_lock<std::mutex> lock(mutex_);
requests_.push(WorkItem{std::move(params), std::move(result)});
lock.unlock();
cond_.notify_one();
}
private:
struct WorkItem
{
std::map<std::string, sdbus::Variant> input;
sdbus::Result<std::string> result;
};
std::mutex mutex_;
std::condition_variable cond_;
std::queue<WorkItem> requests_;
std::vector<std::thread> workers_;
std::atomic<bool> exit_{};
};
class ConcatenatorProxy : public sdbus::ProxyInterfaces<org::sdbuscpp::stresstests::concatenator_proxy>
{
public:
ConcatenatorProxy(sdbus::IConnection& connection, std::string destination, std::string objectPath)
: ProxyInterfaces(connection, std::move(destination), std::move(objectPath))
{
registerProxy();
}
~ConcatenatorProxy()
{
unregisterProxy();
}
private:
virtual void onConcatenateReply(const std::string& result, const sdbus::Error* error) override
{
assert(error == nullptr);
std::stringstream str(result);
std::string aString;
str >> aString;
assert(aString == "sdbus-c++-stress-tests");
uint32_t aNumber;
str >> aNumber;
assert(aNumber > 0);
++repliesReceived_;
}
virtual void onConcatenatedSignal(const std::string& concatenatedString) override
{
std::stringstream str(concatenatedString);
std::string aString;
str >> aString;
assert(aString == "sdbus-c++-stress-tests");
uint32_t aNumber;
str >> aNumber;
assert(aNumber > 0);
++signalsReceived_;
}
public:
std::atomic<uint32_t> repliesReceived_{};
std::atomic<uint32_t> signalsReceived_{};
};
//-----------------------------------------
int main(int argc, char *argv[])
{
long loops;
long loopDuration;
if (argc == 1)
{
loops = 1;
loopDuration = 30000;
}
else if (argc == 3)
{
loops = std::atol(argv[1]);
loopDuration = std::atol(argv[2]);
}
else
throw std::runtime_error("Wrong program options");
std::cout << "Going on with " << loops << " loops and " << loopDuration << "ms loop duration" << std::endl;
std::atomic<uint32_t> concatenationCallsMade{0};
std::atomic<uint32_t> concatenationRepliesReceived{0};
std::atomic<uint32_t> concatenationSignalsReceived{0};
std::atomic<uint32_t> thermometerCallsMade{0};
std::atomic<bool> exitLogger{};
std::thread loggerThread([&]()
{
while (!exitLogger)
{
std::this_thread::sleep_for(1s);
std::cout << "Made " << concatenationCallsMade << " concatenation calls, received " << concatenationRepliesReceived << " replies and " << concatenationSignalsReceived << " signals so far." << std::endl;
std::cout << "Made " << thermometerCallsMade << " thermometer calls so far." << std::endl << std::endl;
}
});
for (long loop = 0; loop < loops; ++loop)
{
std::cout << "Entering loop " << loop+1 << std::endl;
auto service2Connection = sdbus::createSystemBusConnection(SERVICE_2_BUS_NAME);
std::atomic<bool> service2ThreadReady{};
std::thread service2Thread([&con = *service2Connection, &service2ThreadReady]()
{
CelsiusThermometerAdaptor thermometer(con, CELSIUS_THERMOMETER_OBJECT_PATH);
service2ThreadReady = true;
con.enterProcessingLoop();
});
auto service1Connection = sdbus::createSystemBusConnection(SERVICE_1_BUS_NAME);
std::atomic<bool> service1ThreadReady{};
std::thread service1Thread([&con = *service1Connection, &service1ThreadReady]()
{
ConcatenatorAdaptor concatenator(con, CONCATENATOR_OBJECT_PATH);
FahrenheitThermometerAdaptor thermometer(con, FAHRENHEIT_THERMOMETER_OBJECT_PATH, false);
service1ThreadReady = true;
con.enterProcessingLoop();
});
// Wait for both services to export their D-Bus objects
while (!service2ThreadReady || !service1ThreadReady)
std::this_thread::sleep_for(1ms);
auto clientConnection = sdbus::createSystemBusConnection();
std::mutex clientThreadExitMutex;
std::condition_variable clientThreadExitCond;
bool clientThreadExit{};
std::thread clientThread([&, &con = *clientConnection]()
{
std::atomic<bool> stopClients{false};
std::thread concatenatorThread([&]()
{
ConcatenatorProxy concatenator(con, SERVICE_1_BUS_NAME, CONCATENATOR_OBJECT_PATH);
uint32_t localCounter{};
// Issue async concatenate calls densely one after another
while (!stopClients)
{
std::map<std::string, sdbus::Variant> param;
param["key1"] = "sdbus-c++-stress-tests";
param["key2"] = ++localCounter;
concatenator.concatenate(param);
if ((localCounter % 10) == 0)
{
// Make sure the system is catching up with our async requests,
// otherwise sleep a bit to slow down flooding the server.
assert(localCounter >= concatenator.repliesReceived_);
while ((localCounter - concatenator.repliesReceived_) > 40 && !stopClients)
std::this_thread::sleep_for(1ms);
// Update statistics
concatenationCallsMade = localCounter;
concatenationRepliesReceived = (uint32_t)concatenator.repliesReceived_;
concatenationSignalsReceived = (uint32_t)concatenator.signalsReceived_;
}
}
});
std::thread thermometerThread([&]()
{
// Here we continuously remotely call getCurrentTemperature(). We have one proxy object,
// first we use it's factory interface to create another proxy object, call getCurrentTemperature()
// on that one, and then destroy that proxy object. All that continously in a loop.
// This tests dynamic creation and destruction of remote D-Bus objects and local object proxies.
FahrenheitThermometerProxy thermometer(con, SERVICE_1_BUS_NAME, FAHRENHEIT_THERMOMETER_OBJECT_PATH);
uint32_t localCounter{};
uint32_t previousTemperature{};
while (!stopClients)
{
localCounter++;
auto newObjectPath = thermometer.createDelegateObject();
FahrenheitThermometerProxy proxy{con, SERVICE_1_BUS_NAME, newObjectPath};
auto temperature = proxy.getCurrentTemperature();
assert(temperature >= previousTemperature); // The temperature shall rise continually
previousTemperature = temperature;
//std::this_thread::sleep_for(1ms);
if ((localCounter % 10) == 0)
thermometerCallsMade = localCounter;
thermometer.destroyDelegateObject(newObjectPath);
}
});
// We could run the loop in a sync way, but we want it to run also when proxies are destroyed for better
// coverage of multi-threaded scenarios, so we run it async and use condition variable for exit notification
//con.enterProcessingLoop();
con.enterProcessingLoopAsync();
std::unique_lock<std::mutex> lock(clientThreadExitMutex);
clientThreadExitCond.wait(lock, [&]{return clientThreadExit;});
stopClients = true;
thermometerThread.join();
concatenatorThread.join();
});
std::this_thread::sleep_for(std::chrono::milliseconds(loopDuration));
//clientConnection->leaveProcessingLoop();
std::unique_lock<std::mutex> lock(clientThreadExitMutex);
clientThreadExit = true;
lock.unlock();
clientThreadExitCond.notify_one();
clientThread.join();
service1Connection->leaveProcessingLoop();
service1Thread.join();
service2Connection->leaveProcessingLoop();
service2Thread.join();
}
exitLogger = true;
loggerThread.join();
return 0;
}

View File

@ -0,0 +1,150 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file Connection_test.cpp
* @author Ardazishvili Roman (ardazishvili.roman@yandex.ru)
*
* Created on: Feb 4, 2019
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Connection.h"
#include "unittests/mocks/SdBusMock.h"
#include <gtest/gtest.h>
using ::testing::_;
using ::testing::DoAll;
using ::testing::SetArgPointee;
using ::testing::Return;
using ::testing::NiceMock;
using BusType = sdbus::internal::Connection::BusType;
class ConnectionCreationTest : public ::testing::Test
{
protected:
ConnectionCreationTest() = default;
std::unique_ptr<NiceMock<SdBusMock>> mock_ { std::make_unique<NiceMock<SdBusMock>>() };
sd_bus* STUB_ { reinterpret_cast<sd_bus*>(1) };
};
using ASystemBusConnection = ConnectionCreationTest;
using ASessionBusConnection = ConnectionCreationTest;
TEST_F(ASystemBusConnection, OpensAndFlushesBusWhenCreated)
{
EXPECT_CALL(*mock_, sd_bus_open_system(_)).WillOnce(DoAll(SetArgPointee<0>(STUB_), Return(1)));
EXPECT_CALL(*mock_, sd_bus_flush(_)).Times(1);
sdbus::internal::Connection(BusType::eSystem, std::move(mock_));
}
TEST_F(ASessionBusConnection, OpensAndFlushesBusWhenCreated)
{
EXPECT_CALL(*mock_, sd_bus_open_user(_)).WillOnce(DoAll(SetArgPointee<0>(STUB_), Return(1)));
EXPECT_CALL(*mock_, sd_bus_flush(_)).Times(1);
sdbus::internal::Connection(BusType::eSession, std::move(mock_));
}
TEST_F(ASystemBusConnection, ClosesAndUnrefsBusWhenDestructed)
{
ON_CALL(*mock_, sd_bus_open_user(_)).WillByDefault(DoAll(SetArgPointee<0>(STUB_), Return(1)));
EXPECT_CALL(*mock_, sd_bus_flush_close_unref(_)).Times(1);
sdbus::internal::Connection(BusType::eSession, std::move(mock_));
}
TEST_F(ASessionBusConnection, ClosesAndUnrefsBusWhenDestructed)
{
ON_CALL(*mock_, sd_bus_open_user(_)).WillByDefault(DoAll(SetArgPointee<0>(STUB_), Return(1)));
EXPECT_CALL(*mock_, sd_bus_flush_close_unref(_)).Times(1);
sdbus::internal::Connection(BusType::eSession, std::move(mock_));
}
TEST_F(ASystemBusConnection, ThrowsErrorWhenOpeningTheBusFailsDuringConstruction)
{
ON_CALL(*mock_, sd_bus_open_system(_)).WillByDefault(DoAll(SetArgPointee<0>(STUB_), Return(-1)));
ASSERT_THROW(sdbus::internal::Connection(BusType::eSystem, std::move(mock_)), sdbus::Error);
}
TEST_F(ASessionBusConnection, ThrowsErrorWhenOpeningTheBusFailsDuringConstruction)
{
ON_CALL(*mock_, sd_bus_open_user(_)).WillByDefault(DoAll(SetArgPointee<0>(STUB_), Return(-1)));
ASSERT_THROW(sdbus::internal::Connection(BusType::eSession, std::move(mock_)), sdbus::Error);
}
TEST_F(ASystemBusConnection, ThrowsErrorWhenFlushingTheBusFailsDuringConstruction)
{
ON_CALL(*mock_, sd_bus_open_system(_)).WillByDefault(DoAll(SetArgPointee<0>(STUB_), Return(1)));
ON_CALL(*mock_, sd_bus_flush(_)).WillByDefault(Return(-1));
ASSERT_THROW(sdbus::internal::Connection(BusType::eSystem, std::move(mock_)), sdbus::Error);
}
TEST_F(ASessionBusConnection, ThrowsErrorWhenFlushingTheBusFailsDuringConstruction)
{
ON_CALL(*mock_, sd_bus_open_user(_)).WillByDefault(DoAll(SetArgPointee<0>(STUB_), Return(1)));
ON_CALL(*mock_, sd_bus_flush(_)).WillByDefault(Return(-1));
ASSERT_THROW(sdbus::internal::Connection(BusType::eSession, std::move(mock_)), sdbus::Error);
}
class ConnectionRequestTest : public ::testing::TestWithParam<BusType>
{
protected:
ConnectionRequestTest() = default;
void SetUp() override
{
switch (GetParam())
{
case BusType::eSystem:
EXPECT_CALL(*mock_, sd_bus_open_system(_)).WillOnce(DoAll(SetArgPointee<0>(STUB_), Return(1)));
break;
case BusType::eSession:
EXPECT_CALL(*mock_, sd_bus_open_user(_)).WillOnce(DoAll(SetArgPointee<0>(STUB_), Return(1)));
break;
default:
break;
}
ON_CALL(*mock_, sd_bus_flush(_)).WillByDefault(Return(1));
ON_CALL(*mock_, sd_bus_flush_close_unref(_)).WillByDefault(Return(STUB_));
}
std::unique_ptr<NiceMock<SdBusMock>> mock_ { std::make_unique<NiceMock<SdBusMock>>() };
sd_bus* STUB_ { reinterpret_cast<sd_bus*>(1) };
};
using AConnectionNameRequest = ConnectionRequestTest;
TEST_P(AConnectionNameRequest, DoesNotThrowOnSuccess)
{
EXPECT_CALL(*mock_, sd_bus_request_name(_, _, _)).WillOnce(Return(1));
sdbus::internal::Connection(GetParam(), std::move(mock_)).requestName("");
}
TEST_P(AConnectionNameRequest, ThrowsOnFail)
{
EXPECT_CALL(*mock_, sd_bus_request_name(_, _, _)).WillOnce(Return(-1));
sdbus::internal::Connection conn_(GetParam(), std::move(mock_));
ASSERT_THROW(conn_.requestName(""), sdbus::Error);
}
// INSTANTIATE_TEST_SUITE_P is defined in googletest master, but not in googletest v1.8.1 that we are using now
INSTANTIATE_TEST_CASE_P(Request, AConnectionNameRequest, ::testing::Values(BusType::eSystem, BusType::eSession));
//INSTANTIATE_TEST_SUITE_P(Request, AConnectionNameRequest, ::testing::Values(BusType::eSystem, BusType::eSession))

View File

@ -110,13 +110,6 @@ TEST(AMessage, IsNotEmptyWhenContainsAValue)
ASSERT_FALSE(msg.isEmpty());
}
TEST(AMessage, ReturnsItsTypeWhenAsked)
{
sdbus::Message msg{sdbus::createPlainMessage()};
ASSERT_THAT(msg.getType(), Eq(sdbus::Message::Type::ePlainMessage));
}
TEST(AMessage, CanCarryASimpleInteger)
{
sdbus::Message msg{sdbus::createPlainMessage()};

View File

@ -28,6 +28,7 @@
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <cstdint>
#include <type_traits>
using ::testing::Eq;
@ -127,3 +128,42 @@ TYPED_TEST(Type2DBusTypeSignatureConversion, ConvertsTypeToProperDBusSignature)
{
ASSERT_THAT(sdbus::signature_of<TypeParam>::str(), Eq(this->dbusTypeSignature_));
}
TEST(FreeFunctionTypeTraits, DetectsTraitsOfTrivialSignatureFunction)
{
void f();
using Fnc = decltype(f);
static_assert(!sdbus::is_async_method_v<Fnc>, "Free function incorrectly detected as async method");
static_assert(std::is_same<sdbus::function_arguments_t<Fnc>, std::tuple<>>::value, "Incorrectly detected free function parameters");
static_assert(std::is_same<sdbus::tuple_of_function_input_arg_types_t<Fnc>, std::tuple<>>::value, "Incorrectly detected tuple of free function parameters");
static_assert(std::is_same<sdbus::tuple_of_function_output_arg_types_t<Fnc>, void>::value, "Incorrectly detected tuple of free function return types");
static_assert(sdbus::function_argument_count_v<Fnc> == 0, "Incorrectly detected free function parameter count");
static_assert(std::is_void<sdbus::function_result_t<Fnc>>::value, "Incorrectly detected free function return type");
}
TEST(FreeFunctionTypeTraits, DetectsTraitsOfNontrivialSignatureFunction)
{
std::tuple<char, int> f(double&, const char*, int);
using Fnc = decltype(f);
static_assert(!sdbus::is_async_method_v<Fnc>, "Free function incorrectly detected as async method");
static_assert(std::is_same<sdbus::function_arguments_t<Fnc>, std::tuple<double&, const char*, int>>::value, "Incorrectly detected free function parameters");
static_assert(std::is_same<sdbus::tuple_of_function_input_arg_types_t<Fnc>, std::tuple<double, const char*, int>>::value, "Incorrectly detected tuple of free function parameters");
static_assert(std::is_same<sdbus::tuple_of_function_output_arg_types_t<Fnc>, std::tuple<char, int>>::value, "Incorrectly detected tuple of free function return types");
static_assert(sdbus::function_argument_count_v<Fnc> == 3, "Incorrectly detected free function parameter count");
static_assert(std::is_same<sdbus::function_result_t<Fnc>, std::tuple<char, int>>::value, "Incorrectly detected free function return type");
}
TEST(FreeFunctionTypeTraits, DetectsTraitsOfAsyncFunction)
{
void f(sdbus::Result<char, int>, double&, const char*, int);
using Fnc = decltype(f);
static_assert(sdbus::is_async_method_v<Fnc>, "Free async function incorrectly detected as sync method");
static_assert(std::is_same<sdbus::function_arguments_t<Fnc>, std::tuple<double&, const char*, int>>::value, "Incorrectly detected free function parameters");
static_assert(std::is_same<sdbus::tuple_of_function_input_arg_types_t<Fnc>, std::tuple<double, const char*, int>>::value, "Incorrectly detected tuple of free function parameters");
static_assert(std::is_same<sdbus::tuple_of_function_output_arg_types_t<Fnc>, std::tuple<char, int>>::value, "Incorrectly detected tuple of free function return types");
static_assert(sdbus::function_argument_count_v<Fnc> == 3, "Incorrectly detected free function parameter count");
static_assert(std::is_same<sdbus::function_result_t<Fnc>, std::tuple<char, int>>::value, "Incorrectly detected free function return type");
}

View File

@ -203,3 +203,40 @@ TEST(CopiesOfVariant, SerializeToAndDeserializeFromMessageSuccessfully)
ASSERT_THAT(receivedVariant2.get<decltype(value)>(), Eq(value));
ASSERT_THAT(receivedVariant3.get<decltype(value)>(), Eq(value));
}
TEST(AStruct, CreatesStructFromTuple)
{
std::tuple<int32_t, std::string> value{1234, "abcd"};
sdbus::Struct<int32_t, std::string> 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(AnObjectPath, CanBeConstructedFromCString)
{
const char* aPath = "/some/path";
ASSERT_THAT(sdbus::ObjectPath{aPath}, Eq(aPath));
}
TEST(AnObjectPath, CanBeConstructedFromStdString)
{
std::string aPath{"/some/path"};
ASSERT_THAT(sdbus::ObjectPath{aPath}, Eq(aPath));
}
TEST(ASignature, CanBeConstructedFromCString)
{
const char* aSignature = "us";
ASSERT_THAT(sdbus::Signature{aSignature}, Eq(aSignature));
}
TEST(ASignature, CanBeConstructedFromStdString)
{
std::string aSignature{"us"};
ASSERT_THAT(sdbus::Signature{aSignature}, Eq(aSignature));
}

View File

@ -0,0 +1,64 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file SdBusMock.h
* @author Ardazishvili Roman (ardazishvili.roman@yandex.ru)
*
* Created on: Mar 12, 2019
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_SDBUS_MOCK_H
#define SDBUS_CXX_SDBUS_MOCK_H
#include "ISdBus.h"
#include <gmock/gmock.h>
class SdBusMock : public sdbus::internal::ISdBus
{
public:
MOCK_METHOD1(sd_bus_message_ref, sd_bus_message*(sd_bus_message *m));
MOCK_METHOD1(sd_bus_message_unref, sd_bus_message*(sd_bus_message *m));
MOCK_METHOD3(sd_bus_send, int(sd_bus *bus, sd_bus_message *m, uint64_t *cookie));
MOCK_METHOD5(sd_bus_call, int(sd_bus *bus, sd_bus_message *m, uint64_t usec, sd_bus_error *ret_error, sd_bus_message **reply));
MOCK_METHOD6(sd_bus_call_async, int(sd_bus *bus, sd_bus_slot **slot, sd_bus_message *m, sd_bus_message_handler_t callback, void *userdata, uint64_t usec));
MOCK_METHOD6(sd_bus_message_new_method_call, int(sd_bus *bus, sd_bus_message **m, const char *destination, const char *path, const char *interface, const char *member));
MOCK_METHOD5(sd_bus_message_new_signal, int(sd_bus *bus, sd_bus_message **m, const char *path, const char *interface, const char *member));
MOCK_METHOD2(sd_bus_message_new_method_return, int(sd_bus_message *call, sd_bus_message **m));
MOCK_METHOD3(sd_bus_message_new_method_error, int(sd_bus_message *call, sd_bus_message **m, const sd_bus_error *e));
MOCK_METHOD1(sd_bus_open_user, int(sd_bus **ret));
MOCK_METHOD1(sd_bus_open_system, int(sd_bus **ret));
MOCK_METHOD3(sd_bus_request_name, int(sd_bus *bus, const char *name, uint64_t flags));
MOCK_METHOD2(sd_bus_release_name, int(sd_bus *bus, const char *name));
MOCK_METHOD6(sd_bus_add_object_vtable, int(sd_bus *bus, sd_bus_slot **slot, const char *path, const char *interface, const sd_bus_vtable *vtable, void *userdata));
MOCK_METHOD5(sd_bus_add_match, int(sd_bus *bus, sd_bus_slot **slot, const char *match, sd_bus_message_handler_t callback, void *userdata));
MOCK_METHOD1(sd_bus_slot_unref, sd_bus_slot*(sd_bus_slot *slot));
MOCK_METHOD2(sd_bus_process, int(sd_bus *bus, sd_bus_message **r));
MOCK_METHOD2(sd_bus_get_poll_data, int(sd_bus *bus, PollData* data));
MOCK_METHOD1(sd_bus_flush, int(sd_bus *bus));
MOCK_METHOD1(sd_bus_flush_close_unref, sd_bus *(sd_bus *bus));
};
#endif //SDBUS_CXX_SDBUS_MOCK_H

View File

@ -1,7 +1,7 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file libsdbus-c++_unittests.cpp
* @file sdbus-c++-unit-tests.cpp
*
* Created on: Nov 27, 2016
* Project: sdbus-c++