On Fri, 2009-08-14 at 11:26 +0200, Patrick Ohly wrote:
> On Fri, 2009-08-14 at 10:43 +0300, Jussi Kukkonen wrote:
> > Marcel Holtmann wrote:
> > > inside BlueZ, ConnMan, oFono etc. we are using libgdbus which is a nice
> > > and small helper library for writing D-Bus servers in C without any big
> > > extra bloat. We even duplicate the source in all projects to make it
> > > simpler for packages and integrators. I am not saying that this is the
> > > right solution for you, but you asked ;)
> > 
> > Thanks, that may make sense. It's just that dbus-glib was a known evil 
> > for me at the time... I've looked at connman and gdbus does look like a 
> > nice and clean API at least from server POV.
> 
> Can/should we use both? gdbus in the server, dbus-glib in the client?

Even if we go for gdbus, then one problem remains: we need a convenient
method of retrieving and storing values, including STL data structures.
Bonus points if the C++ code can be separated from D-Bus internals
completely (normal method calls) or at least that dependency is hidden
(some kind of abstract callback for completion of asynchronous methods).

I've looked into this the last yesterday and today. The result is a very
experimental C++ binding for D-Bus based on gdbus, which was copied into
"src/gdbus". You can find this in the dbus-api branch in git. Some parts
of the header files are quoted below.

Advantages of this solution: no dependency on a code generator (and thus
simple build rules), flexible prototypes for methods (in contrast to
pure virtual methods prescribed by a code generator), "native" C++ data
types (string, map and vector added so far).

Disadvantages: there is a certain duplication in the parameter
specification. Type mismatches result in compiler errors which are a bit
hard to read (three or four lines per error, so it's not that bad). In
addition to the server-side API (defined by the source code) we also
need to maintain the syncevo-full.xml file for the client and
documentation.

But the biggest drawback is that the implementation of the wrappers is
based on templates which depend on the number of parameters to be
wrapped. This implies that code must be copied and adapted to cover a
wide range of parameter numbers. This would be avoided by a code
generator.

What do you think? Is this worthwhile pursuing further?

Bye, Patrick

-------> gdbus-cxx-bridge <-----

/**
 * This file contains everything that a D-Bus server needs to
 * integrate a normal C++ class into D-Bus. Argument and result
 * marshaling is done in wrapper functions which convert directly
 * to normal C++ types (bool, integers, std::string, std::map<>, ...).
 * See dbus_traits for the full list of supported types.
 *
 * Before explaining the binding, some terminology first:
 * - A function has a return type and multiple parameters.
 * - Input parameters are read-only arguments of the function.
 * - The function can return values to the caller via the
 *   return type and output parameters (retvals).
 *
 * The C++ binding roughly looks like this:
 * - Arguments can be passed as plain types or const references:
     void foo(int arg); void bar(const std::string &str);
 * - A single result can be returned as return value:
 *   int foo();
 * - Multiple results can be copied into instances provided by
 *   the wrapper, passed by reference: void foo(std::string &res);
 * - A return value, arguments and retvals can be combined
 *   arbitrarily. In the D-Bus reply the return code comes before
 *   all return values.
 *
 * Asynchronous methods are possible by declaring one parameter as a
 * Result pointer and later calling the virtual function provided by
 * it. Parameter passing of results is less flexible than that of
 * method parameters: the later allows both std::string as well as
 * const std::string &, for results only the const reference is
 * supported. The Result instance is passed as pointer and then owned
 * by the called method.
 *
 * Reference counting via boost::intrusive_ptr ensures that all
 * D-Bus objects are handled automatically internally.
 */

....

/**
 * Method call with two parameters and one return code. All other
 * calls are variations of this, so this one is fully documented
 * to explain all tricks used in these templates.
 */
template <class I, class R, class A1, class A2, R (I::*m)(A1, A2)>
DBusMessage *methodFunction(DBusConnection *conn,
                            DBusMessage *msg, void *data)
{
    // all exceptions must be caught and translated into
    // a suitable D-Bus reply
    try {
        // Argument types might may be references or pointers.
        // To instantiate a variable we need the underlying
        // datatype, which is provided by the dbus_traits.
        // "typename" is necessary to tell the compiler
        // that host_type really is a type.
        typename dbus_traits<R>::host_type r;
        typename dbus_traits<A1>::host_type a1;
        typename dbus_traits<A2>::host_type a2;

        // Extract all parameters. Because we don't now
        // whether a parameter is an argument or a return
        // value, we call get() for each of them and let
        // the corresponding dbus_traits decide that. Traits
        // for types which are plain types or const references
        // have a non-empty get(), whereas references are treated
        // as return values and have an empty get().
        DBusMessageIter iter;
        dbus_message_iter_init(msg, &iter);
        dbus_traits<A1>::get(iter, a1);
        dbus_traits<A2>::get(iter, a2);

        // The data pointer given to g_dbus_register_interface()
        // must have been a pointer to an object of our interface
        // class. m is a method pointer passed in as template parameter.
        // Combining the two allows us to call the right method.
        // The compiler knows the exact method prototype and thus
        // can handle call-by-value and call-by-reference correctly.
        r = (static_cast<I *>(data)->*m)(a1, a2);

        // Now prepare the reply. As with extracting parameters,
        // append() is empty for those parameters where nothing
        // has to be done.
        DBusMessage *reply = dbus_message_new_method_return(msg);
        if (!reply)
            return NULL;
        dbus_message_iter_init_append(reply, &iter);
        // We know that the return value has to be appended,
        // even though the trait would not normally do that
        // because it is a plain type => call utility function
        // directly.
        dbus_traits<R>::append_retval(iter, r);
        dbus_traits<A1>::append(iter, a1);
        dbus_traits<A2>::append(iter, a2);
        return reply;
    } catch (...) {
        // let handleException rethrow the exception
        // to determine its type
        return handleException(msg);
    }
}
/**
 * Creates a GDBusMethodTable entry.
 * The strings inside the entry are allocated
 * with strdup(). For technical reasons the return type
 * and parameter types have to be listed explicitly.
 * The name passed to the function is the D-Bus method name.
 * Valid flags are G_DBUS_METHOD_FLAG_DEPRECATED,
 * G_DBUS_METHOD_FLAG_NOREPLY (no reply message is sent),
 * G_DBUS_METHOD_FLAG_ASYNC (reply will be sent via
 * callback, method must accept Result parameter).
 */
template <class I, class R1, class A1, class A2, R1 (I::*m)(A1, A2)>
GDBusMethodTable makeMethodEntry(const char *name, GDBusMethodFlags
flags = GDBusMethodFlags(0))
{
    GDBusMethodTable entry;
    entry.name = strdup(name);
    // same trick as before: only argument types
    // are added to the signature
    std::string buffer;
    buffer += dbus_traits<A1>::getSignature();
    buffer += dbus_traits<A2>::getSignature();
    entry.signature = strdup(buffer.c_str());
    // now the same for reply types
    buffer.clear();
    buffer += dbus_traits<A1>::getReply();
    buffer += dbus_traits<A2>::getReply();
    entry.reply = strdup(buffer.c_str());
    // this is the function template above
    entry.function = methodFunction<I, A1, A2, m>;
    entry.flags = flags;
    return entry;
}

-------> gdbus-cxx-bridge <-----

-------> test/example.cpp <-----
// normal C++ class, only depending on gdbus-cxx.h
class Test {
    static gboolean method_idle(gpointer data)
    {
        std::auto_ptr<string_result> result(static_cast<string_result *>(data));
        result->done("Hello World");
        return false;
    }

    typedef Result1<const std::string&> string_result;

public:
    void method(std::string &text)
    {
        text = "Hello World";
    }

    void method_async(int32_t arg, string_result *r)
    {
        g_idle_add(method_idle, r);
    }

    void method2(int32_t arg, int32_t &ret)
    {
        ret = arg * 2;
    }

    int32_t method3(int32_t arg)
    {
        return arg * 3;
    }

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

// the following code exposes the Test class via D-Bus

static GDBusMethodTable methods[] = {
    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 &, &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, &Test::error>("Error"),
    { },
};

static GDBusSignalTable signals[] = {
    { "Tested", "" },
    { },
};

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

    Test test;
    g_dbus_register_interface(conn, "/test", "org.example.Test",
                              methods, signals, NULL, &test, NULL);

    g_main_loop_run(main_loop);

    g_dbus_unregister_interface(conn, "/test", "org.example.Test");

    g_dbus_cleanup_connection(conn);

    g_main_loop_unref(main_loop);

    return 0;
}
-------> test/example.cpp <----- 


_______________________________________________
SyncEvolution mailing list
SyncEvolution@syncevolution.org
http://lists.syncevolution.org/listinfo/syncevolution

Reply via email to