The Tmds.DBus.Protocol
packages provides a D-Bus protocol API.
The following example shows how you can use it to expose and consume a D-Bus service that adds two integers.
Create a console application:
dotnet new console -o example
cd example
Update Program.cs
:
using Tmds.DBus.Protocol;
class Program
{
static async Task Main()
{
string peerName = await StartAddServiceAsync();
var connection = Connection.Session;
var addProxy = new AddProxy(connection, peerName);
int i = 10;
int j = 20;
int sum = await addProxy.AddAsync(i, j);
Console.WriteLine($"The sum of {i} and {j} is {sum}.");
}
private async static Task<string> StartAddServiceAsync()
{
var connection = new Connection(Address.Session!);
await connection.ConnectAsync();
connection.AddMethodHandler(new AddImplementation());
return connection.UniqueName ?? "";
}
}
class AddProxy
{
private const string Interface = "org.example.Adder";
private const string Path = "/org/example/Adder";
private readonly Connection _connection;
private readonly string _peer;
public AddProxy(Connection connection, string peer)
{
_connection = connection;
_peer = peer;
}
public Task<int> AddAsync(int i, int j)
{
return _connection.CallMethodAsync(
CreateAddMessage(),
(Message message, object? state) =>
{
return message.GetBodyReader().ReadInt32();
});
MessageBuffer CreateAddMessage()
{
using var writer = _connection.GetMessageWriter();
writer.WriteMethodCallHeader(
destination: _peer,
path: Path,
@interface: Interface,
signature: "ii",
member: "Add");
writer.WriteInt32(i);
writer.WriteInt32(j);
return writer.CreateMessage();
}
}
}
class AddImplementation : IMethodHandler
{
private const string Interface = "org.example.Adder";
public string Path => "/org/example/Adder";
private static ReadOnlyMemory<byte> InterfaceXml { get; } =
"""
<interface name="org.example.Adder">
<method name="Add">
<arg direction="in" type="i"/>
<arg direction="in" type="i"/>
<arg direction="out" type="i"/>
</method>
</interface>
"""u8.ToArray();
public bool RunMethodHandlerSynchronously(Message message) => true;
public ValueTask HandleMethodAsync(MethodContext context)
{
if (context.IsDBusIntrospectRequest)
{
context.ReplyIntrospectXml([ InterfaceXml ]);
return default;
}
var request = context.Request;
switch (request.InterfaceAsString)
{
case Interface:
switch ((request.MemberAsString, request.SignatureAsString))
{
case ("Add", "ii"):
var reader = request.GetBodyReader();
int i = reader.ReadInt32();
int j = reader.ReadInt32();
return Add(context, i, j);
}
break;
}
return default;
}
private ValueTask Add(MethodContext context, int i, int j)
{
int sum = i + j;
ReplyToAdd(context, sum);
return default;
}
private void ReplyToAdd(MethodContext context, int sum)
{
using var writer = context.CreateReplyWriter("i");
writer.WriteInt32(sum);
context.Reply(writer.CreateMessage());
}
}
Now run the example:
$ dotnet run
The sum of 10 and 20 is 30.
Tmds.DBus.Protocol
is compatible with NativeAOT and trimming.
Methods that are not compatible have been annotated with both the Obsolete
and RequiresUnreferencedCode
attributes. These methods may be removed in a future version of the library.
If you are currently using these methods, the following section shows how you can change your code to make it compatible with NativeAOT/trimming.
The following sections show examples of writing composite types (structs/arrays/dictionaries and variants).
Some types can be read directly using the Reader
's ReadArrayOf
methods.
byte[] array = reader.ReadArrayOfByte();
When there is no such method, the item type can be read using a while
loop as shown by the following example.
List<byte[]> arrayOfByteArrays = new();
ArrayEnd arrayEnd = reader.ReadArrayStart(DBusType.Array);
while (reader.HasNext(arrayEnd))
{
arrayOfByteArrays.Add(reader.ReadArrayOfByte());
}
The MessageWriter
's WriteArray
overloads has overloads that enable directly writing some types.
writer.WriteArray(new int[] { 1, 2, 3 });
If no overload is available for the item type, the array can be written by writing each element separately.
ArrayStart arrayStart = writer.WriteArrayStart(DBusType.String);
foreach (var item in value)
{
writer.WriteString(item);
}
writer.WriteArrayEnd(arrayStart);
Note: this example shows how to writing an array of strings. There is a WriteArray
overload that allows to do this directly.
Dictionary<byte, string> dictionary = new();
ArrayEnd dictEnd = reader.ReadDictionaryStart();
while (reader.HasNext(dictEnd))
{
var key = reader.ReadByte();
var value = reader.ReadString();
dictionary[key] = value;
}
ArrayStart arrayStart = writer.WriteDictionaryStart();
foreach (var item in value)
{
writer.WriteDictionaryEntryStart();
writer.Write... // write the key
writer.Write... // write the value
}
writer.WriteDictionaryEnd(arrayStart);
To read a struct call AlignStruct
and then read the struct fields.
// Read a struct of (int32, string).
reader.AlignStruct();
var value = (reader.ReadInt32(), reader.ReadString());
To write a struct call WriteStructureStart
and write the struct fields.
// Write a struct of (int32, string).
writer.WriteStructureStart();
writer.WriteInt32(i);
writer.WriteString(s);
A variant can be read using the Reader
's ReadVariantValue
method.
VariantValue vv = reader.ReadVariantValue();
switch (vv)
{
case { Type: VariantValueType.Int32 }:
int i = vv.GetInt32();
break;
case { ItemType: VariantValueType.Int32 }:
int[] array = vv.GetArray<int>();
break;
case { KeyType: VariantValueType.String, ValueType: VariantValueType.VariantValue }:
Dictionary<string, VariantValue> dict = vv.GetDictionary<string, VariantValue>();
break;
...
}
Note that VariantValue
is a small struct, there is no need to pass it by reference.
For writing variants, the value must be stored in a VariantValue
struct and passed to Writer.WriteVariant(VariantValue)
.
Simple types have implicit conversion to Variant
.
VariantValue v1 = (byte)1;
VariantValue v2 = "string";
VariantValue v3 = new ObjectPath("/path");
They can also be constructed using a static method:
VariantValue v1 = VariantValue.Byte(1);
VariantValue v2 = VariantValue.String("string");
VariantValue v3 = VariantValue.ObjectPath("/path");
Structs can be created using the static Struct
method:
VariantValue v4 = VariantValue.Struct("string", 5);
Arrays can be created using the static Array
method.
For simple types, the C# array can be passed as the argument:
VariantValue v5 = VariantValue.Array(new int[] { 1, 2, 3 })
The Array
/Dict
/Struct
classes provide a type-safe way to create composite VariantValues.
VariantValue v6 = Struct.Create((byte)1, Struct.Create("string", "string"));
VariantValue v7 = new Dict<byte, VariantValue>()
{
{ 1, Struct.Create(1, 2) },
{ 2, "string" },
};
VariantValue v8 = new Array<int>() { 1, 2 };
VariantValue
avoids copies when possible. You should not modify the data used to construct a VariantValue
until that value was written.
The data returned by some methods (like GetArray
) may return the underlying data. If you modify it, other users of the object may observe the changes.