The library provides 2 ways of parsing a message:

  1. Manual - using message iterators
  2. Automatic - using a predefined schema

Automatic parsing

In most cases, a method call will have a predefined schema for the data that we want to accept. When there is such an arrangement, the library provides a type-safe API for automatically validating a method call with the provided schema and extracting the data correctly.

Defining a schema

Simple schema

A schema is defined using the UDBus::Type<typename T, typename ...More> structure and initialising it with references to the variables that you want your application to store the resulting data in.

For example, here's how you define a simple structure:

int a = 0;
const char* b = "Hello, World!";

auto data = UDBus::Type
{
    a,
    b
};

Structures

If you need to use structures as part of the method call arguments, you can use the UDBus::Struct<typename T, typename ...More> struct, like this:

int a = 0;
const char* b = "Hello, World!";

auto s = UDBus::Struct
{
    a,
    b
};

auto data = UDBus::Type
{
    a,
    b,
    s
};

Due to restrictions of the C++ language and our API, we're required to take lvalue references. This forces the above code style, which is not the prettiest.

To make the code more structured, you can use the UDBus::makeStruct function that will temporarily allocate a struct, which will then be automatically deallocated when the type containing it is freed.

Example:

int a = 0;
const char* b = "Hello, World!";

auto data = UDBus::Type
{
    a,
    b,
    UDBus::makeStruct(UDBus::Struct
    {
        a,
        b
    })
};

Tip

The UDBus::Struct type is actually a child of the UDBus::Type type. You may use this to your advantage in some scenarios.

Variants

To define a variant type, use the UDBus::Variant struct. It looks like this:

struct Variant
{
    std::function<bool(UDBus::Iterator&, void*)> parse = [](UDBus::Iterator&, void*) -> bool { return false; };
};

The parse callback receives references to the parent iterator and a void* user pointer. It should return true on success and false on failure.

To set the user pointer, use call the UDBus::Message::setUserPointer() method.

Inside the callback, you need to manually parse the variant argument. More information on how to do that can be found here.

Arrays and dictionaries

Arrays and dictionaries are parsed automatically, only if and when using the following standard containers:

  1. std::vector
  2. std::map
  3. std::unordered_map

However, you also have the option of defining custom containers using the following macros:

  1. UDBUS_USING_CUSTOM_DICT_TYPES
  2. UDBUS_USING_CUSTOM_ARRAY_TYPES

When these macros are not defined, the following 2 functions are part of the UDBus namespace:

template<typename T>
constexpr bool is_map_type() noexcept
{
    if constexpr (is_specialisation_of<std::unordered_map, T>{} || is_specialisation_of<std::map, T>{})
        return true;
    return false;
}

template<typename T>
constexpr bool is_array_type() noexcept
{
    if constexpr (is_specialisation_of<std::vector, T>{})
        return true;
    return false;
}

When the macros are defined, you should redefine these functions in the UDBus namespace, and you should change your code so that it returns true on the appropriate types.

Caution

A complex array, i.e. an array of type similar to std::vector<UDBus::Struct<...>>, has its data internally heap-allocated, so after usage you need to call the static UDBus::Type<...>::destroyComplex function in order to not cause a memory leak.

Caution

A complex dictionary, i.e. a dictionary with a complex value with a type similar to std::map<int, UDBus::Struct<...>>, has its data internally heap-allocated, so after usage you need to call the static UDBus::Type<...>::destroyComplex function in order to not cause a memory leak.

Tip

To call UDBus::Type<...>::destroyComplex, you can use the . syntax. For example, if we have UDBus::Type<...> t; we can use t.destroyComplex(array);. Alternatively, you can use the :: syntax to silence linter warnings, caused by using the . syntax on a static member like this: decltype(t)::destroyComplex(array).

Arrays and dictionaries of variants

To be able to parse an array or dictionary of variants, you need to provide a template. To do that, use the UDBus::associateWithVariant function:

std::unordered_map<int, UDBus::Variant> v;

auto data = UDBus::Type
{
    UDBus::associateWithVariant(v, UDBus::makeVariant
    (
        UDBus::Variant
        {
            .f = [](UDBus::Iterator& it, void* v) -> bool
            {
                std::cout << "Array with variant" << std::endl;
                return true;
            }
        }
    ))
};

Object paths

Object paths are represented as strings of type char* or const char*. The library does no validation for these types.

Skipping certain types

If you want to skip getting the value from a certain type, you can call the UDBus::ignore() function when constructing the instance of the Type<...> struct.

For example, if receiving a message with a signature of iis, you can skip the second integer like this:

auto type = UDBus::Type
{
    a,
    UDBus::ignore(),
    b,
};

Note

The number of fields of the schema and the data received over the dbus bus should still match when UDBus::ignore() is called as the last variable of the Type<...> instance.

Bumping the size of a type

Types should always have more than 2 types in the variadic list, however you may have a situation where you want to parse only 1 item. To do that, you can use the UDBus::bump utility function to bump the size of the Type<...> structure.

For example:

auto type = UDBus::Type
{
    a,
    UDBus::bump(),
};

Parsing calls to a server

In the main loop of your application, you need to call the UDBus::handleAutomatic with the names of the interface and method, as well as the schema:

while(true)
{
    UDBus::handleMethodCall("interface", "method", data);
}

Once you're done with using the schema, you can free it using the UDBus::Type::destroy static method.

Caution

There is currently no way to cleanly free all data using the destructor of the type structure. Deallocation has to be done manually!

Tip

To call UDBus::Type<...>::destroy, you can use the . syntax. For example, if we have UDBus::Type<...> t; we can use t.destroy(t);. Alternatively, you can use the :: syntax to silence linter warnings, caused by using the . syntax on a static member like this: decltype(t)::destroy(t).

Parsing a generic message

You can also parse a generic message in the same way using the UDBus::Message::handleMessage member function.

Error handling

The UDBus::Message::handleMethodCall function returns an enum of type UDBus::MessageGetResult, which looks like this:

enum MessageGetResult
{
    RESULT_SUCCESS,
    RESULT_NOT_CALLED,
    RESULT_MORE_FIELDS_THAN_REQUIRED,
    RESULT_LESS_FIELDS_THAN_REQUIRED,
    RESULT_INVALID_BASIC_TYPE,
    RESULT_INVALID_STRUCT_TYPE,
    RESULT_INVALID_ARRAY_TYPE,
    RESULT_INVALID_DICTIONARY_TYPE,
    RESULT_INVALID_DICTIONARY_KEY,
    RESULT_INVALID_VARIANT_TYPE,
    RESULT_INVALID_VARIANT_PARSING,
};

You can check against it to see if any errors occurred during the parsing stage.

Note

Note that RESULT_NOT_CALLED is not an error. It is just there to signal that the method or signal has not been called.

Manual parsing

Manual parsing is done using the UDBus::Iterator class. When parsing a message, the get* methods are used. Example of parsing an array of structures:

UDBus::Iterator it, arrayIt;
it.setGet(reply, arrayIt, true);
        
if (it.get_arg_type() == DBUS_TYPE_ARRAY)
{
    it.recurse();
            
    while (arrayIt.get_arg_type() == DBUS_TYPE_STRUCT)
    {
        UDBus::Iterator structIt;
        arrayIt.setGet(reply, structIt, false);
        arrayIt.recurse();
                
        char* id;
        char* userName;
                
        structIt.get_basic(&id);
        structIt.next();
        structIt.next();
        structIt.get_basic(&userName);
                
        if (username == userName)
        {
            sessionID = id;
            break;
        }
         arrayIt.next();
    }
}

Caution

Calling setGet with the last argument being true should only be done when you're parsing the whole data tree. If you're parsing manually with a provided parent iterator(mostly when parsing Variant types), you SHOULD ALWAYS call it with false.