D-Bus C++: C++ helper classes for libdbus/libgdbus

C++ classes and templates provided by header files simplify
programming in C++ with libdbus and libgdbus. libdbus items are
tracked with Boost shared pointers. Errors are turned into
exceptions. The binding to C++ is done via templates which provide the
C-style callbacks and declarations expected by libgdbus and map to
standard C++ types and STL data structures. Neither code generator nor
library code is required.

Synchronous and asynchronous method implementation are supported.
Asynchronous result deliver is done via a callback object with a
C++ signature that matches the values which have to be returned.

Due to the lack of variadic templates, lots of similar templates have
to be written to cover methods with varying combinations of arguments
and return codes. Not particularly elegant, but the alternative would
be to introduce a code generator, which has its own
drawbacks. Currently up to 10 items per method are supported, where
the number of items include the optional return value, arguments and
retvals.

Setting a Watch (base class) notifies the implementor of an when his
caller disconnects.  This can be used to abort a long-running method
when the consumer of the result goes away.

Callers are identified by their unique Bus ID, represented as Caller_t,
an alias for a plain std::string.

Objects are represented by their path with a DBusObject_t, again a
plain std::string. By default, each path comes with one interface
(DBusObject). It is possible to instantiate multiple DBusObjects with
the same path, which appears on D-Bus as a single object with multiple
interfaces.

The DBusObjectHelper simplifies object handling. It stores the
necessary information and connection reference so that the object can
be unregistered when the DBusObjectHelper is destroyed. The intended
usage is that classes own instances of DBusObjectHelper and activate
those with a method table that points to class methods.

Signals are handled as instances of EmitSignalX templates where X
stands for the number of parameters, pretty much like ResultX
callbacks are handled. The function call operator emits the signal,
with the auxiliary information (D-Bus signal name) set when
instantiating the EmitSignalX object as part of a class. The D-Bus
connection and interface is provided at the time of the signal
emission by the parent DBusObject.

The table entry for the signal is created by a static member function
of the template, which allows building a static table as required by
gdbus. Note that the signal name must be passed into that
function. This slight duplication is necessary because the template
cannot be parameterized with a string constant. Turning
makeSignalEntry() into a normal member function would make it
difficult to build the signal table.

DBusErrorCXX is a helper class for DBusError which automatically
initializes the struct, can be used to check for an error and throws
an exception for it.
This commit is contained in:
Patrick Ohly 2009-09-08 17:41:32 +02:00
parent 914dc8d15e
commit 0fb5794a21
5 changed files with 5519 additions and 0 deletions

View file

@ -14,3 +14,8 @@ libgdbus_la_LIBADD = @GLIB_LIBS@ @DBUS_LIBS@
AM_CFLAGS = @GLIB_CFLAGS@ @DBUS_CFLAGS@
MAINTAINERCLEANFILES = Makefile.in
noinst_PROGRAMS = example
example_SOURCES = test/example.cpp
example_CXXFLAGS = @GLIB_CFLAGS@ @DBUS_CFLAGS@
example_LDADD = libgdbus.la @GLIB_LIBS@ @DBUS_LIBS@

5033
src/gdbus/gdbus-cxx-bridge.h Normal file

File diff suppressed because it is too large Load diff

162
src/gdbus/gdbus-cxx.h Normal file
View file

@ -0,0 +1,162 @@
/*
* Copyright (C) 2009 Intel Corporation
*
* This library 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) version 3.
*
* This library 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 this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#ifndef INCL_GDBUS_CXX
#define INCL_GDBUS_CXX
#include <string>
#include <stdexcept>
#include <boost/function.hpp>
#include <boost/noncopyable.hpp>
/**
* An exception class which can be thrown to create
* specific D-Bus exception on the bus.
*/
class dbus_error : public std::runtime_error
{
public:
/**
* @param dbus_name the D-Bus error name, like "org.example.error.Invalid"
* @param what a more detailed description
*/
dbus_error(const std::string &dbus_name, const std::string &what) :
std::runtime_error(what),
m_dbus_name(dbus_name)
{}
~dbus_error() throw() {}
const std::string &dbusName() const { return m_dbus_name; }
private:
std::string m_dbus_name;
};
class Watch : private boost::noncopyable
{
public:
virtual ~Watch() {};
};
/**
* Call object which needs to be called with the results
* of an asynchronous method call. So instead of
* "int foo()" one would implement
* "void foo(Result1<int> > *r)"
* and after foo has returned, call r->done(res). Use const
* references as type for complex results.
*
* A Result instance can be copied, but only called once.
*/
class Result
{
public:
virtual ~Result() {}
/** report failure to caller */
virtual void failed(const dbus_error &error) = 0;
/**
* Calls the given callback once when the peer
* that the result would be delivered to disconnects.
* The callback will also be called if the peer
* is already gone by the time that the watch is
* requested.
*/
virtual Watch *createWatch(const boost::function<void (void)> &callback) = 0;
};
class Result0 : virtual public Result
{
public:
/** tell caller that we are done */
virtual void done() = 0;
};
template <typename A1>
class Result1 : virtual public Result
{
public:
/** return result to caller */
virtual void done(A1 a1) = 0;
};
template <typename A1, typename A2>
struct Result2 : virtual public Result
{
virtual void done(A1 a1, A2 a2) = 0;
};
template <typename A1, typename A2, typename A3>
struct Result3 : virtual public Result
{
virtual void done(A1 a1, A2 a2, A3 a3) = 0;
};
template <typename A1, typename A2, typename A3, typename A4>
struct Result4 : virtual public Result
{
virtual void done(A1 a1, A2 a2, A3 a3, A4 a4) = 0;
};
template <typename A1, typename A2, typename A3, typename A4, typename A5>
struct Result5 : virtual public Result
{
virtual void done(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) = 0;
};
template <typename A1, typename A2, typename A3, typename A4, typename A5,
typename A6>
struct Result6 : virtual public Result
{
virtual void done(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) = 0;
};
template <typename A1, typename A2, typename A3, typename A4, typename A5,
typename A6, typename A7>
struct Result7 : virtual public Result
{
virtual void done(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7) = 0;
};
template <typename A1, typename A2, typename A3, typename A4, typename A5,
typename A6, typename A7, typename A8>
struct Result8 : virtual public Result
{
virtual void done(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8) = 0;
};
template <typename A1, typename A2, typename A3, typename A4, typename A5,
typename A6, typename A7, typename A8, typename A9>
struct Result9 : virtual public Result
{
virtual void done(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9) = 0;
};
template <typename A1, typename A2, typename A3, typename A4, typename A5,
typename A6, typename A7, typename A8, typename A9, typename A10>
struct Result10 : virtual public Result
{
virtual void done(A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10) = 0;
};
#endif // INCL_GDBUS_CXX

273
src/gdbus/test/example.cpp Normal file
View file

@ -0,0 +1,273 @@
/*
*
* Library for simple D-Bus integration with GLib
*
* Copyright (C) 2009 Intel Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/signal.h>
#include "gdbus-cxx-bridge.h"
#include <memory>
#include <iostream>
class Test {
typedef Result1<const std::string&> string_result;
struct async
{
async(Watch *watch, string_result *result):
m_watch(watch),
m_result(result)
{}
~async()
{
delete m_watch;
delete m_result;
}
Watch *m_watch;
string_result *m_result;
};
static gboolean method_idle(gpointer data)
{
std::auto_ptr<async> mydata(static_cast<async *>(data));
mydata->m_result->done("Hello World, asynchronous and delayed");
return false;
}
static void disconnect(const std::string &peer)
{
std::cout << peer << " has disconnected." << std::endl;
}
public:
void method(std::string &text)
{
text = "Hello World";
}
void method_async(int32_t secs, string_result *r)
{
Watch *watch = r->createWatch(boost::bind(disconnect,
"caller of method_async"));
g_timeout_add_seconds(secs, method_idle, new async(watch, r));
}
void method2(int32_t arg, int32_t &ret)
{
ret = arg * 2;
}
int32_t method3(int32_t arg)
{
return arg * 3;
}
void method10(int32_t a1, int32_t a2, int32_t a3, int32_t a4, int32_t a5,
int32_t a6, int32_t a7, int32_t a8, int32_t a9, int32_t a10)
{
}
void method10_async(Result10<int32_t, int32_t, int32_t, int32_t, int32_t,
int32_t, int32_t, int32_t, int32_t, int32_t> *r)
{
r->done(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
delete r;
}
int32_t method9(int32_t a1, int32_t a2, int32_t a3, int32_t a4, int32_t a5,
int32_t a6, int32_t a7, int32_t a8, int32_t a9)
{
return 0;
}
void hash(const std::map<int8_t, int32_t> &in, std::map<int16_t, int32_t> &out)
{
for (std::map<int8_t, int32_t>::const_iterator it = in.begin();
it != in.end();
++it) {
out.insert(std::make_pair((int16_t)it->first, it->second * it->second));
}
}
void array(const std::vector<int32_t> &in, std::vector<int32_t> &out)
{
for (std::vector<int32_t>::const_iterator it = in.begin();
it != in.end();
++it) {
out.push_back(*it * *it);
}
}
void error()
{
throw dbus_error("org.example.error.Invalid", "error");
}
};
class DBusTest : public Test
{
DBusObjectHelper m_object;
DBusObjectHelper m_secondary;
public:
DBusTest(DBusConnection *conn) :
m_object(conn, "/test", "org.example.Test"),
// same path!
m_secondary(conn, m_object.getPath(), "org.example.Secondary"),
signal(m_object, "Signal")
{}
~DBusTest()
{
}
EmitSignal3<int32_t, const std::string &, const std::map<int32_t, int32_t> &>signal;
void hello() {}
void activate()
{
static GDBusMethodTable methods[] = {
makeMethodEntry<Test,
int32_t, int32_t, int32_t, int32_t, int32_t,
int32_t, int32_t, int32_t, int32_t, int32_t,
typeof(&Test::method10), &Test::method10>("Method10"),
makeMethodEntry<Test,
int32_t, int32_t, int32_t, int32_t, int32_t,
int32_t, int32_t, int32_t, int32_t, int32_t,
typeof(&Test::method10_async), &Test::method10_async>
("Method10Async", G_DBUS_METHOD_FLAG_ASYNC),
makeMethodEntry<Test,
int32_t, int32_t, int32_t, int32_t, int32_t,
int32_t, int32_t, int32_t, int32_t, int32_t,
&Test::method9>("Method9"),
makeMethodEntry<Test, int32_t, int32_t &,
typeof(&Test::method2), &Test::method2>("Method2"),
makeMethodEntry<Test, int32_t, int32_t,
&Test::method3>("Method3"),
makeMethodEntry<Test, std::string &,
typeof(&Test::method), &Test::method>("Test"),
makeMethodEntry<Test, int32_t, std::string &,
typeof(&Test::method_async), &Test::method_async>
("TestAsync", G_DBUS_METHOD_FLAG_ASYNC),
makeMethodEntry<Test,
const std::map<int8_t, int32_t> &,
std::map<int16_t, int32_t> &,
typeof(&Test::hash), &Test::hash>
("Hash"),
makeMethodEntry<Test,
const std::vector<int32_t> &,
std::vector<int32_t> &,
typeof(&Test::array), &Test::array>
("Array"),
makeMethodEntry<Test, typeof(&Test::error), &Test::error>("Error"),
{ },
};
static GDBusSignalTable signals[] = {
signal.makeSignalEntry("Signal"),
{ },
};
m_object.activate(methods,
signals,
NULL,
this);
static GDBusMethodTable secondary_methods[] = {
makeMethodEntry<DBusTest, typeof(&DBusTest::hello), &DBusTest::hello>("Hello"),
{}
};
m_secondary.activate(secondary_methods,
NULL,
NULL,
this);
}
void deactivate()
{
m_object.deactivate();
m_secondary.deactivate();
}
};
static GMainLoop *main_loop = NULL;
static void sig_term(int sig)
{
g_main_loop_quit(main_loop);
}
int main(int argc, char *argv[])
{
DBusConnection *conn;
DBusError err;
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = sig_term;
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
sa.sa_handler = SIG_IGN;
sigaction(SIGCHLD, &sa, NULL);
sigaction(SIGPIPE, &sa, NULL);
main_loop = g_main_loop_new(NULL, FALSE);
dbus_error_init(&err);
conn = g_dbus_setup_bus(DBUS_BUS_SESSION, "org.example", &err);
if (conn == NULL) {
if (dbus_error_is_set(&err) == TRUE) {
fprintf(stderr, "%s\n", err.message);
dbus_error_free(&err);
} else
fprintf(stderr, "Can't register with session bus\n");
exit(1);
}
std::auto_ptr<DBusTest> test(new DBusTest(conn));
test->activate();
test->signal(42, "hello world", std::map<int32_t, int32_t>());
test->deactivate();
test->activate();
test->signal(123, "here I am again", std::map<int32_t, int32_t>());
g_main_loop_run(main_loop);
test.reset();
g_dbus_cleanup_connection(conn);
g_main_loop_unref(main_loop);
return 0;
}

46
src/gdbus/test/test-example Executable file
View file

@ -0,0 +1,46 @@
#!/usr/bin/python
import dbus
from dbus.mainloop.glib import DBusGMainLoop
import gobject
DBusGMainLoop(set_as_default=True)
bus = dbus.SessionBus()
dummy = dbus.Interface(bus.get_object('org.example', '/test'),
'org.freedesktop.DBus.Introspectable')
print dummy.Introspect()
object = dbus.Interface(bus.get_object('org.example', '/test'),
'org.example.Secondary')
object.Hello()
object = dbus.Interface(bus.get_object('org.example', '/test'),
'org.example.Test')
print object.Test()
print object.TestAsync(2)
print object.Method2(1)
print object.Method3(1)
print object.Hash({1: 1, 2: 2, 3: 3})
print object.Array([1, 2, 3, 4])
loop = gobject.MainLoop()
def AsyncFinished(x):
print "TestAsync:", x
loop.quit()
# This will trigger the "caller has disconnect"
# because our client-side time out will get us
# out of the loop and then we quit.
object.TestAsync(600,
reply_handler=AsyncFinished,
error_handler=AsyncFinished,
timeout=5)
loop.run()
object.Error()