From 4a617c1cc98e7d85f020d35860f09647443e147c Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Tue, 28 May 2013 15:13:00 +0200 Subject: GDBus GIO: support recursive variant with one type The signature of the new type is just a 'v' for variant. Recursion is only supported in combination with std::vector. This is done to keep implementation simpler. std::vector was chosen over other collections because it is easier to work with in method calls, the main purpose of the new type. The conversion from D-Bus messages accepts anything which can be mapped into the variant: arrays, tuples, plain types, and of course variants containing those. This helps with backward compatibility (one can turn an interface which took a fixed type like std::vector into something with a recursive variant) and with Python, because Python sends arrays and tuples without converting into variants when the app uses those simpler types. One caveat exists with sending an empty list in Python: when using 'v' as signature in the interface, Python doesn't know how to send the empty list without assistance by the programmer (as in dbus.Array([], signature='s'). If possible, avoid the plain 'v' in an interface in favor of something like 'av' (= std::vector). --- src/gdbusxx/gdbus-cxx-bridge.h | 108 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) (limited to 'src/gdbusxx') diff --git a/src/gdbusxx/gdbus-cxx-bridge.h b/src/gdbusxx/gdbus-cxx-bridge.h index 54338642..cc08066e 100644 --- a/src/gdbusxx/gdbus-cxx-bridge.h +++ b/src/gdbusxx/gdbus-cxx-bridge.h @@ -132,6 +132,26 @@ class GVariantCXX : boost::noncopyable } }; +class GVariantIterCXX : boost::noncopyable +{ + GVariantIter *m_var; + public: + /** takes over ownership */ + GVariantIterCXX(GVariantIter *var = NULL) : m_var(var) {} + ~GVariantIterCXX() { if (m_var) { g_variant_iter_free(m_var); } } + + operator GVariantIter * () { return m_var; } + GVariantIterCXX &operator = (GVariantIter *var) { + if (m_var != var) { + if (m_var) { + g_variant_iter_free(m_var); + } + m_var = var; + } + return *this; + } +}; + class DBusMessagePtr; inline void throwFailure(const std::string &object, @@ -2081,6 +2101,94 @@ template struct dbus_traits > : pu typedef const boost::variant &arg_type; }; +/** + * A recursive variant. Can represent values of a certain type V and + * vectors with a mixture of such values and the variant. Limiting + * this to vectors is done for the sake of simplicity and because + * vector is fairly efficient to work with, in particular when + * implementing methods. + * + * It would be nice to not refer to boost internals here. But using + * "typename boost::make_recursive_variant >::type" + * instead of the expanded + * "boost::variant< boost::detail::variant::recursive_flag, A>" + * leads to a compiler error: + * class template partial specialization contains a template parameter that can not be deduced; this partial specialization will never be used [-Werror] + * ...dbus_traits < typename boost::make_recursive_variant >::type > ... + * ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +template struct dbus_traits < boost::variant, A> > : public dbus_traits_base +{ + static std::string getType() { return "v"; } + static std::string getSignature() { return getType(); } + static std::string getReply() { return ""; } + + typedef boost::variant, A> host_type; + typedef const host_type &arg_type; + + static void get(ExtractArgs &context, + GVariantIter &iter, host_type &value) + { + GVariantIterCXX itercopy(g_variant_iter_copy(&iter)); + + // Peek at next element, then decide what to do with it. + GVariantCXX var(g_variant_iter_next_value(&iter)); + // We accept a variant, the plain type V, or an array. + // This is necessary for clients like Python which + // send [['foo', 'bar']] as 'aas' when seeing 'v' + // as signature. + if (var == NULL) { + throw std::runtime_error("g_variant failure " GDBUS_CXX_SOURCE_INFO); + } + + const char *type = g_variant_get_type_string(var); + if (boost::iequals(type, "v")) { + // Strip the outer variant and decode the inner value recursively, in + // our own else branch. + GVariantIter varIter; + g_variant_iter_init(&varIter, var); + dbus_traits::get(context, varIter, value); + } else if (boost::iequals(type, dbus_traits::getSignature())) { + V val; + dbus_traits::get(context, *itercopy, val); + value = val; + } else if (type[0] == 'a') { + std::vector val; + dbus_traits< std::vector >::get(context, *itercopy, val); + value = val; + } else if (type[0] == '(') { + // Treat a tuple like an array. We have to iterate ourself here. + std::vector val; + GVariantIter tupIter; + g_variant_iter_init(&tupIter, var); + GVariantIterCXX copy(g_variant_iter_copy(&tupIter)); + // Step through the elements in lockstep. We need this + // because we must not call the get() method when there is + // nothing to unpack. + while (GVariantCXX(g_variant_iter_next_value(copy))) { + host_type tmp; + dbus_traits::get(context, tupIter, tmp); + val.push_back(tmp); + } + value = val; + } else { + // More strict than the other variants, because it is mostly used for + // method calls where we don't want to silently ignore parts of the + // parameter. + throw std::runtime_error(std::string("expected recursive variant containing " + dbus_traits::getSignature() + ", got " + type)); + return; + } + } + + static void append(GVariantBuilder &builder, const boost::variant &value) + { + g_variant_builder_open(&builder, G_VARIANT_TYPE(getType().c_str())); + boost::apply_visitor(append_visitor(builder), value); + g_variant_builder_close(&builder); + } + +}; + /** * a single member m of type V in a struct K */ -- cgit v1.2.3