Using GVariant as an opaque data type

Although mainly intended for gio's dbus implementation, GVariant objects held using this library's GvarHandle class can be useful for the general passing of opaque types in C++ which are both type safe and dynamically typed (rather than statically typed like other C++ types). Using the implementation takes a little getting used to, but it is efficient and flexible for the types it can handle, which are the types used by dbus with extensions. They can be viewed as immutable type safe unions which are easily serialised for storing on disk or sending over a network.

Supported types comprise integer types of varying sizes, doubles and C strings, together with arrays, hash tables (dictionaries), and structs (tuples) of any of these. Provided only these types are used, GVariant is very flexible as to the ways in which the types can be combined. Variants can hold variants, and the array, dictionary (for its value argument) and tuple containers can contain variants as well as other dictionaries, arrays and tuples, and variants can optionally hold null values.

How these types are combined is well explained in the documentation, but how they are decomposed is less so. This gives a few tips.

Extracting the basic types (integers, doubles and strings) from a GVariant object is very straightforward, and comprises either using the specific extractor function for the type concerned (g_variant_get_uint32(), g_variant_get_double(), g_variant_get_string() and so forth), or by using the formatted extractor g_variant_get() with the appropriate type format string ("u". "d", "s" and so forth).

Extracting values from variants holding arrays, tuples and dictionaries is more complex. g_variant_get_child_value() is the key function for this, and most of the other relevant extractors for containers are just convenience wrappers for that function. g_variant_get_child_value() will extract the object(s) that the array, tuple or dictionary hold, at the position given (which will be 0 if there is only one), wrapped in another GVariant object: it also extracts a variant held by a variant: g_variant_get_variant([variant]), is just a wrapper for g_variant_get_child_value([variant], 0) with additional type checking.

Say you have a variant holding a tuple containing two variants, the first of which holds an int and second a string-array. The string-array can be obtained in this way:

// 'input' is a GvarHandle object representing a variant of type
// "(vv)" which holds a tuple containing two variants, the first
// of which holds an int and the second a string-array
Cgu::GvarHandle decomposed(g_variant_get_child_value(input, 1));
// 'decomposed' now represents a variant of type "v" which holds
// a variant which holds a string-array: index 1 has taken the
// second object contained in the tuple
decomposed.reset(g_variant_get_child_value(decomposed, 0));
// 'decomposed' now represents a variant of type "as" which holds
// a string-array: we could also have used g_variant_get_variant()
gchar** str_arr = g_variant_dup_strv(decomposed, 0);
// str_arr now holds the string-array, free it with g_strfreev()

g_variant_get_child_value() can also be used to extract items from an array. Thus, instead of calling g_variant_dup_strv() on the decomposed object extracted, the strings could have been manipulated in this way:

int num = g_variant_n_children(decomposed);
for (int count = 0; count < num; ++count) {
Cgu::GvarHandle item(g_variant_get_child_value(decomposed, count));
std::cout << g_variant_get_string(item, 0) << std::endl;
}

Alternatively, g_variant_iter_next_value() and its derivatives g_variant_iter_next() and g_variant_iter_loop() (which are all wrappers for g_variant_get_child_value()) can be used to iterate through an array without keeping a separate count variable.

g_variant_get_child_value() can also be used to obtain values from a hash table, for example:

// make a dictionary entry (normally this would be placed in an array)
Cgu::GvarHandle entry(g_variant_new_dict_entry(g_variant_new_uint32(2),
g_variant_new_string("Hello")));
// decompose it and print it out
Cgu::GvarHandle value(g_variant_get_child_value(entry, 0));
std::cout << g_variant_get_uint32(value) << std::endl;
value.reset(g_variant_get_child_value(entry, 1));
std::cout << g_variant_get_string(value, 0) << std::endl;

Many of these extraction operations can be mimicked by the formatted g_variant_get() function. Thus, in the first example above, the two variants held by 'input' in its tuple container could be obtained in this way with one call:

Cgu::GvarHandle h1, h2;
{
GVariant* a;
GVariant* b;
g_variant_get(input, "(vv)", &a, &b);
h1.reset(a);
h2.reset(b);
}
... use 'h1' and 'h2' ...
Cgu::GvarHandle::reset
void reset(GVariant *ptr=0) noexcept
Definition: gvar_handle.h:205
Cgu::GvarHandle
This is a handle for managing the reference count of GVariant objects.
Definition: gvar_handle.h:160