Introduce sdbus-c++ v0.2.3

This commit is contained in:
Stanislav Angelovic
2017-11-27 14:13:55 +01:00
parent 32db1c22e3
commit 35f725a053
69 changed files with 9223 additions and 2 deletions

42
.gitignore vendored Normal file
View File

@ -0,0 +1,42 @@
# c++
*.o
*.lo
*.la
m4/
build/
.deps/
.dirstamp
.libs/
test/libsdbus-c++_unittests
test/libsdbus-c++_integrationtests
test/run-test-on-device.sh
#eclipse
.cproject
.settings
*.log
#autotools
sdbus-cpp.pc
*Makefile
*Makefile.in
aclocal.m4
arm-poky-linux-gnueabi-libtool
autom4te.cache
configure
config.h
config.status
config.guess
config.h.in
config.sub
compile
depcomp
install-sh
ltmain.sh
missing
stamp-h1
test-driver
.autotools
.gdbinit
sdbus-cpp_gdb_arm-poky-linux-gnueabi.launch
sdbus-c++.pc

35
.project Normal file
View File

@ -0,0 +1,35 @@
<?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>

5
AUTHORS Normal file
View File

@ -0,0 +1,5 @@
The library:
Stanislav Angelovic (stanislav.angelovic[at]kistler.com)
Stub generator:
Lukas Durfina (lukas.durfina[at]kistler.com)

View File

0
ChangeLog Normal file
View File

6
INSTALL Normal file
View File

@ -0,0 +1,6 @@
Building:
$ ./autogen.sh ${CONFIGURE_FLAGS}
$ make
Installing:
$ sudo make install

22
Makefile.am Normal file
View File

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

4
NEWS Normal file
View File

@ -0,0 +1,4 @@
sdbus-c++: D-Bus IPC C++ binding library based on sd-bus
v0.2.3:
* sdbus-c++ v0.2.3 released to the public!

1
README Normal file
View File

@ -0,0 +1 @@
See README.md

View File

@ -1,2 +1,44 @@
# sdbus-cpp
D-Bus IPC C++ binding library build on top of sd-bus
sdbus-c++
=========
sdbus-c++ is a C++ API library for D-Bus IPC, based on sd-bus implementation.
Building and installing the library
-----------------------------------
```bash
$ ./autogen.sh ${CONFIGURE_FLAGS}
$ make
$ sudo make install
```
Use `--disable-tests` flag when configuring to disable building unit and integration tests for the library.
Dependencies
------------
* `C++17` - the library uses C++17 `std::uncaught_exceptions()` feature. When building sdbus-c++ manually, make sure you use a compiler that supports that feature.
* `libsystemd` - systemd library containing sd-bus implementation. Systemd v236 at least is needed for sdbus-c++ to compile.
* `googletest` - google unit testing framework, only necessary when building tests
Licensing
---------
The library is distributed under LGPLv2.1+ license.
References/documentation
------------------------
* [D-Bus Specification](https://dbus.freedesktop.org/doc/dbus-specification.html)
* [sd-bus Overview](http://0pointer.net/blog/the-new-sd-bus-api-of-systemd.html)
* [Tutorial: Using sdbus-c++](doc/using-sdbus-c++.md)
Contributing
------------
Contributions that increase the library quality, functionality, or fix issues are very welcome. To introduce a change, please submit a merge request with a description.
Contact
-------
stanislav.angelovic[at]kistler.com

10
autogen.sh Executable file
View File

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

62
configure.ac Normal file
View File

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

727
doc/using-sdbus-c++.md Normal file
View File

@ -0,0 +1,727 @@
Using sdbus-c++ library
=======================
**Table of contents**
1. [Introduction](#introduction)
2. [Integrating sdbus-c++ into your project](#integrating-sdbus-c-into-your-project)
3. [Header files and namespaces](#header-files-and-namespaces)
4. [Error signalling and propagation](#error-signalling-and-propagation)
5. [Multiple layers of sdbus-c++ API](#multiple-layers-of-sdbus-c-api)
6. [An example: Number concatenator](#an-example-number-concatenator)
7. [Implementing the Concatenator example using basic sdbus-c++ API layer](#implementing-the-concatenator-example-using-basic-sdbus-c-api-layer)
8. [Implementing the Concatenator example using convenience sdbus-c++ API layer](#implementing-the-concatenator-example-using-convenience-sdbus-c-api-layer)
9. [Implementing the Concatenator example using sdbus-c++-generated stubs](#implementing-the-concatenator-example-using-sdbus-c-generated-stubs)
10. [Using D-Bus properties](#using-d-bus-properties)
11. [Conclusion](#conclusion)
Introduction
------------
sdbus-c++ is a C++ wrapper library built on top of [sd-bus](http://0pointer.net/blog/the-new-sd-bus-api-of-systemd.html), a lightweight D-Bus client
library implemented within systemd project. It provides D-Bus functionality on a higher level of abstraction, trying to employ C++ type system
to shift as much work as possible from the developer to the compiler.
sdbus-c++ does not cover the entire sd-bus API, but provides tools for implementing the most common functionality - RPC
method calls, signals and properties. There is room for additions and improvements, as needed and when needed.
Integrating sdbus-c++ into your project
---------------------------------------
The library build system is based on Autotools. The library supports `pkg-config`, so integrating it into your autotools project
is a two step process.
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/])
)
```
2. Update `*_CFLAGS` and `*_LDFLAGS` in Makefiles of modules that use *sdbus-c++*, for example like this:
```bash
AM_CXXFLAGS = -std=c++17 -pedantic -W -Wall @SDBUSCPP_CFLAGS@ ...
AM_LDFLAGS = @SDBUSCPP_LIBS@ ...
```
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.
Header files and namespaces
---------------------------
All sdbus-c++ header files reside in the `sdbus-c++` subdirectory within the standard include directory. Users can either include
individual header files, like so:
```cpp
#include <sdbus-c++/IConnection.h>
#include <sdbus-c++/IObjectProxy.h>
```
or just include the global header file that pulls in everything:
```cpp
#include <sdbus-c++/sdbus-c++.h>
```
All public types and functions of sdbus-c++ reside in the `sdbus` namespace.
Error signalling and propagation
--------------------------------
`sdbus::Error` exception is used to signal errors in sdbus-c++. There are two types of errors:
* D-Bus related errors, like call timeouts, failed socket allocation, etc.
* user errors, i.e. errors signalled and propagated from remote methods back to the caller.
The exception object carries the error name and error message with it.
Multiple layers of sdbus-c++ API
-------------------------------
sdbus-c++ API comes in two layers:
* the basic layer, which is almost pure wrapper layer on top of sd-bus, using mechanisms that are native to C++,
* the convenience layer, building on top of the basic layer, which aims at providing shorter, safer, and more expressive way of writing the
client code.
sdbus-c++ also ships with a stub generator tool that converts D-Bus IDL in XML format into stub code for the adaptor as well as proxy part.
Hierarchically, these stubs provide yet another layer of convenience (the "stubs layer"), making it possible for D-Bus RPC calls to look like
native C++ calls on a local object.
An example: Number concatenator
-------------------------------
Let's have an object `/org/sdbuscpp/concatenator` that implements the `org.sdbuscpp.concatenator` interface. The interface exposes the following:
* a `concatenate` method that takes a collection of integers and a separator string and returns a string that is the concatenation of all
integers from the collection using given separator,
* a `concatenated` signal that is emitted at the end of each successful concatenation.
In the following sections, we will elaborate on the ways of implementing such an object on both the server and the client side.
Implementing the Concatenator example using basic sdbus-c++ API layer
---------------------------------------------------------------------
In the basic API layer, we already have abstractions for D-Bus connections, objects and object proxies, with which we can interact via
interfaces. However, we still work with the concept of messages. To issue a method call for example, we have to go through several steps:
we have to create a method call message first, serialize method arguments into the message, and send the message at last. We get the reply
message (if applicable) in return, so we have to deserialize the return values from it manually.
Overloaded versions of C++ insertion/extraction operators are used for serialization/deserialization. That makes the client code much simpler.
### Server side
```c++
#include <sdbus-c++/sdbus-c++.h>
#include <vector>
#include <string>
// Yeah, global variable is ugly, but this is just an example and we want to access
// the concatenator instance from within the concatenate method handler to be able
// to emit signals.
sdbus::IObject* g_concatenator{};
void concatenate(sdbus::Message& msg, sdbus::Message& reply)
{
// Deserialize the collection of numbers from the message
std::vector<int> numbers;
msg >> numbers;
// Deserialize separator from the message
std::string separator;
msg >> separator;
// Return error if there are no numbers in the collection
if (numbers.empty())
throw sdbus::Error("org.sdbuscpp.Concatenator.Error", "No numbers provided");
std::string result;
for (auto number : numbers)
{
result += (result.empty() ? std::string() : separator) + std::to_string(number);
}
// Serialize resulting string to the reply
reply << result;
// Emit 'concatenated' signal
const char* interfaceName = "org.sdbuscpp.Concatenator";
auto signalMsg = g_concatenator->createSignal(interfaceName, "concatenated");
signalMsg << result;
g_concatenator->emitSignal(signalMsg);
}
int main(int argc, char *argv[])
{
// Create D-Bus connection and requests name on it.
const char* serviceName = "org.sdbuscpp.concatenator";
auto connection = sdbus::createConnection(serviceName);
// Create concatenator D-Bus object.
const char* objectPath = "/org/sdbuscpp/concatenator";
auto concatenator = sdbus::createObject(*connection, objectPath);
g_concatenator = concatenator.get();
// Register D-Bus methods and signals on the concatenator object, and exports the object.
const char* interfaceName = "org.sdbuscpp.Concatenator";
concatenator->registerMethod(interfaceName, "concatenate", "ais", "s", &concatenate);
concatenator->registerSignal(interfaceName, "concatenated", "s");
concatenator->finishRegistration();
// Run the loop on the connection.
connection->enterProcessingLoop();
}
```
### Client side
```c++
#include <sdbus-c++/sdbus-c++.h>
#include <vector>
#include <string>
#include <iostream>
#include <unistd.h>
void onConcatenated(sdbus::Message& signalMsg)
{
std::string concatenatedString;
msg >> concatenatedString;
std::cout << "Received signal with concatenated string " << concatenatedString << std::endl;
}
int main(int argc, char *argv[])
{
// Create proxy object for the concatenator object on the server side
const char* destinationName = "org.sdbuscpp.concatenator";
const char* objectPath = "/org/sdbuscpp/concatenator";
auto concatenatorProxy = sdbus::createObjectProxy(destinationName, objectPath);
// Let's subscribe for the 'concatenated' signals
const char* interfaceName = "org.sdbuscpp.Concatenator";
concatenatorProxy->registerSignalHandler(interfaceName, "concatenated", &onConcatenated);
concatenatorProxy->finishRegistration();
std::vector<int> numbers = {1, 2, 3};
std::string separator = ":";
// Invoke concatenate on given interface of the object
{
auto method = concatenatorProxy->createMethodCall(interfaceName, "concatenate");
method << numbers << separator;
auto reply = concatenatorProxy->callMethod(method);
std::string result;
reply >> result;
assert(result == "1:2:3");
}
// Invoke concatenate again, this time with no numbers and we shall get an error
{
auto method = concatenatorProxy->createMethodCall(interfaceName, "concatenate");
method << std::vector<int>() << separator;
try
{
auto reply = concatenatorProxy->callMethod(method);
assert(false);
}
catch(const sdbus::Error& e)
{
std::cerr << "Got concatenate error " << e.getName() << " with message " << e.getMessage() << std::endl;
}
}
// Give sufficient time to receive 'concatenated' signal from the first concatenate invocation
sleep(1);
return 0;
}
```
The object proxy is created without explicitly providing a D-Bus connection as an argument in its factory function. In that case, the proxy
will create its own connection and listen to signals on it in a separate thread. That means the `onConcatenated` method is invoked always
in the context of a thread different from the main thread.
Implementing the Concatenator example using convenience sdbus-c++ API layer
---------------------------------------------------------------------------
One of the major sdbus-c++ design goals is to make the sdbus-c++ API easy to use correctly, and hard to use incorrectly.
The convenience API layer abstracts the concept of underlying D-Bus messages away completely. It abstracts away D-Bus signatures. And it tries
to provide an interface that uses small, focused functions, with one or zero parameters, to form a chained function statement that reads like
a sentence to a human reading the code. To achieve that, sdbus-c++ utilizes the power of the C++ type system, so many issues are resolved at
compile time, and the run-time performance cost compared to the basic layer is close to zero.
Thus, in the end of the day, the code written using the convenience API is:
- more expressive,
- closer to the abstraction level of the problem being solved,
- shorter,
- almost as fast (if not equally fast) as one written using the basic API layer.
Rather than *how*, the code written using this layer expresses *what* it does. Let's look at code samples to see if you agree :)
### Server side
```c++
#include <sdbus-c++/sdbus-c++.h>
#include <vector>
#include <string>
// Yeah, global variable is ugly, but this is just an example and we want to access
// the concatenator instance from within the concatenate method handler to be able
// to emit signals.
sdbus::IObject* g_concatenator{};
std::string concatenate(const std::vector<int> numbers, const std::string& separator)
{
// Return error if there are no numbers in the collection
if (numbers.empty())
throw sdbus::Error("org.sdbuscpp.Concatenator.Error", "No numbers provided");
std::string result;
for (auto number : numbers)
{
result += (result.empty() ? std::string() : separator) + std::to_string(number);
}
// Emit 'concatenated' signal
const char* interfaceName = "org.sdbuscpp.Concatenator";
g_concatenator->emitSignal("concatenated").onInterface(interfaceName).withArguments(result);
return result;
}
int main(int argc, char *argv[])
{
// Create D-Bus connection and requests name on it.
const char* serviceName = "org.sdbuscpp.concatenator";
auto connection = sdbus::createConnection(serviceName);
// Create concatenator D-Bus object.
const char* objectPath = "/org/sdbuscpp/concatenator";
auto concatenator = sdbus::createObject(*connection, objectPath);
g_concatenator = concatenator.get();
// Register D-Bus methods and signals on the concatenator object, and exports the object.
const char* interfaceName = "org.sdbuscpp.Concatenator";
concatenator->registerMethod("concatenate").onInterface(interfaceName).implementedAs(&concatenate);
concatenator->registerSignal("concatenated").onInterface(interfaceName).withParameters<std::string>();
concatenator->finishRegistration();
// Run the loop on the connection.
connection->enterProcessingLoop();
}
```
### Client side
```c++
#include <sdbus-c++/sdbus-c++.h>
#include <vector>
#include <string>
#include <iostream>
#include <unistd.h>
void onConcatenated(const std::string& concatenatedString)
{
std::cout << "Received signal with concatenated string " << concatenatedString << std::endl;
}
int main(int argc, char *argv[])
{
// Create proxy object for the concatenator object on the server side
const char* destinationName = "org.sdbuscpp.concatenator";
const char* objectPath = "/org/sdbuscpp/concatenator";
auto concatenatorProxy = sdbus::createObjectProxy(destinationName, objectPath);
// Let's subscribe for the 'concatenated' signals
const char* interfaceName = "org.sdbuscpp.Concatenator";
concatenatorProxy->uponSignal("concatenated").onInterface(interfaceName).call([this](const std::string& str){ onConcatenated(str); });
concatenatorProxy->finishRegistration();
std::vector<int> numbers = {1, 2, 3};
std::string separator = ":";
// Invoke concatenate on given interface of the object
{
std::string concatenatedString;
concatenatorProxy->callMethod("concatenate").onInterface(interfaceName).withArguments(numbers, separator).storeResultsTo(concatenatedString);
assert(concatenatedString == "1:2:3");
}
// Invoke concatenate again, this time with no numbers and we shall get an error
{
try
{
concatenatorProxy->callMethod("concatenate").onInterface(interfaceName).withArguments(std::vector<int>(), separator);
assert(false);
}
catch(const sdbus::Error& e)
{
std::cerr << "Got concatenate error " << e.getName() << " with message " << e.getMessage() << std::endl;
}
}
// Give sufficient time to receive 'concatenated' signal from the first concatenate invocation
sleep(1);
return 0;
}
```
Several lines of code have shrunk into one-liners when registering/calling methods or signals. D-Bus signatures and the serialization/deserialization
of arguments from the messages is generated at compile time, by introspecting signatures of provided callbacks or deducing types of provided arguments.
Implementing the Concatenator example using sdbus-c++-generated stubs
---------------------------------------------------------------------
sdbus-c++ ships with the native stub generator tool called sdbuscpp-xml2cpp. The tool is very similar to dbusxx-xml2cpp tool that comes from
dbus-c++ project.
The generator tool takes D-Bus XML IDL description of D-Bus interfaces on its input, and can be instructed to generate one or both of these:
an adaptor header file for use at server side, and a proxy header file for use at client side. Like this:
```bash
sdbuscpp-xml2cpp database-bindings.xml --adaptor=database-server-glue.h --proxy=database-client-glue.h
```
The adaptor header file contains classes that can be used to implement described interfaces. The proxy header file contains classes that can be used
to make calls to remote objects.
### XML description of the Concatenator interface
As an example, let's look at an XML description of our Concatenator's interfaces.
```xml
<?xml version="1.0" encoding="UTF-8"?>
<node name="/org/sdbuscpp/concatenator">
<interface name="org.sdbuscpp.Concatenator">
<method name="concatenate">
<arg type="ai" name="numbers" direction="in" />
<arg type="s" name="separator" direction="in" />
<arg type="s" name="concatenatedString" direction="out" />
</method>
<signal name="concatenated">
<arg type="s" name="concatenatedString" />
</signal>
</interface>
</node>
```
After running this through the stubs generator, we get the stub code that is described in the following two subsections.
### concatenator-server-glue.h
There is specific class for each interface in the XML IDL file. The class is de facto an interface which shall be implemented by inheriting from it.
The class' constructor takes care of registering all methods, signals and properties. For each D-Bus method there is a pure virtual member function.
These pure virtual functions must be implemented in the child class. For each signal, there is a public function member that emits this signal.
```cpp
/*
* This file was automatically generated by sdbuscpp-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__concatenator_server_glue_h__adaptor__H__
#define __sdbuscpp__concatenator_server_glue_h__adaptor__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace sdbuscpp {
class Concatenator_adaptor
{
public:
static constexpr const char* interfaceName = "org.sdbuscpp.Concatenator";
protected:
Concatenator_adaptor(sdbus::IObject& object)
: object_(object)
{
object_.registerMethod("concatenate").onInterface(interfaceName).implementedAs([this](const std::vector<int32_t>& numbers, const std::string& separator){ return this->concatenate(numbers, separator); });
object_.registerSignal("concatenated").onInterface(interfaceName).withParameters<std::string>();
}
public:
void concatenated(const std::string& concatenatedString)
{
object_.emitSignal("concatenated").onInterface(interfaceName).withArguments(concatenatedString);
}
private:
virtual std::string concatenate(const std::vector<int32_t>& numbers, const std::string& separator) = 0;
private:
sdbus::IObject& object_;
};
}} // namespaces
#endif
```
### concatenator-client-glue.h
Analogously to the adaptor classes described above, there is specific class for each interface in the XML IDL file. The class is de facto a proxy
to the concrete interface of a remote object. For each D-Bus signal there is a pure virtual member function whose body must be provided in a child
class. For each method, there is a public function member that calls the method remotely.
```cpp
/*
* This file was automatically generated by sdbuscpp-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__concatenator_client_glue_h__proxy__H__
#define __sdbuscpp__concatenator_client_glue_h__proxy__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace sdbuscpp {
class Concatenator_proxy
{
public:
static constexpr const char* interfaceName = "org.sdbuscpp.Concatenator";
protected:
Concatenator_proxy(sdbus::IObjectProxy& object)
: object_(object)
{
object_.uponSignal("concatenated").onInterface(interfaceName).call([this](const std::string& concatenatedString){ this->onConcatenated(concatenatedString); });
}
virtual void onConcatenated(const std::string& concatenatedString) = 0;
public:
std::string concatenate(const std::vector<int32_t>& numbers, const std::string& separator)
{
std::string result;
object_.callMethod("concatenate").onInterface(interfaceName).withArguments(numbers, separator).storeResultsTo(result);
return result;
}
private:
sdbus::IObjectProxy& object_;
};
}} // namespaces
#endif
```
### Providing server implementation based on generated adaptors
To implement a D-Bus object that implements all its D-Bus interfaces, we shall create a class representing the object that inherits from all
corresponding `*_adaptor` classes and implements all pure virtual member functions. Specifically, the object class shall inherit from the
`Interfaces` template class, the template arguments of which are individual adaptor classes. The `Interfaces` is just a convenience class that
hides a few boiler-plate details. For example, in its constructor, it creates an `Object` instance, it takes care of proper initialization of
all adaptor superclasses, and exports the object finally.
```cpp
#include <sdbus-c++/sdbus-c++.h>
#include "concatenator-server-glue.h"
class Concatenator : public sdbus::Interfaces<org::sdbuscpp::Concatenator_adaptor /*, more adaptor classes if there are more interfaces*/>
{
public:
Concatenator(sdbus::IConnection& connection, std::string objectPath)
: sdbus::Interfaces<org::sdbuscpp::Concatenator_adaptor>(connection, std::move(objectPath))
{
}
protected:
std::string concatenate(const std::vector<int32_t>& numbers, const std::string& separator) override
{
// Return error if there are no numbers in the collection
if (numbers.empty())
throw sdbus::Error("org.sdbuscpp.Concatenator.Error", "No numbers provided");
// Concatenate the numbers
std::string result;
for (auto number : numbers)
{
result += (result.empty() ? std::string() : separator) + std::to_string(number);
}
// Emit the 'concatenated' signal with the resulting string
concatenated(result);
// Return the resulting string
return result;
}
};
```
That's it. We now have an implementation of a D-Bus object implementing `org.sdbuscpp.Concatenator` interface. Let's now create a service
publishing the object.
```cpp
#include "Concatenator.h"
int main(int argc, char *argv[])
{
// Create D-Bus connection and requests name on it.
const char* serviceName = "org.sdbuscpp.concatenator";
auto connection = sdbus::createConnection(serviceName);
// Create concatenator D-Bus object.
const char* objectPath = "/org/sdbuscpp/concatenator";
Concatenator concatenator(*connection, objectPath);
// Run the loop on the connection.
connection->enterProcessingLoop();
}
```
It's that simple!
### Providing client implementation based on generated proxies
To implement a proxy for a remote D-Bus object, we shall create a class representing the object proxy that inherits from all corresponding
`*_proxy` classes and -- if applicable -- implements all pure virtual member functions. Specifically, the object proxy class shall inherit
from the `ProxyInterfaces` template class. As its template arguments we shall provide all proxy classes. The `ProxyInterfaces` is just a
convenience class that hides a few boiler-plate details. For example, in its constructor, it creates an `ObjectProxy` instance, and it takes
care of proper initialization of all proxy superclasses.
```cpp
#include <sdbus-c++/sdbus-c++.h>
#include "concatenator-client-glue.h"
class ConcatenatorProxy : public sdbus::ProxyInterfaces<org::sdbuscpp::Concatenator_proxy /*, more proxy classes if there are more interfaces*/>
{
public:
ConcatenatorProxy(std::string destination, std::string objectPath)
: sdbus::ProxyInterfaces<org::sdbuscpp::Concatenator_proxy>(std::move(destination), std::move(objectPath))
{
}
protected:
void onConcatenated(const std::string& concatenatedString) override
{
std::cout << "Received signal with concatenated string " << concatenatedString << std::endl;
}
};
```
Now let's use this proxy to make remote calls and listen to signals in a real application.
```cpp
#include "ConcatenatorProxy.h"
int main(int argc, char *argv[])
{
// Create proxy object for the concatenator object on the server side
const char* destinationName = "org.sdbuscpp.concatenator";
const char* objectPath = "/org/sdbuscpp/concatenator";
ConcatenatorProxy concatenatorProxy(destinationName, objectPath);
std::vector<int> numbers = {1, 2, 3};
std::string separator = ":";
// Invoke concatenate with some numbers
auto concatenatedString = concatenatorProxy.concatenate(numbers, separator);
assert(concatenatedString == "1:2:3");
// Invoke concatenate again, this time with no numbers and we shall get an error
try
{
auto concatenatedString = concatenatorProxy.concatenate(std::vector<int>(), separator);
assert(false);
}
catch(const sdbus::Error& e)
{
std::cerr << "Got concatenate error " << e.getName() << " with message " << e.getMessage() << std::endl;
}
// Give sufficient time to receive 'concatenated' signal from the first concatenate invocation
sleep(1);
return 0;
}
```
Using D-Bus properties
----------------------
Defining and working with D-Bus properties using XML description is quite easy.
### Defining a property in the XML
A property element has no arg child element. It just has the attributes name, type and access, which are all mandatory. The access attribute allows the values readwrite, read, and write.
An example of a read-write property `status`:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<node name="/org/sdbuscpp/propertyprovider">
<interface name="org.sdbuscpp.PropertyProvider">
<!--...-->
<property name="status" type="u" access="readwrite"/>
<!--...-->
</interface>
</node>
```
### Generated stubs
This is how generated adaptor and proxy classes would look like with the read-write `status` property. The adaptor:
```cpp
class PropertyProvider_adaptor
{
/*...*/
public:
PropertyProvider_adaptor(sdbus::IObject& object)
: object_(object)
{
object_.registerProperty("status").onInterface(INTERFACE_NAME).withGetter([this](){ return this->status(); }).withSetter([this](const uint32_t& value){ this->status(value); });
}
private:
// property getter
virtual uint32_t status() = 0;
// property setter
virtual void status(const uint32_t& value) = 0;
/*...*/
};
#endif
```
The proxy:
```cpp
class PropertyProvider_proxy
{
/*...*/
public:
// getting the property value
uint32_t status()
{
return object_.getProperty("status").onInterface(INTERFACE_NAME);
}
// setting the property value
void status(const uint32_t& value)
{
object_.setProperty("status").onInterface(INTERFACE_NAME).toValue(value);
}
/*...*/
};
```
When implementing the adaptor, we simply need to provide the body for `status` getter and setter method by overriding them. Then in the proxy, we just call them.
Conclusion
----------
There is no conclusion. Happy journeys by D-Bus with sdbus-c++!

23
include/Makefile.am Normal file
View File

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

View File

@ -0,0 +1,169 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file ConvenienceClasses.h
*
* Created on: Jan 19, 2017
* 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_CONVENIENCECLASSES_H_
#define SDBUS_CXX_CONVENIENCECLASSES_H_
#include <sdbus-c++/Message.h>
#include <string>
// Forward declarations
namespace sdbus {
class IObject;
class IObjectProxy;
class Variant;
}
namespace sdbus {
class MethodRegistrator
{
public:
MethodRegistrator(IObject& object, const std::string& methodName);
MethodRegistrator& onInterface(const std::string& interfaceName);
template <typename _Function> void implementedAs(_Function&& callback);
private:
IObject& object_;
const std::string& methodName_;
std::string interfaceName_;
};
class SignalRegistrator
{
public:
SignalRegistrator(IObject& object, const std::string& signalName);
SignalRegistrator(SignalRegistrator&& other) = default;
SignalRegistrator& operator=(SignalRegistrator&& other) = default;
~SignalRegistrator() noexcept(false);
SignalRegistrator& onInterface(std::string interfaceName);
template <typename... _Args> void withParameters();
private:
IObject& object_;
const std::string& signalName_;
std::string interfaceName_;
std::string signalSignature_;
int exceptions_{}; // Number of active exceptions when SignalRegistrator is constructed
};
class PropertyRegistrator
{
public:
PropertyRegistrator(IObject& object, const std::string& propertyName);
PropertyRegistrator(PropertyRegistrator&& other) = default;
PropertyRegistrator& 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);
private:
IObject& object_;
const std::string& propertyName_;
std::string interfaceName_;
std::string propertySignature_;
property_get_callback getter_;
property_set_callback setter_;
int exceptions_{}; // Number of active exceptions when PropertyRegistrator is constructed
};
class SignalEmitter
{
public:
SignalEmitter(IObject& object, const std::string& signalName);
SignalEmitter(SignalEmitter&& other) = default;
SignalEmitter& operator=(SignalEmitter&& other) = default;
~SignalEmitter() noexcept(false);
SignalEmitter& onInterface(const std::string& interfaceName);
template <typename... _Args> void withArguments(_Args&&... args);
private:
IObject& object_;
const std::string& signalName_;
Message signal_;
int exceptions_{}; // Number of active exceptions when SignalEmitter is constructed
};
class MethodInvoker
{
public:
MethodInvoker(IObjectProxy& objectProxy, const std::string& methodName);
MethodInvoker(MethodInvoker&& other) = default;
MethodInvoker& operator=(MethodInvoker&& other) = default;
~MethodInvoker() noexcept(false);
MethodInvoker& onInterface(const std::string& interfaceName);
template <typename... _Args> MethodInvoker& withArguments(_Args&&... args);
template <typename... _Args> void storeResultsTo(_Args&... args);
private:
IObjectProxy& objectProxy_;
const std::string& methodName_;
Message method_;
int exceptions_{}; // Number of active exceptions when MethodInvoker is constructed
bool methodCalled_{};
};
class SignalSubscriber
{
public:
SignalSubscriber(IObjectProxy& objectProxy, const std::string& signalName);
SignalSubscriber& onInterface(const std::string& interfaceName);
template <typename _Function> void call(_Function&& callback);
private:
IObjectProxy& objectProxy_;
std::string signalName_;
std::string interfaceName_;
};
class PropertyGetter
{
public:
PropertyGetter(IObjectProxy& objectProxy, const std::string& propertyName);
sdbus::Variant onInterface(const std::string& interfaceName);
private:
IObjectProxy& objectProxy_;
std::string propertyName_;
};
class PropertySetter
{
public:
PropertySetter(IObjectProxy& objectProxy, const std::string& propertyName);
PropertySetter& onInterface(const std::string& interfaceName);
template <typename _Value> void toValue(const _Value& value);
private:
IObjectProxy& objectProxy_;
const std::string& propertyName_;
std::string interfaceName_;
};
}
#endif /* SDBUS_CXX_CONVENIENCECLASSES_H_ */

View File

@ -0,0 +1,401 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file ConvenienceClasses.inl
*
* Created on: Dec 19, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CPP_CONVENIENCECLASSES_INL_
#define SDBUS_CPP_CONVENIENCECLASSES_INL_
#include <sdbus-c++/IObject.h>
#include <sdbus-c++/IObjectProxy.h>
#include <sdbus-c++/Message.h>
#include <sdbus-c++/Types.h>
#include <sdbus-c++/TypeTraits.h>
#include <sdbus-c++/Error.h>
#include <string>
#include <tuple>
/*#include <exception>*/
namespace sdbus {
inline MethodRegistrator::MethodRegistrator(IObject& object, const std::string& methodName)
: object_(object)
, methodName_(methodName)
{
}
inline MethodRegistrator& MethodRegistrator::onInterface(const std::string& interfaceName)
{
interfaceName_ = interfaceName;
return *this;
}
template <typename _Function>
inline void MethodRegistrator::implementedAs(_Function&& callback)
{
SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when registering a DBus method", EINVAL);
object_.registerMethod( interfaceName_
, methodName_
, signature_of_function_input_arguments<_Function>::str()
, signature_of_function_output_arguments<_Function>::str()
, [callback = std::forward<_Function>(callback)](Message& msg, Message& reply)
{
// Create a tuple of callback input arguments' types, which will be used
// as a storage for the argument values deserialized from the message.
tuple_of_function_input_arg_types_t<_Function> inputArgs;
// Deserialize input arguments from the message into the tuple
msg >> inputArgs;
// Invoke callback with input arguments from the tuple.
// For callbacks returning a non-void value, `apply' also returns that value.
// For callbacks returning void, `apply' returns an empty tuple.
auto ret = apply(callback, inputArgs); // We don't yet have C++17's std::apply :-(
// 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;
});
}
// Moved into the library to isolate from C++17 dependency
/*
inline SignalRegistrator::SignalRegistrator(IObject& object, std::string signalName)
: object_(object)
, signalName_(std::move(signalName))
, exceptions_(std::uncaught_exceptions())
{
}
inline SignalRegistrator::~SignalRegistrator() noexcept(false) // since C++11, destructors must
{ // explicitly be allowed to throw
// Don't register the signal if SignalRegistrator threw an exception in one of its methods
if (std::uncaught_exceptions() != exceptions_)
return;
if (interfaceName_.empty())
throw sdbus::Exception("DBus interface not specified when registering a DBus signal");
// registerSignal() can throw. But as the SignalRegistrator shall always be used as an unnamed,
// temporary object, i.e. not as a stack-allocated object, the double-exception situation
// shall never happen. I.e. it should not happen that this destructor is directly called
// in the stack-unwinding process of another flying exception (which would lead to immediate
// termination). It can be called indirectly in the destructor of another object, but that's
// fine and safe provided that the caller catches exceptions thrown from here.
// Therefore, we can allow registerSignal() to throw even if we are in the destructor.
// Bottomline is, to be on the safe side, the caller must take care of catching and reacting
// to the exception thrown from here if the caller is a destructor itself.
object_.registerSignal(interfaceName_, signalName_, signalSignature_);
}
*/
inline SignalRegistrator& SignalRegistrator::onInterface(std::string interfaceName)
{
interfaceName_ = std::move(interfaceName);
return *this;
}
template <typename... _Args>
inline void SignalRegistrator::withParameters()
{
signalSignature_ = signature_of_function_input_arguments<void(_Args...)>::str();
}
// Moved into the library to isolate from C++17 dependency
/*
inline PropertyRegistrator::PropertyRegistrator(IObject& object, std::string propertyName)
: object_(object)
, propertyName_(std::move(propertyName))
, exceptions_(std::uncaught_exceptions())
{
}
inline PropertyRegistrator::~PropertyRegistrator() noexcept(false) // since C++11, destructors must
{ // explicitly be allowed to throw
// Don't register the property if PropertyRegistrator threw an exception in one of its methods
if (std::uncaught_exceptions() != exceptions_)
return;
SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when registering a DBus property", EINVAL);
// registerProperty() can throw. But as the PropertyRegistrator shall always be used as an unnamed,
// temporary object, i.e. not as a stack-allocated object, the double-exception situation
// shall never happen. I.e. it should not happen that this destructor is directly called
// in the stack-unwinding process of another flying exception (which would lead to immediate
// termination). It can be called indirectly in the destructor of another object, but that's
// fine and safe provided that the caller catches exceptions thrown from here.
// Therefore, we can allow registerProperty() to throw even if we are in the destructor.
// Bottomline is, to be on the safe side, the caller must take care of catching and reacting
// to the exception thrown from here if the caller is a destructor itself.
object_.registerProperty( std::move(interfaceName_)
, std::move(propertyName_)
, std::move(propertySignature_)
, std::move(getter_)
, std::move(setter_) );
}
*/
inline PropertyRegistrator& PropertyRegistrator::onInterface(const std::string& interfaceName)
{
interfaceName_ = interfaceName;
return *this;
}
template <typename _Function>
inline PropertyRegistrator& PropertyRegistrator::withGetter(_Function&& callback)
{
static_assert(function_traits<_Function>::arity == 0, "Property getter function must not take any arguments");
static_assert(!std::is_void<function_result_t<_Function>>::value, "Property getter function must return property value");
if (propertySignature_.empty())
propertySignature_ = signature_of_function_output_arguments<_Function>::str();
getter_ = [callback = std::forward<_Function>(callback)](Message& msg)
{
// Get the propety value and serialize it into the message
msg << callback();
};
return *this;
}
template <typename _Function>
inline PropertyRegistrator& PropertyRegistrator::withSetter(_Function&& callback)
{
static_assert(function_traits<_Function>::arity == 1, "Property setter function must take one parameter - the property value");
static_assert(std::is_void<function_result_t<_Function>>::value, "Property setter function must not return any value");
if (propertySignature_.empty())
propertySignature_ = signature_of_function_input_arguments<_Function>::str();
setter_ = [callback = std::forward<_Function>(callback)](Message& msg)
{
// Default-construct property value
using property_type = function_argument_t<_Function, 0>;
std::decay_t<property_type> property;
// Deserialize property value from the message
msg >> property;
// Invoke setter with the value
callback(property);
};
return *this;
}
// Moved into the library to isolate from C++17 dependency
/*
inline SignalEmitter::SignalEmitter(IObject& object, const std::string& signalName)
: object_(object)
, signalName_(signalName)
, exceptions_(std::uncaught_exceptions())
{
}
inline SignalEmitter::~SignalEmitter() noexcept(false) // since C++11, destructors must
{ // explicitly be allowed to throw
// Don't emit the signal if SignalEmitter threw an exception in one of its methods
if (std::uncaught_exceptions() != exceptions_)
return;
if (!signal_.isValid())
throw sdbus::Exception("DBus interface not specified when emitting a DBus signal");
// emitSignal() can throw. But as the SignalEmitter 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 emitSignal() 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_.emitSignal(signal_);
}
*/
inline SignalEmitter& SignalEmitter::onInterface(const std::string& interfaceName)
{
signal_ = object_.createSignal(interfaceName, signalName_);
return *this;
}
template <typename... _Args>
inline void SignalEmitter::withArguments(_Args&&... args)
{
SDBUS_THROW_ERROR_IF(!signal_.isValid(), "DBus interface not specified when emitting a DBus signal", EINVAL);
detail::serialize_pack(signal_, std::forward<_Args>(args)...);
}
// Moved into the library to isolate from C++17 dependency
/*
inline MethodInvoker::MethodInvoker(IObjectProxy& objectProxy, const std::string& methodName)
: objectProxy_(objectProxy)
, methodName_(methodName)
, exceptions_(std::uncaught_exceptions())
{
}
inline MethodInvoker::~MethodInvoker() noexcept(false) // since C++11, destructors must
{ // explicitly be allowed to throw
// Don't call the method if it has been called already or if MethodInvoker
// threw an exception in one of its methods
if (methodCalled_ || std::uncaught_exceptions() != exceptions_)
return;
if (!method_.isValid())
throw sdbus::Exception("DBus interface not specified when calling a DBus method");
// callMethod() can throw. But as the MethodInvoker 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 callMethod() to throw even if we are in the destructor.
// Bottomline is, to be on the safe side, the caller must take care of catching and reacting
// to the exception thrown from here if the caller is a destructor itself.
objectProxy_.callMethod(method_);
}
*/
inline MethodInvoker& MethodInvoker::onInterface(const std::string& interfaceName)
{
method_ = objectProxy_.createMethodCall(interfaceName, methodName_);
return *this;
}
template <typename... _Args>
inline MethodInvoker& MethodInvoker::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... _Args>
inline void MethodInvoker::storeResultsTo(_Args&... args)
{
SDBUS_THROW_ERROR_IF(!method_.isValid(), "DBus interface not specified when calling a DBus method", EINVAL);
auto reply = objectProxy_.callMethod(method_);
methodCalled_ = true;
detail::deserialize_pack(reply, args...);
}
inline SignalSubscriber::SignalSubscriber(IObjectProxy& objectProxy, const std::string& signalName)
: objectProxy_(objectProxy)
, signalName_(signalName)
{
}
inline SignalSubscriber& SignalSubscriber::onInterface(const std::string& interfaceName)
{
interfaceName_ = interfaceName;
return *this;
}
template <typename _Function>
inline void SignalSubscriber::call(_Function&& callback)
{
SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when subscribing to a signal", EINVAL);
objectProxy_.registerSignalHandler( interfaceName_
, signalName_
, [callback = std::forward<_Function>(callback)](Message& signal)
{
// Create a tuple of callback input arguments' types, which will be used
// as a storage for the argument values deserialized from the signal message.
tuple_of_function_input_arg_types_t<_Function> signalArgs;
// Deserialize input arguments from the signal message into the tuple
signal >> signalArgs;
// Invoke callback with input arguments from the tuple.
apply(callback, signalArgs); // We don't yet have C++17's std::apply :-(
});
}
inline PropertyGetter::PropertyGetter(IObjectProxy& objectProxy, const std::string& propertyName)
: objectProxy_(objectProxy)
, propertyName_(propertyName)
{
}
inline sdbus::Variant PropertyGetter::onInterface(const std::string& interfaceName)
{
sdbus::Variant var;
objectProxy_
.callMethod("Get")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(interfaceName, propertyName_)
.storeResultsTo(var);
return var;
}
inline PropertySetter::PropertySetter(IObjectProxy& objectProxy, const std::string& propertyName)
: objectProxy_(objectProxy)
, propertyName_(propertyName)
{
}
inline PropertySetter& PropertySetter::onInterface(const std::string& interfaceName)
{
interfaceName_ = interfaceName;
return *this;
}
template <typename _Value>
inline void PropertySetter::toValue(const _Value& value)
{
SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when setting a property", EINVAL);
objectProxy_
.callMethod("Set")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(interfaceName_, propertyName_, sdbus::Variant{value});
}
}
#endif /* SDBUS_CPP_CONVENIENCECLASSES_INL_ */

76
include/sdbus-c++/Error.h Executable file
View File

@ -0,0 +1,76 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file ConvenienceClasses.h
*
* Created on: Nov 8, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_ERROR_H_
#define SDBUS_CXX_ERROR_H_
#include <stdexcept>
namespace sdbus {
/********************************************//**
* @class Error
*
* Represents a common sdbus-c++ exception.
*
***********************************************/
class Error
: public std::runtime_error
{
public:
Error(const std::string& name, const std::string& message)
: std::runtime_error("[" + name + "] " + message)
, name_(name)
, message_(message)
{
}
const std::string& getName() const
{
return name_;
}
const std::string& getMessage() const
{
return message_;
}
private:
std::string name_;
std::string message_;
};
sdbus::Error createError(int errNo, const std::string& customMsg);
}
#define SDBUS_THROW_ERROR(_MSG, _ERRNO) \
throw sdbus::createError((_ERRNO), (_MSG)) \
/**/
#define SDBUS_THROW_ERROR_IF(_COND, _MSG, _ERRNO) \
if (_COND) SDBUS_THROW_ERROR((_MSG), (_ERRNO)) \
/**/
#endif /* SDBUS_CXX_ERROR_H_ */

View File

@ -0,0 +1,157 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file IConnection.h
*
* Created on: Nov 8, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_ICONNECTION_H_
#define SDBUS_CXX_ICONNECTION_H_
//#include <cstdint>
#include <string>
#include <memory>
namespace sdbus {
/********************************************//**
* @class IConnection
*
* An interface to D-Bus bus connection. Incorporates implementation
* of both synchronous and asynchronous processing loop.
*
* All methods throw @sdbus::Error in case of failure. The class is
* thread-aware, but not thread-safe.
*
***********************************************/
class IConnection
{
public:
/*!
* @brief Requests D-Bus name on the connection
*
* @param[in] name Name to request
*
* @throws sdbus::Error in case of failure
*/
virtual void requestName(const std::string& name) = 0;
/*!
* @brief Releases D-Bus name on the connection
*
* @param[in] name Name to release
*
* @throws sdbus::Error in case of failure
*/
virtual void releaseName(const std::string& name) = 0;
/*!
* @brief Enters the D-Bus processing loop
*
* The incoming D-Bus messages are processed in the loop. The method
* blocks indefinitely, until unblocked via @leaveProcessingLoop.
*
* @throws sdbus::Error in case of failure
*/
virtual void enterProcessingLoop() = 0;
/*!
* @brief Enters the D-Bus processing loop in a separate thread
*
* The same as @enterProcessingLoop, except that it doesn't block
* because it runs the loop in a separate thread managed internally.
*/
virtual void enterProcessingLoopAsync() = 0;
/*!
* @brief Leaves the D-Bus processing loop
*
* Ends the previously started processing loop.
*
* @throws sdbus::Error in case of failure
*/
virtual void leaveProcessingLoop() = 0;
inline virtual ~IConnection() = 0;
};
IConnection::~IConnection() {}
/*!
* @brief Creates/opens D-Bus system connection
*
* @return Connection instance
*
* @throws sdbus::Error in case of failure
*/
std::unique_ptr<sdbus::IConnection> createConnection();
/*!
* @brief Creates/opens D-Bus system connection with a name
*
* @param[in] name Name to request on the connection after its opening
* @return Connection instance
*
* @throws sdbus::Error in case of failure
*/
std::unique_ptr<sdbus::IConnection> createConnection(const std::string& name);
/*!
* @brief Creates/opens D-Bus system connection
*
* @return Connection instance
*
* @throws sdbus::Error in case of failure
*/
std::unique_ptr<sdbus::IConnection> createSystemBusConnection();
/*!
* @brief Creates/opens D-Bus system connection with a name
*
* @param[in] name Name to request on the connection after its opening
* @return Connection instance
*
* @throws sdbus::Error in case of failure
*/
std::unique_ptr<sdbus::IConnection> createSystemBusConnection(const std::string& name);
/*!
* @brief Creates/opens D-Bus session connection
*
* @return Connection instance
*
* @throws sdbus::Error in case of failure
*/
std::unique_ptr<sdbus::IConnection> createSessionBusConnection();
/*!
* @brief Creates/opens D-Bus session connection with a name
*
* @param[in] name Name to request on the connection after its opening
* @return Connection instance
*
* @throws sdbus::Error in case of failure
*/
std::unique_ptr<sdbus::IConnection> createSessionBusConnection(const std::string& name);
}
#endif /* SDBUS_CXX_ICONNECTION_H_ */

279
include/sdbus-c++/IObject.h Executable file
View File

@ -0,0 +1,279 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file IObject.h
*
* Created on: Nov 8, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_IOBJECT_H_
#define SDBUS_CXX_IOBJECT_H_
#include <sdbus-c++/ConvenienceClasses.h>
#include <sdbus-c++/TypeTraits.h>
#include <functional>
#include <string>
#include <memory>
// Forward declarations
namespace sdbus {
class Message;
class IConnection;
}
namespace sdbus {
/********************************************//**
* @class IObject
*
* An interface to D-Bus object. Provides API for registration
* of methods, signals, properties, and for emitting signals.
*
* All methods throw @c sdbus::Error in case of failure. The class is
* thread-aware, but not thread-safe.
*
***********************************************/
class IObject
{
public:
/*!
* @brief Registers method that the object will provide on D-Bus
*
* @param[in] interfaceName Name of an interface that the method will belong to
* @param[in] methodName Name of the method
* @param[in] inputSignature D-Bus signature of method input parameters
* @param[in] outputSignature D-Bus signature of method output parameters
* @param[in] methodCallback Callback that implements the body of the method
*
* @throws sdbus::Error in case of failure
*/
virtual void registerMethod( const std::string& interfaceName
, const std::string& methodName
, const std::string& inputSignature
, const std::string& outputSignature
, method_callback methodCallback ) = 0;
/*!
* @brief Registers signal that the object will emit on D-Bus
*
* @param[in] interfaceName Name of an interface that the signal will fall under
* @param[in] signalName Name of the signal
* @param[in] signature D-Bus signature of signal parameters
*
* @throws sdbus::Error in case of failure
*/
virtual void registerSignal( const std::string& interfaceName
, const std::string& signalName
, const std::string& signature ) = 0;
/*!
* @brief Registers read-only property that the object will provide on D-Bus
*
* @param[in] interfaceName Name of an interface that the property will fall under
* @param[in] propertyName Name of the property
* @param[in] signature D-Bus signature of property parameters
* @param[in] getCallback Callback that implements the body of the property getter
*
* @throws sdbus::Error in case of failure
*/
virtual void registerProperty( const std::string& interfaceName
, const std::string& propertyName
, const std::string& signature
, property_get_callback getCallback ) = 0;
/*!
* @brief Registers read/write property that the object will provide on D-Bus
*
* @param[in] interfaceName Name of an interface that the property will fall under
* @param[in] propertyName Name of the property
* @param[in] signature D-Bus signature of property parameters
* @param[in] getCallback Callback that implements the body of the property getter
* @param[in] setCallback Callback that implements the body of the property setter
*
* @throws sdbus::Error in case of failure
*/
virtual void registerProperty( const std::string& interfaceName
, const std::string& propertyName
, const std::string& signature
, property_get_callback getCallback
, property_set_callback setCallback ) = 0;
/*!
* @brief Finishes the registration and exports object API on D-Bus
*
* The method exports all up to now registered methods, signals and properties on D-Bus.
* Must be called only once, after all methods, signals and properties have been registered.
*
* @throws sdbus::Error in case of failure
*/
virtual void finishRegistration() = 0;
/*!
* @brief Creates a signal message
*
* @param[in] interfaceName Name of an interface that the signal belongs under
* @param[in] signalName Name of the signal
* @return A signal message
*
* Serialize signal arguments into the returned message and emit the signal by passing
* the message with serialized arguments to the @c emitSignal function.
* Alternatively, use higher-level API @c emitSignal(const std::string& signalName) defined below.
*
* @throws sdbus::Error in case of failure
*/
virtual Message createSignal(const std::string& interfaceName, const std::string& signalName) = 0;
/*!
* @brief Emits signal on D-Bus
*
* @param[in] message Signal message to be sent out
*
* Note: To avoid messing with messages, use higher-level API defined below.
*
* @throws sdbus::Error in case of failure
*/
virtual void emitSignal(const sdbus::Message& message) = 0;
/*!
* @brief Registers method that the object will provide on D-Bus
*
* @param[in] methodName Name of the method
* @return A helper object for convenient registration of the method
*
* This is a high-level, convenience way of registering D-Bus methods that abstracts
* from the D-Bus message concept. Method arguments/return value are automatically (de)serialized
* in a message and D-Bus signatures automatically deduced from the parameters and return type
* of the provided native method implementation callback.
*
* Example of use:
* @code
* object.registerMethod("doFoo").onInterface("com.kistler.foo").implementedAs([this](int value){ return this->doFoo(value); });
* @endcode
*
* @throws sdbus::Error in case of failure
*/
MethodRegistrator registerMethod(const std::string& methodName);
/*!
* @brief Registers signal that the object will provide on D-Bus
*
* @param[in] signalName Name of the signal
* @return A helper object for convenient registration of the signal
*
* This is a high-level, convenience way of registering D-Bus signals that abstracts
* from the D-Bus message concept. Signal arguments are automatically (de)serialized
* in a message and D-Bus signatures automatically deduced from the provided native parameters.
*
* Example of use:
* @code
* object.registerSignal("paramChange").onInterface("com.kistler.foo").withParameters<std::map<int32_t, std::string>>();
* @endcode
*
* @throws sdbus::Error in case of failure
*/
SignalRegistrator registerSignal(const std::string& signalName);
/*!
* @brief Registers property that the object will provide on D-Bus
*
* @param[in] propertyName Name of the property
* @return A helper object for convenient registration of the property
*
* This is a high-level, convenience way of registering D-Bus properties that abstracts
* from the D-Bus message concept. Property arguments are automatically (de)serialized
* in a message and D-Bus signatures automatically deduced from the provided native callbacks.
*
* Example of use:
* @code
* object_.registerProperty("state").onInterface("com.kistler.foo").withGetter([this](){ return this->state(); });
* @endcode
*
* @throws sdbus::Error in case of failure
*/
PropertyRegistrator registerProperty(const std::string& propertyName);
/*!
* @brief Emits signal on D-Bus
*
* @param[in] signalName Name of the signal
* @return A helper object for convenient emission of signals
*
* This is a high-level, convenience way of emitting D-Bus signals that abstracts
* from the D-Bus message concept. Signal arguments are automatically serialized
* in a message and D-Bus signatures automatically deduced from the provided native arguments.
*
* Example of use:
* @code
* int arg1 = ...;
* double arg2 = ...;
* object_.emitSignal("fooSignal").onInterface("com.kistler.foo").withArguments(arg1, arg2);
* @endcode
*
* @throws sdbus::Error in case of failure
*/
SignalEmitter emitSignal(const std::string& signalName);
virtual ~IObject() = 0;
};
inline MethodRegistrator IObject::registerMethod(const std::string& methodName)
{
return MethodRegistrator(*this, methodName);
}
inline SignalRegistrator IObject::registerSignal(const std::string& signalName)
{
return SignalRegistrator(*this, std::move(signalName));
}
inline PropertyRegistrator IObject::registerProperty(const std::string& propertyName)
{
return PropertyRegistrator(*this, std::move(propertyName));
}
inline SignalEmitter IObject::emitSignal(const std::string& signalName)
{
return SignalEmitter(*this, signalName);
}
inline IObject::~IObject() {}
/*!
* @brief Creates instance representing a D-Bus object
*
* @param[in] connection D-Bus connection to be used by the object
* @param[in] objectPath Path of the D-Bus object
* @return Pointer to the object representation instance
*
* The provided connection will be used by the object to export methods,
* issue signals and provide properties.
*
* Code example:
* @code
* auto proxy = sdbus::createObject(connection, "/com/kistler/foo");
* @endcode
*/
std::unique_ptr<sdbus::IObject> createObject(sdbus::IConnection& connection, std::string objectPath);
}
#include <sdbus-c++/ConvenienceClasses.inl>
#endif /* SDBUS_CXX_IOBJECT_H_ */

249
include/sdbus-c++/IObjectProxy.h Executable file
View File

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

View File

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

View File

@ -0,0 +1,84 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file Introspection.h
*
* Created on: Dec 13, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_INTROSPECTION_H_
#define SDBUS_CXX_INTROSPECTION_H_
#include <sdbus-c++/IObject.h>
#include <sdbus-c++/IObjectProxy.h>
#include <string>
namespace sdbus {
// Proxy for introspection
class introspectable_proxy
{
static constexpr const char* interfaceName = "org.freedesktop.DBus.Introspectable";
protected:
introspectable_proxy(sdbus::IObjectProxy& object)
: object_(object)
{
}
public:
std::string Introspect()
{
std::string xml;
object_.callMethod("Introspect").onInterface(interfaceName).storeResultsTo(xml);
return xml;
}
private:
sdbus::IObjectProxy& object_;
};
// Adaptor is not necessary if we want to rely on sdbus-provided introspection
// class introspectable_adaptor
// {
// static constexpr const char* interfaceName = "org.freedesktop.DBus.Introspectable";
//
// protected:
// introspectable_adaptor(sdbus::IObject& object)
// : object_(object)
// {
// object_.registerMethod("Introspect").onInterface(interfaceName).implementedAs([this](){ return object_.introspect(); });
// }
//
// public:
// std::string introspect()
// {
// std::string xml;
// object_.callMethod("Introspect").onInterface(interfaceName).storeResultsTo(xml);
// return xml;
// }
//
// private:
// sdbus::IObject& object_;
// };
}
#endif /* SDBUS_CXX_INTROSPECTION_H_ */

330
include/sdbus-c++/Message.h Normal file
View File

@ -0,0 +1,330 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file Message.h
*
* Created on: Nov 9, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_MESSAGE_H_
#define SDBUS_CXX_MESSAGE_H_
#include <sdbus-c++/TypeTraits.h>
#include <sdbus-c++/Error.h>
#include <string>
#include <vector>
#include <map>
#include <memory>
#include <utility>
#include <cstdint>
#include <cassert>
#include <iostream>
// Forward declarations
namespace sdbus {
class Variant;
class ObjectPath;
class Signature;
template <typename... _ValueTypes> class Struct;
}
namespace sdbus {
/********************************************//**
* @class Message
*
* Message represents a D-Bus message, which can be either method call message,
* method reply message, signal message, or a plain message serving as a storage
* for serialized data.
*
* Serialization and deserialization functions are provided for types supported
* by D-Bus.
*
* You don't need to work with this class directly if you use high-level APIs
* of @c IObject and @c IObjectProxy.
*
***********************************************/
class Message
{
public:
enum class Type
{
ePlainMessage
, eMethodCall
, eMethodReply
, eSignal
};
Message() = default;
Message(void *msg, Type type = Type::ePlainMessage) noexcept;
Message(const Message&) noexcept;
Message& operator=(const Message&) noexcept;
Message(Message&& other) noexcept;
Message& operator=(Message&& other) noexcept;
~Message();
Message& operator<<(bool item);
Message& operator<<(int16_t item);
Message& operator<<(int32_t item);
Message& operator<<(int64_t item);
Message& operator<<(uint8_t item);
Message& operator<<(uint16_t item);
Message& operator<<(uint32_t item);
Message& operator<<(uint64_t item);
Message& operator<<(double item);
Message& operator<<(const char *item);
Message& operator<<(const std::string &item);
Message& operator<<(const Variant &item);
Message& operator<<(const ObjectPath &item);
Message& operator<<(const Signature &item);
Message& operator>>(bool& item);
Message& operator>>(int16_t& item);
Message& operator>>(int32_t& item);
Message& operator>>(int64_t& item);
Message& operator>>(uint8_t& item);
Message& operator>>(uint16_t& item);
Message& operator>>(uint32_t& item);
Message& operator>>(uint64_t& item);
Message& operator>>(double& item);
Message& operator>>(char*& item);
Message& operator>>(std::string &item);
Message& operator>>(Variant &item);
Message& operator>>(ObjectPath &item);
Message& operator>>(Signature &item);
Message& openContainer(const std::string& signature);
Message& closeContainer();
Message& openDictEntry(const std::string& signature);
Message& closeDictEntry();
Message& openVariant(const std::string& signature);
Message& closeVariant();
Message& openStruct(const std::string& signature);
Message& closeStruct();
Message& enterContainer(const std::string& signature);
Message& exitContainer();
Message& enterDictEntry(const std::string& signature);
Message& exitDictEntry();
Message& enterVariant(const std::string& signature);
Message& exitVariant();
Message& enterStruct(const std::string& signature);
Message& exitStruct();
operator bool() const;
void clearFlags();
std::string getInterfaceName() const;
std::string getMemberName() const;
void peekType(std::string& type, std::string& contents) const;
bool isValid() const;
bool isEmpty() const;
Type getType() const;
void copyTo(Message& destination, bool complete) const;
void seal();
void rewind(bool complete);
Message createReply() const;
Message send() const;
private:
void* msg_{};
Type type_{Type::ePlainMessage};
mutable bool ok_{true};
};
template <typename _Element>
inline Message& operator<<(Message& msg, const std::vector<_Element>& items)
{
msg.openContainer(signature_of<_Element>::str());
for (const auto& item : items)
msg << item;
msg.closeContainer();
return msg;
}
template <typename _Key, typename _Value>
inline Message& operator<<(Message& msg, const std::map<_Key, _Value>& items)
{
const std::string dictEntrySignature = signature_of<_Key>::str() + signature_of<_Value>::str();
const std::string arraySignature = "{" + dictEntrySignature + "}";
msg.openContainer(arraySignature);
for (const auto& item : items)
{
msg.openDictEntry(dictEntrySignature);
msg << item.first;
msg << item.second;
msg.closeDictEntry();
}
msg.closeContainer();
return msg;
}
namespace detail
{
template <typename... _Args>
void serialize_pack(Message& msg, _Args&&... args)
{
// Use initializer_list because it guarantees left to right order, and can be empty
using _ = std::initializer_list<int>;
// We are not interested in the list itself, but in the side effects
(void)_{(void(msg << std::forward<_Args>(args)), 0)...};
}
template <class _Tuple, std::size_t... _Is>
void serialize_tuple( Message& msg
, const _Tuple& t
, std::index_sequence<_Is...>)
{
serialize_pack(msg, std::get<_Is>(t)...);
}
}
template <typename... _ValueTypes>
inline Message& operator<<(Message& msg, const Struct<_ValueTypes...>& item)
{
auto structSignature = signature_of<Struct<_ValueTypes...>>::str();
assert(structSignature.size() > 2);
// Remove opening and closing parenthesis from the struct signature to get contents signature
auto structContentSignature = structSignature.substr(1, structSignature.size()-2);
msg.openStruct(structContentSignature);
detail::serialize_tuple(msg, item, std::index_sequence_for<_ValueTypes...>{});
msg.closeStruct();
return msg;
}
template <typename... _ValueTypes>
inline Message& operator<<(Message& msg, const std::tuple<_ValueTypes...>& item)
{
detail::serialize_tuple(msg, item, std::index_sequence_for<_ValueTypes...>{});
return msg;
}
template <typename _Element>
inline Message& operator>>(Message& msg, std::vector<_Element>& items)
{
if(!msg.enterContainer(signature_of<_Element>::str()))
return msg;
while (true)
{
_Element elem;
if (msg >> elem)
items.emplace_back(std::move(elem));
else
break;
}
msg.clearFlags();
msg.exitContainer();
return msg;
}
template <typename _Key, typename _Value>
inline Message& operator>>(Message& msg, std::map<_Key, _Value>& items)
{
const std::string dictEntrySignature = signature_of<_Key>::str() + signature_of<_Value>::str();
const std::string arraySignature = "{" + dictEntrySignature + "}";
if (!msg.enterContainer(arraySignature))
return msg;
while (true)
{
if (!msg.enterDictEntry(dictEntrySignature))
break;
_Key key;
_Value value;
msg >> key >> value;
items.emplace(std::move(key), std::move(value));
msg.exitDictEntry();
}
msg.clearFlags();
msg.exitContainer();
return msg;
}
namespace detail
{
template <typename... _Args>
void deserialize_pack(Message& msg, _Args&... args)
{
// Use initializer_list because it guarantees left to right order, and can be empty
using _ = std::initializer_list<int>;
// We are not interested in the list itself, but in the side effects
(void)_{(void(msg >> args), 0)...};
}
template <class _Tuple, std::size_t... _Is>
void deserialize_tuple( Message& msg
, _Tuple& t
, std::index_sequence<_Is...> )
{
deserialize_pack(msg, std::get<_Is>(t)...);
}
}
template <typename... _ValueTypes>
inline Message& operator>>(Message& msg, Struct<_ValueTypes...>& item)
{
auto structSignature = signature_of<Struct<_ValueTypes...>>::str();
// Remove opening and closing parenthesis from the struct signature to get contents signature
auto structContentSignature = structSignature.substr(1, structSignature.size()-2);
if (!msg.enterStruct(structContentSignature))
return msg;
detail::deserialize_tuple(msg, item, std::index_sequence_for<_ValueTypes...>{});
msg.exitStruct();
return msg;
}
template <typename... _ValueTypes>
inline Message& operator>>(Message& msg, std::tuple<_ValueTypes...>& item)
{
detail::deserialize_tuple(msg, item, std::index_sequence_for<_ValueTypes...>{});
return msg;
}
}
#endif /* SDBUS_CXX_MESSAGE_H_ */

View File

@ -0,0 +1,441 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file TypeTraits.h
*
* Created on: Nov 9, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_TYPETRAITS_H_
#define SDBUS_CXX_TYPETRAITS_H_
#include <string>
#include <vector>
#include <map>
#include <cstdint>
#include <functional>
#include <tuple>
// Forward declarations
namespace sdbus {
class Variant;
template <typename... _ValueTypes> class Struct;
class ObjectPath;
class Signature;
class Message;
}
namespace sdbus {
using method_callback = std::function<void(Message& msg, Message& reply)>;
using signal_handler = std::function<void(Message& signal)>;
using property_set_callback = std::function<void(Message& msg)>;
using property_get_callback = std::function<void(Message& reply)>;
// Primary template
template <typename _T>
struct signature_of
{
static const std::string str()
{
// sizeof(_T) < 0 is here to make compiler not being able to figure out
// the assertion expression before the template instantiation takes place.
static_assert(sizeof(_T) < 0, "Unknown DBus type");
return "";
}
};
template <>
struct signature_of<void>
{
static const std::string str()
{
return "";
}
};
template <>
struct signature_of<bool>
{
static const std::string str()
{
return "b";
}
};
template <>
struct signature_of<uint8_t>
{
static const std::string str()
{
return "y";
}
};
template <>
struct signature_of<int16_t>
{
static const std::string str()
{
return "n";
}
};
template <>
struct signature_of<uint16_t>
{
static const std::string str()
{
return "q";
}
};
template <>
struct signature_of<int32_t>
{
static const std::string str()
{
return "i";
}
};
template <>
struct signature_of<uint32_t>
{
static const std::string str()
{
return "u";
}
};
template <>
struct signature_of<int64_t>
{
static const std::string str()
{
return "x";
}
};
template <>
struct signature_of<uint64_t>
{
static const std::string str()
{
return "t";
}
};
template <>
struct signature_of<double>
{
static const std::string str()
{
return "d";
}
};
template <>
struct signature_of<char*>
{
static const std::string str()
{
return "s";
}
};
template <>
struct signature_of<const char*>
{
static const std::string str()
{
return "s";
}
};
template <std::size_t _N>
struct signature_of<char[_N]>
{
static const std::string str()
{
return "s";
}
};
template <std::size_t _N>
struct signature_of<const char[_N]>
{
static const std::string str()
{
return "s";
}
};
template <>
struct signature_of<std::string>
{
static const std::string str()
{
return "s";
}
};
template <typename... _ValueTypes>
struct signature_of<Struct<_ValueTypes...>>
{
static const std::string str()
{
std::initializer_list<std::string> signatures{signature_of<_ValueTypes>::str()...};
std::string signature;
signature += "(";
for (const auto& item : signatures)
signature += item;
signature += ")";
return signature;
}
};
template <>
struct signature_of<Variant>
{
static const std::string str()
{
return "v";
}
};
template <>
struct signature_of<ObjectPath>
{
static const std::string str()
{
return "o";
}
};
template <>
struct signature_of<Signature>
{
static const std::string str()
{
return "g";
}
};
template <typename _Element>
struct signature_of<std::vector<_Element>>
{
static const std::string str()
{
return "a" + signature_of<_Element>::str();
}
};
template <typename _Key, typename _Value>
struct signature_of<std::map<_Key, _Value>>
{
static const std::string str()
{
return "a{" + signature_of<_Key>::str() + signature_of<_Value>::str() + "}";
}
};
template <typename _Type>
struct function_traits
: public function_traits<decltype(&_Type::operator())>
{};
template <typename _Type>
struct function_traits<const _Type>
: public function_traits<_Type>
{};
template <typename _Type>
struct function_traits<_Type&>
: public function_traits<_Type>
{};
// Function traits implementation inspired by (c) kennytm,
// https://github.com/kennytm/utils/blob/master/traits.hpp
template <typename _ReturnType, typename... _Args>
struct function_traits<_ReturnType(_Args...)>
{
typedef _ReturnType result_type;
typedef std::tuple<_Args...> arguments_type;
typedef _ReturnType function_type(_Args...);
static constexpr std::size_t arity = sizeof...(_Args);
template <size_t _Idx>
struct arg
{
typedef std::tuple_element_t<_Idx, std::tuple<_Args...>> type;
};
template <size_t _Idx>
using arg_t = typename arg<_Idx>::type;
};
template <typename _ReturnType, typename... _Args>
struct function_traits<_ReturnType(*)(_Args...)>
: public function_traits<_ReturnType(_Args...)>
{};
template <typename _ClassType, typename _ReturnType, typename... _Args>
struct function_traits<_ReturnType(_ClassType::*)(_Args...)>
: public function_traits<_ReturnType(_Args...)>
{
typedef _ClassType& owner_type;
};
template <typename _ClassType, typename _ReturnType, typename... _Args>
struct function_traits<_ReturnType(_ClassType::*)(_Args...) const>
: public function_traits<_ReturnType(_Args...)>
{
typedef const _ClassType& owner_type;
};
template <typename _ClassType, typename _ReturnType, typename... _Args>
struct function_traits<_ReturnType(_ClassType::*)(_Args...) volatile>
: public function_traits<_ReturnType(_Args...)>
{
typedef volatile _ClassType& owner_type;
};
template <typename _ClassType, typename _ReturnType, typename... _Args>
struct function_traits<_ReturnType(_ClassType::*)(_Args...) const volatile>
: public function_traits<_ReturnType(_Args...)>
{
typedef const volatile _ClassType& owner_type;
};
template <typename FunctionType>
struct function_traits<std::function<FunctionType>>
: public function_traits<FunctionType>
{};
template <typename _FunctionType, size_t _Idx>
using function_argument_t = typename function_traits<_FunctionType>::template arg_t<_Idx>;
template <typename _FunctionType>
using function_result_t = typename function_traits<_FunctionType>::result_type;
template <typename _Type>
struct aggregate_signature
{
static const std::string str()
{
return signature_of<std::decay_t<_Type>>::str();
}
};
template <typename... _Types>
struct aggregate_signature<std::tuple<_Types...>>
{
static const std::string str()
{
std::initializer_list<std::string> signatures{signature_of<std::decay_t<_Types>>::str()...};
std::string signature;
for (const auto& item : signatures)
signature += item;
return signature;
}
};
// Get a tuple of function input argument types from function signature.
// But first, convert provided function signature to the standardized form `out(in...)'.
template <typename _Function>
struct tuple_of_function_input_arg_types
: public tuple_of_function_input_arg_types<typename function_traits<_Function>::function_type>
{};
// Get a tuple of function input argument types from function signature.
// Function signature is expected in the standardized form `out(in...)'.
template <typename _ReturnType, typename... _Args>
struct tuple_of_function_input_arg_types<_ReturnType(_Args...)>
{
// Arguments may be cv-qualified and may be references, so we have to strip cv and references
// with decay_t in order to get real 'naked' types.
// Example: for a function with signature void(const int i, const std::vector<float> v, double d)
// the `type' will be `std::tuple<int, std::vector<float>, double>'.
typedef std::tuple<std::decay_t<_Args>...> type;
};
template <typename _Function>
using tuple_of_function_input_arg_types_t = typename tuple_of_function_input_arg_types<_Function>::type;
template <typename _Function>
struct signature_of_function_input_arguments
{
static const std::string str()
{
return aggregate_signature<tuple_of_function_input_arg_types_t<_Function>>::str();
}
};
template <typename _Function>
struct signature_of_function_output_arguments
{
static const std::string str()
{
return aggregate_signature<function_result_t<_Function>>::str();
}
};
namespace detail
{
// 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>
constexpr decltype(auto) apply_impl( _Function&& f
, _Tuple&& t
, std::index_sequence<_I...>
, std::enable_if_t<!std::is_void<function_result_t<_Function>>::value>* = nullptr)
{
return std::forward<_Function>(f)(std::get<_I>(std::forward<_Tuple>(t))...);
}
// Version of apply_impl for functions returning void.
// In this case, to have uniform code on the caller side, return empty tuple, our synonym for `void'.
template <class _Function, class _Tuple, std::size_t... _I>
constexpr decltype(auto) apply_impl( _Function&& f
, _Tuple&& t
, std::index_sequence<_I...>
, std::enable_if_t<std::is_void<function_result_t<_Function>>::value>* = nullptr)
{
std::forward<_Function>(f)(std::get<_I>(std::forward<_Tuple>(t))...);
return std::tuple<>{};
}
}
// 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, _Tuple&& t)
{
return detail::apply_impl( std::forward<_Function>(f)
, std::forward<_Tuple>(t)
, std::make_index_sequence<std::tuple_size<std::decay_t<_Tuple>>::value>{} );
}
}
#endif /* SDBUS_CXX_TYPETRAITS_H_ */

137
include/sdbus-c++/Types.h Normal file
View File

@ -0,0 +1,137 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file Types.h
*
* Created on: Nov 23, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_TYPES_H_
#define SDBUS_CXX_TYPES_H_
#include <sdbus-c++/Message.h>
#include <sdbus-c++/TypeTraits.h>
#include <string>
#include <type_traits>
#include <typeinfo>
#include <memory>
#include <tuple>
namespace sdbus {
/********************************************//**
* @class Variant
*
* Variant can hold value of any D-Bus-supported type.
*
***********************************************/
class Variant
{
public:
Variant();
template <typename _ValueType>
Variant(const _ValueType& value)
: Variant()
{
msg_.openVariant(signature_of<_ValueType>::str());
msg_ << value;
msg_.closeVariant();
msg_.seal();
}
template <typename _ValueType>
_ValueType get() const
{
_ValueType val;
msg_.rewind(false);
msg_.enterVariant(signature_of<_ValueType>::str());
msg_ >> val;
msg_.exitVariant();
return val;
}
template <typename _ValueType>
operator _ValueType() const
{
return get<_ValueType>();
}
template <typename _Type>
bool containsValueOfType() const
{
return signature_of<_Type>::str() == peekValueType();
}
bool isEmpty() const;
void serializeTo(Message& msg) const;
void deserializeFrom(Message& msg);
std::string peekValueType() const;
private:
mutable Message msg_{};
};
template <typename... _ValueTypes>
class Struct
: public std::tuple<_ValueTypes...>
{
public:
using std::tuple<_ValueTypes...>::tuple;
template <std::size_t _I>
auto& get()
{
return std::get<_I>(*this);
}
template <std::size_t _I>
const auto& get() const
{
return std::get<_I>(*this);
}
};
template<typename... _Elements>
constexpr Struct<std::decay_t<_Elements>...>
make_struct(_Elements&&... args)
{
typedef Struct<std::decay_t<_Elements>...> result_type;
return result_type(std::forward<_Elements>(args)...);
}
class ObjectPath : public std::string
{
public:
using std::string::string;
using std::string::operator=;
};
class Signature : public std::string
{
public:
using std::string::string;
using std::string::operator=;
};
}
#endif /* SDBUS_CXX_TYPES_H_ */

View File

@ -0,0 +1,34 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file sdbus-c++.h
*
* Created on: Jan 19, 2017
* 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++/IConnection.h>
#include <sdbus-c++/IObject.h>
#include <sdbus-c++/IObjectProxy.h>
#include <sdbus-c++/Interfaces.h>
#include <sdbus-c++/Message.h>
#include <sdbus-c++/Types.h>
#include <sdbus-c++/TypeTraits.h>
#include <sdbus-c++/Introspection.h>
#include <sdbus-c++/Error.h>

11
sdbus-c++.pc.in Normal file
View File

@ -0,0 +1,11 @@
prefix=@prefix@
exec_prefix=@exec_prefix@
libdir=@libdir@
includedir=@includedir@
Name: @PACKAGE@
Description: C++ bindings library for sd-bus
Requires: libsystemd
Version: @VERSION@
Libs: -L${libdir} -lsdbus-c++
Cflags: -I${includedir}

272
src/Connection.cpp Executable file
View File

@ -0,0 +1,272 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file Connection.cpp
*
* Created on: Nov 8, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Connection.h"
#include <sdbus-c++/Message.h>
#include <sdbus-c++/Error.h>
#include "ScopeGuard.h"
#include <systemd/sd-bus.h>
#include <unistd.h>
#include <poll.h>
#include <sys/eventfd.h>
namespace {
std::map<sdbus::internal::Connection::BusType, int(*)(sd_bus **)> busTypeToFactory
{
{sdbus::internal::Connection::BusType::eSystem, &sd_bus_open_system},
{sdbus::internal::Connection::BusType::eSession, &sd_bus_open_user}
};
}
namespace sdbus { namespace internal {
Connection::Connection(Connection::BusType type)
: busType_(type)
{
sd_bus* bus{};
auto r = busTypeToFactory[busType_](&bus);
if (r < 0)
SDBUS_THROW_ERROR("Failed to open system bus", -r);
bus_.reset(bus);
// Process all requests that are part of the initial handshake,
// like processing the Hello message response, authentication etc.,
// to avoid connection authentication timeout in dbus daemon.
r = sd_bus_flush(bus_.get());
if (r < 0)
SDBUS_THROW_ERROR("Failed to flush system bus on opening", -r);
r = eventfd(0, EFD_SEMAPHORE);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to create event object", -errno);
runFd_ = r;
}
Connection::~Connection()
{
leaveProcessingLoop();
close(runFd_);
}
void Connection::requestName(const std::string& name)
{
auto r = sd_bus_request_name(bus_.get(), name.c_str(), 0);
if (r < 0)
SDBUS_THROW_ERROR("Failed to request bus name", -r);
}
void Connection::releaseName(const std::string& name)
{
auto r = sd_bus_release_name(bus_.get(), name.c_str());
if (r < 0)
SDBUS_THROW_ERROR("Failed to release bus name", -r);
}
void Connection::enterProcessingLoop()
{
int semaphoreFd = runFd_;
short int semaphoreEvents = POLLIN;
while (true)
{
/* Process requests */
int r = sd_bus_process(bus_.get(), nullptr);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to process bus requests", -r);
if (r > 0) /* we processed a request, try to process another one, right-away */
continue;
r = sd_bus_get_fd(bus_.get());
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get bus descriptor", -r);
auto sdbusFd = r;
r = sd_bus_get_events(bus_.get());
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get bus descriptor", -r);
short int sdbusEvents = r;
struct pollfd fds[] = {{sdbusFd, sdbusEvents, 0}, {semaphoreFd, semaphoreEvents, 0}};
/* Wait for the next request to process */
uint64_t usec;
sd_bus_get_timeout(bus_.get(), &usec);
auto fdsCount = sizeof(fds)/sizeof(fds[0]);
r = poll(fds, fdsCount, usec == (uint64_t) -1 ? -1 : (usec+999)/1000);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to wait on the bus", -errno);
if (fds[1].revents & POLLIN)
break;
}
}
void Connection::enterProcessingLoopAsync()
{
asyncLoopThread_ = std::thread([this](){ enterProcessingLoop(); });
}
void Connection::leaveProcessingLoop()
{
assert(runFd_ >= 0);
uint64_t value = 1;
write(runFd_, &value, sizeof(value));
if (asyncLoopThread_.joinable())
asyncLoopThread_.join();
}
void* Connection::addObjectVTable( const std::string& objectPath
, const std::string& interfaceName
, const void* vtable
, void* userData )
{
sd_bus_slot *slot{};
auto r = sd_bus_add_object_vtable( bus_.get()
, &slot
, objectPath.c_str()
, interfaceName.c_str()
, static_cast<const sd_bus_vtable*>(vtable)
, userData );
if (r < 0)
SDBUS_THROW_ERROR("Failed to register object vtable", -r);
return slot;
}
void Connection::removeObjectVTable(void* vtableHandle)
{
sd_bus_slot_unref((sd_bus_slot *)vtableHandle);
}
sdbus::Message Connection::createMethodCall( const std::string& destination
, const std::string& objectPath
, const std::string& interfaceName
, const std::string& methodName ) const
{
sd_bus_message *sdbusMsg{};
SCOPE_EXIT{ sd_bus_message_unref(sdbusMsg); }; // Returned message will become an owner of sdbusMsg
auto r = sd_bus_message_new_method_call( bus_.get()
, &sdbusMsg
, destination.c_str()
, objectPath.c_str()
, interfaceName.c_str()
, methodName.c_str() );
if (r < 0)
SDBUS_THROW_ERROR("Failed to create method call", -r);
return Message(sdbusMsg, Message::Type::eMethodCall);
}
sdbus::Message Connection::createSignal( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName ) const
{
sd_bus_message *sdbusSignal{};
SCOPE_EXIT{ sd_bus_message_unref(sdbusSignal); }; // Returned message will become an owner of sdbusSignal
auto r = sd_bus_message_new_signal( bus_.get()
, &sdbusSignal
, objectPath.c_str()
, interfaceName.c_str()
, signalName.c_str() );
if (r < 0)
SDBUS_THROW_ERROR("Failed to create signal", -r);
return Message(sdbusSignal, Message::Type::eSignal);
}
void* Connection::registerSignalHandler( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName
, sd_bus_message_handler_t callback
, void* userData )
{
sd_bus_slot *slot{};
auto filter = composeSignalMatchFilter(objectPath, interfaceName, signalName);
auto r = sd_bus_add_match(bus_.get(), &slot, filter.c_str(), callback, userData);
if (r < 0)
SDBUS_THROW_ERROR("Failed to register signal handler", -r);
return slot;
}
void Connection::unregisterSignalHandler(void* handlerCookie)
{
sd_bus_slot_unref((sd_bus_slot *)handlerCookie);
}
std::unique_ptr<sdbus::internal::IConnection> Connection::clone() const
{
return std::make_unique<sdbus::internal::Connection>(busType_);
}
std::string Connection::composeSignalMatchFilter( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName )
{
std::string filter;
filter += "type='signal',";
filter += "interface='" + interfaceName + "',";
filter += "member='" + signalName + "',";
filter += "path='" + objectPath + "'";
return filter;
}
}}
namespace sdbus {
std::unique_ptr<sdbus::IConnection> createConnection()
{
return createSystemBusConnection();
}
std::unique_ptr<sdbus::IConnection> createConnection(const std::string& name)
{
return createSystemBusConnection(name);
}
std::unique_ptr<sdbus::IConnection> createSystemBusConnection()
{
return std::make_unique<sdbus::internal::Connection>(sdbus::internal::Connection::BusType::eSystem);
}
std::unique_ptr<sdbus::IConnection> createSystemBusConnection(const std::string& name)
{
auto conn = createSystemBusConnection();
conn->requestName(name);
return conn;
}
std::unique_ptr<sdbus::IConnection> createSessionBusConnection()
{
return std::make_unique<sdbus::internal::Connection>(sdbus::internal::Connection::BusType::eSession);
}
std::unique_ptr<sdbus::IConnection> createSessionBusConnection(const std::string& name)
{
auto conn = createSessionBusConnection();
conn->requestName(name);
return conn;
}
}

98
src/Connection.h Executable file
View File

@ -0,0 +1,98 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file Connection.h
*
* Created on: Nov 8, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_INTERNAL_CONNECTION_H_
#define SDBUS_CXX_INTERNAL_CONNECTION_H_
#include <sdbus-c++/IConnection.h>
#include <systemd/sd-bus.h>
#include <memory>
#include <atomic>
#include <thread>
#include "IConnection.h"
namespace sdbus { namespace internal {
class Connection
: public sdbus::IConnection // External, public interface
, public sdbus::internal::IConnection // Internal, private interface
{
public:
enum class BusType
{
eSystem,
eSession
};
Connection(BusType type);
~Connection();
void requestName(const std::string& name) override;
void releaseName(const std::string& name) override;
void enterProcessingLoop() override;
void enterProcessingLoopAsync() override;
void leaveProcessingLoop() override;
void* addObjectVTable( const std::string& objectPath
, const std::string& interfaceName
, const void* vtable
, void* userData ) override;
void removeObjectVTable(void* vtableHandle) override;
sdbus::Message createMethodCall( const std::string& destination
, const std::string& objectPath
, const std::string& interfaceName
, const std::string& methodName ) const override;
sdbus::Message createSignal( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName ) const override;
void* registerSignalHandler( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName
, sd_bus_message_handler_t callback
, void* userData ) override;
void unregisterSignalHandler(void* handlerCookie) override;
std::unique_ptr<sdbus::internal::IConnection> clone() const override;
private:
static std::string composeSignalMatchFilter( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName );
private:
std::unique_ptr<sd_bus, decltype(&sd_bus_flush_close_unref)> bus_{nullptr, &sd_bus_flush_close_unref};
std::thread asyncLoopThread_;
std::atomic<int> runFd_{-1};
BusType busType_;
static constexpr const uint64_t POLL_TIMEOUT_USEC = 500000;
};
}}
#endif /* SDBUS_CXX_INTERNAL_CONNECTION_H_ */

150
src/ConvenienceClasses.cpp Normal file
View File

@ -0,0 +1,150 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file ConvenienceClasses.cpp
*
* Created on: Jan 19, 2017
* 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++/ConvenienceClasses.h>
#include <sdbus-c++/IObject.h>
#include <sdbus-c++/IObjectProxy.h>
#include <string>
#include <exception>
namespace sdbus {
SignalRegistrator::SignalRegistrator(IObject& object, const std::string& signalName)
: object_(object)
, signalName_(signalName)
, exceptions_(std::uncaught_exceptions()) // Needs C++17
{
}
SignalRegistrator::~SignalRegistrator() noexcept(false) // since C++11, destructors must
{ // explicitly be allowed to throw
// Don't register the signal if SignalRegistrator threw an exception in one of its methods
if (std::uncaught_exceptions() != exceptions_)
return;
SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when registering a DBus signal", EINVAL);
// registerSignal() can throw. But as the SignalRegistrator shall always be used as an unnamed,
// temporary object, i.e. not as a stack-allocated object, the double-exception situation
// shall never happen. I.e. it should not happen that this destructor is directly called
// in the stack-unwinding process of another flying exception (which would lead to immediate
// termination). It can be called indirectly in the destructor of another object, but that's
// fine and safe provided that the caller catches exceptions thrown from here.
// Therefore, we can allow registerSignal() to throw even if we are in the destructor.
// Bottomline is, to be on the safe side, the caller must take care of catching and reacting
// to the exception thrown from here if the caller is a destructor itself.
object_.registerSignal(interfaceName_, signalName_, signalSignature_);
}
PropertyRegistrator::PropertyRegistrator(IObject& object, const std::string& propertyName)
: object_(object)
, propertyName_(propertyName)
, exceptions_(std::uncaught_exceptions())
{
}
PropertyRegistrator::~PropertyRegistrator() noexcept(false) // since C++11, destructors must
{ // explicitly be allowed to throw
// Don't register the property if PropertyRegistrator threw an exception in one of its methods
if (std::uncaught_exceptions() != exceptions_)
return;
SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when registering a DBus property", EINVAL);
// registerProperty() can throw. But as the PropertyRegistrator shall always be used as an unnamed,
// temporary object, i.e. not as a stack-allocated object, the double-exception situation
// shall never happen. I.e. it should not happen that this destructor is directly called
// in the stack-unwinding process of another flying exception (which would lead to immediate
// termination). It can be called indirectly in the destructor of another object, but that's
// fine and safe provided that the caller catches exceptions thrown from here.
// Therefore, we can allow registerProperty() to throw even if we are in the destructor.
// Bottomline is, to be on the safe side, the caller must take care of catching and reacting
// to the exception thrown from here if the caller is a destructor itself.
object_.registerProperty( std::move(interfaceName_)
, std::move(propertyName_)
, std::move(propertySignature_)
, std::move(getter_)
, std::move(setter_) );
}
SignalEmitter::SignalEmitter(IObject& object, const std::string& signalName)
: object_(object)
, signalName_(signalName)
, exceptions_(std::uncaught_exceptions()) // Needs C++17
{
}
SignalEmitter::~SignalEmitter() noexcept(false) // since C++11, destructors must
{ // explicitly be allowed to throw
// Don't emit the signal if SignalEmitter threw an exception in one of its methods
if (std::uncaught_exceptions() != exceptions_)
return;
SDBUS_THROW_ERROR_IF(!signal_.isValid(), "DBus interface not specified when emitting a DBus signal", EINVAL);
// emitSignal() can throw. But as the SignalEmitter 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 emitSignal() 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_.emitSignal(signal_);
}
MethodInvoker::MethodInvoker(IObjectProxy& objectProxy, const std::string& methodName)
: objectProxy_(objectProxy)
, methodName_(methodName)
, exceptions_(std::uncaught_exceptions()) // Needs C++17
{
}
MethodInvoker::~MethodInvoker() noexcept(false) // since C++11, destructors must
{ // explicitly be allowed to throw
// Don't call the method if it has been called already or if MethodInvoker
// threw an exception in one of its methods
if (methodCalled_ || std::uncaught_exceptions() != exceptions_)
return;
SDBUS_THROW_ERROR_IF(!method_.isValid(), "DBus interface not specified when calling a DBus method", EINVAL);
// callMethod() can throw. But as the MethodInvoker 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 callMethod() to throw even if we are in the destructor.
// Bottomline is, to be on the safe side, the caller must take care of catching and reacting
// to the exception thrown from here if the caller is a destructor itself.
objectProxy_.callMethod(method_);
}
}

40
src/Error.cpp Normal file
View File

@ -0,0 +1,40 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file Error.cpp
*
* Created on: Nov 8, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include <sdbus-c++/Error.h>
#include <systemd/sd-bus.h>
namespace sdbus
{
sdbus::Error createError(int errNo, const std::string& customMsg)
{
sd_bus_error sdbusError = SD_BUS_ERROR_NULL;
sd_bus_error_set_errno(&sdbusError, errNo);
std::string name(sdbusError.name);
std::string message(customMsg + " (" + sdbusError.message + ")");
sd_bus_error_free(&sdbusError);
return sdbus::Error(name, message);
}
}

77
src/IConnection.h Executable file
View File

@ -0,0 +1,77 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file IConnection.h
*
* Created on: Nov 9, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_INTERNAL_ICONNECTION_H_
#define SDBUS_CXX_INTERNAL_ICONNECTION_H_
#include <systemd/sd-bus.h>
#include <string>
#include <memory>
// Forward declaration
namespace sdbus {
class Message;
}
namespace sdbus {
namespace internal {
class IConnection
{
public:
virtual void* addObjectVTable( const std::string& objectPath
, const std::string& interfaceName
, const void* vtable
, void* userData ) = 0;
virtual void removeObjectVTable(void* vtableHandle) = 0;
virtual sdbus::Message createMethodCall( const std::string& destination
, const std::string& objectPath
, const std::string& interfaceName
, const std::string& methodName ) const = 0;
virtual sdbus::Message 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
, const std::string& signalName
, sd_bus_message_handler_t callback
, void* userData ) = 0;
virtual void unregisterSignalHandler(void* handlerCookie) = 0;
virtual void enterProcessingLoopAsync() = 0;
virtual void leaveProcessingLoop() = 0;
virtual std::unique_ptr<sdbus::internal::IConnection> clone() const = 0;
virtual ~IConnection() = default;
};
}
}
#endif /* SDBUS_CXX_INTERNAL_ICONNECTION_H_ */

26
src/Makefile.am Normal file
View File

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

676
src/Message.cpp Executable file
View File

@ -0,0 +1,676 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file Message.cpp
*
* Created on: Nov 9, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include <sdbus-c++/Message.h>
#include <sdbus-c++/Types.h>
#include <sdbus-c++/Error.h>
#include "MessageUtils.h"
#include "ScopeGuard.h"
#include <systemd/sd-bus.h>
#include <cassert>
namespace sdbus { /*namespace internal {*/
Message::Message(void *msg, Type type) noexcept
: msg_(msg)
, type_(type)
{
assert(msg_ != nullptr);
sd_bus_message_ref((sd_bus_message*)msg_);
}
Message::Message(const Message& other) noexcept
{
*this = other;
}
Message& Message::operator=(const Message& other) noexcept
{
msg_ = other.msg_;
type_ = other.type_;
ok_ = other.ok_;
sd_bus_message_ref((sd_bus_message*)msg_);
return *this;
}
Message::Message(Message&& other) noexcept
{
*this = std::move(other);
}
Message& Message::operator=(Message&& other) noexcept
{
msg_ = other.msg_;
other.msg_ = nullptr;
type_ = other.type_;
other.type_ = {};
ok_ = other.ok_;
other.ok_ = true;
return *this;
}
Message::~Message()
{
if (msg_)
sd_bus_message_unref((sd_bus_message*)msg_);
}
Message& Message::operator<<(bool item)
{
int intItem = item;
auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_BOOLEAN, &intItem);
if (r < 0)
SDBUS_THROW_ERROR("Failed to serialize a bool value", -r);
return *this;
}
Message& Message::operator<<(int16_t item)
{
auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_INT16, &item);
if (r < 0)
SDBUS_THROW_ERROR("Failed to serialize a int16_t value", -r);
return *this;
}
Message& Message::operator<<(int32_t item)
{
auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_INT32, &item);
if (r < 0)
SDBUS_THROW_ERROR("Failed to serialize a int32_t value", -r);
return *this;
}
Message& Message::operator<<(int64_t item)
{
auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_INT64, &item);
if (r < 0)
SDBUS_THROW_ERROR("Failed to serialize a int64_t value", -r);
return *this;
}
Message& Message::operator<<(uint8_t item)
{
auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_BYTE, &item);
if (r < 0)
SDBUS_THROW_ERROR("Failed to serialize a byte value", -r);
return *this;
}
Message& Message::operator<<(uint16_t item)
{
auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_UINT16, &item);
if (r < 0)
SDBUS_THROW_ERROR("Failed to serialize a uint16_t value", -r);
return *this;
}
Message& Message::operator<<(uint32_t item)
{
auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_UINT32, &item);
if (r < 0)
SDBUS_THROW_ERROR("Failed to serialize a uint32_t value", -r);
return *this;
}
Message& Message::operator<<(uint64_t item)
{
auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_UINT64, &item);
if (r < 0)
SDBUS_THROW_ERROR("Failed to serialize a uint64_t value", -r);
return *this;
}
Message& Message::operator<<(double item)
{
auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_DOUBLE, &item);
if (r < 0)
SDBUS_THROW_ERROR("Failed to serialize a double value", -r);
return *this;
}
Message& Message::operator<<(const char* item)
{
auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_STRING, item);
if (r < 0)
SDBUS_THROW_ERROR("Failed to serialize a C-string value", -r);
return *this;
}
Message& Message::operator<<(const std::string& item)
{
auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_STRING, item.c_str());
if (r < 0)
SDBUS_THROW_ERROR("Failed to serialize a string value", -r);
return *this;
}
Message& Message::operator<<(const Variant &item)
{
item.serializeTo(*this);
return *this;
}
Message& Message::operator<<(const ObjectPath &item)
{
auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_OBJECT_PATH, item.c_str());
if (r < 0)
SDBUS_THROW_ERROR("Failed to serialize an ObjectPath value", -r);
return *this;
}
Message& Message::operator<<(const Signature &item)
{
auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_SIGNATURE, item.c_str());
if (r < 0)
SDBUS_THROW_ERROR("Failed to serialize an Signature value", -r);
return *this;
}
Message& Message::operator>>(bool& item)
{
int intItem;
auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_BOOLEAN, &intItem);
if (r == 0)
ok_ = false;
else if (r < 0)
SDBUS_THROW_ERROR("Failed to deserialize a bool value", -r);
item = static_cast<bool>(intItem);
return *this;
}
Message& Message::operator>>(int16_t& item)
{
auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_INT16, &item);
if (r == 0)
ok_ = false;
else if (r < 0)
SDBUS_THROW_ERROR("Failed to deserialize a int16_t value", -r);
return *this;
}
Message& Message::operator>>(int32_t& item)
{
auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_INT32, &item);
if (r == 0)
ok_ = false;
else if (r < 0)
SDBUS_THROW_ERROR("Failed to deserialize a int32_t value", -r);
return *this;
}
Message& Message::operator>>(int64_t& item)
{
auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_INT64, &item);
if (r == 0)
ok_ = false;
else if (r < 0)
SDBUS_THROW_ERROR("Failed to deserialize a bool value", -r);
return *this;
}
Message& Message::operator>>(uint8_t& item)
{
auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_BYTE, &item);
if (r == 0)
ok_ = false;
else if (r < 0)
SDBUS_THROW_ERROR("Failed to deserialize a byte value", -r);
return *this;
}
Message& Message::operator>>(uint16_t& item)
{
auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_UINT16, &item);
if (r == 0)
ok_ = false;
else if (r < 0)
SDBUS_THROW_ERROR("Failed to deserialize a uint16_t value", -r);
return *this;
}
Message& Message::operator>>(uint32_t& item)
{
auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_UINT32, &item);
if (r == 0)
ok_ = false;
else if (r < 0)
SDBUS_THROW_ERROR("Failed to deserialize a uint32_t value", -r);
return *this;
}
Message& Message::operator>>(uint64_t& item)
{
auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_UINT64, &item);
if (r == 0)
ok_ = false;
else if (r < 0)
SDBUS_THROW_ERROR("Failed to deserialize a uint64_t value", -r);
return *this;
}
Message& Message::operator>>(double& item)
{
auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_DOUBLE, &item);
if (r == 0)
ok_ = false;
else if (r < 0)
SDBUS_THROW_ERROR("Failed to deserialize a double value", -r);
return *this;
}
Message& Message::operator>>(char*& item)
{
auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_STRING, &item);
if (r == 0)
ok_ = false;
else if (r < 0)
SDBUS_THROW_ERROR("Failed to deserialize a string value", -r);
return *this;
}
Message& Message::operator>>(std::string& item)
{
char* str{};
(*this) >> str;
if (str != nullptr)
item = str;
return *this;
}
Message& Message::operator>>(Variant &item)
{
item.deserializeFrom(*this);
// Empty variant is normally prohibited. Users cannot send empty variants.
// Therefore in this context an empty variant means that we are at the end
// of deserializing a container, and thus we shall set ok_ flag to false.
if (item.isEmpty())
ok_ = false;
return *this;
}
Message& Message::operator>>(ObjectPath &item)
{
char* str{};
auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_OBJECT_PATH, &str);
if (r == 0)
ok_ = false;
else if (r < 0)
SDBUS_THROW_ERROR("Failed to deserialize an ObjectPath value", -r);
if (str != nullptr)
item = str;
return *this;
}
Message& Message::operator>>(Signature &item)
{
char* str{};
auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_SIGNATURE, &str);
if (r == 0)
ok_ = false;
else if (r < 0)
SDBUS_THROW_ERROR("Failed to deserialize a Signature value", -r);
if (str != nullptr)
item = str;
return *this;
}
Message& Message::openContainer(const std::string& signature)
{
auto r = sd_bus_message_open_container((sd_bus_message*)msg_, SD_BUS_TYPE_ARRAY, signature.c_str());
if (r < 0)
SDBUS_THROW_ERROR("Failed to open a container", -r);
return *this;
}
Message& Message::closeContainer()
{
auto r = sd_bus_message_close_container((sd_bus_message*)msg_);
if (r < 0)
SDBUS_THROW_ERROR("Failed to close a container", -r);
return *this;
}
Message& Message::openDictEntry(const std::string& signature)
{
auto r = sd_bus_message_open_container((sd_bus_message*)msg_, SD_BUS_TYPE_DICT_ENTRY, signature.c_str());
if (r < 0)
SDBUS_THROW_ERROR("Failed to open a dictionary entry", -r);
return *this;
}
Message& Message::closeDictEntry()
{
auto r = sd_bus_message_close_container((sd_bus_message*)msg_);
if (r < 0)
SDBUS_THROW_ERROR("Failed to close a dictionary entry", -r);
return *this;
}
Message& Message::openVariant(const std::string& signature)
{
auto r = sd_bus_message_open_container((sd_bus_message*)msg_, SD_BUS_TYPE_VARIANT, signature.c_str());
if (r < 0)
SDBUS_THROW_ERROR("Failed to open a variant", -r);
return *this;
}
Message& Message::closeVariant()
{
auto r = sd_bus_message_close_container((sd_bus_message*)msg_);
if (r < 0)
SDBUS_THROW_ERROR("Failed to close a variant", -r);
return *this;
}
Message& Message::openStruct(const std::string& signature)
{
auto r = sd_bus_message_open_container((sd_bus_message*)msg_, SD_BUS_TYPE_STRUCT, signature.c_str());
if (r < 0)
SDBUS_THROW_ERROR("Failed to open a struct", -r);
return *this;
}
Message& Message::closeStruct()
{
auto r = sd_bus_message_close_container((sd_bus_message*)msg_);
if (r < 0)
SDBUS_THROW_ERROR("Failed to close a struct", -r);
return *this;
}
Message& Message::enterContainer(const std::string& signature)
{
auto r = sd_bus_message_enter_container((sd_bus_message*)msg_, SD_BUS_TYPE_ARRAY, signature.c_str());
if (r == 0)
ok_ = false;
else if (r < 0)
SDBUS_THROW_ERROR("Failed to enter a container", -r);
return *this;
}
Message& Message::exitContainer()
{
auto r = sd_bus_message_exit_container((sd_bus_message*)msg_);
if (r < 0)
SDBUS_THROW_ERROR("Failed to exit a container", -r);
return *this;
}
Message& Message::enterDictEntry(const std::string& signature)
{
auto r = sd_bus_message_enter_container((sd_bus_message*)msg_, SD_BUS_TYPE_DICT_ENTRY, signature.c_str());
if (r == 0)
ok_ = false;
else if (r < 0)
SDBUS_THROW_ERROR("Failed to enter a dictionary entry", -r);
return *this;
}
Message& Message::exitDictEntry()
{
auto r = sd_bus_message_exit_container((sd_bus_message*)msg_);
if (r < 0)
SDBUS_THROW_ERROR("Failed to exit a dictionary entry", -r);
return *this;
}
Message& Message::enterVariant(const std::string& signature)
{
auto r = sd_bus_message_enter_container((sd_bus_message*)msg_, SD_BUS_TYPE_VARIANT, signature.c_str());
if (r == 0)
ok_ = false;
else if (r < 0)
SDBUS_THROW_ERROR("Failed to enter a variant", -r);
return *this;
}
Message& Message::exitVariant()
{
auto r = sd_bus_message_exit_container((sd_bus_message*)msg_);
if (r < 0)
SDBUS_THROW_ERROR("Failed to exit a variant", -r);
return *this;
}
Message& Message::enterStruct(const std::string& signature)
{
auto r = sd_bus_message_enter_container((sd_bus_message*)msg_, SD_BUS_TYPE_STRUCT, signature.c_str());
if (r == 0)
ok_ = false;
else if (r < 0)
SDBUS_THROW_ERROR("Failed to enter a struct", -r);
return *this;
}
Message& Message::exitStruct()
{
auto r = sd_bus_message_exit_container((sd_bus_message*)msg_);
if (r < 0)
SDBUS_THROW_ERROR("Failed to exit a struct", -r);
return *this;
}
Message::operator bool() const
{
return ok_;
}
void Message::clearFlags()
{
ok_ = true;
}
void Message::copyTo(Message& destination, bool complete) const
{
auto r = sd_bus_message_copy((sd_bus_message*)destination.msg_, (sd_bus_message*)msg_, complete);
if (r < 0)
SDBUS_THROW_ERROR("Failed to copy the message", -r);
}
void Message::seal()
{
const auto messageCookie = 1;
const auto sealTimeout = 0;
auto r = sd_bus_message_seal((sd_bus_message*)msg_, messageCookie, sealTimeout);
if (r < 0)
SDBUS_THROW_ERROR("Failed to seal the message", -r);
}
void Message::rewind(bool complete)
{
auto r = sd_bus_message_rewind((sd_bus_message*)msg_, complete);
if (r < 0)
SDBUS_THROW_ERROR("Failed to rewind the message", -r);
}
Message Message::send() const
{
if (type_ == Type::eMethodCall)
{
sd_bus_message* sdbusReply{};
SCOPE_EXIT{ sd_bus_message_unref(sdbusReply); }; // Returned message will become an owner of sdbusReply
sd_bus_error sdbusError = SD_BUS_ERROR_NULL;
SCOPE_EXIT{ sd_bus_error_free(&sdbusError); };
auto r = sd_bus_call(nullptr, (sd_bus_message*)msg_, 0, &sdbusError, &sdbusReply);
if (sd_bus_error_is_set(&sdbusError))
{
throw sdbus::Error(sdbusError.name, sdbusError.message);
}
SDBUS_THROW_ERROR_IF(r < 0, "Failed to call method", -r);
return Message(sdbusReply);
}
else if (type_ == Type::eMethodReply)
{
auto r = sd_bus_send(nullptr, (sd_bus_message*)msg_, nullptr);
if (r < 0)
SDBUS_THROW_ERROR("Failed to send reply", -r);
return Message();
}
else if (type_ == Type::eSignal)
{
auto r = sd_bus_send(nullptr, (sd_bus_message*)msg_, nullptr);
if (r < 0)
SDBUS_THROW_ERROR("Failed to emit signal", -r);
return Message();
}
else
{
assert(false);
return Message();
}
}
Message Message::createReply() const
{
sd_bus_message *sdbusReply{};
SCOPE_EXIT{ sd_bus_message_unref(sdbusReply); }; // Returned message will become an owner of sdbusReply
auto r = sd_bus_message_new_method_return((sd_bus_message*)msg_, &sdbusReply);
if (r < 0)
SDBUS_THROW_ERROR("Failed to create method reply", -r);
assert(sdbusReply != nullptr);
return Message(sdbusReply, Type::eMethodReply);
}
std::string Message::getInterfaceName() const
{
return sd_bus_message_get_interface((sd_bus_message*)msg_);
}
std::string Message::getMemberName() const
{
return sd_bus_message_get_member((sd_bus_message*)msg_);
}
void Message::peekType(std::string& type, std::string& contents) const
{
char typeSig;
const char* contentsSig;
auto r = sd_bus_message_peek_type((sd_bus_message*)msg_, &typeSig, &contentsSig);
if (r < 0)
SDBUS_THROW_ERROR("Failed to peek message type", -r);
type = typeSig;
contents = contentsSig;
}
bool Message::isValid() const
{
return msg_ != nullptr;
}
bool Message::isEmpty() const
{
return sd_bus_message_is_empty((sd_bus_message*)msg_);
}
Message::Type Message::getType() const
{
return type_;
}
Message createPlainMessage()
{
int r;
sd_bus* bus{};
SCOPE_EXIT{ sd_bus_unref(bus); }; // sdbusMsg will hold reference to the bus
r = sd_bus_default_system(&bus);
if (r < 0)
SDBUS_THROW_ERROR("Failed to get default system bus", -r);
sd_bus_message* sdbusMsg{};
SCOPE_EXIT{ sd_bus_message_unref(sdbusMsg); }; // Returned message will become an owner of sdbusMsg
r = sd_bus_message_new(bus, &sdbusMsg, _SD_BUS_MESSAGE_TYPE_INVALID);
if (r < 0)
SDBUS_THROW_ERROR("Failed to create a new message", -r);
return Message(sdbusMsg, Message::Type::ePlainMessage);
}
/*}*/}

36
src/MessageUtils.h Normal file
View File

@ -0,0 +1,36 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file MessageUtils.h
*
* Created on: Dec 5, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_INTERNAL_MESSAGEUTILS_H_
#define SDBUS_CXX_INTERNAL_MESSAGEUTILS_H_
#include <sdbus-c++/Message.h>
namespace sdbus
{
Message createPlainMessage();
}
#endif /* SDBUS_CXX_INTERNAL_MESSAGEUTILS_H_ */

261
src/Object.cpp Executable file
View File

@ -0,0 +1,261 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file Object.cpp
*
* Created on: Nov 8, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Object.h"
#include <sdbus-c++/IConnection.h>
#include <sdbus-c++/Message.h>
#include <sdbus-c++/Error.h>
#include "IConnection.h"
#include "VTableUtils.h"
#include <systemd/sd-bus.h>
#include <utility>
#include <cassert>
namespace sdbus { namespace internal {
Object::Object(sdbus::internal::IConnection& connection, std::string objectPath)
: connection_(connection), objectPath_(std::move(objectPath))
{
}
void Object::registerMethod( const std::string& interfaceName
, const std::string& methodName
, const std::string& inputSignature
, const std::string& outputSignature
, method_callback methodCallback )
{
SDBUS_THROW_ERROR_IF(!methodCallback, "Invalid method callback provided", EINVAL);
auto& interface = interfaces_[interfaceName];
InterfaceData::MethodData methodData{inputSignature, outputSignature, std::move(methodCallback)};
auto inserted = interface.methods_.emplace(methodName, std::move(methodData)).second;
SDBUS_THROW_ERROR_IF(!inserted, "Failed to register method: method already exists", EINVAL);
}
void Object::registerSignal( const std::string& interfaceName
, const std::string& signalName
, const std::string& signature )
{
auto& interface = interfaces_[interfaceName];
InterfaceData::SignalData signalData{signature};
auto inserted = interface.signals_.emplace(signalName, std::move(signalData)).second;
SDBUS_THROW_ERROR_IF(!inserted, "Failed to register signal: signal already exists", EINVAL);
}
void Object::registerProperty( const std::string& interfaceName
, const std::string& propertyName
, const std::string& signature
, property_get_callback getCallback )
{
registerProperty( interfaceName
, propertyName
, signature
, getCallback
, property_set_callback{} );
}
void Object::registerProperty( const std::string& interfaceName
, const std::string& propertyName
, const std::string& signature
, property_get_callback getCallback
, property_set_callback setCallback )
{
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)};
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::finishRegistration()
{
for (auto& item : interfaces_)
{
const auto& interfaceName = item.first;
auto& interfaceData = item.second;
auto& vtable = interfaceData.vtable_;
assert(vtable.empty());
vtable.push_back(createVTableStartItem());
for (const auto& item : interfaceData.methods_)
{
const auto& methodName = item.first;
const auto& methodData = item.second;
vtable.push_back(createVTableMethodItem( methodName.c_str()
, methodData.inputArgs_.c_str()
, methodData.outputArgs_.c_str()
, &Object::sdbus_method_callback ));
}
for (const auto& item : interfaceData.signals_)
{
const auto& signalName = item.first;
const auto& signalData = item.second;
vtable.push_back(createVTableSignalItem( signalName.c_str()
, signalData.signature_.c_str() ));
}
for (const auto& item : interfaceData.properties_)
{
const auto& propertyName = item.first;
const auto& propertyData = item.second;
if (!propertyData.setCallback_)
vtable.push_back(createVTablePropertyItem( propertyName.c_str()
, propertyData.signature_.c_str()
, &Object::sdbus_property_get_callback ));
else
vtable.push_back(createVTableWritablePropertyItem( propertyName.c_str()
, propertyData.signature_.c_str()
, &Object::sdbus_property_get_callback
, &Object::sdbus_property_set_callback ));
}
vtable.push_back(createVTableEndItem());
// Tell, don't ask
auto slot = (sd_bus_slot*) connection_.addObjectVTable(objectPath_, interfaceName, &vtable[0], this);
interfaceData.slot_.reset(slot);
interfaceData.slot_.get_deleter() = [this](void *slot){ connection_.removeObjectVTable(slot); };
}
}
sdbus::Message Object::createSignal(const std::string& interfaceName, const std::string& signalName)
{
// Tell, don't ask
return connection_.createSignal(objectPath_, interfaceName, signalName);
}
void Object::emitSignal(const sdbus::Message& message)
{
message.send();
}
int Object::sdbus_method_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError)
{
Message message(sdbusMessage, Message::Type::eMethodCall);
auto* object = static_cast<Object*>(userData);
// Note: The lookup can be optimized by using sorted vectors instead of associative containers
auto& callback = object->interfaces_[message.getInterfaceName()].methods_[message.getMemberName()].callback_;
assert(callback);
auto reply = message.createReply();
try
{
callback(message, reply);
}
catch (const sdbus::Error& e)
{
sd_bus_error_set(retError, e.getName().c_str(), e.getMessage().c_str());
return 1;
}
reply.send();
return 1;
}
int Object::sdbus_property_get_callback( sd_bus */*bus*/
, const char */*objectPath*/
, const char *interface
, const char *property
, sd_bus_message *sdbusReply
, void *userData
, sd_bus_error *retError )
{
Message reply(sdbusReply, Message::Type::ePlainMessage);
auto* object = static_cast<Object*>(userData);
// Note: The lookup can be optimized by using sorted vectors instead of associative containers
auto& callback = object->interfaces_[interface].properties_[property].getCallback_;
// Getter can be empty - the case of "write-only" property
if (!callback)
{
sd_bus_error_set(retError, "org.freedesktop.DBus.Error.Failed", "Cannot read property as it is write-only");
return 1;
}
try
{
callback(reply);
}
catch (const sdbus::Error& e)
{
sd_bus_error_set(retError, e.getName().c_str(), e.getMessage().c_str());
}
return 1;
}
int Object::sdbus_property_set_callback( sd_bus */*bus*/
, const char */*objectPath*/
, const char *interface
, const char *property
, sd_bus_message *sdbusValue
, void *userData
, sd_bus_error *retError )
{
Message value(sdbusValue, Message::Type::ePlainMessage);
auto* object = static_cast<Object*>(userData);
// Note: The lookup can be optimized by using sorted vectors instead of associative containers
auto& callback = object->interfaces_[interface].properties_[property].setCallback_;
assert(callback);
try
{
callback(value);
}
catch (const sdbus::Error& e)
{
sd_bus_error_set(retError, e.getName().c_str(), e.getMessage().c_str());
return 1;
}
return 1;
}
}}
namespace sdbus {
std::unique_ptr<sdbus::IObject> createObject(sdbus::IConnection& connection, std::string objectPath)
{
auto* sdbusConnection = dynamic_cast<sdbus::internal::IConnection*>(&connection);
SDBUS_THROW_ERROR_IF(!sdbusConnection, "Connection is not a real sdbus-c++ connection", EINVAL);
return std::make_unique<sdbus::internal::Object>(*sdbusConnection, std::move(objectPath));
}
}

129
src/Object.h Normal file
View File

@ -0,0 +1,129 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file Object.h
*
* Created on: Nov 8, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_INTERNAL_OBJECT_H_
#define SDBUS_CXX_INTERNAL_OBJECT_H_
#include <sdbus-c++/IObject.h>
#include "IConnection.h"
#include <systemd/sd-bus.h>
#include <string>
#include <map>
#include <vector>
#include <memory>
#include <cassert>
namespace sdbus {
namespace internal {
class Object
: public IObject
{
public:
Object(sdbus::internal::IConnection& connection, std::string objectPath);
void registerMethod( const std::string& interfaceName
, const std::string& methodName
, const std::string& inputSignature
, const std::string& outputSignature
, method_callback methodCallback ) 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;
void registerProperty( const std::string& interfaceName
, const std::string& propertyName
, const std::string& signature
, property_get_callback getCallback
, property_set_callback setCallback ) override;
void finishRegistration() override;
sdbus::Message createSignal(const std::string& interfaceName, const std::string& signalName) override;
void emitSignal(const sdbus::Message& message) override;
private:
static int sdbus_method_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError);
static int sdbus_property_get_callback( sd_bus *bus
, const char *objectPath
, const char *interface
, const char *property
, sd_bus_message *sdbusReply
, void *userData
, sd_bus_error *retError );
static int sdbus_property_set_callback( sd_bus *bus
, const char *objectPath
, const char *interface
, const char *property
, sd_bus_message *sdbusValue
, void *userData
, sd_bus_error *retError );
private:
sdbus::internal::IConnection& connection_;
std::string objectPath_;
using InterfaceName = std::string;
struct InterfaceData
{
using MethodName = std::string;
struct MethodData
{
std::string inputArgs_;
std::string outputArgs_;
method_callback callback_;
};
std::map<MethodName, MethodData> methods_;
using SignalName = std::string;
struct SignalData
{
std::string signature_;
};
std::map<SignalName, SignalData> signals_;
using PropertyName = std::string;
struct PropertyData
{
std::string signature_;
property_get_callback getCallback_;
property_set_callback setCallback_;
};
std::map<PropertyName, PropertyData> properties_;
std::vector<sd_bus_vtable> vtable_;
std::unique_ptr<void, std::function<void(void*)>> slot_;
};
std::map<InterfaceName, InterfaceData> interfaces_;
};
}
}
#endif /* SDBUS_CXX_INTERNAL_OBJECT_H_ */

182
src/ObjectProxy.cpp Executable file
View File

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

93
src/ObjectProxy.h Executable file
View File

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

131
src/ScopeGuard.h Normal file
View File

@ -0,0 +1,131 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file ScopeGuard.h
*
* Created on: Apr 29, 2015
* 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_CPP_INTERNAL_SCOPEGUARD_H_
#define SDBUS_CPP_INTERNAL_SCOPEGUARD_H_
#include <utility>
// Straightforward, modern, easy-to-use RAII utility to perform work on scope exit in an exception-safe manner.
//
// The utility helps providing basic exception safety guarantee by ensuring that the resources are always
// released in face of an exception and released or kept when exiting the scope normally.
//
// Use SCOPE_EXIT if you'd like to perform an (mostly clean-up) operation when the scope ends, either due
// to an exception or because it just ends normally.
// Use SCOPE_EXIT_NAMED if you'd like to conditionally deactivate given scope-exit operation. This is useful
// if, for example, we want the operation to be executed only in face of an exception.
//
// Example usage (maybe a bit contrived):
// SqlDb* g_pDb = nullptr;
// void fnc() {
// auto* pDb = open_database(...); // Resource to be released when scope exits due to whatever reason
// SCOPE_EXIT{ close_database(pDb); }; // Executes body when exiting the scope due to whatever reason
// g_pDb = open_database(...); // Resource to be released only in face of an exception
// SCOPE_EXIT_NAMED(releaseGlobalDbOnException) // Executes body when exiting the scope in face of an exception
// {
// close_database(g_pDb);
// g_pDb = nullptr;
// };
// //... do operations (that may or may not possibly throw) on the database here
// releaseGlobalDbOnException.dismiss(); // Don't release global DB on normal scope exit
// return; // exiting scope normally
// }
#define SCOPE_EXIT \
auto ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE) \
= ::skybase::utils::detail::ScopeGuardOnExit() + [&]() \
/**/
#define SCOPE_EXIT_NAMED(NAME) \
auto NAME \
= ::skybase::utils::detail::ScopeGuardOnExit() + [&]() \
/**/
namespace skybase {
namespace utils {
template <class _Fun>
class ScopeGuard
{
_Fun fnc_;
bool active_;
public:
ScopeGuard(_Fun f)
: fnc_(std::move(f))
, active_(true)
{
}
~ScopeGuard()
{
if (active_)
fnc_();
}
void dismiss()
{
active_ = false;
}
ScopeGuard() = delete;
ScopeGuard(const ScopeGuard&) = delete;
ScopeGuard& operator=(const ScopeGuard&) = delete;
ScopeGuard(ScopeGuard&& rhs)
: fnc_(std::move(rhs.fnc_))
, active_(rhs.active_)
{
rhs.dismiss();
}
};
namespace detail
{
enum class ScopeGuardOnExit
{
};
// Helper function to auto-deduce type of the callable entity
template <typename _Fun>
ScopeGuard<_Fun> operator+(ScopeGuardOnExit, _Fun&& fnc)
{
return ScopeGuard<_Fun>(std::forward<_Fun>(fnc));
}
}
}}
#define CONCATENATE_IMPL(s1, s2) s1##s2
#define CONCATENATE(s1, s2) CONCATENATE_IMPL(s1, s2)
#ifdef __COUNTER__
#define ANONYMOUS_VARIABLE(str) \
CONCATENATE(str, __COUNTER__) \
/**/
#else
#define ANONYMOUS_VARIABLE(str) \
CONCATENATE(str, __LINE__) \
/**/
#endif
#endif /* SDBUS_CPP_INTERNAL_SCOPEGUARD_H_ */

65
src/Types.cpp Normal file
View File

@ -0,0 +1,65 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file Types.cpp
*
* Created on: Nov 30, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include <sdbus-c++/Types.h>
#include <sdbus-c++/Error.h>
#include "MessageUtils.h"
#include <systemd/sd-bus.h>
#include <cassert>
namespace sdbus { /*namespace internal {*/
Variant::Variant()
: msg_(createPlainMessage())
{
}
void Variant::serializeTo(Message& msg) const
{
SDBUS_THROW_ERROR_IF(isEmpty(), "Empty variant is not allowed", EINVAL);
msg_.rewind(true);
msg_.copyTo(msg, true);
}
void Variant::deserializeFrom(Message& msg)
{
msg.copyTo(msg_, false);
msg_.seal();
}
std::string Variant::peekValueType() const
{
std::string type;
std::string contents;
msg_.peekType(type, contents);
return contents;
}
bool Variant::isEmpty() const
{
return msg_.isEmpty();
}
}

72
src/VTableUtils.c Normal file
View File

@ -0,0 +1,72 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file VTableUtils.c
*
* Created on: Nov 8, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include "VTableUtils.h"
#include <systemd/sd-bus.h>
sd_bus_vtable createVTableStartItem()
{
struct sd_bus_vtable vtableStart = SD_BUS_VTABLE_START(0);
return vtableStart;
}
sd_bus_vtable createVTableMethodItem( const char *member
, const char *signature
, const char *result
, sd_bus_message_handler_t handler )
{
struct sd_bus_vtable vtableItem = SD_BUS_METHOD(member, signature, result, handler, SD_BUS_VTABLE_UNPRIVILEGED);
return vtableItem;
}
sd_bus_vtable createVTableSignalItem( const char *member
, const char *signature )
{
struct sd_bus_vtable vtableItem = SD_BUS_SIGNAL(member, signature, 0);
return vtableItem;
}
sd_bus_vtable createVTablePropertyItem( const char *member
, const char *signature
, sd_bus_property_get_t getter )
{
struct sd_bus_vtable vtableItem = SD_BUS_PROPERTY(member, signature, getter, 0, 0);
return vtableItem;
}
sd_bus_vtable createVTableWritablePropertyItem( const char *member
, const char *signature
, sd_bus_property_get_t getter
, sd_bus_property_set_t setter )
{
struct sd_bus_vtable vtableItem = SD_BUS_WRITABLE_PROPERTY(member, signature, getter, setter, 0, 0);
return vtableItem;
}
sd_bus_vtable createVTableEndItem()
{
struct sd_bus_vtable vtableEnd = SD_BUS_VTABLE_END;
return vtableEnd;
}

55
src/VTableUtils.h Normal file
View File

@ -0,0 +1,55 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file VTableUtils.h
*
* Created on: Nov 8, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_INTERNAL_VTABLEUTILS_H_
#define SDBUS_CXX_INTERNAL_VTABLEUTILS_H_
#include <systemd/sd-bus.h>
#ifdef __cplusplus
extern "C" {
#endif
sd_bus_vtable createVTableStartItem();
sd_bus_vtable createVTableMethodItem( const char *member
, const char *signature
, const char *result
, sd_bus_message_handler_t handler );
sd_bus_vtable createVTableSignalItem( const char *member
, const char *signature );
sd_bus_vtable createVTablePropertyItem( const char *member
, const char *signature
, sd_bus_property_get_t getter );
sd_bus_vtable createVTableWritablePropertyItem( const char *member
, const char *signature
, sd_bus_property_get_t getter
, sd_bus_property_set_t setter );
sd_bus_vtable createVTableEndItem();
#ifdef __cplusplus
}
#endif
#endif /* SDBUS_CXX_INTERNAL_VTABLEUTILS_H_ */

View File

@ -0,0 +1,244 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file AdaptorGenerator.cpp
*
* Created on: Feb 1, 2017
* 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/>.
*/
// Own
#include "generator_utils.h"
#include "AdaptorGenerator.h"
// STL
#include <iostream>
#include <fstream>
#include <cstdlib>
#include <algorithm>
#include <iterator>
using std::endl;
using sdbuscpp::xml::Document;
using sdbuscpp::xml::Node;
using sdbuscpp::xml::Nodes;
/**
* Generate adaptor code - server glue
*/
int AdaptorGenerator::transformXmlToFileImpl(const Document& doc, const char* filename) const
{
Node &root = *(doc.root);
Nodes interfaces = root["interface"];
std::ostringstream code;
code << createHeader(filename, StubType::ADAPTOR);
for (const auto& interface : interfaces)
{
code << processInterface(*interface);
}
code << "#endif" << endl;
return writeToFile(filename, code.str());
}
std::string AdaptorGenerator::processInterface(Node& interface) const
{
std::string ifaceName = interface.get("name");
std::cout << "Generating adaptor code for interface " << ifaceName << endl;
unsigned int namespacesCount = 0;
std::string namespacesStr;
std::tie(namespacesCount, namespacesStr) = generateNamespaces(ifaceName);
std::ostringstream body;
body << namespacesStr;
std::string className = ifaceName.substr(ifaceName.find_last_of(".") + 1)
+ "_adaptor";
body << "class " << className << endl
<< "{" << endl
<< "public:" << endl
<< tab << "static constexpr const char* interfaceName = \"" << ifaceName << "\";" << endl << endl
<< "protected:" << endl
<< tab << className << "(sdbus::IObject& object)" << endl
<< tab << tab << ": object_(object)" << endl;
Nodes methods = interface["method"];
Nodes signals = interface["signal"];
Nodes properties = interface["property"];
std::string methodRegistration, methodDeclaration;
std::tie(methodRegistration, methodDeclaration) = processMethods(methods);
std::string signalRegistration, signalMethods;
std::tie(signalRegistration, signalMethods) = processSignals(signals);
std::string propertyRegistration, propertyAccessorDeclaration;
std::tie(propertyRegistration, propertyAccessorDeclaration) = processProperties(properties);
body << tab << "{" << endl
<< methodRegistration
<< signalRegistration
<< propertyRegistration
<< tab << "}" << endl << endl;
if (!signalMethods.empty())
{
body << "public:" << endl << signalMethods;
}
if (!methodDeclaration.empty())
{
body << "private:" << endl << methodDeclaration << endl;
}
if (!propertyAccessorDeclaration.empty())
{
body << "private:" << endl << propertyAccessorDeclaration << endl;
}
body << "private:" << endl
<< tab << "sdbus::IObject& object_;" << endl
<< "};" << endl << endl
<< std::string(namespacesCount, '}') << " // namespaces" << endl << endl;
return body.str();
}
std::tuple<std::string, std::string> AdaptorGenerator::processMethods(const Nodes& methods) const
{
std::ostringstream registrationSS, declarationSS;
for (const auto& method : methods)
{
auto methodName = method->get("name");
Nodes args = (*method)["arg"];
Nodes inArgs = args.select("direction" , "in");
Nodes outArgs = args.select("direction" , "out");
std::string argStr, argTypeStr;
std::tie(argStr, argTypeStr, std::ignore) = argsToNamesAndTypes(inArgs);
registrationSS << tab << tab << "object_.registerMethod(\""
<< methodName << "\")"
<< ".onInterface(interfaceName)"
<< ".implementedAs("
<< "[this]("
<< argTypeStr
<< "){ return this->" << methodName << "("
<< argStr << "); });" << endl;
declarationSS << tab
<< "virtual " << outArgsToType(outArgs) << " " << methodName
<< "(" << argTypeStr << ") = 0;" << endl;
}
return std::make_tuple(registrationSS.str(), declarationSS.str());
}
std::tuple<std::string, std::string> AdaptorGenerator::processSignals(const Nodes& signals) const
{
std::ostringstream signalRegistrationSS, signalMethodSS;
for (const auto& signal : signals)
{
auto name = signal->get("name");
Nodes args = (*signal)["arg"];
std::string argStr, argTypeStr, typeStr;;
std::tie(argStr, argTypeStr, typeStr) = argsToNamesAndTypes(args);
signalRegistrationSS << tab << tab
<< "object_.registerSignal(\"" << name << "\")"
".onInterface(interfaceName)";
if (args.size() > 0)
{
signalRegistrationSS << ".withParameters<" << typeStr << ">()";
}
signalRegistrationSS << ";" << endl;
signalMethodSS << tab << "void " << name << "(" << argTypeStr << ")" << endl
<< tab << "{" << endl
<< tab << tab << "object_.emitSignal(\"" << name << "\")"
".onInterface(interfaceName)";
if (!argStr.empty())
{
signalMethodSS << ".withArguments(" << argStr << ")";
}
signalMethodSS << ";" << endl
<< tab << "}" << endl << endl;
}
return std::make_tuple(signalRegistrationSS.str(), signalMethodSS.str());
}
std::tuple<std::string, std::string> AdaptorGenerator::processProperties(const Nodes& properties) const
{
std::ostringstream registrationSS, declarationSS;
for (const auto& property : properties)
{
auto propertyName = property->get("name");
auto propertyAccess = property->get("access");
auto propertySignature = property->get("type");
auto propertyType = signature_to_type(propertySignature);
auto propertyArg = std::string("value");
auto propertyTypeArg = std::string("const ") + propertyType + "& " + propertyArg;
registrationSS << tab << tab << "object_.registerProperty(\""
<< propertyName << "\")"
<< ".onInterface(interfaceName)";
if (propertyAccess == "read" || propertyAccess == "readwrite")
{
registrationSS << ".withGetter([this](){ return this->" << propertyName << "(); })";
}
if (propertyAccess == "readwrite" || propertyAccess == "write")
{
registrationSS
<< ".withSetter([this](" << propertyTypeArg << ")"
"{ this->" << propertyName << "(" << propertyArg << "); })";
}
registrationSS << ";" << endl;
if (propertyAccess == "read" || propertyAccess == "readwrite")
declarationSS << tab << "virtual " << propertyType << " " << propertyName << "() = 0;" << endl;
if (propertyAccess == "readwrite" || propertyAccess == "write")
declarationSS << tab << "virtual void " << propertyName << "(" << propertyTypeArg << ") = 0;" << endl;
}
return std::make_tuple(registrationSS.str(), declarationSS.str());
}

View File

@ -0,0 +1,80 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file AdaptorGenerator.h
*
* Created on: Feb 1, 2017
* 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 __SDBUSCPP_TOOLS_ADAPTOR_GENERATOR_H
#define __SDBUSCPP_TOOLS_ADAPTOR_GENERATOR_H
// Own headers
#include "xml.h"
#include "BaseGenerator.h"
// STL
#include <tuple>
class AdaptorGenerator : public BaseGenerator
{
protected:
/**
* Transform xml to adaptor code
* @param doc
* @param filename
* @return 0 if ok
*/
int transformXmlToFileImpl(const sdbuscpp::xml::Document& doc, const char* filename) const override;
private:
/**
* Generate source code for interface
* @param interface
* @return source code
*/
std::string processInterface(sdbuscpp::xml::Node& interface) const;
/**
* Generate source code for methods
* @param methods
* @return tuple: registration of methods, declaration of abstract methods
*/
std::tuple<std::string, std::string> processMethods(const sdbuscpp::xml::Nodes& methods) const;
/**
* Generate source code for signals
* @param signals
* @return tuple: registration of signals, definition of signal methods
*/
std::tuple<std::string, std::string> processSignals(const sdbuscpp::xml::Nodes& signals) const;
/**
* Generate source code for properties
* @param properties
* @return tuple: registration of properties, declaration of property accessor virtual methods
*/
std::tuple<std::string, std::string> processProperties(const sdbuscpp::xml::Nodes& properties) const;
};
#endif //__SDBUSCPP_TOOLS_ADAPTOR_GENERATOR_H

View File

@ -0,0 +1,158 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file BaseGenerator.cpp
*
* Created on: Feb 1, 2017
* 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/>.
*/
// Own
#include "generator_utils.h"
#include "BaseGenerator.h"
// STL
#include <algorithm>
#include <fstream>
#include <iterator>
#include <iostream>
#include <map>
#include <sstream>
using std::endl;
using sdbuscpp::xml::Document;
using sdbuscpp::xml::Node;
using sdbuscpp::xml::Nodes;
int BaseGenerator::transformXmlToFile(const Document& doc, const char* filename) const
{
return transformXmlToFileImpl(doc, filename);
}
int BaseGenerator::writeToFile(const char* filename, const std::string& data) const
{
std::ofstream file(filename);
if (file.bad())
{
std::cerr << "Unable to write file " << filename << endl;
return 1;
}
file << data;
file.close();
return 0;
}
std::string BaseGenerator::createHeader(const char* filename, const StubType& stubType) const
{
std::ostringstream head;
head << getHeaderComment();
std::string specialization = stubType == StubType::ADAPTOR ? "adaptor" : "proxy";
std::string cond_comp{"__sdbuscpp__" + underscorize(filename)
+ "__" + specialization + "__H__"};
head << "#ifndef " << cond_comp << endl
<< "#define " << cond_comp << endl << endl;
head << "#include <sdbus-c++/sdbus-c++.h>" << endl
<< "#include <string>" << endl
<< "#include <tuple>" << endl
<< endl;
return head.str();
}
std::tuple<unsigned, std::string> BaseGenerator::generateNamespaces(const std::string& ifaceName) const
{
std::stringstream ss{ifaceName};
std::ostringstream body;
unsigned count{0};
// prints the namespaces X and Y defined with <interface name="X.Y.Z">
while (ss.str().find('.', ss.tellg()) != std::string::npos)
{
std::string nspace;
getline(ss, nspace, '.');
body << "namespace " << nspace << " {" << endl;
++count;
}
body << endl;
return std::make_tuple(count, body.str());
}
std::tuple<std::string, std::string, std::string> BaseGenerator::argsToNamesAndTypes(const Nodes& args) const
{
std::ostringstream argSS, argTypeSS, typeSS;
bool firstArg{true};
for (const auto& arg : args)
{
if (firstArg) firstArg = false; else { argSS << ", "; argTypeSS << ", "; typeSS << ", "; }
auto argName = arg->get("name");
auto type = signature_to_type(arg->get("type"));
argSS << argName;
argTypeSS << "const " << type << "& " << argName;
typeSS << type;
}
return std::make_tuple(argSS.str(), argTypeSS.str(), typeSS.str());
}
/**
*
*/
std::string BaseGenerator::outArgsToType(const Nodes& args) const
{
std::ostringstream retTypeSS;
if (args.size() == 0)
{
retTypeSS << "void";
}
else if (args.size() == 1)
{
const auto& arg = *args.begin();
retTypeSS << signature_to_type(arg->get("type"));
}
else if (args.size() >= 2)
{
retTypeSS << "std::tuple<";
bool firstArg = true;
for (const auto& arg : args)
{
if (firstArg) firstArg = false; else retTypeSS << ", ";
retTypeSS << signature_to_type(arg->get("type"));
}
retTypeSS << ">";
}
return retTypeSS.str();
}

View File

@ -0,0 +1,103 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file BaseGenerator.h
*
* Created on: Feb 1, 2017
* 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 __SDBUSCPP_TOOLS_BASE_GENERATOR_H
#define __SDBUSCPP_TOOLS_BASE_GENERATOR_H
// Own headers
#include "xml.h"
// STL
#include <string>
#include <tuple>
class BaseGenerator
{
public:
int transformXmlToFile(const sdbuscpp::xml::Document& doc, const char* filename) const;
protected:
enum class StubType
{
ADAPTOR,
PROXY
};
constexpr static const char *tab = " ";
virtual ~BaseGenerator() {}
/**
* Implementation of public function that is provided by inherited class
* @param doc
* @param filename
* @return
*/
virtual int transformXmlToFileImpl(const sdbuscpp::xml::Document& doc, const char* filename) const = 0;
/**
* Write data to file
* @param filename Written file
* @param data Data to write
* @return 0 if ok
*/
int writeToFile(const char* filename, const std::string& data) const;
/**
* Crete header of file - include guard, includes
* @param filename
* @param stubType
* @return
*/
std::string createHeader(const char* filename, const StubType& stubType) const;
/**
* Namespaces according to the interface name
* @param ifaceName
* @return tuple: count of namespaces, string with code
*/
std::tuple<unsigned, std::string> generateNamespaces(const std::string& ifaceName) const;
/**
* Transform arguments into source code
* @param args
* @return tuple: argument names, argument types and names, argument types
*/
std::tuple<std::string, std::string, std::string> argsToNamesAndTypes(const sdbuscpp::xml::Nodes& args) const;
/**
* Output arguments to return type
* @param args
* @return return type
*/
std::string outArgsToType(const sdbuscpp::xml::Nodes& args) const;
};
#endif //__SDBUSCPP_TOOLS_BASE_GENERATOR_H

View File

@ -0,0 +1,13 @@
cmake_minimum_required (VERSION 3.3)
add_definitions(-std=c++14)
project (sdbuscpp-xml2cpp)
add_executable(${PROJECT_NAME} xml2cpp.cpp xml.cpp generator_utils.cpp BaseGenerator.cpp AdaptorGenerator.cpp ProxyGenerator.cpp)
find_package (EXPAT REQUIRED)
target_link_libraries (${PROJECT_NAME} ${EXPAT_LIBRARIES})
install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME} DESTINATION bin)

View File

@ -0,0 +1,224 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file ProxyGenerator.cpp
*
* Created on: Feb 1, 2017
* 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/>.
*/
// Own
#include "generator_utils.h"
#include "ProxyGenerator.h"
// STL
#include <iostream>
#include <cstdlib>
#include <algorithm>
#include <iterator>
using std::endl;
using sdbuscpp::xml::Document;
using sdbuscpp::xml::Node;
using sdbuscpp::xml::Nodes;
/**
* Generate proxy code - client glue
*/
int ProxyGenerator::transformXmlToFileImpl(const Document &doc, const char *filename) const
{
Node &root = *(doc.root);
Nodes interfaces = root["interface"];
std::ostringstream code;
code << createHeader(filename, StubType::PROXY);
for (const auto& interface : interfaces)
{
code << processInterface(*interface);
}
code << "#endif" << endl;
return writeToFile(filename, code.str());
}
std::string ProxyGenerator::processInterface(Node& interface) const
{
std::string ifaceName = interface.get("name");
std::cout << "Generating proxy code for interface " << ifaceName << endl;
unsigned int namespacesCount = 0;
std::string namespacesStr;
std::tie(namespacesCount, namespacesStr) = generateNamespaces(ifaceName);
std::ostringstream body;
body << namespacesStr;
std::string className = ifaceName.substr(ifaceName.find_last_of(".") + 1)
+ "_proxy";
body << "class " << className << endl
<< "{" << endl
<< "public:" << endl
<< tab << "static constexpr const char* interfaceName = \"" << ifaceName << "\";" << endl << endl
<< "protected:" << endl
<< tab << className << "(sdbus::IObjectProxy& object)" << endl
<< tab << tab << ": object_(object)" << endl;
Nodes methods = interface["method"];
Nodes signals = interface["signal"];
Nodes properties = interface["property"];
std::string registration, declaration;
std::tie(registration, declaration) = processSignals(signals);
body << tab << "{" << endl
<< registration
<< tab << "}" << endl << endl
<< declaration << endl;
std::string methodDefinitions = processMethods(methods);
if (!methodDefinitions.empty())
{
body << "public:" << endl << methodDefinitions;
}
std::string propertyDefinitions = processProperties(properties);
if (!propertyDefinitions.empty())
{
body << "public:" << endl << propertyDefinitions;
}
body << "private:" << endl
<< tab << "sdbus::IObjectProxy& object_;" << endl
<< "};" << endl << endl
<< std::string(namespacesCount, '}') << " // namespaces" << endl << endl;
return body.str();
}
std::string ProxyGenerator::processMethods(const Nodes& methods) const
{
std::ostringstream methodSS;
for (const auto& method : methods)
{
auto name = method->get("name");
Nodes args = (*method)["arg"];
Nodes inArgs = args.select("direction" , "in");
Nodes outArgs = args.select("direction" , "out");
auto retType = outArgsToType(outArgs);
std::string argStr, argTypeStr;
std::tie(argStr, argTypeStr, std::ignore) = argsToNamesAndTypes(inArgs);
methodSS << tab << retType << " " << name << "(" << argTypeStr << ")" << endl
<< tab << "{" << endl;
if (outArgs.size() > 0)
{
methodSS << tab << tab << retType << " result;" << endl;
}
methodSS << tab << tab << "object_.callMethod(\"" << name << "\")"
".onInterface(interfaceName)";
if (inArgs.size() > 0)
{
methodSS << ".withArguments(" << argStr << ")";
}
if (outArgs.size() > 0)
{
methodSS << ".storeResultsTo(result);" << endl
<< tab << tab << "return result";
}
methodSS << ";" << endl << tab << "}" << endl << endl;
}
return methodSS.str();
}
std::tuple<std::string, std::string> ProxyGenerator::processSignals(const Nodes& signals) const
{
std::ostringstream registrationSS, declarationSS;
for (const auto& signal : signals)
{
auto name = signal->get("name");
Nodes args = (*signal)["arg"];
auto nameBigFirst = name;
nameBigFirst[0] = islower(nameBigFirst[0]) ? nameBigFirst[0] + 'A' - 'a' : nameBigFirst[0];
std::string argStr, argTypeStr;
std::tie(argStr, argTypeStr, std::ignore) = argsToNamesAndTypes(args);
registrationSS << tab << tab << "object_"
".uponSignal(\"" << name << "\")"
".onInterface(interfaceName)"
".call([this](" << argTypeStr << ")"
"{ this->on" << nameBigFirst << "(" << argStr << "); });" << endl;
declarationSS << tab << "virtual void on" << nameBigFirst << "(" << argTypeStr << ") = 0;" << endl;
}
return std::make_tuple(registrationSS.str(), declarationSS.str());
}
std::string ProxyGenerator::processProperties(const Nodes& properties) const
{
std::ostringstream propertySS;
for (const auto& property : properties)
{
auto propertyName = property->get("name");
auto propertyAccess = property->get("access");
auto propertySignature = property->get("type");
auto propertyType = signature_to_type(propertySignature);
auto propertyArg = std::string("value");
auto propertyTypeArg = std::string("const ") + propertyType + "& " + propertyArg;
if (propertyAccess == "read" || propertyAccess == "readwrite")
{
propertySS << tab << propertyType << " " << propertyName << "()" << endl
<< tab << "{" << endl;
propertySS << tab << tab << "return object_.getProperty(\"" << propertyName << "\")"
".onInterface(interfaceName)";
propertySS << ";" << endl << tab << "}" << endl << endl;
}
if (propertyAccess == "readwrite" || propertyAccess == "write")
{
propertySS << tab << "void " << propertyName << "(" << propertyTypeArg << ")" << endl
<< tab << "{" << endl;
propertySS << tab << tab << "object_.setProperty(\"" << propertyName << "\")"
".onInterface(interfaceName)"
".toValue(" << propertyArg << ")";
propertySS << ";" << endl << tab << "}" << endl << endl;
}
}
return propertySS.str();
}

View File

@ -0,0 +1,81 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file ProxyGenerator.h
*
* Created on: Feb 1, 2017
* 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 __SDBUSCPP_TOOLS_PROXY_GENERATOR_H
#define __SDBUSCPP_TOOLS_PROXY_GENERATOR_H
// Own headers
#include "xml.h"
#include "BaseGenerator.h"
// STL
#include <tuple>
class ProxyGenerator : public BaseGenerator
{
protected:
/**
* Transform xml to proxy code
* @param doc
* @param filename
* @return 0 if ok
*/
int transformXmlToFileImpl(const sdbuscpp::xml::Document& doc, const char* filename) const override;
private:
/**
* Generate source code for interface
* @param interface
* @return source code
*/
std::string processInterface(sdbuscpp::xml::Node& interface) const;
/**
* Generate method calls
* @param methods
* @return source code
*/
std::string processMethods(const sdbuscpp::xml::Nodes& methods) const;
/**
* Generate code for handling signals
* @param signals
* @return tuple: registration, declaration of virtual methods
*/
std::tuple<std::string, std::string> processSignals(const sdbuscpp::xml::Nodes& signals) const;
/**
* Generate calls for properties
* @param properties
* @return source code
*/
std::string processProperties(const sdbuscpp::xml::Nodes& properties) const;
};
#endif //__SDBUSCPP_TOOLS_PROXY_GENERATOR_H

View File

@ -0,0 +1,143 @@
/**
* Inspired by: http://dbus-cplusplus.sourceforge.net/
*/
#include <iostream>
#include <cstdlib>
#include <map>
#include "generator_utils.h"
std::string underscorize(const std::string& str)
{
std::string res = str;
for (unsigned int i = 0; i < res.length(); ++i)
{
if (!isalpha(res[i]) && !isdigit(res[i]))
{
res[i] = '_';
}
}
return res;
}
std::string stub_name(const std::string& name)
{
return "_" + underscorize(name) + "_stub";
}
const char *atomic_type_to_string(char t)
{
static std::map<char, const char*> atos
{
{ 'y', "uint8_t" },
{ 'b', "bool" },
{ 'n', "int16_t" },
{ 'q', "uint16_t" },
{ 'i', "int32_t" },
{ 'u', "uint32_t" },
{ 'x', "int64_t" },
{ 't', "uint64_t" },
{ 'd', "double" },
{ 's', "std::string" },
{ 'o', "sdbus::ObjectPath" },
{ 'g', "sdbus::Signature" },
{ 'v', "sdbus::Variant" },
{ '\0', "" }
};
if (atos.count(t))
{
return atos[t];
}
return nullptr;
}
static void _parse_signature(const std::string &signature, std::string &type, unsigned int &i, bool only_once = false)
{
for (; i < signature.length(); ++i)
{
switch (signature[i])
{
case 'a':
{
switch (signature[++i])
{
case '{':
{
type += "std::map<";
++i;
_parse_signature(signature, type, i);
type += ">";
break;
}
case '(':
{
type += "std::vector<sdbus::Struct<";
++i;
_parse_signature(signature, type, i);
type += ">>";
break;
}
default:
{
type += "std::vector<";
_parse_signature(signature, type, i, true);
type += ">";
break;
}
}
break;
}
case '(':
{
type += "sdbus::Struct<";
++i;
_parse_signature(signature, type, i);
type += ">";
break;
}
case ')':
case '}':
{
return;
}
default:
{
const char *atom = atomic_type_to_string(signature[i]);
if (!atom)
{
std::cerr << "Invalid signature: " << signature << std::endl;
exit(-1);
}
type += atom;
break;
}
}
if (only_once)
return;
if (i + 1 < signature.length() && signature[i + 1] != ')' && signature[i + 1] != '}')
{
type += ", ";
}
}
}
std::string signature_to_type(const std::string& signature)
{
std::string type;
unsigned int i = 0;
_parse_signature(signature, type, i);
return type;
}

View File

@ -0,0 +1,22 @@
/**
* Inspired by: http://dbus-cplusplus.sourceforge.net/
*/
#ifndef __SDBUSCPP_TOOLS_GENERATOR_UTILS_H
#define __SDBUSCPP_TOOLS_GENERATOR_UTILS_H
#include <string>
#include <sstream>
#include <iomanip>
const char *atomic_type_to_string(char t);
std::string stub_name(const std::string& name);
std::string signature_to_type(const std::string& signature);
std::string underscorize(const std::string& str);
constexpr const char* getHeaderComment() noexcept { return "\n/*\n * This file was automatically generated by sdbuscpp-xml2cpp; DO NOT EDIT!\n */\n\n"; }
#endif //__SDBUSCPP_TOOLS_GENERATOR_UTILS_H

309
stub-generator/xml.cpp Normal file
View File

@ -0,0 +1,309 @@
/**
* Inspired by: http://dbus-cplusplus.sourceforge.net/
*/
#include "xml.h"
#include <expat.h>
using namespace sdbuscpp::xml;
std::istream &operator >> (std::istream& in, Document& doc)
{
std::stringbuf xmlbuf;
in.get(xmlbuf, '\0');
doc.from_xml(xmlbuf.str());
return in;
}
std::ostream &operator << (std::ostream& out, const Document& doc)
{
return out << doc.to_xml();
}
Error::Error(const char *error, int line, int column)
{
std::ostringstream estream;
estream << "line " << line << ", column " << column << ": " << error;
m_error = estream.str();
}
Node::Node(const char *n, const char **a)
: name(n)
{
if (a)
{
for (int i = 0; a[i]; i += 2)
{
m_attrs[a[i]] = a[i + 1];
}
}
}
Nodes Nodes::operator[](const std::string& key) const
{
Nodes result;
for (auto it = begin(), endIt = end(); it != endIt; ++it)
{
Nodes part = (**it)[key];
result.insert(result.end(), part.begin(), part.end());
}
return result;
}
Nodes Nodes::select(const std::string& attr, const std::string& value) const
{
Nodes result;
for (auto it = begin(), itEnd = end(); it != itEnd; ++it)
{
if ((*it)->get(attr) == value)
{
result.insert(result.end(), *it);
}
}
return result;
}
Nodes Node::operator[](const std::string& key)
{
Nodes result;
if (key.length() == 0)
{
return result;
}
for (auto it = children.begin(), itEnd = children.end(); it != itEnd; ++it)
{
if (it->name == key)
{
result.push_back(&(*it));
}
}
return result;
}
std::string Node::get(const std::string& attribute) const
{
const auto it = m_attrs.find(attribute);
if (it != m_attrs.end())
{
return it->second;
}
return "";
}
void Node::set(const std::string& attribute, std::string value)
{
if (value.length())
{
m_attrs[attribute] = value;
}
else
{
m_attrs.erase(value);
}
}
std::string Node::to_xml() const
{
std::string xml;
int depth = 0;
_raw_xml(xml, depth);
return xml;
}
void Node::_raw_xml(std::string& xml, int& depth) const
{
xml.append(depth * 2, ' ');
xml.append("<" + name);
for (auto it = m_attrs.begin(), itEnd = m_attrs.end(); it != itEnd; ++it)
{
xml.append(" " + it->first + "=\"" + it->second + "\"");
}
if (cdata.length() == 0 && children.size() == 0)
{
xml.append("/>\n");
}
else
{
xml.append(">");
if (cdata.length())
{
xml.append(cdata);
}
if (children.size())
{
xml.append("\n");
++depth;
for (auto it = children.begin(), itEnd = children.end(); it != itEnd; ++it)
{
it->_raw_xml(xml, depth);
}
--depth;
xml.append(depth * 2, ' ');
}
xml.append("</" + name + ">\n");
}
}
Document::Document() :
root(nullptr),
m_depth(0)
{
}
Document::Document(const std::string &xml) :
root(nullptr),
m_depth(0)
{
from_xml(xml);
}
Document::~Document()
{
delete root;
}
struct Document::Expat
{
static void start_doctype_decl_handler(
void *data, const XML_Char *name, const XML_Char *sysid, const XML_Char *pubid, int has_internal_subset
);
static void end_doctype_decl_handler(void *data);
static void start_element_handler(void *data, const XML_Char *name, const XML_Char **atts);
static void character_data_handler(void *data, const XML_Char *chars, int len);
static void end_element_handler(void *data, const XML_Char *name);
};
void Document::from_xml(const std::string& xml)
{
m_depth = 0;
if (root)
{
delete root;
}
root = nullptr;
XML_Parser parser = XML_ParserCreate("UTF-8");
XML_SetUserData(parser, this);
XML_SetDoctypeDeclHandler(
parser,
Document::Expat::start_doctype_decl_handler,
Document::Expat::end_doctype_decl_handler
);
XML_SetElementHandler(
parser,
Document::Expat::start_element_handler,
Document::Expat::end_element_handler
);
XML_SetCharacterDataHandler(
parser,
Document::Expat::character_data_handler
);
XML_Status status = XML_Parse(parser, xml.c_str(), xml.length(), true);
if (status == XML_STATUS_ERROR)
{
const char* error = XML_ErrorString(XML_GetErrorCode(parser));
int line = XML_GetCurrentLineNumber(parser);
int column = XML_GetCurrentColumnNumber(parser);
XML_ParserFree(parser);
throw Error(error, line, column);
}
else
{
XML_ParserFree(parser);
}
}
std::string Document::to_xml() const
{
return root->to_xml();
}
void Document::Expat::start_doctype_decl_handler(
void* data,
const XML_Char* name,
const XML_Char* sysid,
const XML_Char *pubid,
int has_internal_subset)
{
}
void Document::Expat::end_doctype_decl_handler(void* data)
{
}
void Document::Expat::start_element_handler(void* data, const XML_Char* name, const XML_Char** atts)
{
Document* doc = static_cast<Document*>(data);
if (!doc->root)
{
doc->root = new Node(name, atts);
}
else
{
Node::Children* cld = &(doc->root->children);
for (int i = 1; i < doc->m_depth; ++i)
{
cld = &(cld->back().children);
}
cld->push_back(Node(name, atts));
}
doc->m_depth++;
}
void Document::Expat::character_data_handler(void* data, const XML_Char* chars, int len)
{
Document* doc = static_cast<Document*>(data);
Node* nod = doc->root;
for (int i = 1; i < doc->m_depth; ++i)
{
nod = &(nod->children.back());
}
int x = 0, y = len - 1;
while (isspace(chars[y]) && y > 0) --y;
while (isspace(chars[x]) && x < y) ++x;
nod->cdata = std::string(chars, x, y + 1);
}
void Document::Expat::end_element_handler(void* data, const XML_Char* name)
{
Document* doc = static_cast<Document*>(data);
doc->m_depth--;
}

113
stub-generator/xml.h Normal file
View File

@ -0,0 +1,113 @@
/**
* Inspired by: http://dbus-cplusplus.sourceforge.net/
*/
#ifndef __SDBUSCPP_TOOLS_XML_H
#define __SDBUSCPP_TOOLS_XML_H
#include <exception>
#include <string>
#include <vector>
#include <map>
#include <memory>
#include <iostream>
#include <sstream>
namespace sdbuscpp
{
namespace xml
{
class Error : public std::exception
{
public:
Error(const char *error, int line, int column);
~Error() {}
const char *what() const noexcept { return m_error.c_str(); }
private:
std::string m_error;
};
class Node;
class Nodes : public std::vector<Node *>
{
public:
Nodes operator[](const std::string &key) const;
Nodes select(const std::string &attr, const std::string &value) const;
};
class Node
{
public:
using Attributes = std::map<std::string, std::string>;
using Children = std::vector<Node>;
std::string name;
std::string cdata;
Children children;
Node(std::string n, Attributes a) :
name(n),
m_attrs(a)
{}
Node(const char* n, const char** a = nullptr);
Nodes operator[](const std::string& key);
std::string get(const std::string& attribute) const;
void set(const std::string &attribute, std::string value);
std::string to_xml() const;
Node& add(Node child)
{
children.push_back(child);
return children.back();
}
private:
void _raw_xml(std::string& xml, int& depth) const;
Attributes m_attrs;
};
class Document
{
public:
struct Expat;
Node* root;
Document();
~Document();
Document(const std::string& xml);
void from_xml(const std::string& xml);
std::string to_xml() const;
private:
int m_depth;
};
} // namespace xml
} // namespace sdbuscpp
std::istream &operator >> (std::istream &, sdbuscpp::xml::Document &);
std::ostream &operator << (std::ostream &, sdbuscpp::xml::Document &);
#endif//__SDBUSCPP_TOOLS_XML_H

202
stub-generator/xml2cpp.cpp Normal file
View File

@ -0,0 +1,202 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file xml2cpp.cpp
*
* Created on: Feb 1, 2017
* 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/>.
*/
// Own
#include "xml.h"
#include "AdaptorGenerator.h"
#include "ProxyGenerator.h"
// STL
#include <cstdlib>
#include <cstring>
#include <string>
#include <map>
#include <iostream>
#include <fstream>
#include <sstream>
using std::endl;
using namespace sdbuscpp;
void usage(std::ostream& output, const char* programName)
{
output << "Usage: " << programName << " [OPTION]... [FILE]" << endl <<
"Creates C++ stubs for DBus API for adaptor and/or client" << endl <<
endl <<
"Available options:" << endl <<
" --proxy=FILE Generate header file FILE with proxy class (client)" << endl <<
" --adaptor=FILE Generate header file FILE with stub class (server)" << endl <<
" -h, --help " << endl <<
" --verbose Explain what is being done" << endl <<
endl <<
"The stub generator takes an XML file describing DBus interface and creates" << endl <<
"C++ header files to be used by C++ code wanting to cumminicate through that" << endl <<
"interface. Clients of the interface (those making the calls) need header" << endl <<
"created with the --proxy option as this header forwards the calls via DBus" << endl <<
"to provider of the service and the returns the result to the caller. Server" << endl <<
"implementing the service should derive from interface classes in header" << endl <<
"generated for --adaptor option and implement their methods." << endl <<
endl <<
"When FILE is not specified, standard input is read. Exit status is 0 when" << endl <<
"no error was encountered and all requested headers were sucessfully generated." << endl <<
"Otherwise 1 is returned." << endl;
}
int main(int argc, char **argv)
{
const char* programName = argv[0];
argv++;
argc--;
const char* proxy = nullptr;
const char* adaptor = nullptr;
const char* xmlFile = nullptr;
bool verbose = false;
while (argc > 0)
{
if (!strncmp(*argv, "--proxy=", 8))
{
if (proxy != nullptr)
{
std::cerr << "Multiple occurrencies of --proxy is not allowed" << endl;
usage(std::cerr, programName);
return 1;
}
proxy = *argv + 8;
}
else if (!strncmp(*argv, "--adaptor=", 10) || !strncmp(*argv, "--adapter=", 10))
{
if (adaptor != nullptr)
{
std::cerr << "Multiple occurrencies of --adaptor is not allowed" << endl;
usage(std::cerr, programName);
return 1;
}
adaptor = *argv + 10;
}
else if (!strcmp(*argv, "--help") || !strcmp(*argv, "-h"))
{
usage(std::cout, programName);
return 0;
}
else if (!strcmp(*argv, "--verbose"))
{
verbose = true;
}
else if (**argv == '-')
{
std::cerr << "Unknown option " << *argv << endl;
usage(std::cerr, programName);
return 1;
}
else
{
if (xmlFile != nullptr)
{
std::cerr << "More than one input file specified: " << *argv << endl;
usage(std::cerr, programName);
return 1;
}
xmlFile = *argv;
}
argc--;
argv++;
}
if (!proxy && !adaptor)
{
std::cerr << "Either --proxy or --adapter need to be specified" << endl;
usage(std::cerr, programName);
return 1;
}
xml::Document doc;
try
{
if (xmlFile != nullptr)
{
if (verbose)
{
std::cerr << "Reading DBus interface from " << xmlFile << endl;
}
std::ifstream input(xmlFile);
if (input.bad())
{
std::cerr << "Unable to open file " << xmlFile << endl;
return 1;
}
input >> doc;
}
else
{
if (verbose)
{
std::cerr << "Reading DBus interface from standard input" << endl;
}
std::cin >> doc;
}
}
catch (const xml::Error& e)
{
std::cerr << "Parsing error: " << e.what() << endl;
return 1;
}
if (!doc.root)
{
std::cerr << "Empty document" << endl;
return 1;
}
if (proxy)
{
if (verbose)
{
std::cerr << "Generating proxy header " << proxy << endl;
}
ProxyGenerator pg;
pg.transformXmlToFile(doc, proxy);
}
if (adaptor)
{
if (verbose)
{
std::cerr << "Generating adaptor header " << adaptor << endl;
}
AdaptorGenerator ag;
ag.transformXmlToFile(doc, adaptor);
}
return 0;
}

68
test/Makefile.am Normal file
View File

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

View File

@ -0,0 +1,289 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file AdaptorAndProxy_test.cpp
*
* Created on: Jan 2, 2017
* 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/>.
*/
// Own
#include "Connection.h"
#include "TestingAdaptor.h"
#include "TestingProxy.h"
// sdbus
#include "sdbus-c++/sdbus-c++.h"
// gmock
#include <gtest/gtest.h>
#include <gmock/gmock.h>
// STL
#include <string>
#include <thread>
#include <tuple>
using ::testing::Eq;
using ::testing::Gt;
namespace
{
class AdaptorAndProxyFixture : public ::testing::Test
{
public:
static void SetUpTestCase()
{
m_connection.requestName(INTERFACE_NAME);
m_connection.enterProcessingLoopAsync();
}
static void TearDownTestCase()
{
m_connection.leaveProcessingLoop();
m_connection.releaseName(INTERFACE_NAME);
}
private:
void SetUp() override
{
m_adaptor = std::make_unique<TestingAdaptor>(m_connection);
m_proxy = std::make_unique<TestingProxy>(INTERFACE_NAME, OBJECT_PATH);
usleep(50000); // Give time for the proxy to start listening to signals
}
void TearDown() override
{
m_proxy.reset();
m_adaptor.reset();
}
public:
static sdbus::internal::Connection m_connection;
std::unique_ptr<TestingAdaptor> m_adaptor;
std::unique_ptr<TestingProxy> m_proxy;
};
sdbus::internal::Connection AdaptorAndProxyFixture::m_connection{sdbus::internal::Connection::BusType::eSystem};
}
/*-------------------------------------*/
/* -- TEST CASES -- */
/*-------------------------------------*/
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));
connection->releaseName(INTERFACE_NAME);
}
// Methods
using SdbusTestObject = AdaptorAndProxyFixture;
TEST_F(SdbusTestObject, CallsEmptyMethodSuccesfully)
{
ASSERT_NO_THROW(m_proxy->noArgNoReturn());
}
TEST_F(SdbusTestObject, CallsMethodsWithBaseTypesSuccesfully)
{
auto resInt = m_proxy->getInt();
ASSERT_THAT(resInt, Eq(INT32_VALUE));
auto multiplyRes = m_proxy->multiply(INT64_VALUE, DOUBLE_VALUE);
ASSERT_THAT(multiplyRes, Eq(INT64_VALUE * DOUBLE_VALUE));
}
TEST_F(SdbusTestObject, CallsMethodsWithTuplesSuccesfully)
{
auto resTuple = m_proxy->getTuple();
ASSERT_THAT(std::get<0>(resTuple), Eq(UINT32_VALUE));
ASSERT_THAT(std::get<1>(resTuple), Eq(STRING_VALUE));
}
TEST_F(SdbusTestObject, CallsMethodsWithStructSuccesfully)
{
sdbus::Struct<uint8_t, int16_t, double, std::string, std::vector<int16_t>> a{};
auto vectorRes = m_proxy->getInts16FromStruct(a);
ASSERT_THAT(vectorRes, Eq(std::vector<int16_t>{0})); // because second item is by default initialized to 0
sdbus::Struct<uint8_t, int16_t, double, std::string, std::vector<int16_t>> b{
UINT8_VALUE, INT16_VALUE, DOUBLE_VALUE, STRING_VALUE, {INT16_VALUE, -INT16_VALUE}
};
vectorRes = m_proxy->getInts16FromStruct(b);
ASSERT_THAT(vectorRes, Eq(std::vector<int16_t>{INT16_VALUE, INT16_VALUE, -INT16_VALUE}));
}
TEST_F(SdbusTestObject, CallsMethodWithVariantSuccesfully)
{
sdbus::Variant v{DOUBLE_VALUE};
auto variantRes = m_proxy->processVariant(v);
ASSERT_THAT(variantRes.get<int32_t>(), Eq(static_cast<int32_t>(DOUBLE_VALUE)));
}
TEST_F(SdbusTestObject, CallsMethodWithStructVariantsAndGetMapSuccesfully)
{
std::vector<int32_t> x{-2, 0, 2};
sdbus::Struct<sdbus::Variant, sdbus::Variant> y{false, true};
auto mapOfVariants = m_proxy->getMapOfVariants(x, y);
decltype(mapOfVariants) res{{-2, false}, {0, false}, {2, true}};
ASSERT_THAT(mapOfVariants[-2].get<bool>(), Eq(res[-2].get<bool>()));
ASSERT_THAT(mapOfVariants[0].get<bool>(), Eq(res[0].get<bool>()));
ASSERT_THAT(mapOfVariants[2].get<bool>(), Eq(res[2].get<bool>()));
}
TEST_F(SdbusTestObject, CallsMethodWithStructInStructSuccesfully)
{
auto val = m_proxy->getStructInStruct();
ASSERT_THAT(val.get<0>(), Eq(STRING_VALUE));
ASSERT_THAT(std::get<0>(std::get<1>(val))[INT32_VALUE], Eq(INT32_VALUE));
}
TEST_F(SdbusTestObject, CallsMethodWithTwoStructsSuccesfully)
{
auto val = m_proxy->sumStructItems({1, 2}, {3, 4});
ASSERT_THAT(val, Eq(1 + 2 + 3 + 4));
}
TEST_F(SdbusTestObject, CallsMethodWithTwoVectorsSuccesfully)
{
auto val = m_proxy->sumVectorItems({1, 7}, {2, 3});
ASSERT_THAT(val, Eq(1 + 7 + 2 + 3));
}
TEST_F(SdbusTestObject, CallsMethodWithSignatureSuccesfully)
{
auto resSignature = m_proxy->getSignature();
ASSERT_THAT(resSignature, Eq(SIGNATURE_VALUE));
}
TEST_F(SdbusTestObject, CallsMethodWithObjectPathSuccesfully)
{
auto resObjectPath = m_proxy->getObjectPath();
ASSERT_THAT(resObjectPath, Eq(OBJECT_PATH_VALUE));
}
TEST_F(SdbusTestObject, CallsMethodWithComplexTypeSuccesfully)
{
auto resComplex = m_proxy->getComplex();
ASSERT_THAT(resComplex.count(0), Eq(1));
}
TEST_F(SdbusTestObject, FailsCallingNonexistentMethod)
{
ASSERT_THROW(m_proxy->callNonexistentMethod(), sdbus::Error);
}
TEST_F(SdbusTestObject, FailsCallingMethodOnNonexistentInterface)
{
ASSERT_THROW(m_proxy->callMethodOnNonexistentInterface(), sdbus::Error);
}
TEST_F(SdbusTestObject, FailsCallingMethodOnNonexistentDestination)
{
TestingProxy proxy("wrongDestination", OBJECT_PATH);
ASSERT_THROW(proxy.getInt(), sdbus::Error);
}
TEST_F(SdbusTestObject, FailsCallingMethodOnNonexistentObject)
{
TestingProxy proxy(INTERFACE_NAME, "/wrong/path");
ASSERT_THROW(proxy.getInt(), sdbus::Error);
}
// Signals
TEST_F(SdbusTestObject, EmitsSimpleSignalSuccesfully)
{
auto count = m_proxy->getSimpleCallCount();
m_adaptor->simpleSignal();
usleep(10000);
ASSERT_THAT(m_proxy->getSimpleCallCount(), Eq(count + 1));
}
TEST_F(SdbusTestObject, EmitsSignalWithMapSuccesfully)
{
m_adaptor->signalWithMap({{0, "zero"}, {1, "one"}});
usleep(10000);
auto map = m_proxy->getMap();
ASSERT_THAT(map[0], Eq("zero"));
ASSERT_THAT(map[1], Eq("one"));
}
TEST_F(SdbusTestObject, EmitsSignalWithVariantSuccesfully)
{
double d = 3.14;
m_adaptor->signalWithVariant(3.14);
usleep(10000);
ASSERT_THAT(m_proxy->getVariantValue(), d);
}
TEST_F(SdbusTestObject, EmitsSignalWithoutRegistrationSuccesfully)
{
m_adaptor->signalWithoutRegistration({"platform", {"av"}});
usleep(10000);
auto signature = m_proxy->getSignatureFromSignal();
ASSERT_THAT(signature["platform"], Eq("av"));
}
TEST_F(SdbusTestObject, failsEmitingSignalOnNonexistentInterface)
{
ASSERT_THROW(m_adaptor->emitSignalOnNonexistentInterface(), sdbus::Error);
}
// Properties
TEST_F(SdbusTestObject, ReadsReadPropertySuccesfully)
{
ASSERT_THAT(m_proxy->state(), Eq(STRING_VALUE));
}
TEST_F(SdbusTestObject, WritesAndReadsReadWritePropertySuccesfully)
{
auto x = 42;
ASSERT_NO_THROW(m_proxy->action(x));
ASSERT_THAT(m_proxy->action(), Eq(x));
}
TEST_F(SdbusTestObject, WritesToWritePropertySuccesfully)
{
auto x = true;
ASSERT_NO_THROW(m_proxy->blocking(x));
}
TEST_F(SdbusTestObject, CannotReadFromWriteProperty)
{
ASSERT_THROW(m_proxy->blocking(), sdbus::Error);
}

View File

@ -0,0 +1,91 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file Connection_test.cpp
*
* Created on: Jan 2, 2017
* 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/>.
*/
// Own
#include "defs.h"
// sdbus
#include <sdbus-c++/Error.h>
#include <sdbus-c++/IConnection.h>
// gmock
#include <gtest/gtest.h>
#include <gmock/gmock.h>
// STL
#include <thread>
using ::testing::Eq;
/*-------------------------------------*/
/* -- TEST CASES -- */
/*-------------------------------------*/
TEST(Connection, CanBeDefaultConstructed)
{
ASSERT_NO_THROW(sdbus::createConnection());
}
TEST(Connection, CanRequestRegisteredDbusName)
{
auto connection = sdbus::createConnection();
ASSERT_NO_THROW(connection->requestName(INTERFACE_NAME));
connection->releaseName(INTERFACE_NAME);
}
TEST(Connection, CannotRequestNonregisteredDbusName)
{
auto connection = sdbus::createConnection();
ASSERT_THROW(connection->requestName("some_random_not_supported_dbus_name"), sdbus::Error);
}
TEST(Connection, CanReleasedRequestedName)
{
auto connection = sdbus::createConnection();
connection->requestName(INTERFACE_NAME);
ASSERT_NO_THROW(connection->releaseName(INTERFACE_NAME));
}
TEST(Connection, CannotReleaseNonrequestedName)
{
auto connection = sdbus::createConnection();
ASSERT_THROW(connection->releaseName("some_random_nonrequested_name"), sdbus::Error);
}
TEST(Connection, CanEnterAndLeaveProcessingLoop)
{
auto connection = sdbus::createConnection();
connection->requestName(INTERFACE_NAME);
std::thread t([&](){ connection->enterProcessingLoop(); });
connection->leaveProcessingLoop();
t.join();
connection->releaseName(INTERFACE_NAME);
}

View File

@ -0,0 +1,146 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file TestingAdaptor.h
*
* Created on: Jan 2, 2017
* 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_CPP_INTEGRATIONTESTS_TESTINGADAPTOR_H_
#define SDBUS_CPP_INTEGRATIONTESTS_TESTINGADAPTOR_H_
#include "adaptor-glue.h"
class TestingAdaptor : public sdbus::Interfaces<testing_adaptor>
{
public:
TestingAdaptor(sdbus::IConnection& connection) :
sdbus::Interfaces<::testing_adaptor>(connection, OBJECT_PATH) { }
virtual ~TestingAdaptor() { }
protected:
void noArgNoReturn() const { }
int32_t getInt() const { return INT32_VALUE; }
std::tuple<uint32_t, std::string> getTuple() const { return std::make_tuple(UINT32_VALUE, STRING_VALUE); }
double multiply(const int64_t& a, const double& b) const { return a * b; }
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>()};
auto y = std::get<std::vector<int16_t>>(x);
res.insert(res.end(), y.begin(), y.end());
return res;
}
sdbus::Variant processVariant(sdbus::Variant& v)
{
sdbus::Variant res = static_cast<int32_t>(v.get<double>());
return res;
}
std::map<int32_t, sdbus::Variant> getMapOfVariants(const std::vector<int32_t>& x, const sdbus::Struct<sdbus::Variant, sdbus::Variant>& y) const
{
std::map<int32_t, sdbus::Variant> res;
for (auto item : x)
{
res[item] = (item <= 0) ? std::get<0>(y) : std::get<1>(y);
}
return res;
}
sdbus::Struct<std::string, sdbus::Struct<std::map<int32_t, int32_t>>> getStructInStruct() const
{
sdbus::Struct<std::string, sdbus::Struct<std::map<int32_t, int32_t>>> x{STRING_VALUE, {{{INT32_VALUE, INT32_VALUE}}}};
return x;
}
int32_t sumStructItems(const sdbus::Struct<uint8_t, uint16_t>& a, const sdbus::Struct<int32_t, int64_t>& b)
{
int32_t res{0};
res += std::get<0>(a) + std::get<1>(a);
res += std::get<0>(b) + std::get<1>(b);
return res;
}
uint32_t sumVectorItems(const std::vector<uint16_t>& a, const std::vector<uint64_t>& b)
{
uint32_t res{0};
for (auto x : a)
{
res += x;
}
for (auto x : b)
{
res += x;
}
return res;
}
sdbus::Signature getSignature() const { return SIGNATURE_VALUE; }
sdbus::ObjectPath getObjectPath() const { return OBJECT_PATH_VALUE; }
ComplexType getComplex() const
{
return { // map
{
0, // uint_64_t
{ // struct
{ // map
{
'a', // uint8_t
{ // vector
{ // struct
"/object/path", // object path
false,
3.14,
{ // map
{0, "zero"}
}
}
}
}
},
"a{t(a{ya(obva{is})}gs)}", // signature
""
}
}
};
}
std::string state() { return STRING_VALUE; }
uint32_t action() { return m_action; }
void action(const uint32_t& value) { m_action = value; }
bool blocking() { return m_blocking; }
void blocking(const bool& value) { m_blocking = value; }
private:
uint32_t m_action;
bool m_blocking;
};
#endif /* INTEGRATIONTESTS_TESTINGADAPTOR_H_ */

View File

@ -0,0 +1,66 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file TestingProxy.h
*
* Created on: Jan 2, 2017
* 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_CPP_INTEGRATIONTESTS_TESTINGPROXY_H_
#define SDBUS_CPP_INTEGRATIONTESTS_TESTINGPROXY_H_
#include "proxy-glue.h"
class TestingProxy : public sdbus::ProxyInterfaces<::testing_proxy>
{
public:
using sdbus::ProxyInterfaces<::testing_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; }
protected:
void onSimpleSignal() override { ++m_simpleCallCounter; }
void onSignalWithMap(const std::map<int32_t, std::string>& m) override { m_map = m; }
void onSignalWithVariant(const sdbus::Variant& v) override
{
m_variantValue = v.get<double>();
}
void onSignalWithoutRegistration(const sdbus::Struct<std::string, sdbus::Struct<sdbus::Signature>>& s) override
{
m_signature[std::get<0>(s)] = static_cast<std::string>(std::get<0>(std::get<1>(s)));
}
private:
int m_simpleCallCounter{};
std::map<int32_t, std::string> m_map;
double m_variantValue;
std::map<std::string, std::string> m_signature;
};
#endif /* SDBUS_CPP_INTEGRATIONTESTS_TESTINGPROXY_H_ */

View File

@ -0,0 +1,156 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file adaptor-glue.h
*
* Created on: Jan 2, 2017
* 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_CPP_INTEGRATIONTESTS_ADAPTOR_GLUE_H_
#define SDBUS_CPP_INTEGRATIONTESTS_ADAPTOR_GLUE_H_
#include "defs.h"
// sdbus
#include "sdbus-c++/sdbus-c++.h"
using ComplexType = std::map<
uint64_t,
sdbus::Struct<
std::map<
uint8_t,
std::vector<
sdbus::Struct<
sdbus::ObjectPath,
bool,
sdbus::Variant,
std::map<int, std::string>
>
>
>,
sdbus::Signature,
std::string // char* leads to type and memory issues, std::string is best choice
>
>;
class testing_adaptor
{
protected:
testing_adaptor(sdbus::IObject& object) :
object_(object)
{
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("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); });
object_.registerMethod("processVariant").onInterface(INTERFACE_NAME).implementedAs([this](sdbus::Variant& v){ return this->processVariant(v); });
object_.registerMethod("getMapOfVariants").onInterface(INTERFACE_NAME).implementedAs([this](
const std::vector<int32_t>& x, const sdbus::Struct<sdbus::Variant, sdbus::Variant>& y){ return this->getMapOfVariants(x ,y); });
object_.registerMethod("getStructInStruct").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->getStructInStruct(); });
object_.registerMethod("sumStructItems").onInterface(INTERFACE_NAME).implementedAs([this](
const sdbus::Struct<uint8_t, uint16_t>& a, const sdbus::Struct<int32_t, int64_t>& b){
return this->sumStructItems(a, b);
});
object_.registerMethod("sumVectorItems").onInterface(INTERFACE_NAME).implementedAs([this](
const std::vector<uint16_t>& a, const std::vector<uint64_t>& b){
return this->sumVectorItems(a, b);
});
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(); });
// registration of signals is optional, it is useful because of introspection
object_.registerSignal("simpleSignal").onInterface(INTERFACE_NAME);
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("blocking").onInterface(INTERFACE_NAME)./*withGetter([this](){ return this->blocking(); }).*/withSetter([this](const bool& value){ this->blocking(value); });
}
public:
void simpleSignal()
{
object_.emitSignal("simpleSignal").onInterface(INTERFACE_NAME);
}
void signalWithMap(const std::map<int32_t, std::string>& map)
{
object_.emitSignal("signalWithMap").onInterface(INTERFACE_NAME).withArguments(map);
}
void signalWithVariant(const sdbus::Variant& v)
{
object_.emitSignal("signalWithVariant").onInterface(INTERFACE_NAME).withArguments(v);
}
void signalWithoutRegistration(const sdbus::Struct<std::string, sdbus::Struct<sdbus::Signature>>& s)
{
object_.emitSignal("signalWithoutRegistration").onInterface(INTERFACE_NAME).withArguments(s);
}
void emitSignalOnNonexistentInterface()
{
object_.emitSignal("simpleSignal").onInterface("interfaceThatDoesNotExists");
}
private:
sdbus::IObject& object_;
protected:
virtual void noArgNoReturn() const = 0;
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 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 sdbus::Signature getSignature() const = 0;
virtual sdbus::ObjectPath getObjectPath() const = 0;
virtual ComplexType getComplex() const = 0;
virtual std::string state() = 0;
virtual uint32_t action() = 0;
virtual void action(const uint32_t& value) = 0;
virtual bool blocking() = 0;
virtual void blocking(const bool& value) = 0;
};
#endif /* SDBUS_CPP_INTEGRATIONTESTS_ADAPTOR_GLUE_H_ */

View File

@ -0,0 +1,46 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file defs.h
*
* Created on: Jan 2, 2017
* 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_CPP_INTEGRATIONTESTS_DEFS_H_
#define SDBUS_CPP_INTEGRATIONTESTS_DEFS_H_
#include "sdbus-c++/Types.h"
const std::string INTERFACE_NAME{"com.kistler.testsdbuscpp"};
const std::string OBJECT_PATH{"/"};
constexpr const uint8_t UINT8_VALUE{1};
constexpr const int16_t INT16_VALUE{21};
constexpr const uint32_t UINT32_VALUE{42};
constexpr const int32_t INT32_VALUE{-42};
constexpr const int32_t INT64_VALUE{-1024};
const std::string STRING_VALUE{"sdbus-c++-testing"};
const sdbus::Signature SIGNATURE_VALUE{"a{is}"};
const sdbus::ObjectPath OBJECT_PATH_VALUE{"/"};
constexpr const double DOUBLE_VALUE{3.24L};
#endif /* SDBUS_CPP_INTEGRATIONTESTS_DEFS_H_ */

View File

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

View File

@ -0,0 +1,33 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file libsdbus-c++_integrationtests.cpp
*
* Created on: Jan 2, 2017
* 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 "gmock/gmock.h"
int main(int argc, char **argv)
{
::testing::InitGoogleMock(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@ -0,0 +1,190 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file proxy-glue.h
*
* Created on: Jan 2, 2017
* 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_CPP_INTEGRATIONTESTS_PROXY_GLUE_H_
#define SDBUS_CPP_INTEGRATIONTESTS_PROXY_GLUE_H_
#include "defs.h"
// sdbus
#include "sdbus-c++/sdbus-c++.h"
class testing_proxy
{
protected:
testing_proxy(sdbus::IObjectProxy& object) :
object_(object)
{
object_.uponSignal("simpleSignal").onInterface(INTERFACE_NAME).call([this](){ this->onSimpleSignal(); });
object_.uponSignal("signalWithMap").onInterface(INTERFACE_NAME).call([this](const std::map<int32_t, std::string>& map){ this->onSignalWithMap(map); });
object_.uponSignal("signalWithVariant").onInterface(INTERFACE_NAME).call([this](const sdbus::Variant& v){ this->onSignalWithVariant(v); });
object_.uponSignal("signalWithoutRegistration").onInterface(INTERFACE_NAME).call([this](const sdbus::Struct<std::string, sdbus::Struct<sdbus::Signature>>& s)
{ 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;
public:
void noArgNoReturn()
{
object_.callMethod("noArgNoReturn").onInterface(INTERFACE_NAME);
}
int32_t getInt()
{
int32_t result;
object_.callMethod("getInt").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result;
}
std::tuple<uint32_t, std::string> getTuple()
{
std::tuple<uint32_t, std::string> result;
object_.callMethod("getTuple").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result;
}
double multiply(const int64_t& a, const double& b)
{
double result;
object_.callMethod("multiply").onInterface(INTERFACE_NAME).withArguments(a, b).storeResultsTo(result);
return result;
}
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;
object_.callMethod("getInts16FromStruct").onInterface(INTERFACE_NAME).withArguments(x).storeResultsTo(result);
return result;
}
sdbus::Variant processVariant(const sdbus::Variant& v)
{
sdbus::Variant result;
object_.callMethod("processVariant").onInterface(INTERFACE_NAME).withArguments(v).storeResultsTo(result);
return result;
}
std::map<int32_t, sdbus::Variant> getMapOfVariants(const std::vector<int32_t>& x, const sdbus::Struct<sdbus::Variant, sdbus::Variant>& y)
{
std::map<int32_t, sdbus::Variant> result;
object_.callMethod("getMapOfVariants").onInterface(INTERFACE_NAME).withArguments(x, y).storeResultsTo(result);
return result;
}
sdbus::Struct<std::string, sdbus::Struct<std::map<int32_t, int32_t>>> getStructInStruct()
{
sdbus::Struct<std::string, sdbus::Struct<std::map<int32_t, int32_t>>> result;
object_.callMethod("getStructInStruct").onInterface(INTERFACE_NAME).withArguments().storeResultsTo(result);
return result;
}
int32_t sumStructItems(const sdbus::Struct<uint8_t, uint16_t>& a, const sdbus::Struct<int32_t, int64_t>& b)
{
int32_t result;
object_.callMethod("sumStructItems").onInterface(INTERFACE_NAME).withArguments(a, b).storeResultsTo(result);
return result;
}
uint32_t sumVectorItems(const std::vector<uint16_t>& a, const std::vector<uint64_t>& b)
{
uint32_t result;
object_.callMethod("sumVectorItems").onInterface(INTERFACE_NAME).withArguments(a, b).storeResultsTo(result);
return result;
}
sdbus::Signature getSignature()
{
sdbus::Signature result;
object_.callMethod("getSignature").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result;
}
sdbus::ObjectPath getObjectPath()
{
sdbus::ObjectPath result;
object_.callMethod("getObjectPath").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result;
}
ComplexType getComplex()
{
ComplexType result;
object_.callMethod("getComplex").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result;
}
int32_t callNonexistentMethod()
{
int32_t result;
object_.callMethod("callNonexistentMethod").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result;
}
int32_t callMethodOnNonexistentInterface()
{
int32_t result;
object_.callMethod("someMethod").onInterface("interfaceThatDoesNotExist").storeResultsTo(result);
return result;
}
std::string state()
{
return object_.getProperty("state").onInterface(INTERFACE_NAME);
}
uint32_t action()
{
return object_.getProperty("action").onInterface(INTERFACE_NAME);
}
void action(const uint32_t& value)
{
object_.setProperty("action").onInterface(INTERFACE_NAME).toValue(value);
}
bool blocking()
{
return object_.getProperty("blocking").onInterface(INTERFACE_NAME);
}
void blocking(const bool& value)
{
object_.setProperty("blocking").onInterface(INTERFACE_NAME).toValue(value);
}
private:
sdbus::IObjectProxy& object_;
};
#endif /* SDBUS_CPP_INTEGRATIONTESTS_PROXY_GLUE_H_ */

View File

@ -0,0 +1,230 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file Message_test.cpp
*
* Created on: Dec 3, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include <sdbus-c++/Types.h>
#include "MessageUtils.h"
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <cstdint>
using ::testing::Eq;
using ::testing::DoubleEq;
using namespace std::string_literals;
namespace
{
std::string deserializeString(sdbus::Message& msg)
{
std::string str;
msg >> str;
return str;
}
}
/*-------------------------------------*/
/* -- TEST CASES -- */
/*-------------------------------------*/
TEST(AMessage, CanBeDefaultConstructed)
{
ASSERT_NO_THROW(sdbus::Message());
}
TEST(AMessage, IsInvalidAfterDefaultConstructed)
{
sdbus::Message msg;
ASSERT_FALSE(msg.isValid());
}
TEST(AMessage, IsValidWhenConstructedAsRealMessage)
{
sdbus::Message msg{sdbus::createPlainMessage()};
ASSERT_TRUE(msg.isValid());
}
TEST(AMessage, CreatesShallowCopyWhenCopyConstructed)
{
sdbus::Message msg{sdbus::createPlainMessage()};
msg << "I am a string"s;
msg.seal();
sdbus::Message msgCopy = msg;
std::string str;
msgCopy >> str;
ASSERT_THAT(str, Eq("I am a string"));
ASSERT_THROW(msgCopy >> str, sdbus::Error);
}
TEST(AMessage, CreatesDeepCopyWhenEplicitlyCopied)
{
sdbus::Message msg{sdbus::createPlainMessage()};
msg << "I am a string"s;
msg.seal();
sdbus::Message msgCopy{sdbus::createPlainMessage()};
msg.copyTo(msgCopy, true);
msgCopy.seal(); // Seal to be able to read from it subsequently
msg.rewind(true); // Rewind to the beginning after copying
ASSERT_THAT(deserializeString(msg), Eq("I am a string"));
ASSERT_THAT(deserializeString(msgCopy), Eq("I am a string"));
}
TEST(AMessage, IsEmptyWhenContainsNoValue)
{
sdbus::Message msg{sdbus::createPlainMessage()};
ASSERT_TRUE(msg.isEmpty());
}
TEST(AMessage, IsNotEmptyWhenContainsAValue)
{
sdbus::Message msg{sdbus::createPlainMessage()};
msg << "I am a string"s;
ASSERT_FALSE(msg.isEmpty());
}
TEST(AMessage, ReturnsItsTypeWhenAsked)
{
sdbus::Message msg{sdbus::createPlainMessage()};
ASSERT_THAT(msg.getType(), Eq(sdbus::Message::Type::ePlainMessage));
}
TEST(AMessage, CanCarryASimpleInteger)
{
sdbus::Message msg{sdbus::createPlainMessage()};
int dataWritten = 5;
msg << dataWritten;
msg.seal();
int dataRead;
msg >> dataRead;
ASSERT_THAT(dataRead, Eq(dataWritten));
}
TEST(AMessage, CanCarryAVariant)
{
sdbus::Message msg{sdbus::createPlainMessage()};
auto dataWritten = sdbus::Variant((double)3.14);
msg << dataWritten;
msg.seal();
sdbus::Variant dataRead;
msg >> dataRead;
ASSERT_THAT(dataRead.get<double>(), Eq(dataWritten.get<double>()));
}
TEST(AMessage, CanCarryACollectionOfEmbeddedVariants)
{
sdbus::Message msg{sdbus::createPlainMessage()};
auto value = std::vector<sdbus::Variant>{"hello"s, (double)3.14};
auto dataWritten = sdbus::Variant{value};
msg << dataWritten;
msg.seal();
sdbus::Variant dataRead;
msg >> dataRead;
ASSERT_THAT(dataRead.get<decltype(value)>()[0].get<std::string>(), Eq(value[0].get<std::string>()));
ASSERT_THAT(dataRead.get<decltype(value)>()[1].get<double>(), Eq(value[1].get<double>()));
}
TEST(AMessage, CanCarryAnArray)
{
sdbus::Message msg{sdbus::createPlainMessage()};
std::vector<int64_t> dataWritten{3545342, 43643532, 324325};
msg << dataWritten;
msg.seal();
std::vector<int64_t> dataRead;
msg >> dataRead;
ASSERT_THAT(dataRead, Eq(dataWritten));
}
TEST(AMessage, CanCarryADictionary)
{
sdbus::Message msg{sdbus::createPlainMessage()};
std::map<int, std::string> dataWritten{{1, "one"}, {2, "two"}};
msg << dataWritten;
msg.seal();
std::map<int, std::string> dataRead;
msg >> dataRead;
ASSERT_THAT(dataRead, Eq(dataWritten));
}
TEST(AMessage, CanCarryAComplexType)
{
sdbus::Message msg{sdbus::createPlainMessage()};
using ComplexType = std::map<
uint64_t,
sdbus::Struct<
std::map<
uint8_t,
std::vector<
sdbus::Struct<
sdbus::ObjectPath,
bool,
int16_t,
/*sdbus::Variant,*/
std::map<int, std::string>
>
>
>,
sdbus::Signature,
double
>
>;
ComplexType dataWritten = { {1, {{{5, {{"/some/object", true, 45, {{6, "hello"}, {7, "world"}}}}}}, "av", 3.14}}};
msg << dataWritten;
msg.seal();
ComplexType dataRead;
msg >> dataRead;
ASSERT_THAT(dataRead, Eq(dataWritten));
}

View File

@ -0,0 +1,129 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file TypeTraits_test.cpp
*
* Created on: Nov 27, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include <sdbus-c++/TypeTraits.h>
#include <sdbus-c++/Types.h>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <cstdint>
using ::testing::Eq;
namespace
{
// ---
// FIXTURE DEFINITION FOR TYPED TESTS
// ----
template <typename _T>
class Type2DBusTypeSignatureConversion
: public ::testing::Test
{
protected:
const std::string dbusTypeSignature_{getDBusTypeSignature()};
private:
static std::string getDBusTypeSignature();
};
#define TYPE(...) \
template <> \
std::string Type2DBusTypeSignatureConversion<__VA_ARGS__>::getDBusTypeSignature() \
/**/
#define HAS_DBUS_TYPE_SIGNATURE(_SIG) \
{ \
return (_SIG); \
} \
/**/
TYPE(bool)HAS_DBUS_TYPE_SIGNATURE("b")
TYPE(uint8_t)HAS_DBUS_TYPE_SIGNATURE("y")
TYPE(int16_t)HAS_DBUS_TYPE_SIGNATURE("n")
TYPE(uint16_t)HAS_DBUS_TYPE_SIGNATURE("q")
TYPE(int32_t)HAS_DBUS_TYPE_SIGNATURE("i")
TYPE(uint32_t)HAS_DBUS_TYPE_SIGNATURE("u")
TYPE(int64_t)HAS_DBUS_TYPE_SIGNATURE("x")
TYPE(uint64_t)HAS_DBUS_TYPE_SIGNATURE("t")
TYPE(double)HAS_DBUS_TYPE_SIGNATURE("d")
TYPE(const char*)HAS_DBUS_TYPE_SIGNATURE("s")
TYPE(std::string)HAS_DBUS_TYPE_SIGNATURE("s")
TYPE(sdbus::ObjectPath)HAS_DBUS_TYPE_SIGNATURE("o")
TYPE(sdbus::Signature)HAS_DBUS_TYPE_SIGNATURE("g")
TYPE(sdbus::Variant)HAS_DBUS_TYPE_SIGNATURE("v")
TYPE(sdbus::Struct<bool>)HAS_DBUS_TYPE_SIGNATURE("(b)")
TYPE(sdbus::Struct<uint16_t, double, std::string, sdbus::Variant>)HAS_DBUS_TYPE_SIGNATURE("(qdsv)")
TYPE(std::vector<int16_t>)HAS_DBUS_TYPE_SIGNATURE("an")
TYPE(std::map<int32_t, int64_t>)HAS_DBUS_TYPE_SIGNATURE("a{ix}")
using ComplexType = std::map<
uint64_t,
sdbus::Struct<
std::map<
uint8_t,
std::vector<
sdbus::Struct<
sdbus::ObjectPath,
bool,
sdbus::Variant,
std::map<int, std::string>
>
>
>,
sdbus::Signature,
const char*
>
>;
TYPE(ComplexType)HAS_DBUS_TYPE_SIGNATURE("a{t(a{ya(obva{is})}gs)}")
typedef ::testing::Types< bool
, uint8_t
, int16_t
, uint16_t
, int32_t
, uint32_t
, int64_t
, uint64_t
, double
, const char*
, std::string
, sdbus::ObjectPath
, sdbus::Signature
, sdbus::Variant
, sdbus::Struct<bool>
, sdbus::Struct<uint16_t, double, std::string, sdbus::Variant>
, std::vector<int16_t>
, std::map<int32_t, int64_t>
, ComplexType
> DBusSupportedTypes;
TYPED_TEST_CASE(Type2DBusTypeSignatureConversion, DBusSupportedTypes);
}
/*-------------------------------------*/
/* -- TEST CASES -- */
/*-------------------------------------*/
TYPED_TEST(Type2DBusTypeSignatureConversion, ConvertsTypeToProperDBusSignature)
{
ASSERT_THAT(sdbus::signature_of<TypeParam>::str(), Eq(this->dbusTypeSignature_));
}

View File

@ -0,0 +1,205 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file Types_test.cpp
*
* Created on: Dec 3, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include <sdbus-c++/Types.h>
#include "MessageUtils.h"
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <cstdint>
using ::testing::Eq;
using namespace std::string_literals;
namespace
{
constexpr const uint64_t ANY_UINT64 = 84578348354;
constexpr const double ANY_DOUBLE = 3.14;
}
/*-------------------------------------*/
/* -- TEST CASES -- */
/*-------------------------------------*/
TEST(AVariant, CanBeDefaultConstructed)
{
ASSERT_NO_THROW(sdbus::Variant());
}
TEST(AVariant, ContainsNoValueAfterDefaultConstructed)
{
sdbus::Variant v;
ASSERT_TRUE(v.isEmpty());
}
TEST(AVariant, CanBeConstructedFromASimpleValue)
{
int value = 5;
ASSERT_NO_THROW(sdbus::Variant{value});
}
TEST(AVariant, CanBeConstructedFromAComplexValue)
{
using ComplexType = std::map<uint64_t, std::vector<sdbus::Struct<std::string, double>>>;
ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{sdbus::make_struct("hello", ANY_DOUBLE), sdbus::make_struct("world", ANY_DOUBLE)}} };
ASSERT_NO_THROW(sdbus::Variant(value));
}
TEST(AVariant, CanBeCopied)
{
auto value = "hello"s;
sdbus::Variant variant(value);
auto variantCopy1{variant};
auto variantCopy2 = variantCopy1;
ASSERT_THAT(variantCopy1.get<std::string>(), Eq(value));
ASSERT_THAT(variantCopy2.get<std::string>(), Eq(value));
}
TEST(AVariant, IsNotEmptyWhenContainsAValue)
{
sdbus::Variant v("hello");
ASSERT_FALSE(v.isEmpty());
}
TEST(ASimpleVariant, ReturnsTheSimpleValueWhenAsked)
{
int value = 5;
sdbus::Variant variant(value);
ASSERT_THAT(variant.get<int>(), Eq(value));
}
TEST(AComplexVariant, ReturnsTheComplexValueWhenAsked)
{
using ComplexType = std::map<uint64_t, std::vector<sdbus::Struct<std::string, double>>>;
ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{sdbus::make_struct("hello", ANY_DOUBLE), sdbus::make_struct("world", ANY_DOUBLE)}} };
sdbus::Variant variant(value);
ASSERT_THAT(variant.get<decltype(value)>(), Eq(value));
}
TEST(AVariant, HasConceptuallyNonmutableGetMethodWhichCanBeCalledXTimes)
{
std::string value{"I am a string"};
sdbus::Variant variant(value);
ASSERT_THAT(variant.get<std::string>(), Eq(value));
ASSERT_THAT(variant.get<std::string>(), Eq(value));
ASSERT_THAT(variant.get<std::string>(), Eq(value));
}
TEST(AVariant, ReturnsTrueWhenAskedIfItContainsTheTypeItReallyContains)
{
using ComplexType = std::map<uint64_t, std::vector<sdbus::Struct<std::string, double>>>;
ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{sdbus::make_struct("hello", ANY_DOUBLE), sdbus::make_struct("world", ANY_DOUBLE)}} };
sdbus::Variant variant(value);
ASSERT_TRUE(variant.containsValueOfType<ComplexType>());
}
TEST(ASimpleVariant, ReturnsFalseWhenAskedIfItContainsTypeItDoesntReallyContain)
{
int value = 5;
sdbus::Variant variant(value);
ASSERT_FALSE(variant.containsValueOfType<double>());
}
TEST(AVariant, CanContainOtherEmbeddedVariants)
{
using TypeWithVariants = std::vector<sdbus::Struct<sdbus::Variant, double>>;
TypeWithVariants value;
value.emplace_back(sdbus::make_struct(sdbus::Variant("a string"), ANY_DOUBLE));
value.emplace_back(sdbus::make_struct(sdbus::Variant(ANY_UINT64), ANY_DOUBLE));
sdbus::Variant variant(value);
ASSERT_TRUE(variant.containsValueOfType<TypeWithVariants>());
}
TEST(ANonEmptyVariant, SerializesSuccessfullyToAMessage)
{
sdbus::Variant variant("a string");
sdbus::Message msg = sdbus::createPlainMessage();
ASSERT_NO_THROW(variant.serializeTo(msg));
}
TEST(AnEmptyVariant, ThrowsWhenBeingSerializedToAMessage)
{
sdbus::Variant variant;
sdbus::Message msg = sdbus::createPlainMessage();
ASSERT_THROW(variant.serializeTo(msg), sdbus::Error);
}
TEST(ANonEmptyVariant, SerializesToAndDeserializesFromAMessageSuccessfully)
{
using ComplexType = std::map<uint64_t, std::vector<sdbus::Struct<std::string, double>>>;
ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{sdbus::make_struct("hello", ANY_DOUBLE), sdbus::make_struct("world", ANY_DOUBLE)}} };
sdbus::Variant variant(value);
sdbus::Message msg = sdbus::createPlainMessage();
variant.serializeTo(msg);
msg.seal();
sdbus::Variant variant2;
variant2.deserializeFrom(msg);
ASSERT_THAT(variant2.get<decltype(value)>(), Eq(value));
}
TEST(CopiesOfVariant, SerializeToAndDeserializeFromMessageSuccessfully)
{
using ComplexType = std::map<uint64_t, std::vector<sdbus::Struct<std::string, double>>>;
ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{sdbus::make_struct("hello", ANY_DOUBLE), sdbus::make_struct("world", ANY_DOUBLE)}} };
sdbus::Variant variant(value);
auto variantCopy1{variant};
auto variantCopy2 = variant;
sdbus::Message msg = sdbus::createPlainMessage();
variant.serializeTo(msg);
variantCopy1.serializeTo(msg);
variantCopy2.serializeTo(msg);
msg.seal();
sdbus::Variant receivedVariant1, receivedVariant2, receivedVariant3;
receivedVariant1.deserializeFrom(msg);
receivedVariant2.deserializeFrom(msg);
receivedVariant3.deserializeFrom(msg);
ASSERT_THAT(receivedVariant1.get<decltype(value)>(), Eq(value));
ASSERT_THAT(receivedVariant2.get<decltype(value)>(), Eq(value));
ASSERT_THAT(receivedVariant3.get<decltype(value)>(), Eq(value));
}

View File

@ -0,0 +1,33 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file libsdbus-c++_unittests.cpp
*
* Created on: Nov 27, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include "gmock/gmock.h"
int main(int argc, char **argv)
{
::testing::InitGoogleMock(&argc, argv);
return RUN_ALL_TESTS();
}