Compare commits

...

19 Commits

Author SHA1 Message Date
d2d1a2ddbc WIP 2019-02-04 22:31:09 +01:00
9292f293ec WIP 2019-01-31 21:42:37 +01:00
dd0a975243 WIP 2019-01-30 07:57:07 +01:00
e59afb827b Fix introspection xml 2019-01-27 15:13:15 +01:00
0cf27f7262 Introduce support for client-side asynchronous method invocations 2019-01-27 14:55:20 +01:00
97c47cb6df Put perftests in proper place 2019-01-26 23:16:37 +01:00
3839c3ffd7 Introduce simple method call and signal-based manual performance tests 2019-01-25 13:30:27 +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
46 changed files with 2203 additions and 271 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>

View File

@ -4,7 +4,7 @@
cmake_minimum_required(VERSION 3.8)
project(sdbus-c++ VERSION 0.3.2 LANGUAGES C CXX)
project(sdbus-c++ VERSION 0.4.1 LANGUAGES C CXX)
include(GNUInstallDirs) # Installation directories for `install` command and pkgconfig file
@ -13,7 +13,9 @@ include(GNUInstallDirs) # Installation directories for `install` command and pkg
#-------------------------------
find_package(PkgConfig REQUIRED)
pkg_check_modules(SYSTEMD REQUIRED libsystemd>=236)
#pkg_check_modules(SYSTEMD REQUIRED libsystemd>=236)
set(CMAKE_CXX_FLAGS "-O0 -g")
#-------------------------------
# SOURCE FILES CONFIGURATION
@ -32,6 +34,7 @@ set(SDBUSCPP_CPP_SRCS
${SDBUSCPP_SOURCE_DIR}/Object.cpp
${SDBUSCPP_SOURCE_DIR}/ObjectProxy.cpp
${SDBUSCPP_SOURCE_DIR}/Types.cpp
${SDBUSCPP_SOURCE_DIR}/Flags.cpp
${SDBUSCPP_SOURCE_DIR}/VTableUtils.c)
set(SDBUSCPP_HDR_SRCS
@ -56,7 +59,8 @@ set(SDBUSCPP_PUBLIC_HDRS
${SDBUSCPP_INCLUDE_DIR}/MethodResult.h
${SDBUSCPP_INCLUDE_DIR}/sdbus-c++.h
${SDBUSCPP_INCLUDE_DIR}/Types.h
${SDBUSCPP_INCLUDE_DIR}/TypeTraits.h)
${SDBUSCPP_INCLUDE_DIR}/TypeTraits.h
${SDBUSCPP_INCLUDE_DIR}/Flags.h)
set(SDBUSCPP_SRCS ${SDBUSCPP_CPP_SRCS} ${SDBUSCPP_HDR_SRCS} ${SDBUSCPP_PUBLIC_HDRS})
@ -65,7 +69,6 @@ set(SDBUSCPP_SRCS ${SDBUSCPP_CPP_SRCS} ${SDBUSCPP_HDR_SRCS} ${SDBUSCPP_PUBLIC_HD
#-------------------------------
set(CMAKE_CXX_STANDARD 17)
add_compile_options(-W -Wextra -Wall -Werror -pedantic)
include_directories("${CMAKE_SOURCE_DIR}/include")
include_directories("${CMAKE_SOURCE_DIR}/src")
@ -79,7 +82,7 @@ set(SDBUSCPP_VERSION "${PROJECT_VERSION}")
# We are building in two steps: first objects, then link them into a library,
# and that's because we need object files since unit tests link against them.
add_library(sdbuscppobjects OBJECT ${SDBUSCPP_SRCS})
target_include_directories(sdbuscppobjects PUBLIC ${SYSTEMD_INCLUDE_DIRS})
#target_include_directories(sdbuscppobjects PUBLIC ${SYSTEMD_INCLUDE_DIRS})
target_compile_definitions(sdbuscppobjects PRIVATE BUILDLIB=1)
set_target_properties(sdbuscppobjects PROPERTIES POSITION_INDEPENDENT_CODE ON)
@ -90,7 +93,7 @@ set_target_properties(sdbus-c++
VERSION "${SDBUSCPP_VERSION}"
SOVERSION "${SDBUSCPP_VERSION_MAJOR}"
OUTPUT_NAME "sdbus-c++")
target_link_libraries(sdbus-c++ ${SYSTEMD_LIBRARIES})
target_link_libraries(sdbus-c++ -L/home/aeywalee/data/repos/systemd/.libs/ -lsystemd)
#----------------------------------
# INSTALLATION
@ -123,6 +126,7 @@ if(BUILD_CODE_GEN)
add_subdirectory("${CMAKE_SOURCE_DIR}/stub-generator")
endif()
#----------------------------------
# DOCUMENTATION
#----------------------------------

View File

@ -29,3 +29,15 @@ v0.3.1
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

62
Notes.txt Normal file
View File

@ -0,0 +1,62 @@
Aktualne spravanie:
* Proxy dostane connection ako referenciu zvonka:
* Ak na tej connection bezi processing loop, tak
* bud mozeme robit RPC volanie, ked je to z callbacku nasho serveroveho D-Bus API (overene od Lennarta)
- vsetko je serializovane - volania a prijimania, i viacere proxy objekty medzi sebou,
* alebo nemozeme (t.j. z ineho threadu) lebo connection nie je thread-safe.
* Ak nebezi (nie idealny stav z hladiska D-Bus daemona), tak
* mozeme robit RPC volania iba ak mame zarucenu synchronizaciu medzi viacerymi proxy objektami nad touto istou connection,
* neprijimame signaly samozrejme, lebo nebezi loop.
Vyhody: Skalovatelnost - proxy zdielaju jednu connection a jeden jej loop thread.
Nevyhody: Nie je to dotiahnute do konca, v podsate to nefunguje hlavne ak bezi loop nad connection.
* Proxy dostane vlastnu connection zvonka alebo si ju vytvori vlastnu:
* Tu connection pouziva na volania, nebezi na nej loop.
Vyhody: Je nezavisly threadovo i message-komunikativne od inych proxy
Nevyhody: Skalovatelnost
* Plus, ak pocuva na signaly, tak si vytvori kopiu connection, na nej thread s loop.
Nevyhody: Skalovatelnost - V tomto pripade mame teda 2x connection per proxy a 1 thread per proxy
Navrhovane spravanie:
Pouzivatel si zvoli
* Skalovatelnost a nenarocnost na resourcy - pouzivatel chce mat 1 connection a 1 processing loop thread nad nou.
Pripadne 2 ak chce time-slicingovo oddelit server cast od proxy casti (ze nie su incoming server / outgoing proxy requesty serializovane medzi sebou).
* Alebo sa nestara a kazdy proxy bude mat vlastnu connection a vlastny event loop thread nad nou.
Oba pristupy z hladiska implementacie znamenaju, ze proxy ma vzdy connection handlovanu niekym.
Teraz ako by fungovalo RPC volanie z proxy:
* Proxy musi nejak poslat message do connection:
* Ak je call thread rovnaky ako connection event loop thread (vtedy ak sme v nejakom server method callbacku a pouzivame tuto connection,
alebo ak sme v handleri na signal), tak v podstate mozeme vykonat to volania priamo, rovnako ako to je teraz.
* Ak je call thread iny (robime volanie cez proxy odkialkolvek), tak musime MethodCall msg poslat cez queue do connection threadu.
(Tu mozeme vyuzit queue ktory tam uz mame na async replies - davat tam std::pair<Message, MsgType>)
Teraz:
-> Mame sync call? Tak spolu s msg vsak musime poslat z proxy aj promise<MethodReply>, a v proxy si z promise ziskat future
a na nom cakat na odpoved z Connection.
Zatial Connection posle msg cez msg.send(), hned dostane reply, a reply setne do promise.
Cakajuci proxy sa zobudi a vrati reply.
TODO: Jednoducho to urobit tak ako Object::sendReplyAsynchronously - ze send() deleguje hned to connection::send, a ta bude mat if.
-> Mame async call? Tak otazka je ci bude mat mapu slotov na std::function ObjectProxy alebo Connection. Vyzera ze Connection, kvoli timingu. Nie, tak nakoniec ObjectProxy, juchuu, lebo nekorelujeme cez slot.
Ked connection, tak ta zavola AsyncMethodCall::send(), ktory zoberie callback, user data, timeout a vrati slot.
Connection si ulozi do mapy slot a std::function, a je to.
Takze finalna impelmentacia ObjectProxy::send(MethodCall):
connection_->send(msg);
Takze finalna impelmentacia ObjectProxy::send(AsyncMethodCall):
connection_->send(msg); -> a ta vnutri da userdata ako new std::function
* Proxy dostane referenciu na connection zvonka.
* Na nej si zaregistruje signaly a metody
TODO: Dokumentacia
* Ze proxy komplet zmenil pohlad na koneksny - ze koneksny vzdy maju svoj thread, pokial ich vlastni proxy.
TODO: Prerobit i Object na taky style ze berie bud Connection& alebo Connection&& (a vtedy si pusti vlastny thread, ak uz nebezi);

View File

@ -29,21 +29,19 @@ method calls, signals and properties. There is room for additions and improvemen
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.
@ -357,7 +355,7 @@ int main(int argc, char *argv[])
// Let's subscribe for the 'concatenated' signals
const char* interfaceName = "org.sdbuscpp.Concatenator";
concatenatorProxy->uponSignal("concatenated").onInterface(interfaceName).call([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};
@ -636,6 +634,7 @@ Now let's use this proxy to make remote calls and listen to signals in a real ap
```cpp
#include "ConcatenatorProxy.h"
#include <unistd.h>
int main(int argc, char *argv[])
{

View File

@ -28,6 +28,7 @@
#include <sdbus-c++/Message.h>
#include <sdbus-c++/TypeTraits.h>
#include <sdbus-c++/Flags.h>
#include <string>
#include <type_traits>
@ -36,6 +37,7 @@ namespace sdbus {
class IObject;
class IObjectProxy;
class Variant;
class Error;
}
namespace sdbus {
@ -44,16 +46,29 @@ 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>
std::enable_if_t<!is_async_method_v<_Function>> implementedAs(_Function&& callback);
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>> implementedAs(_Function&& callback);
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 syncCallback_;
async_method_callback asyncCallback_;
Flags flags_;
int exceptions_{}; // Number of active exceptions when SignalRegistrator is constructed
};
class SignalRegistrator
@ -63,14 +78,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
};
@ -81,9 +99,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_;
@ -92,9 +114,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:
@ -124,6 +167,8 @@ namespace sdbus {
template <typename... _Args> MethodInvoker& withArguments(_Args&&... args);
template <typename... _Args> void storeResultsTo(_Args&... args);
void dontExpectReply();
private:
IObjectProxy& objectProxy_;
const std::string& methodName_;
@ -132,6 +177,21 @@ namespace sdbus {
bool methodCalled_{};
};
class AsyncMethodInvoker
{
public:
AsyncMethodInvoker(IObjectProxy& objectProxy, const std::string& methodName);
AsyncMethodInvoker& onInterface(const std::string& interfaceName);
template <typename... _Args> AsyncMethodInvoker& withArguments(_Args&&... args);
//template <typename... _OutputArgs> void uponReplyInvoke(std::function<void(const Error*, _OutputArgs...)> callback);
template <typename _Function> void uponReplyInvoke(_Function&& callback);
private:
IObjectProxy& objectProxy_;
const std::string& methodName_;
AsyncMethodCall method_;
};
class SignalSubscriber
{
public:

View File

@ -39,12 +39,41 @@
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);
// 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.
if (syncCallback_)
object_.registerMethod(interfaceName_, methodName_, inputSignature_, outputSignature_, std::move(syncCallback_));
else if(asyncCallback_)
object_.registerMethod(interfaceName_, methodName_, inputSignature_, outputSignature_, std::move(asyncCallback_));
else
SDBUS_THROW_ERROR("Method handler not specified when registering a DBus method", EINVAL);
}
*/
inline MethodRegistrator& MethodRegistrator::onInterface(const std::string& interfaceName)
{
interfaceName_ = interfaceName;
@ -53,15 +82,11 @@ namespace sdbus {
}
template <typename _Function>
inline std::enable_if_t<!is_async_method_v<_Function>> 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)](MethodCall& msg, MethodReply& reply)
inputSignature_ = signature_of_function_input_arguments<_Function>::str();
outputSignature_ = signature_of_function_output_arguments<_Function>::str();
syncCallback_ = [callback = std::forward<_Function>(callback)](MethodCall& msg, MethodReply& reply)
{
// Create a tuple of callback input arguments' types, which will be used
// as a storage for the argument values deserialized from the message.
@ -78,33 +103,54 @@ namespace sdbus {
// The return value is stored to the reply message.
// In case of void functions, ret is an empty tuple and thus nothing is stored.
reply << ret;
});
};
return *this;
}
template <typename _Function>
inline std::enable_if_t<is_async_method_v<_Function>> 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() //signature_of<last_function_argument_t<_Function>>::str() // because last argument contains output types
, [callback = std::forward<_Function>(callback)](MethodCall& msg, MethodResult result)
inputSignature_ = signature_of_function_input_arguments<_Function>::str();
outputSignature_ = signature_of_function_output_arguments<_Function>::str();
asyncCallback_ = [callback = std::forward<_Function>(callback)](MethodCall& msg, MethodResult result)
{
// Create a tuple of callback input arguments' types, which will be used
// as a storage for the argument values deserialized from the message.
tuple_of_function_input_arg_types_t<_Function> inputArgs;
// Deserialize input arguments from the message into the tuple,
// plus store the result object as a last item of the tuple.
// Deserialize input arguments from the message into the tuple.
msg >> inputArgs;
// Invoke callback with input arguments from the tuple.
sdbus::apply(callback, std::move(result), inputArgs); // TODO: Use std::apply when switching to full C++17 support
});
};
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;
}
// Moved into the library to isolate from C++17 dependency
/*
inline SignalRegistrator::SignalRegistrator(IObject& object, std::string signalName)
@ -144,9 +190,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;
}
@ -234,6 +289,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
/*
@ -342,6 +478,56 @@ namespace sdbus {
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);
method_.dontExpectReply();
}
inline AsyncMethodInvoker::AsyncMethodInvoker(IObjectProxy& objectProxy, const std::string& methodName)
: objectProxy_(objectProxy)
, methodName_(methodName)
{
}
inline AsyncMethodInvoker& AsyncMethodInvoker::onInterface(const std::string& interfaceName)
{
method_ = objectProxy_.createAsyncMethodCall(interfaceName, methodName_);
return *this;
}
template <typename... _Args>
inline AsyncMethodInvoker& AsyncMethodInvoker::withArguments(_Args&&... args)
{
SDBUS_THROW_ERROR_IF(!method_.isValid(), "DBus interface not specified when calling a DBus method", EINVAL);
detail::serialize_pack(method_, std::forward<_Args>(args)...);
return *this;
}
template <typename _Function>
void AsyncMethodInvoker::uponReplyInvoke(_Function&& callback)
{
SDBUS_THROW_ERROR_IF(!method_.isValid(), "DBus interface not specified when calling a DBus method", EINVAL);
objectProxy_.callMethod(method_, [callback = std::forward<_Function>(callback)](MethodReply& reply, const Error* error)
{
// Create a tuple of callback input arguments' types, which will be used
// as a storage for the argument values deserialized from the message.
tuple_of_function_input_arg_types_t<_Function> args;
// Deserialize input arguments from the message into the tuple.
reply >> args;
// Invoke callback with input arguments from the tuple.
sdbus::apply(callback, error, args); // TODO: Use std::apply when switching to full C++17 support
});
}
inline SignalSubscriber::SignalSubscriber(IObjectProxy& objectProxy, const std::string& signalName)
: objectProxy_(objectProxy)

View File

@ -57,6 +57,11 @@ namespace sdbus {
return message_;
}
bool isValid() const
{
return !getName().empty();
}
private:
std::string name_;
std::string message_;

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

@ -28,6 +28,7 @@
#include <sdbus-c++/ConvenienceClasses.h>
#include <sdbus-c++/TypeTraits.h>
#include <sdbus-c++/Flags.h>
#include <functional>
#include <string>
#include <memory>
@ -61,6 +62,7 @@ namespace sdbus {
* @param[in] inputSignature D-Bus signature of method input parameters
* @param[in] outputSignature D-Bus signature of method output parameters
* @param[in] methodCallback Callback that implements the body of the method
* @param[in] noReply If true, the method isn't expected to send reply
*
* @throws sdbus::Error in case of failure
*/
@ -68,7 +70,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 method that the object will provide on D-Bus
@ -78,6 +81,7 @@ namespace sdbus {
* @param[in] inputSignature D-Bus signature of method input parameters
* @param[in] outputSignature D-Bus signature of method output parameters
* @param[in] asyncMethodCallback Callback that implements the body of the method
* @param[in] noReply If true, the method isn't expected to send reply
*
* This overload register a method callback that will have a freedom to execute
* its body in asynchronous contexts, and send the results from those contexts.
@ -90,7 +94,8 @@ namespace sdbus {
, const std::string& methodName
, const std::string& inputSignature
, const std::string& outputSignature
, async_method_callback asyncMethodCallback ) = 0;
, async_method_callback asyncMethodCallback
, Flags flags = {} ) = 0;
/*!
* @brief Registers signal that the object will emit on D-Bus
@ -103,7 +108,8 @@ namespace sdbus {
*/
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
@ -118,7 +124,8 @@ namespace sdbus {
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
@ -135,7 +142,18 @@ 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 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 the registration and exports object API on D-Bus
@ -231,6 +249,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
*
@ -270,6 +305,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);

View File

@ -34,6 +34,7 @@
// Forward declarations
namespace sdbus {
class MethodCall;
class AsyncMethodCall;
class MethodReply;
class IConnection;
}
@ -58,7 +59,7 @@ namespace sdbus {
*
* @param[in] interfaceName Name of an interface that the method is defined under
* @param[in] methodName Name of the method
* @return A method call message message
* @return A method call message
*
* Serialize method arguments into the returned message and invoke the method by passing
* the message with serialized arguments to the @c callMethod function.
@ -68,16 +69,55 @@ namespace sdbus {
*/
virtual MethodCall createMethodCall(const std::string& interfaceName, const std::string& methodName) = 0;
/*!
* @brief Creates an asynchronous method call message
*
* @param[in] interfaceName Name of an interface that the method is defined under
* @param[in] methodName Name of the method
* @return A method call message
*
* Serialize method arguments into the returned message and invoke the method by passing
* the message with serialized arguments to the @c callMethod function.
* Alternatively, use higher-level API @c callMethodAsync(const std::string& methodName) defined below.
*
* @throws sdbus::Error in case of failure
*/
virtual AsyncMethodCall createAsyncMethodCall(const std::string& interfaceName, const std::string& methodName) = 0;
/*!
* @brief Calls method on the proxied D-Bus object
*
* @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 sdbus::MethodCall& message) = 0;
virtual MethodReply callMethod(const MethodCall& message) = 0;
/*!
* @brief Calls method on the proxied D-Bus object asynchronously
*
* @param[in] message Message representing an async method call
* @param[in] asyncReplyHandler 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
@ -123,6 +163,30 @@ namespace sdbus {
*/
MethodInvoker callMethod(const std::string& methodName);
/*!
* @brief Calls method on the proxied D-Bus object asynchronously
*
* @param[in] methodName Name of the method
* @return A helper object for convenient asynchronous invocation of the method
*
* This is a high-level, convenience way of calling D-Bus methods that abstracts
* from the D-Bus message concept. Method arguments/return value are automatically (de)serialized
* in a message and D-Bus signatures automatically deduced from the provided native arguments
* and return values.
*
* Example of use:
* @code
* int a = ..., b = ...;
* object_.callMethodAsync("multiply").onInterface(INTERFACE_NAME).withArguments(a, b).uponReplyInvoke([](int result)
* {
* std::cout << "Got result of multiplying " << a << " and " << b << ": " << result << std::endl;
* });
* @endcode
*
* @throws sdbus::Error in case of failure
*/
AsyncMethodInvoker callMethodAsync(const std::string& methodName);
/*!
* @brief Registers signal handler for a given signal of the proxied D-Bus object
*
@ -189,6 +253,11 @@ namespace sdbus {
return MethodInvoker(*this, methodName);
}
inline AsyncMethodInvoker IObjectProxy::callMethodAsync(const std::string& methodName)
{
return AsyncMethodInvoker(*this, methodName);
}
inline SignalSubscriber IObjectProxy::uponSignal(const std::string& signalName)
{
return SignalSubscriber(*this, signalName);

View File

@ -120,7 +120,6 @@ namespace sdbus {
{
getObject().finishRegistration();
}
};
}

View File

@ -70,6 +70,16 @@ namespace sdbus {
***********************************************/
class Message
{
public:
enum class Type
{ METHOD_CALL
, ASYNC_METHOD_CALL
, METHOD_REPLY
/*, ASYNC_METHOD_REPLY? */
, SIGNAL
, PLAIN_MESSAGE
};
public:
Message() = default;
Message(void *msg) noexcept;
@ -155,6 +165,20 @@ namespace sdbus {
MethodReply send() const;
MethodReply createReply() const;
MethodReply createErrorReply(const sdbus::Error& error) const;
void dontExpectReply();
bool doesntExpectReply() const;
private:
MethodReply sendWithReply() const;
MethodReply sendWithNoReply() const;
};
class AsyncMethodCall : public Message
{
public:
using Message::Message;
AsyncMethodCall(MethodCall&& call) noexcept;
void send(void* callback, void* userData) const;
};
class MethodReply : public Message

View File

@ -46,12 +46,14 @@ namespace sdbus {
class Signal;
class MethodResult;
template <typename... _Results> class Result;
class Error;
}
namespace sdbus {
using method_callback = std::function<void(MethodCall& msg, MethodReply& reply)>;
using async_method_callback = std::function<void(MethodCall& msg, MethodResult result)>;
using async_reply_handler = std::function<void(MethodReply& reply, const Error* error)>;
using signal_handler = std::function<void(Signal& signal)>;
using property_set_callback = std::function<void(Message& msg)>;
using property_get_callback = std::function<void(Message& reply)>;
@ -368,6 +370,12 @@ namespace sdbus {
static constexpr bool is_async = false;
};
template <typename... _Args>
struct function_traits<void(const Error*, _Args...)>
: public function_traits_base<void, _Args...>
{
};
template <typename... _Args, typename... _Results>
struct function_traits<void(Result<_Results...>, _Args...)>
: public function_traits_base<std::tuple<_Results...>, _Args...>
@ -498,6 +506,15 @@ namespace sdbus {
return std::forward<_Function>(f)(std::move(r), std::get<_I>(std::forward<_Tuple>(t))...);
}
template <class _Function, class _Tuple, std::size_t... _I>
constexpr decltype(auto) apply_impl( _Function&& f
, const Error* e
, _Tuple&& t
, std::index_sequence<_I...> )
{
return std::forward<_Function>(f)(e, std::get<_I>(std::forward<_Tuple>(t))...);
}
// Version of apply_impl for functions returning non-void values.
// In this case just forward function return value.
template <class _Function, class _Tuple, std::size_t... _I>
@ -542,6 +559,37 @@ namespace sdbus {
, std::forward<_Tuple>(t)
, std::make_index_sequence<std::tuple_size<std::decay_t<_Tuple>>::value>{} );
}
// Convert tuple `t' of values into a list of arguments
// and invoke function `f' with those arguments.
template <class _Function, class _Tuple>
constexpr decltype(auto) apply(_Function&& f, const Error* e, _Tuple&& t)
{
return detail::apply_impl( std::forward<_Function>(f)
, e
, std::forward<_Tuple>(t)
, std::make_index_sequence<std::tuple_size<std::decay_t<_Tuple>>::value>{} );
}
// Invoke a member function (custom version of C++17's invoke until we have full C++17 support)
template< typename _Function
, typename... _Args
, std::enable_if_t<std::is_member_pointer<std::decay_t<_Function>>{}, int> = 0 >
constexpr decltype(auto) invoke(_Function&& f, _Args&&... args)
noexcept(noexcept(std::mem_fn(f)(std::forward<_Args>(args)...)))
{
return std::mem_fn(f)(std::forward<_Args>(args)...);
}
// Invoke non-member function (custom version of C++17's invoke until we have full C++17 support)
template< typename _Function
, typename... _Args
, std::enable_if_t<!std::is_member_pointer<std::decay_t<_Function>>{}, int> = 0 >
constexpr decltype(auto) invoke(_Function&& f, _Args&&... args)
noexcept(noexcept(std::forward<_Function>(f)(std::forward<_Args>(args)...)))
{
return std::forward<_Function>(f)(std::forward<_Args>(args)...);
}
}
#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

View File

@ -43,11 +43,13 @@ Connection::Connection(Connection::BusType type)
finishHandshake(bus);
notificationFd_ = createLoopNotificationDescriptor();
std::cerr << "Created eventfd " << notificationFd_ << " of " << this << std::endl;
}
Connection::~Connection()
{
leaveProcessingLoop();
std::cerr << "Closing eventfd " << notificationFd_ << " of " << this << std::endl;
closeLoopNotificationDescriptor(notificationFd_);
}
@ -65,6 +67,10 @@ void Connection::releaseName(const std::string& name)
void Connection::enterProcessingLoop()
{
loopThreadId_ = std::this_thread::get_id();
std::lock_guard<std::mutex> guard(loopMutex_);
while (true)
{
auto processed = processPendingRequest();
@ -75,13 +81,18 @@ void Connection::enterProcessingLoop()
if (!success)
break; // Exit processing loop
if (success.asyncMsgsToProcess)
processAsynchronousMessages();
processUserRequests();
}
loopThreadId_ = std::thread::id{};
}
void Connection::enterProcessingLoopAsync()
{
asyncLoopThread_ = std::thread([this](){ enterProcessingLoop(); });
std::cerr << "--> enterProcessingLoopAsync() for connection " << this << std::endl;
// TODO: Check that joinable() means a valid non-empty thread
//if (!asyncLoopThread_.joinable())
asyncLoopThread_ = std::thread([this](){ enterProcessingLoop(); });
}
void Connection::leaveProcessingLoop()
@ -114,16 +125,17 @@ void Connection::removeObjectVTable(void* vtableHandle)
sd_bus_slot_unref((sd_bus_slot *)vtableHandle);
}
sdbus::MethodCall Connection::createMethodCall( const std::string& destination
, const std::string& objectPath
, const std::string& interfaceName
, const std::string& methodName ) const
MethodCall Connection::createMethodCall( const std::string& destination
, const std::string& objectPath
, const std::string& interfaceName
, const std::string& methodName ) const
{
sd_bus_message *sdbusMsg{};
// Returned message will become an owner of sdbusMsg
SCOPE_EXIT{ sd_bus_message_unref(sdbusMsg); };
// It is thread-safe to create a message this way
auto r = sd_bus_message_new_method_call( bus_.get()
, &sdbusMsg
, destination.c_str()
@ -136,15 +148,16 @@ sdbus::MethodCall Connection::createMethodCall( const std::string& destination
return MethodCall(sdbusMsg);
}
sdbus::Signal Connection::createSignal( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName ) const
Signal Connection::createSignal( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName ) const
{
sd_bus_message *sdbusSignal{};
// Returned message will become an owner of sdbusSignal
SCOPE_EXIT{ sd_bus_message_unref(sdbusSignal); };
// It is thread-safe to create a message this way
auto r = sd_bus_message_new_signal( bus_.get()
, &sdbusSignal
, objectPath.c_str()
@ -156,32 +169,194 @@ sdbus::Signal Connection::createSignal( const std::string& objectPath
return Signal(sdbusSignal);
}
template<typename _Callable, typename... _Args, std::enable_if_t<std::is_void<function_result_t<_Callable>>::value, int>>
inline auto Connection::tryExecuteSync(_Callable&& fnc, const _Args&... args)
{
std::thread::id loopThreadId = loopThreadId_.load(std::memory_order_relaxed);
// Is the loop not yet on? => Go make synchronous call
while (loopThreadId == std::thread::id{})
{
// Did the loop begin in the meantime? Or try_lock() failed spuriously?
if (!loopMutex_.try_lock())
{
loopThreadId = loopThreadId_.load(std::memory_order_relaxed);
continue;
}
// Synchronous call
std::lock_guard<std::mutex> guard(loopMutex_, std::adopt_lock);
sdbus::invoke(std::forward<_Callable>(fnc), args...);
return true;
}
// Is the loop on and we are in the same thread? => Go for synchronous call
if (loopThreadId == std::this_thread::get_id())
{
assert(!loopMutex_.try_lock());
sdbus::invoke(std::forward<_Callable>(fnc), args...);
return true;
}
return false;
}
template<typename _Callable, typename... _Args, std::enable_if_t<!std::is_void<function_result_t<_Callable>>::value, int>>
inline auto Connection::tryExecuteSync(_Callable&& fnc, const _Args&... args)
{
std::thread::id loopThreadId = loopThreadId_.load(std::memory_order_relaxed);
// Is the loop not yet on? => Go make synchronous call
while (loopThreadId == std::thread::id{})
{
// Did the loop begin in the meantime? Or try_lock() failed spuriously?
if (!loopMutex_.try_lock())
{
loopThreadId = loopThreadId_.load(std::memory_order_relaxed);
continue;
}
// Synchronous call
std::lock_guard<std::mutex> guard(loopMutex_, std::adopt_lock);
return std::make_pair(true, sdbus::invoke(std::forward<_Callable>(fnc), args...));
}
// Is the loop on and we are in the same thread? => Go for synchronous call
if (loopThreadId == std::this_thread::get_id())
{
assert(!loopMutex_.try_lock());
return std::make_pair(true, sdbus::invoke(std::forward<_Callable>(fnc), args...));
}
return std::make_pair(false, function_result_t<_Callable>{});
}
template<typename _Callable, typename... _Args, std::enable_if_t<std::is_void<function_result_t<_Callable>>::value, int>>
inline void Connection::executeAsync(_Callable&& fnc, const _Args&... args)
{
std::promise<void> result;
auto future = result.get_future();
queueUserRequest([fnc = std::forward<_Callable>(fnc), args..., &result]()
{
SCOPE_EXIT_NAMED(onSdbusError){ result.set_exception(std::current_exception()); };
std::cerr << " [lt] ... Invoking void request from within event loop thread..." << std::endl;
sdbus::invoke(fnc, args...);
std::cerr << " [lt] Request invoked" << std::endl;
result.set_value();
onSdbusError.dismiss();
});
// Wait for the the processing loop thread to process the request
future.get();
}
template<typename _Callable, typename... _Args, std::enable_if_t<!std::is_void<function_result_t<_Callable>>::value, int>>
inline auto Connection::executeAsync(_Callable&& fnc, const _Args&... args)
{
std::promise<function_result_t<_Callable>> result;
auto future = result.get_future();
queueUserRequest([fnc = std::forward<_Callable>(fnc), args..., &result]()
{
SCOPE_EXIT_NAMED(onSdbusError){ result.set_exception(std::current_exception()); };
std::cerr << " [lt] ... Invoking request from within event loop thread..." << std::endl;
auto returnValue = sdbus::invoke(fnc, args...);
std::cerr << " [lt] Request invoked and got result" << std::endl;
result.set_value(returnValue);
onSdbusError.dismiss();
});
// Wait for the reply from the processing loop thread
return future.get();
}
template<typename _Callable, typename... _Args>
inline void Connection::executeAsyncAndDontWaitForResult(_Callable&& fnc, const _Args&... args)
{
queueUserRequest([fnc = std::forward<_Callable>(fnc), args...]()
{
sdbus::invoke(fnc, args...);
});
}
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 registerSignalHandler = [this]( 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);
auto filter = composeSignalMatchFilter(objectPath, interfaceName, signalName);
auto r = sd_bus_add_match(bus_.get(), &slot, filter.c_str(), callback, userData);
std::cerr << "Registered signal " << signalName << " with slot " << slot << std::endl;
SDBUS_THROW_ERROR_IF(r < 0, "Failed to register signal handler", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to register signal handler", -r);
return slot;
return slot;
};
std::cerr << "Trying to register signal " << signalName << " synchronously..." << std::endl;
auto result = tryExecuteSync(registerSignalHandler, objectPath, interfaceName, signalName, callback, userData);
if (!result.first) std::cerr << " ... Nope, going async way" << std::endl;
return result.first ? result.second
: executeAsync(registerSignalHandler, objectPath, interfaceName, signalName, callback, userData);
}
void Connection::unregisterSignalHandler(void* handlerCookie)
{
sd_bus_slot_unref((sd_bus_slot *)handlerCookie);
auto result = tryExecuteSync(sd_bus_slot_unref, (sd_bus_slot *)handlerCookie);
// if (!result.first)
// executeAsync(sd_bus_slot_unref, (sd_bus_slot *)handlerCookie);
if (result.first)
{
std::cerr << "Synchronously unregistered signal " << handlerCookie << ": " << result.second << std::endl;
return;
}
auto slot = executeAsync(sd_bus_slot_unref, (sd_bus_slot *)handlerCookie);
std::cerr << "Asynchronously unregistered signal " << handlerCookie << ": " << slot << std::endl;
}
void Connection::sendReplyAsynchronously(const sdbus::MethodReply& reply)
MethodReply Connection::callMethod(const MethodCall& message)
{
std::lock_guard<std::mutex> guard(mutex_);
asyncReplies_.push(reply);
notifyProcessingLoop();
std::cerr << "Trying to call method synchronously..." << std::endl;
auto result = tryExecuteSync(&MethodCall::send, message);
if (!result.first) std::cerr << " ... Nope, going async way" << std::endl;
return result.first ? result.second
: executeAsync(&MethodCall::send, message);
}
void Connection::callMethod(const AsyncMethodCall& message, void* callback, void* userData)
{
auto result = tryExecuteSync(&AsyncMethodCall::send, message, callback, userData);
if (!result)
executeAsyncAndDontWaitForResult(&AsyncMethodCall::send, message, callback, userData);
}
void Connection::sendMethodReply(const MethodReply& message)
{
auto result = tryExecuteSync(&MethodReply::send, message);
if (!result)
executeAsyncAndDontWaitForResult(&MethodReply::send, message);
}
void Connection::emitSignal(const Signal& message)
{
auto result = tryExecuteSync(&Signal::send, message);
if (!result)
executeAsyncAndDontWaitForResult(&Signal::send, message);
}
std::unique_ptr<sdbus::internal::IConnection> Connection::clone() const
@ -238,10 +413,14 @@ void Connection::notifyProcessingLoop()
{
assert(notificationFd_ >= 0);
uint64_t value = 1;
auto r = write(notificationFd_, &value, sizeof(value));
SDBUS_THROW_ERROR_IF(r < 0, "Failed to notify processing loop", -errno);
for (int i = 0; i < 1; ++i)
{
//std::this_thread::sleep_for(std::chrono::milliseconds(5));
uint64_t value = 1;
auto r = write(notificationFd_, &value, sizeof(value));
std::cerr << "Wrote to notification fd " << notificationFd_ << std::endl;
SDBUS_THROW_ERROR_IF(r < 0, "Failed to notify processing loop", -errno);
}
}
void Connection::notifyProcessingLoopToExit()
@ -270,14 +449,24 @@ bool Connection::processPendingRequest()
return r > 0;
}
void Connection::processAsynchronousMessages()
void Connection::queueUserRequest(UserRequest&& request)
{
std::lock_guard<std::mutex> guard(mutex_);
while (!asyncReplies_.empty())
{
auto reply = asyncReplies_.front();
asyncReplies_.pop();
reply.send();
std::lock_guard<std::mutex> guard(userRequestsMutex_);
userRequests_.push(std::move(request));
std::cerr << "Pushed to user request queue. Size: " << userRequests_.size() << std::endl;
}
notifyProcessingLoop();
}
void Connection::processUserRequests()
{
std::lock_guard<std::mutex> guard(userRequestsMutex_);
while (!userRequests_.empty())
{
auto& request = userRequests_.front();
request();
userRequests_.pop();
}
}
@ -299,22 +488,31 @@ Connection::WaitResult Connection::waitForNextRequest()
uint64_t usec;
sd_bus_get_timeout(bus, &usec);
struct pollfd fds[] = {{sdbusFd, sdbusEvents, 0}, {notificationFd_, POLLIN, 0}};
struct pollfd fds[] = {{sdbusFd, sdbusEvents, 0}, {notificationFd_, POLLIN | POLLHUP | POLLERR | POLLNVAL, 0}};
auto fdsCount = sizeof(fds)/sizeof(fds[0]);
std::cerr << "[lt] Going to poll on fs " << sdbusFd << ", " << notificationFd_ << " with timeout " << usec << " and fdscount == " << fdsCount << std::endl;
r = poll(fds, fdsCount, usec == (uint64_t) -1 ? -1 : (usec+999)/1000);
if (r < 0 && errno == EINTR)
{
std::cerr << "<<<>>>> GOT EINTR" << std::endl;
return {true, false}; // Try again
}
SDBUS_THROW_ERROR_IF(r < 0, "Failed to wait on the bus", -errno);
if ((fds[1].revents & POLLHUP) || (fds[1].revents & POLLERR) || ((fds[1].revents & POLLNVAL)))
{
std::cerr << "!!!!!!!!!! Something went wrong on polling" << std::endl;
}
if (fds[1].revents & POLLIN)
{
if (exitLoopThread_)
return {false, false}; // Got exit notification
// Otherwise we have some async messages to process
// Otherwise we have some user requests to process
std::cerr << "Loop found it has some async requests to process" << std::endl;
uint64_t value{};
auto r = read(notificationFd_, &value, sizeof(value));

View File

@ -29,11 +29,13 @@
#include <sdbus-c++/IConnection.h>
#include <sdbus-c++/Message.h>
#include "IConnection.h"
#include "ScopeGuard.h"
#include <systemd/sd-bus.h>
#include <memory>
#include <thread>
#include <atomic>
#include <mutex>
#include <future>
#include <queue>
namespace sdbus { namespace internal {
@ -50,7 +52,7 @@ namespace sdbus { namespace internal {
};
Connection(BusType type);
~Connection();
~Connection() override;
void requestName(const std::string& name) override;
void releaseName(const std::string& name) override;
@ -64,13 +66,13 @@ namespace sdbus { namespace internal {
, void* userData ) override;
void removeObjectVTable(void* vtableHandle) override;
sdbus::MethodCall createMethodCall( const std::string& destination
, const std::string& objectPath
, const std::string& interfaceName
, const std::string& methodName ) const override;
sdbus::Signal createSignal( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName ) const override;
MethodCall createMethodCall( const std::string& destination
, const std::string& objectPath
, const std::string& interfaceName
, const std::string& methodName ) const override;
Signal createSignal( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName ) const override;
void* registerSignalHandler( const std::string& objectPath
, const std::string& interfaceName
@ -79,7 +81,10 @@ namespace sdbus { namespace internal {
, void* userData ) override;
void unregisterSignalHandler(void* handlerCookie) override;
void sendReplyAsynchronously(const sdbus::MethodReply& reply) override;
MethodReply callMethod(const MethodCall& message) override;
void callMethod(const AsyncMethodCall& message, void* callback, void* userData) override;
void sendMethodReply(const MethodReply& message) override;
void emitSignal(const Signal& message) override;
std::unique_ptr<sdbus::internal::IConnection> clone() const override;
@ -93,12 +98,16 @@ namespace sdbus { namespace internal {
return msgsToProcess || asyncMsgsToProcess;
}
};
using UserRequest = std::function<void()>;
static sd_bus* openBus(Connection::BusType type);
static void finishHandshake(sd_bus* bus);
static int createLoopNotificationDescriptor();
static void closeLoopNotificationDescriptor(int fd);
bool processPendingRequest();
void processAsynchronousMessages();
void queueUserRequest(UserRequest&& request);
void processUserRequests();
WaitResult waitForNextRequest();
static std::string composeSignalMatchFilter( const std::string& objectPath
, const std::string& interfaceName
@ -107,11 +116,27 @@ namespace sdbus { namespace internal {
void notifyProcessingLoopToExit();
void joinWithProcessingLoop();
// TODO move this and threading logic and method around it to separate class?
template <typename _Callable, typename... _Args, std::enable_if_t<std::is_void<function_result_t<_Callable>>::value, int> = 0>
inline auto tryExecuteSync(_Callable&& fnc, const _Args&... args);
template <typename _Callable, typename... _Args, std::enable_if_t<!std::is_void<function_result_t<_Callable>>::value, int> = 0>
inline auto tryExecuteSync(_Callable&& fnc, const _Args&... args);
template <typename _Callable, typename... _Args, std::enable_if_t<std::is_void<function_result_t<_Callable>>::value, int> = 0>
inline void executeAsync(_Callable&& fnc, const _Args&... args);
template <typename _Callable, typename... _Args, std::enable_if_t<!std::is_void<function_result_t<_Callable>>::value, int> = 0>
inline auto executeAsync(_Callable&& fnc, const _Args&... args);
template <typename _Callable, typename... _Args>
inline void executeAsyncAndDontWaitForResult(_Callable&& fnc, const _Args&... args);
private:
std::unique_ptr<sd_bus, decltype(&sd_bus_flush_close_unref)> bus_{nullptr, &sd_bus_flush_close_unref};
std::thread asyncLoopThread_;
std::mutex mutex_;
std::queue<MethodReply> asyncReplies_;
std::atomic<std::thread::id> loopThreadId_;
std::mutex loopMutex_;
std::queue<UserRequest> userRequests_;
std::mutex userRequestsMutex_;
std::atomic<bool> exitLoopThread_;
int notificationFd_{-1};
BusType busType_;

View File

@ -31,6 +31,38 @@
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);
// 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.
if (syncCallback_)
object_.registerMethod(interfaceName_, methodName_, inputSignature_, outputSignature_, std::move(syncCallback_), flags_);
else if(asyncCallback_)
object_.registerMethod(interfaceName_, methodName_, inputSignature_, outputSignature_, std::move(asyncCallback_), flags_);
else
SDBUS_THROW_ERROR("Method handler not specified when registering a DBus method", EINVAL);
}
SignalRegistrator::SignalRegistrator(IObject& object, const std::string& signalName)
: object_(object)
, signalName_(signalName)
@ -55,7 +87,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 +119,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_) );
}

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;
}
}

View File

@ -33,6 +33,7 @@
// Forward declaration
namespace sdbus {
class MethodCall;
class AsyncMethodCall;
class MethodReply;
class Signal;
}
@ -49,14 +50,14 @@ namespace internal {
, void* userData ) = 0;
virtual void removeObjectVTable(void* vtableHandle) = 0;
virtual sdbus::MethodCall createMethodCall( const std::string& destination
, const std::string& objectPath
, const std::string& interfaceName
, const std::string& methodName ) const = 0;
virtual MethodCall createMethodCall( const std::string& destination
, const std::string& objectPath
, const std::string& interfaceName
, const std::string& methodName ) const = 0;
virtual sdbus::Signal createSignal( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName ) const = 0;
virtual Signal createSignal( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName ) const = 0;
virtual void* registerSignalHandler( const std::string& objectPath
, const std::string& interfaceName
@ -68,7 +69,10 @@ namespace internal {
virtual void enterProcessingLoopAsync() = 0;
virtual void leaveProcessingLoop() = 0;
virtual void sendReplyAsynchronously(const sdbus::MethodReply& reply) = 0;
virtual MethodReply callMethod(const MethodCall& message) = 0;
virtual void callMethod(const AsyncMethodCall& message, void* callback, void* userData) = 0;
virtual void sendMethodReply(const MethodReply& message) = 0;
virtual void emitSignal(const Signal& message) = 0;
virtual std::unique_ptr<sdbus::internal::IConnection> clone() const = 0;

View File

@ -571,7 +571,28 @@ void* Message::getMsg() const
return msg_;
}
void MethodCall::dontExpectReply()
{
auto r = sd_bus_message_set_expect_reply((sd_bus_message*)getMsg(), 0);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to set the dont-expect-reply flag", -r);
}
bool MethodCall::doesntExpectReply() const
{
auto r = sd_bus_message_get_expect_reply((sd_bus_message*)getMsg());
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_message* sdbusReply{};
SCOPE_EXIT{ sd_bus_message_unref(sdbusReply); }; // Returned message will become an owner of sdbusReply
@ -590,6 +611,13 @@ MethodReply MethodCall::send() const
return MethodReply(sdbusReply);
}
MethodReply MethodCall::sendWithNoReply() const
{
auto r = sd_bus_send(nullptr, (sd_bus_message*)getMsg(), 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{};
@ -620,6 +648,17 @@ MethodReply MethodCall::createErrorReply(const Error& error) const
return MethodReply(sdbusErrorReply);
}
AsyncMethodCall::AsyncMethodCall(MethodCall&& call) noexcept
: Message(call)
{
}
void AsyncMethodCall::send(void* callback, void* userData) const
{
auto r = sd_bus_call_async(nullptr, nullptr, (sd_bus_message*)getMsg(), (sd_bus_message_handler_t)callback, userData, 0);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to call method asynchronously", -r);
}
void MethodReply::send() const
{
auto r = sd_bus_send(nullptr, (sd_bus_message*)getMsg(), nullptr);

View File

@ -37,7 +37,7 @@ MethodResult::MethodResult(const MethodCall& msg, sdbus::internal::Object& objec
void MethodResult::send(const MethodReply& reply) const
{
assert(object_ != nullptr);
object_->sendReplyAsynchronously(reply);
object_->sendMethodReply(reply);
}
}

View File

@ -28,6 +28,7 @@
#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>
@ -45,7 +46,8 @@ 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);
@ -57,7 +59,7 @@ void Object::registerMethod( const std::string& interfaceName
};
auto& interface = interfaces_[interfaceName];
InterfaceData::MethodData methodData{inputSignature, outputSignature, std::move(syncCallback)};
InterfaceData::MethodData methodData{inputSignature, outputSignature, std::move(syncCallback), flags};
auto inserted = interface.methods_.emplace(methodName, std::move(methodData)).second;
SDBUS_THROW_ERROR_IF(!inserted, "Failed to register method: method already exists", EINVAL);
@ -67,7 +69,8 @@ void Object::registerMethod( const std::string& interfaceName
, const std::string& methodName
, const std::string& inputSignature
, const std::string& outputSignature
, async_method_callback asyncMethodCallback )
, async_method_callback asyncMethodCallback
, Flags flags )
{
SDBUS_THROW_ERROR_IF(!asyncMethodCallback, "Invalid method callback provided", EINVAL);
@ -78,7 +81,7 @@ void Object::registerMethod( const std::string& interfaceName
};
auto& interface = interfaces_[interfaceName];
InterfaceData::MethodData methodData{inputSignature, outputSignature, std::move(asyncCallback)};
InterfaceData::MethodData methodData{inputSignature, outputSignature, std::move(asyncCallback), flags};
auto inserted = interface.methods_.emplace(methodName, std::move(methodData)).second;
SDBUS_THROW_ERROR_IF(!inserted, "Failed to register method: method already exists", EINVAL);
@ -86,11 +89,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);
@ -99,31 +103,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_)
@ -143,15 +156,12 @@ sdbus::Signal Object::createSignal(const std::string& interfaceName, const std::
void Object::emitSignal(const sdbus::Signal& message)
{
// TODO: Make signal emitting asynchronous. Now signal can probably be emitted only from user code
// handled within the D-Bus processing loop thread, but not from any thread. In principle it will
// be the same as async replies.
message.send();
connection_.emitSignal(message);
}
void Object::sendReplyAsynchronously(const MethodReply& reply)
void Object::sendMethodReply(const MethodReply& reply)
{
connection_.sendReplyAsynchronously(reply);
connection_.sendMethodReply(reply);
}
const std::vector<sd_bus_vtable>& Object::createInterfaceVTable(InterfaceData& interfaceData)
@ -159,7 +169,7 @@ const std::vector<sd_bus_vtable>& Object::createInterfaceVTable(InterfaceData& i
auto& vtable = interfaceData.vtable_;
assert(vtable.empty());
vtable.push_back(createVTableStartItem());
vtable.push_back(createVTableStartItem(interfaceData.flags_.toSdBusInterfaceFlags()));
registerMethodsToVTable(interfaceData, vtable);
registerSignalsToVTable(interfaceData, vtable);
registerPropertiesToVTable(interfaceData, vtable);
@ -178,7 +188,8 @@ void Object::registerMethodsToVTable(const InterfaceData& interfaceData, std::ve
vtable.push_back(createVTableMethodItem( methodName.c_str()
, methodData.inputArgs_.c_str()
, methodData.outputArgs_.c_str()
, &Object::sdbus_method_callback ));
, &Object::sdbus_method_callback
, methodData.flags_.toSdBusMethodFlags() ));
}
}
@ -190,7 +201,8 @@ void Object::registerSignalsToVTable(const InterfaceData& interfaceData, std::ve
const auto& signalData = item.second;
vtable.push_back(createVTableSignalItem( signalName.c_str()
, signalData.signature_.c_str() ));
, signalData.signature_.c_str()
, signalData.flags_.toSdBusSignalFlags() ));
}
}
@ -204,12 +216,14 @@ void Object::registerPropertiesToVTable(const InterfaceData& interfaceData, std:
if (!propertyData.setCallback_)
vtable.push_back(createVTablePropertyItem( propertyName.c_str()
, propertyData.signature_.c_str()
, &Object::sdbus_property_get_callback ));
, &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 ));
, &Object::sdbus_property_set_callback
, propertyData.flags_.toSdBusWritablePropertyFlags() ));
}
}

View File

@ -49,35 +49,42 @@ 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 registerMethod( const std::string& interfaceName
, const std::string& methodName
, const std::string& inputSignature
, const std::string& outputSignature
, async_method_callback asyncMethodCallback ) override;
, async_method_callback asyncMethodCallback
, 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;
sdbus::Signal createSignal(const std::string& interfaceName, const std::string& signalName) override;
void emitSignal(const sdbus::Signal& message) override;
void sendReplyAsynchronously(const MethodReply& reply);
void sendMethodReply(const MethodReply& reply);
private:
using InterfaceName = std::string;
@ -89,12 +96,14 @@ namespace internal {
std::string inputArgs_;
std::string outputArgs_;
std::function<void(MethodCall&)> 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;
@ -103,9 +112,11 @@ namespace internal {
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<void, std::function<void(void*)>> slot_;
};

View File

@ -30,12 +30,13 @@
#include "IConnection.h"
#include <systemd/sd-bus.h>
#include <cassert>
#include <chrono>
#include <thread>
namespace sdbus { namespace internal {
ObjectProxy::ObjectProxy(sdbus::internal::IConnection& connection, std::string destination, std::string objectPath)
: connection_(&connection, [](sdbus::internal::IConnection *){ /* Intentionally left empty */ })
, ownConnection_(false)
, destination_(std::move(destination))
, objectPath_(std::move(objectPath))
{
@ -45,30 +46,37 @@ ObjectProxy::ObjectProxy( std::unique_ptr<sdbus::internal::IConnection>&& connec
, std::string destination
, std::string objectPath )
: connection_(std::move(connection))
, ownConnection_(true)
, destination_(std::move(destination))
, objectPath_(std::move(objectPath))
{
}
ObjectProxy::~ObjectProxy()
{
// If the dedicated connection for signals is used, we have to stop the processing loop
// upon this connection prior to unregistering signal slots in the interfaces_ container,
// otherwise we might have a race condition of two threads working upon one connection.
if (signalConnection_ != nullptr)
signalConnection_->leaveProcessingLoop();
// The connection is ours only, so we have to manage event loop upon this connection,
// so we get signals, async replies, and other messages from D-Bus.
connection_->enterProcessingLoopAsync();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
MethodCall ObjectProxy::createMethodCall(const std::string& interfaceName, const std::string& methodName)
{
// Tell, don't ask
return connection_->createMethodCall(destination_, objectPath_, interfaceName, methodName);
}
AsyncMethodCall ObjectProxy::createAsyncMethodCall(const std::string& interfaceName, const std::string& methodName)
{
return AsyncMethodCall{ObjectProxy::createMethodCall(interfaceName, methodName)};
}
MethodReply ObjectProxy::callMethod(const MethodCall& message)
{
return message.send();
return connection_->callMethod(message);
}
void ObjectProxy::callMethod(const AsyncMethodCall& message, async_reply_handler asyncReplyCallback)
{
auto callback = (void*)&ObjectProxy::sdbus_async_reply_handler;
// Allocated userData gets deleted in the sdbus_async_reply_handler
auto userData = new async_reply_handler(std::move(asyncReplyCallback));
connection_->callMethod(message, callback, userData);
}
void ObjectProxy::registerSignalHandler( const std::string& interfaceName
@ -88,30 +96,7 @@ void ObjectProxy::registerSignalHandler( const std::string& interfaceName
void ObjectProxy::finishRegistration()
{
bool hasSignals = listensToSignals();
if (hasSignals && ownConnection_)
{
// Let's use dedicated signalConnection_ for signals,
// which will then be used by the processing loop thread.
signalConnection_ = connection_->clone();
registerSignalHandlers(*signalConnection_);
signalConnection_->enterProcessingLoopAsync();
}
else if (hasSignals)
{
// Let's used connection provided from the outside.
registerSignalHandlers(*connection_);
}
}
bool ObjectProxy::listensToSignals() const
{
for (auto& interfaceItem : interfaces_)
if (!interfaceItem.second.signals_.empty())
return true;
return false;
registerSignalHandlers(*connection_);
}
void ObjectProxy::registerSignalHandlers(sdbus::internal::IConnection& connection)
@ -136,11 +121,30 @@ void ObjectProxy::registerSignalHandlers(sdbus::internal::IConnection& connectio
}
}
int ObjectProxy::sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError)
{
MethodReply message(sdbusMessage);
std::unique_ptr<async_reply_handler> asyncReplyCallback{static_cast<async_reply_handler*>(userData)};
assert(asyncReplyCallback != nullptr);
if (!sd_bus_error_is_set(retError))
{
(*asyncReplyCallback)(message, nullptr);
}
else
{
sdbus::Error error(retError->name, retError->message);
(*asyncReplyCallback)(message, &error);
}
}
int ObjectProxy::sdbus_signal_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error */*retError*/)
{
Signal message(sdbusMessage);
auto* object = static_cast<ObjectProxy*>(userData);
assert(object != nullptr);
// 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);

View File

@ -50,10 +50,11 @@ namespace internal {
ObjectProxy( std::unique_ptr<sdbus::internal::IConnection>&& connection
, std::string destination
, std::string objectPath );
~ObjectProxy();
MethodCall createMethodCall(const std::string& interfaceName, const std::string& methodName) override;
AsyncMethodCall createAsyncMethodCall(const std::string& interfaceName, const std::string& methodName) override;
MethodReply callMethod(const MethodCall& message) override;
void callMethod(const AsyncMethodCall& message, async_reply_handler asyncReplyCallback) override;
void registerSignalHandler( const std::string& interfaceName
, const std::string& signalName
@ -61,16 +62,14 @@ namespace internal {
void finishRegistration() override;
private:
bool listensToSignals() const;
void registerSignalHandlers(sdbus::internal::IConnection& connection);
static int sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError);
static int sdbus_signal_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError);
private:
std::unique_ptr< sdbus::internal::IConnection
, std::function<void(sdbus::internal::IConnection*)>
> connection_;
bool ownConnection_{};
std::unique_ptr<sdbus::internal::IConnection> signalConnection_;
std::string destination_;
std::string objectPath_;

View File

@ -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,17 +161,25 @@ std::tuple<std::string, std::string> AdaptorGenerator::processMethods(const Node
{
auto methodName = method->get("name");
auto annotations = getAnnotations(*method);
bool async{false};
Nodes annotations = (*method)["annotation"];
std::string annotationRegistration;
for (const auto& annotation : annotations)
{
if (annotation->get("name") == "org.freedesktop.DBus.Method.Async"
&& (annotation->get("value") == "server" || annotation->get("value") == "clientserver"))
{
const auto& annotationName = annotation.first;
const auto& annotationValue = annotation.second;
if (annotationName == "org.freedesktop.DBus.Deprecated" && annotationValue == "true")
annotationRegistration += ".markAsDeprecated()";
else if (annotationName == "org.freedesktop.DBus.Method.NoReply" && annotationValue == "true")
annotationRegistration += ".withNoReply()";
else if (annotationName == "org.freedesktop.DBus.Method.Async" && (annotationValue == "server" || annotationValue == "clientserver"))
async = true;
break;
}
else if (annotationName == "org.freedesktop.systemd1.Privileged" && 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"];
@ -167,7 +200,8 @@ std::tuple<std::string, std::string> AdaptorGenerator::processMethods(const Node
<< argTypeStr
<< "){ " << (async ? "" : "return ") << "this->" << methodName << "("
<< (async ? "std::move(result)"s + (argTypeStr.empty() ? "" : ", ") : "")
<< argStr << "); });" << endl;
<< argStr << "); })"
<< annotationRegistration << ";" << endl;
declarationSS << tab
<< "virtual "
@ -190,6 +224,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;;
@ -204,6 +253,7 @@ std::tuple<std::string, std::string> AdaptorGenerator::processSignals(const Node
signalRegistrationSS << ".withParameters<" << typeStr << ">()";
}
signalRegistrationSS << annotationRegistration;
signalRegistrationSS << ";" << endl;
signalMethodSS << tab << "void " << name << "(" << argTypeStr << ")" << endl
@ -238,6 +288,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)";
@ -254,6 +322,7 @@ std::tuple<std::string, std::string> AdaptorGenerator::processProperties(const N
"{ this->" << propertyName << "(" << propertyArg << "); })";
}
registrationSS << annotationRegistration;
registrationSS << ";" << endl;
if (propertyAccess == "read" || propertyAccess == "readwrite")
@ -264,3 +333,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

@ -18,13 +18,24 @@ find_package(EXPAT REQUIRED)
# SOURCE FILES CONFIGURATION
#-------------------------------
set(SDBUSCPP_XML2CPP_SRCS xml2cpp.cpp xml.cpp generator_utils.cpp BaseGenerator.cpp AdaptorGenerator.cpp ProxyGenerator.cpp)
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 17)
set(CMAKE_CXX_STANDARD 14)
#----------------------------------
# EXECUTABLE BUILD INFORMATION

View File

@ -127,6 +127,24 @@ std::string ProxyGenerator::processMethods(const Nodes& methods) const
Nodes inArgs = args.select("direction" , "in");
Nodes outArgs = args.select("direction" , "out");
bool dontExpectReply{false};
Nodes annotations = (*method)["annotation"];
for (const auto& annotation : annotations)
{
if (annotation->get("name") == "org.freedesktop.DBus.Method.NoReply"
&& annotation->get("value") == "true")
{
dontExpectReply = true;
break;
}
}
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;
}
auto retType = outArgsToType(outArgs);
std::string argStr, argTypeStr;
std::tie(argStr, argTypeStr, std::ignore) = argsToNamesAndTypes(inArgs);
@ -152,6 +170,10 @@ std::string ProxyGenerator::processMethods(const Nodes& methods) const
methodSS << ".storeResultsTo(result);" << endl
<< tab << tab << "return result";
}
else if (dontExpectReply)
{
methodSS << ".dontExpectReply()";
}
methodSS << ";" << endl << tab << "}" << endl << endl;
}
@ -159,7 +181,6 @@ std::string ProxyGenerator::processMethods(const Nodes& methods) const
return methodSS.str();
}
std::tuple<std::string, std::string> ProxyGenerator::processSignals(const Nodes& signals) const
{
std::ostringstream registrationSS, declarationSS;

View File

@ -63,20 +63,35 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR})
# targets even when INTERFACE_INCLUDE_DIRECTORIES is used.
set(CMAKE_NO_SYSTEM_FROM_IMPORTED "1")
add_executable(libsdbus-c++_unittests ${UNITTESTS_SRCS} $<TARGET_OBJECTS:sdbuscppobjects>)
target_link_libraries(libsdbus-c++_unittests ${SYSTEMD_LIBRARIES} gmock gmock_main)
#add_executable(libsdbus-c++_unittests ${UNITTESTS_SRCS} $<TARGET_OBJECTS:sdbuscppobjects>)
#target_link_libraries(libsdbus-c++_unittests ${SYSTEMD_LIBRARIES} gmock gmock_main)
add_executable(libsdbus-c++_integrationtests ${INTEGRATIONTESTS_SRCS})
target_link_libraries(libsdbus-c++_integrationtests sdbus-c++ gmock gmock_main)
# Manual performance tests
option(ENABLE_PERFTESTS "Build and install manual performance tests (default OFF)" OFF)
if(ENABLE_PERFTESTS)
add_executable(libsdbus-c++_perftests_client perftests/client.cpp perftests/perftest-proxy.h)
target_link_libraries(libsdbus-c++_perftests_client sdbus-c++)
add_executable(libsdbus-c++_perftests_server perftests/server.cpp perftests/perftest-adaptor.h)
target_link_libraries(libsdbus-c++_perftests_server sdbus-c++)
endif()
#----------------------------------
# INSTALLATION
#----------------------------------
install(TARGETS libsdbus-c++_unittests DESTINATION /opt/test/bin)
#install(TARGETS libsdbus-c++_unittests DESTINATION /opt/test/bin)
install(TARGETS libsdbus-c++_integrationtests DESTINATION /opt/test/bin)
install(FILES ${INTEGRATIONTESTS_SOURCE_DIR}/files/libsdbus-cpp-test.conf DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/dbus-1/system.d)
if(ENABLE_PERFTESTS)
install(TARGETS libsdbus-c++_perftests_client DESTINATION /opt/test/bin)
install(TARGETS libsdbus-c++_perftests_server DESTINATION /opt/test/bin)
install(FILES perftests/files/org.sdbuscpp.perftest.conf DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/dbus-1/system.d)
endif()
#----------------------------------
# RUNNING THE TESTS UPON BUILD
#----------------------------------
@ -92,9 +107,9 @@ if(CMAKE_CROSSCOMPILING)
if(NOT (UNIT_TESTS_RUNNER AND TEST_DEVICE_IP))
message(WARNING "UNIT_TESTS_RUNNER and TEST_DEVICE_IP variables must be defined to run tests remotely")
endif()
add_test(NAME libsdbus-c++_unittests COMMAND ${UNIT_TESTS_RUNNER} --deviceip=${TEST_DEVICE_IP} --testbin=libsdbus-c++_unittests)
#add_test(NAME libsdbus-c++_unittests COMMAND ${UNIT_TESTS_RUNNER} --deviceip=${TEST_DEVICE_IP} --testbin=libsdbus-c++_unittests)
add_test(NAME libsdbus-c++_integrationtests COMMAND ${UNIT_TESTS_RUNNER} --deviceip=${TEST_DEVICE_IP} --testbin=libsdbus-c++_integrationtests)
else()
add_test(NAME libsdbus-c++_unittests COMMAND libsdbus-c++_unittests)
#add_test(NAME libsdbus-c++_unittests COMMAND libsdbus-c++_unittests)
add_test(NAME libsdbus-c++_integrationtests COMMAND libsdbus-c++_integrationtests)
endif()

View File

@ -41,6 +41,8 @@
#include <thread>
#include <tuple>
#include <chrono>
#include <fstream>
#include <future>
using ::testing::Eq;
using ::testing::Gt;
@ -57,10 +59,12 @@ public:
{
m_connection.requestName(INTERFACE_NAME);
m_connection.enterProcessingLoopAsync();
//m_connection2.enterProcessingLoopAsync();
}
static void TearDownTestCase()
{
//m_connection2.leaveProcessingLoop();
m_connection.leaveProcessingLoop();
m_connection.releaseName(INTERFACE_NAME);
}
@ -69,7 +73,7 @@ private:
void SetUp() override
{
m_adaptor = std::make_unique<TestingAdaptor>(m_connection);
m_proxy = std::make_unique<TestingProxy>(INTERFACE_NAME, OBJECT_PATH);
m_proxy = std::make_unique<TestingProxy>(/*m_connection2, */INTERFACE_NAME, OBJECT_PATH);
std::this_thread::sleep_for(50ms); // Give time for the proxy to start listening to signals
}
@ -81,12 +85,14 @@ private:
public:
static sdbus::internal::Connection m_connection;
//static sdbus::internal::Connection m_connection2;
std::unique_ptr<TestingAdaptor> m_adaptor;
std::unique_ptr<TestingProxy> m_proxy;
};
sdbus::internal::Connection AdaptorAndProxyFixture::m_connection{sdbus::internal::Connection::BusType::eSystem};
//sdbus::internal::Connection AdaptorAndProxyFixture::m_connection2{sdbus::internal::Connection::BusType::eSystem};
}
@ -94,16 +100,16 @@ sdbus::internal::Connection AdaptorAndProxyFixture::m_connection{sdbus::internal
/* -- TEST CASES -- */
/*-------------------------------------*/
TEST(AdaptorAndProxy, CanBeConstructedSuccesfully)
{
auto connection = sdbus::createConnection();
connection->requestName(INTERFACE_NAME);
//TEST(AdaptorAndProxy, CanBeConstructedSuccesfully)
//{
// auto connection = sdbus::createConnection();
// connection->requestName(INTERFACE_NAME);
ASSERT_NO_THROW(TestingAdaptor adaptor(*connection));
ASSERT_NO_THROW(TestingProxy proxy(INTERFACE_NAME, OBJECT_PATH));
// ASSERT_NO_THROW(TestingAdaptor adaptor(*connection));
// ASSERT_NO_THROW(TestingProxy proxy(INTERFACE_NAME, OBJECT_PATH));
connection->releaseName(INTERFACE_NAME);
}
// connection->releaseName(INTERFACE_NAME);
//}
// Methods
@ -200,7 +206,52 @@ TEST_F(SdbusTestObject, CallsMethodWithComplexTypeSuccesfully)
ASSERT_THAT(resComplex.count(0), Eq(1));
}
TEST_F(SdbusTestObject, DoesServerSideAsynchoronousMethodInParallel)
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;
@ -227,7 +278,6 @@ TEST_F(SdbusTestObject, DoesServerSideAsynchoronousMethodInParallel)
TEST_F(SdbusTestObject, HandlesCorrectlyABulkOfParallelServerSideAsyncMethods)
{
std::mutex mtx;
std::atomic<size_t> resultCount{};
std::atomic<bool> invoke{};
std::atomic<int> startedCount{};
@ -255,7 +305,41 @@ TEST_F(SdbusTestObject, HandlesCorrectlyABulkOfParallelServerSideAsyncMethods)
ASSERT_THAT(resultCount, Eq(1500));
}
/*
TEST_F(SdbusTestObject, InvokesMethodAsynchronouslyOnClientSide)
{
std::promise<uint32_t> promise;
auto future = promise.get_future();
m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t res, const sdbus::Error* err)
{
if (err == nullptr)
promise.set_value(res);
else
promise.set_exception(std::make_exception_ptr(*err));
});
m_proxy->doOperationClientSideAsync(100);
ASSERT_THAT(future.get(), Eq(100));
}
TEST_F(SdbusTestObject, InvokesErroneousMethodAsynchronouslyOnClientSide)
{
std::promise<uint32_t> promise;
auto future = promise.get_future();
m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t res, const sdbus::Error* err)
{
if (err == nullptr)
promise.set_value(res);
else
promise.set_exception(std::make_exception_ptr(*err));
});
m_proxy->doErroneousOperationClientSideAsync();
ASSERT_THROW(future.get(), sdbus::Error);
}
*/
TEST_F(SdbusTestObject, FailsCallingNonexistentMethod)
{
ASSERT_THROW(m_proxy->callNonexistentMethod(), sdbus::Error);
@ -331,7 +415,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));
}
@ -346,3 +430,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

@ -29,6 +29,7 @@
#include "adaptor-glue.h"
#include <thread>
#include <chrono>
#include <atomic>
class TestingAdaptor : public sdbus::Interfaces<testing_adaptor>
{
@ -38,9 +39,13 @@ public:
virtual ~TestingAdaptor() { }
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 { std::cerr << "Server: noArgNoReturn() called;" << std::endl;}
int32_t getInt() const { return INT32_VALUE; }
@ -48,6 +53,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>()};
@ -99,7 +110,7 @@ protected:
return res;
}
uint32_t doOperationSync(uint32_t param)
uint32_t doOperation(uint32_t param)
{
std::this_thread::sleep_for(std::chrono::milliseconds(param));
return param;
@ -154,6 +165,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; }
@ -164,6 +181,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,21 @@
#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;
using sdbus::ProxyInterfaces<::testing_proxy, sdbus::introspectable_proxy>::ProxyInterfaces;
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 +58,19 @@ protected:
m_signature[std::get<0>(s)] = static_cast<std::string>(std::get<0>(std::get<1>(s)));
}
void onDoOperationReply(uint32_t returnValue, const sdbus::Error* error) override
{
if (m_DoOperationClientSideAsyncReplyHandler)
m_DoOperationClientSideAsyncReplyHandler(returnValue, error);
}
private:
int m_simpleCallCounter{};
std::map<int32_t, std::string> m_map;
double m_variantValue;
std::map<std::string, std::string> m_signature;
std::function<void(uint32_t res, const sdbus::Error* err)> m_DoOperationClientSideAsyncReplyHandler;
};

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,9 +85,9 @@ protected:
return this->sumVectorItems(a, b);
});
object_.registerMethod("doOperationSync").onInterface(INTERFACE_NAME).implementedAs([this](uint32_t param)
object_.registerMethod("doOperation").onInterface(INTERFACE_NAME).implementedAs([this](uint32_t param)
{
return this->doOperationSync(param);
return this->doOperation(param);
});
object_.registerMethod("doOperationAsync").onInterface(INTERFACE_NAME).implementedAs([this](sdbus::Result<uint32_t> result, uint32_t param)
@ -95,15 +98,20 @@ protected:
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); });
}
@ -143,17 +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 doOperationSync(uint32_t param) = 0;
virtual uint32_t doOperation(uint32_t param) = 0;
virtual void doOperationAsync(uint32_t param, sdbus::Result<uint32_t> result) = 0;
virtual sdbus::Signature getSignature() const = 0;
virtual sdbus::ObjectPath getObjectPath() const = 0;
virtual ComplexType getComplex() const = 0;
virtual void throwError() const = 0;
virtual std::string state() = 0;
virtual uint32_t action() = 0;
@ -161,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="com.kistler.testsdbuscpp">
<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

@ -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,10 +126,10 @@ public:
return result;
}
uint32_t doOperationSync(uint32_t param)
uint32_t doOperation(uint32_t param)
{
uint32_t result;
object_.callMethod("doOperationSync").onInterface(INTERFACE_NAME).withArguments(param).storeResultsTo(result);
object_.callMethod("doOperation").onInterface(INTERFACE_NAME).withArguments(param).storeResultsTo(result);
return result;
}
@ -134,6 +140,25 @@ public:
return result;
}
uint32_t doOperationClientSideAsync(uint32_t param)
{
object_.callMethodAsync("doOperation")
.onInterface(INTERFACE_NAME)
.withArguments(param)
.uponReplyInvoke([this](const sdbus::Error* error, uint32_t returnValue)
{
this->onDoOperationReply(returnValue, error);
});
}
uint32_t doErroneousOperationClientSideAsync()
{
object_.callMethodAsync("throwError").onInterface(INTERFACE_NAME).uponReplyInvoke([this](const sdbus::Error* error)
{
this->onDoOperationReply(0, error);
});
}
sdbus::Signature getSignature()
{
sdbus::Signature result;
@ -155,6 +180,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;

View File

@ -0,0 +1,26 @@
#-------------------------------
# GENERAL COMPILER CONFIGURATION
#-------------------------------
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
#----------------------------------
# BUILD INFORMATION
#----------------------------------
# Turn off -isystem gcc option that CMake uses for imported
# targets even when INTERFACE_INCLUDE_DIRECTORIES is used.
#set(CMAKE_NO_SYSTEM_FROM_IMPORTED "1")
add_executable(perftestclient client.cpp perftest-proxy.h)
target_link_libraries(perftestclient sdbus-c++)
add_executable(perftestserver server.cpp perftest-adaptor.h)
target_link_libraries(perftestserver sdbus-c++)
#----------------------------------
# INSTALLATION
#----------------------------------
install(TARGETS perftestclient DESTINATION /opt/test/bin)
install(TARGETS perftestserver DESTINATION /opt/test/bin)

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

@ -0,0 +1,174 @@
/**
* (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 "perftest-proxy.h"
#include <sdbus-c++/sdbus-c++.h>
#include <vector>
#include <string>
#include <iostream>
#include <unistd.h>
#include <thread>
#include <chrono>
#include <cassert>
using namespace std::chrono_literals;
class PerftestClient : public sdbus::ProxyInterfaces<org::sdbuscpp::perftest_proxy>
{
public:
PerftestClient(std::string destination, std::string objectPath)
: sdbus::ProxyInterfaces<org::sdbuscpp::perftest_proxy>(std::move(destination), std::move(objectPath))
{
}
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.perftest";
const char* objectPath = "/org/sdbuscpp/perftest";
//PerftestClient client(destinationName, objectPath);
const unsigned int repetitions{2};
unsigned int msgCount = 1000;
unsigned int msgSize{};
{
PerftestClient client(destinationName, objectPath);
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);
}
}
{
PerftestClient client(destinationName, objectPath);
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);
}
}
{
PerftestClient client(destinationName, objectPath);
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);
}
}
{
PerftestClient client(destinationName, objectPath);
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

@ -0,0 +1,16 @@
<!-- 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.perftest"/>
<allow send_destination="org.sdbuscpp.perftest"/>
<allow send_interface="org.sdbuscpp.perftest"/>
</policy>
</busconfig>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<node name="/org/sdbuscpp/perftest">
<interface name="org.sdbuscpp.perftest">
<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 sdbuscpp-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 perftest_adaptor
{
public:
static constexpr const char* interfaceName = "org.sdbuscpp.perftest";
protected:
perftest_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 sdbuscpp-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 perftest_proxy
{
public:
static constexpr const char* interfaceName = "org.sdbuscpp.perftest";
protected:
perftest_proxy(sdbus::IObjectProxy& object)
: object_(object)
{
object_.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)
{
object_.callMethod("sendDataSignals").onInterface(interfaceName).withArguments(numberOfSignals, signalMsgSize);
}
std::string concatenateTwoStrings(const std::string& string1, const std::string& string2)
{
std::string result;
object_.callMethod("concatenateTwoStrings").onInterface(interfaceName).withArguments(string1, string2).storeResultsTo(result);
return result;
}
private:
sdbus::IObjectProxy& object_;
};
}} // namespaces
#endif

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

@ -0,0 +1,94 @@
/**
* (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 "perftest-adaptor.h"
#include <sdbus-c++/sdbus-c++.h>
#include <vector>
#include <string>
#include <thread>
#include <chrono>
using namespace std::chrono_literals;
std::string createRandomString(size_t length);
class PerftestServer : public sdbus::Interfaces<org::sdbuscpp::perftest_adaptor>
{
public:
PerftestServer(sdbus::IConnection& connection, std::string objectPath)
: sdbus::Interfaces<org::sdbuscpp::perftest_adaptor>(connection, std::move(objectPath))
{
}
protected:
virtual void sendDataSignals(const uint32_t& numberOfSignals, const uint32_t& signalMsgSize) override
{
auto data = createRandomString(signalMsgSize);
char digits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
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.perftest";
auto connection = sdbus::createSystemBusConnection(serviceName);
const char* objectPath = "/org/sdbuscpp/perftest";
PerftestServer server(*connection, objectPath);
connection->enterProcessingLoop();
}