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.
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.
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.
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.
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);
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);
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);
// ...
};
};
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.
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.
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.
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.
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.
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.
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.
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.
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.
This namespace contains several functions to retrieve the D-Bus identifier for various C++ data types.
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 themain()
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.
This project is licensed under AGPLv3.