Skip to content

GDBus++ - a glib2 D-Bus wrapper for C++ This is a mirror repository of the main repository: https://codeberg.org/OpenVPN/gdbuspp Use the main repository for development and bug tracking.

Notifications You must be signed in to change notification settings

OpenVPN/gdbuspp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GDBus++ :: glib2 D-Bus C++ interface

This library provides a simpler C++ based interface to implement D-Bus into applications in a more C++ approach, based on the C++17 standard.

How to build

This project uses the Meson Build System to compile and install the GDBus++ library and header files.

  $ meson setup --prefix=/usr _builddir
  $ cd _builddir
  $ meson compile
  $ meson test
  $ meson install
  $ sudo ldconfig

To see the configuration options available:

  $ meson configure

See the meson documentation how to modify these settings.

Basic concepts and the namespaces

The GDBus++ API is split up across multiple C++ namespaces, all using DBus as the root starting point.

All objects created in the DBus namespace should return a Ptr based object. This is just a short form of a std::shared_ptr<>. The DBus::Connection::Ptr is the same as std::shared_ptr<DBus::Connection>. It is not a requirement to use the Ptr style, this is more a code style aspect.

The code excerpts used below can be found in docs/example-service.cpp and docs/example-proxy.cpp.

DBus::Connection

This contains the class and declarations used to connect to the D-Bus on the running system.

To create a connection to the session D-Bus:

  auto connection = DBus::Connection::Create(DBus::BusType::Session);

To create a connection to the system D-Bus:

  auto connection = DBus::Connection::Create(DBus::BusType::System);

No other bus connections are currently supported. The Create() method will return a DBus::Connection::Ptr object. This object is used both by the service providing side and the proxy client side.

DBus::Service

This provides the main building block to provide a service on the D-Bus on the system. This is a class which your own implementation must extend. The key elements for a service are:

class MyService : public DBus::Service
{
  public:
    MyService(DBus::Connection::Ptr connection)
        : DBus::Service(con, "net.example.myservice")
    {
    }

    void BusNameAcquired(const std::string &busname) override
    {
        // Code run when the name of your service is accepted
    }

    void BusNameLost(const std::string &busname) override
    {
        // Code run when your service could not get or keep the bus name
    }
};

In the main() function of your program, you initialize the service like this; this example uses the Session Bus:

  auto connection = DBus::Connection::Create(DBus::BusType::SESSION);
  auto my_service = DBus::Service::Create<MyService>(connection);

DBus::Object::Base

The Base class in the DBus::Object namespace provides the needed glue to bind your own C++ object into a D-Bus object. Inheritance is used here as well.

In relation to DBus::Object, there is also DBus::Object::Path implementation available. This behaves just like std::string, but does have some additional checks to ensure it's a valid D-Bus object path. It is recommended to use this as container for object paths, since the GDBus++ APIs will also treat this as an object path instead of a string data type when passing the data over the D-Bus. Using std::string will in these cases require overriding the data type if the D-Bus interface expects an object path.

class MyObject : public DBus::Object::Base
{
public:
  MyObject()
      : DBus::Object::Base("/example/myobject",
                            "net.example.myinterface")
  {
  }

  const bool Authorize(const DBus::Authz::Request::Ptr request) override
  {
    // code to authorize a D-Bus proxy client accessing this object
  }
};

All D-Bus services must have a root D-Bus object. This is referred to as the "service handler" in GDBus++. This service handler is a basic DBus::Object::Base based object and is added to the D-Bus service using the DBus::Service::CreateServiceHandler<>() method

  auto connection = DBus::Connection::Create(DBus::BusType::SESSION);
  auto my_service = DBus::Service::Create<MyService>(connection);
  my_service->CreateServiceHandler<MyObject>(connection);
Adding D-Bus methods

To add D-Bus methods, use the DBus::Object::Base::AddMethod() method, where you define a callback function and the arguments it will receive and return. This must be setup during the initialization of the object.

  MyObject()
      : DBus::Object::Base("/example/myobject",
                            "net.example.myinterface")
  {
    AddMethod("MyMethod",
              [](DBus::Object::Method::Arguments::Ptr args)
              {
                // Code performed on access
              }
      );
  }

This adds a very simple method not receiving any input nor sending any output back to the calling D-Bus proxy client.

To add arguments to a D-bus method, you use the AddInput() and AddOutput() methods in the object the AddMethod() method returns:

  // ...
  auto args = AddMethod("MethodWithArgs",
                        [](DBus::Object::Method::Arguments::Ptr args)
                        {
                          // Code performed on access
                        }
      );
  args->AddInput("string_1", glib2::DataType::DBus<std::string>());
  args->AddInput("string_2", glib2::DataType::DBus<std::string>());
  args->AddOutput("result", glib2::DataType::DBus<std::string>());

With these two methods declared, the D-Bus object introspection will look something like this:

$ gdbus introspect --session --dest net.example.myservice --object-path /example/myobject
node /example/myobject {
  // ...
  interface net.example.myinterface {
    methods:
      MyMethod();
      MethodWithArgs(in  s string_1,
                     in  s string_2,
                     out s result);
  // ...
  };
};
Adding D-Bus object properties

To add D-Bus properties (directly accessible variables), also set up via the constructor:

  MyObject()
      : DBus::Object::Base("/example/myobject",
                            "net.example.myinterface")
  {
    // ...
    bool readwrite = true;
    AddProperty("my_property", my_property, readwrite);
  }

  // ...
  private:
    std::string my_property{"My Initial Value"};

This will add a direct mapping between a C++ variable and a D-Bus object property. In this case, the readwrite variable is true, which allows the D-Bus proxy client to modify this variable. The C++ variable is then updated automatically. If the C++ variable is modified by your program, a "Get Property" call to your D-Bus object will reflect that as well. The D-Bus data type is automatically identified via the C++ variable data type.

The D-Bus introspection of this property will look something like this:

$ gdbus introspect --session --dest net.example.myservice --object-path /example/myobject
node /example/myobject {
  // ...
  interface net.example.myinterface {
    properties:
      readwrite s my_property = 'My Initial Value';
  // ...
  };
};

There are more ways to add more complex data types as well, by using the AddPropertyBySpec() method instead of the AddProperty(). Please see the example code for more details how do make use of that.

Adding D-Bus object signals - via DBus::Signals::Group

The Group class in DBus::Signals is used to create an object sending various types of D-Bus signals from a DBus::Object::Base based object.

This is done by creating your own "signal object":

class MySignalGroup : public DBus::Signals::Group
{
  public:
    using Ptr = std::shared_ptr<MySignalGroup>;

    MySignalGroup(DBus::Connection::Ptr connection)
        : DBus::Signals::Group(connection,
                               "/example/myobject",
                               "net.example.myinterface")
    {
        RegisterSignal("MySignal",
                       {{"message", glib2::DataType::DBus<std::string>()}});
    }

    void MySignal(const std::string &message_content)
    {
        GVariant *message = glib2::Value::CreateTupleWrapped(message_content);
        SendGVariant("MySignal", message);
    }
};

This class can be extended with specialised functions to satisfy your need for sending D-Bus signals. All you need in your code is to instantiate this as an object in your DBus::Object::Base based constructor:

  MyObject(DBus::Connection::Ptr connection)
      : DBus::Object::Base("/example/myobject",
                            "net.example.myinterface")
  {
    // ...
    my_signals = DBus::Signals::Group::Create<MySignalGroup>(connection);
    my_signals->AddTarget("")
    RegisterSignals(my_signals);
  }

  private:
    MySignals::Ptr signals = nullptr;

The RegisterSignals() call will add the signal details to the D-Bus object introspection:

$ gdbus introspect --session --dest net.example.myservice --object-path /example/myobject
node /example/myobject {
  // ...
  interface net.example.myinterface {
    signals:
      MySignal(s message);
  // ...
  };
};

The any of the code inside the MyObject object can now call my_signals->MySignal("message"); to send the MySignal D-Bus signal. Since the AddTarget() used "" as the destination target, this will be be sent to any application listening to the `MySignal signal on the session bus.

D-Bus method arguments - via DBus::Object::Method::Arguments

A smart-pointer to an Arguments object will be returned when calling the DBus::Object::Base::AddMethod() method. This object is used to add input and output arguments to a D-Bus method you declare, as well as preparing for passing file descriptors via this D-Bus method call.

In addition, this object is also passed to the D-Bus method callback as the only argument. This must be used to extract the arguments passed to the D-Bus method by the proxy client, as well as the response this method should return back to the caller. This happens currently via glib2's GVariant objects, which are generic value containers.

To retrieve the arguments passed to the callback function:

[](DBus::Object::Method::Arguments::Ptr args)
{
  GVariant *params = args->GetMethodParameters();
  std::string string1 = glib2::Value::Extract(params, 0);
  std::string string2 = glib2::Value::Extract(params, 1);
}

To return a value back to the caller:

[](DBus::Object::Method::Arguments::Ptr args)
{
  // ....
  std::string result = string1 + " <=> " + string2;
  GVariant *ret = glib2::Value::CreateTupleWrapped(result);
  args->SetMethodReturn(ret);
}

To return an error back to the caller, create an exception class based on the DBus::Exception class and throw that as an exception.

D-Bus object management via DBus::Object::Manager

A D-Bus service may want to dynamically add or remove D-Bus objects during its runtime. This is functionality provided via the DBus::Object::Manager. When creating the DBus::Service, an object manager is prepared for you.

  auto my_service = DBus::Service::Create<MyService>(connection);
  auto object_manager = my_service->GetObjectManager();

This manager provides two primary methods, first to create a new D-Bus object in the D-Bus service the application is providing. The second call will remove the object from the service.

  auto my_new_object = object_manager->CreateObject<MY_CLASS>(MY_CLASS_CONSTRUCTOR_ARGUMENTS)
  object_manager->RemoveObject(DBus::Object::Path &object_path)

The D-Bus path this new object will use comes from the DBus::Object::Base() constructor; the first argument here is the D-Bus object path to use. The DBus::Object::Manager::CreateObject() call will return a Ptr (std::shared_ptr<MY_CLASS>) to the newly created object. If you preserve a reference to this object, you can remove the object like this:

  object_manager->RemoveObject(my_new_object->GetPath());

A DBus::Service implementation wanting to provide access to the DBus::Object::Manager in its object, can pass this via the class constructor:

  MyObject(DBus::Connection::Ptr connection, DBus::Object::Manager::Ptr obj_mgr)
      : DBus::Object::Base("/example/myobject",
                            "net.example.myinterface"),
        object_manager(obj_mgr)
  {
    // ...
    private:
      DBus::Object::Manager::Ptr object_manager = nullptr;
  };

See the tests/simple-service.cpp for a more thorough implementation how to use this functionality.

DBus::Proxy::Client

This is the main class used to connect to a D-Bus service. This can be used both as a stand-alone class or being implemented into your own class providing a more direct C++ API to a D-Bus service.

To call a D-Bus method:

auto connection = DBus::Connection::Create(DBus::BusType::SESSION);
auto proxy = Proxy::Client::Create(connection, "net.example.myservice");

auto preset = Proxy::TargetPreset::Create("/example/myobject",
                                          "net.example.myinterface");

GVariantBuilder *args_builder = glib2::Builder::Create("(ss)");
glib2::Builder::Add(args_builder, std::string("My first string"));
glib2::Builder::Add(args_builder, std::string("My Second String"));
GVariant *arguments = glib2::Builder::Finish(args_builder);

GVariant *response = proxy->Call(preset, "MethodWithArgs", arguments);
auto result = glib2::Value::Extract<std::string>(response, 0);
g_variant_unref(response);

This will call the MethodWithArgs D-Bus method with the required arguments and extract the result from the response.

To retrieve a D-Bus object property:

std::string my_property = proxy->GetProperty<std::string>(preset, "my_property");

To modify a D-Bus object property:

std::string new_property_value = "A changed property";
proxy->SetProperty(preset, "my_property", new_property_value);

See the example-proxy.cpp and example-proxy2.cpp for the a full example of the code snippets above.

DBus::MainLoop

This class contains the glib2 main loop logic. Unless there is a need to control the main loop directly, it is recommended to use the main loop built into the DBus::Service object.

auto connection = DBus::Connection::Create(DBus::BusType::SESSION);
auto my_service = DBus::Service::Create<MyService>(connection);

my_service->Run();

To set up an independent main loop:

auto mainloop = DBus::MainLoop::Create();
mainloop->Run();

The advantage of using the DBus::Service provided main loop, is that the service itself can more easily shut itself down, by calling the DBus::Service::Stop() method. Alternatively, the DBus::Service implementation need to get the DBus::MainLoop::Ptr object provided via its constructor or another approach.

glib2 namespace - various low-level helper functions

The glib2 namespace contains a set of sub-namespaces to help glue the C based API with a more native C++ API. This uses C++ templates heavily and the type deduction happening at compile time.

When working with GVariant based glib2 objects, the glib2::Value namespace is typically the most useful place to start looking.

glib2::Value

Functions to extract or retrieve values from GVariant objects to C++ variables are found in this namespace. The Extract<>() group of functions will extract values from variable lists (when the D-Bus data type is encapsulated with ( and )). The Get<>() group of functions will only read the value out of a GVariant object where the value is accessible directly.

This namespace also contains the ExtractVector() function to convert a GVariant based array to a C++ std::vector<>. This can only be used if all the values in the GVariant array is of the same data type. The

The Create<>() set of functions will convert a C++ variable into a GVariant object with the appropriate D-Bus data type. Similarly, the CreateVector() takes a std::vector<> C++ object and converts that to a GVariant based object.

glib2::Builder

In this namespace there are functions to work with the GVariantBuilder API in glib2. This is most commonly used to construct more complex data types and processing data structures. It also contains methods to convert std::vector<> to GVariantBuilder objects.

glib2::DataType

This namespace contains several functions to retrieve the D-Bus identifier for various C++ data types.

Examples code

Several test programs and a few example programs are shipped in this project. All the test programs are indirectly used when running meson test and is expected to always work.

  • example-service.cpp A very simple D-Bus service. All the service example code excerpts in this file comes from this example.

  • example-proxy.cpp A very simple D-Bus client proxy, accessing a D-Bus object provided by example-service.cpp. All the client proxy examples comes in this README from this example code.

  • example-proxy2.cpp An alternative example-proxy.cpp implementation, implementing the proxy as an object which is used directly from the main() function. This also uses the service implemented in example-service.cpp.

  • simple-service.cpp This implements a simple D-Bus test service using the Session Bus

  • proxy-client.cpp This implements a generic client side D-Bus proxy, which has similar functionality to the gdbus program.

  • signal-subscribe.cpp Implements a simple signal event handler, used for testing purposes.

  • signal-emit.cpp Provides a simple program to send one or more signals with various data types

  • signal-group.cpp Similar to signal-emit.cpp but gives the possibility to wrap the signal emission of more signals into its own class.

  • fd-receive-read.cpp An test program for receiving a file descriptor over the D-Bus. This requires the D-Bus service to prepare a file descriptor intended to be sent back to the caller.

  • fd-send-fstat.cpp Similar to fd-receive-read.cpp, but here the D-Bus method caller will send a file descriptor to the D-Bus service.

License

This project is licensed under AGPLv3.

About

GDBus++ - a glib2 D-Bus wrapper for C++ This is a mirror repository of the main repository: https://codeberg.org/OpenVPN/gdbuspp Use the main repository for development and bug tracking.

Topics

Resources

Stars

Watchers

Forks