Main Page DBus Documentation

From NDesk

Jump to: navigation, search

Contents

NDesk.DBus Documentation

This page is a work in progress as of early 2009. ndesk.org editors are invited to update and enhance it.

Getting started

Managed D-Bus exists in the NDesk.DBus namespace. It requires a 2.0 or later CLR implementation due to extensive use of generics. D-Bus is typically used in Unix-like desktop environments and is included in Linux distributions. See the main Managed D-Bus page for source code tarball and repository details.

using NDesk.DBus;

Main loop

Main loop integration or manual polling is essential for anything but the simplest uses of D-Bus. Failure to set up the main loop is the most common mistake encountered by developers learning managed D-Bus.

GLib main loop integration

TestExport.cs demonstrates how the additional NDesk.DBus.GLib library can be used to provide main loop integration with GLib and GTK+ applications.

NDesk.DBus.GLib provides a single static method, BusG.Init (), for hooking NDesk.DBus to the GLib main loop. This method should be called early within the lifetime of the application:

  public static void Main ()
  {
    BusG.Init ();
    Application.Init ();
    ...
  }

BusG.Init() may be called more than once in the lifetime of an application and will be a no-op on subsequent calls.

The previous example initializes the shared System and Session bus connections. If you've created your own bus Connection, it's also possible to hook this up to the GLib main loop:

BusG.Init (myConnection);

BusG.Init(Connection) should be called at most once per Connection in the lifetime of an application. If you ever call it more than once on the same Connection, tragedy will ensue.

NDesk.DBus.GLib does not depend on glib-sharp/gtk-sharp but instead invokes GLib directly.

Custom main loop

The GLib technique described above is strongly recommended where available. It is however possible to use NDesk.DBus without GLib. Message processing can be driven using NDesk.DBus.Connection.Iterate () and related methods.

//run the main loop
while (true)
  bus.Iterate ();

bus.Iterate() will block while waiting for messages, so depending on the design of your application, you may wish to run this loop in a thread. You must take great care not to Iterate() a shared Bus/Connection if there is any possibility it may already/later be hooked up to GLib or iterated elsewhere. You should typically create a new Connection when polling manually. If developing a library or plug-in for re-use in third-party applications, be considerate of other uses of managed D-Bus within the same process.

Connecting to a D-Bus daemon

In general one of the shared connections should be used. These connect and authenticate automatically (but main loop hookup still needs to be performed as described above).

In a typical desktop application use the shared Bus.System and Bus.Session connections.

// Get the system bus
Bus bus = Bus.System;
// Get the session bus
Bus bus = Bus.Session;

In a typical D-Bus service use the shared Bus.Starter connection.

// Get the starter bus
Bus bus = Bus.Starter;

Bus is a subclass of Connection which provides additional bus-related brokerage. The assigned unique bus name, of the form ':number' can be retrieved with bus.UniqueName.

Peer-to-peer connections

It is also possible to create a new, unshared D-Bus connection:

// Open a TCP/IP D-Bus connection
Connection connection = Connection.Open ("tcp:host=localhost,port=12345");

// Hook it up to the GLib main loop
BusG.Init (connection);

The D-Bus address format is specified in the freedesktop.org D-Bus documentation. It is roughly of the form

(unix:(path|abstract)=.*,guid=.*|tcp:host=.*(,port=.*)?)

Interfaces

Managed D-Bus exports and imports objects implementing interfaces marked with the NDesk.DBus.Interface attribute. This allows developers to either extend existing code with inter-process and network transparency or develop new code to proxy the state of the application. TestExportInterface.cs provides examples of exported and imported interfaces, methods and properties.

NDesk.DBus maps all primitive CLR types as well as arrays, generic IDictionary<TKey,TValue> types and structs to their equivalent D-Bus types, while primitives boxed in System.Object are mapped to D-Bus variants. All of these mappings are transparent and fully interoperable with other languages and platforms implementing the D-Bus specification.

[NDesk.DBus.Interface ("org.ndesk.Demo")]
public interface IDemo
{
  void Say (string str);
}

The the NDesk.DBus.Interface attribute is typically applied to interface classes only, but can also be applied to MarshalByRefObject subclasses. This use mode goes through the CLR remoting layer and has some limitations, and its use is not recommended in new code.

Importing a remote D-Bus object

The method Connection.GetObject<T>(busName, objectPath) retrieves a proxy object representing a remote D-Bus object. T must be the type of an interface (or, in older code, a MarshalByRefObject) marked with the NDesk.DBus.Interface attribute. It is not possible to import interfaces that do not meet these criteria.

Bus bus = Bus.Session;

// Import a remote object to a local proxy
IDemo demo = bus.GetObject<IDemo> (busName, objectPath);

// Invoke the remote method
demo.Say ("hello world");

Exporting a managed object

Signals / Events

Managed events are mapped to D-Bus signals. The event delegate type's parameters must correspond directly to the D-Bus signal -- this means that System.EventHandler / System.EventArgs parameters are not supported and there should also be no "object sender" parameter.

Obtaining a bus name

Obtaining a strong bus name is very useful when exporting a service. It is also an effective technique for enforcing a single instance policy for your GUI application.

string busName = "org.ndesk.TestApplication";
if (bus.RequestName (busName) != RequestNameReply.PrimaryOwner) {
  // Our name is already owned!
  // Notify the existing owner here if desired.
  // Now let's bail out...
  Application.Quit ();
  return;
}

The atomicity of the RequestName() method is a critical feature. Developers should never perform this operation in two steps as below.

// WRONG WRONG WRONG
// Use the technique described above instead!
string busName = "org.ndesk.TestApplication";
if (!bus.NameHasOwner (busName))
  bus.RequestName (busName);

If you see code similar to the above anywhere, please submit a patch that replaces it with the correct, atomic version.

General documentation about bus names and their role in D-Bus can be found in documentation on the D-Bus website.

Properties

Managed properties are mapped to D-Bus properties. This mapping was/is(?) not standardized by the specification.

The convention used is to map properties to Get/Set method calls. The org.freedesktop.DBus.Properties interface is not supported automatically but a managed interface description is available for developers wishing to support this interface explicitly (org.freedesktop.DBus.Properties in NDesk.DBus).

Object paths

To be written.

D-Bus variants

D-Bus variants are represented as System.Object in managed interfaces. Variants are a kind of wildcard data type that may contain a single simple or complex value, the type of which is unknown until the call is made.

When exporting an interface, any object that can be represented by D-Bus (null is not supported) may be passed in to a System.Object parameter or field. It is recommended to restrict transmitted values to primitives (strings, numbers etc. and arrays thereof).

When importing an interface, managed D-Bus will make a best attempt to convert a variant value to its managed equivalent, boxed in System.Object.

New feature in git as of March 2009: If it is not possible to determine the managed equivalent (for example if the received variant contains an anonymous structure, the type and fields of which are unknown), an opaque boxed object is returned. This object may be converted to its actual type, say a managed struct, array or IDictionary<,>, using System.Convert at any later point:

object boxedVariant = demo.ComplexAsVariant ();
MyStruct actualStruct = (MyStruct)Convert.ChangeType (boxedVariant, typeof (MyStruct));

If MyStruct cannot represent boxedVariant, Convert.ChangeType() will safely throw an InvalidCastException.

There is currently no official way to determine the signature of the received boxed variant, as the receiver will typically already know what format to expect based on an enum or string value in that call. Unofficially, ToString() may return the D-Bus signature of the variant as a string which can be helpful when dealing with poorly designed D-Bus interfaces from third parties. However, we recommend that developers treat the boxed opaque object as a strictly sealed and private object, only accessing it with System.Convert.

Classes and structures

Classes and structs not marked with the DBus.Interface attribute are marshaled by value.

(EDITOR TODO: Surely the below code example will trigger a C# error due to self-referential structs?)

struct MyData
{
  public int Number;
  public MyData[] Children;
}

[NDesk.DBus.Interface ("org.ndesk.Demo")]
public interface IDemo
{
  void FrobData (string info, IDictionary<int,MyData> dataTable);
}

Introspection

Knowledge of D-Bus introspection is not necessary to use managed D-Bus, however we will describe here how managed D-Bus supports introspection internally.

Exported managed objects implicitly support D-Bus introspection. Managed D-Bus does not make use of introspection data for imported objects but instead relies on the managed interface descriptions which must accurately represent the remote interface (with the exception that the managed interfaces need only cover the methods, properties and events that are desired for use in the application).

Exceptions and error messages

When calling a remote method, if the method call returns a D-Bus error message instead of a reply, the error will be mapped to a managed exception which is raised and can be caught or allowed to halt execution of the application as with any exception. Similarly, exceptions thrown by exported methods will be mapped to a D-Bus error message which is sent to the caller. In both cases, exception handling is supported as part of the core D-Bus mapping, thus message handling can continue without interruption during and after the exception.

Note however that managed D-Bus exceptions do not round-trip in the current implementation. That is to say, if you throw a FooException on one end, it will be raised as a System.Exception on the calling end. The exception message may or may not contain an indication as to the name of the original exception which may still be helpful to developers who really need this information.

Asynchronous calls and multi-threaded use

NDesk.DBus is fully thread-safe. Incoming method calls and signals are raised on the main UI thread and main loop. Outgoing method calls receive their replies as expected on the calling thread without blocking the main loop and without blocking any other pending calls. This is currently the primary technique for making asynchronous method calls.

(EDITOR TODO: Document use of delegate BeginInvoke() for async calls.)

Debugging applications

Defining the environment variable DBUS_VERBOSE will cause NDesk.DBus and NDesk.DBus.GLib to report warnings and possible error conditions to standard error. This is the first recourse if the library appears to malfunction and its output is essential when reporting bugs.

DBUS_VERBOSE=1 mono --debug HelloDBus.exe

The dbus-monitor utility can be used to observe messages on the bus. Monitor utilities are provided with the freedesktop.org D-Bus implementation and with NDesk.DBus itself.

See also NDesk D-Bus Explorer.

Typical warnings and errors

Issue: "Warning: The signature of the message does not match that of the handler ..." Resolution: Ensure that the signatures in your managed interface are correct.

Issue: "Warning: Cannot yet fully expose EventHandler and its subclasses ..." Resolution: Managed event delegates cannot be EventArgs. Correct your managed interface as described in the section on signals.

Issue: "Warning: Protocol version 'x' is not explicitly supported but may be compatible" Resolution: Let the developers at NDesk know so we can support the new protocol version fully.

Further examples

Examples in the source code repository. Note that some of these examples are intentionally complex or obtuse to test corner cases in the implementation -- you don't need to do things exactly the same way in your application.

There is a list of applications on the main Managed D-Bus page, many of which are open source. Keep in mind that some of these applications use managed D-Bus in slightly awkward ways, either because the code was ported from older, obsolete D-Bus libraries or because they were developed for very old versions of the library.

Developing on Linux

Managed D-Bus supports Linux and Unix systems out of the box. Deployment is typically done as a packaged dependency. This means that Linux distributors (eg. Debian, Ubuntu, SUSE) will provide official packages of ndesk-dbus and ndesk-dbus-glib which applications can depend on and re-use.

Managed D-Bus supports Linux with the Mono runtime. It has been deployed in stand-alone services as well as GTK#, Qyoto (qt-sharp), etc. GUI applications.

Developing on OS X

Managed D-Bus supports Apple Mac OS X with the Mono runtime. It has been deployed in stand-alone services as well as GTK#, Qyoto (qt-sharp), cocoa-sharp etc. GUI applications.

It is typically used with the TCP transport on this platform. The Unix transport backend may work on OS X with or without modification but this has not yet been tested.

Developing on Windows

Managed D-Bus (as of April 2009, only in git head at time of writing) includes Visual Studio project files and works well on Windows with the .NET runtime. It has been deployed in stand-alone services as well as GTK#, Qyoto (qt-sharp), Windows.Forms etc. GUI applications.

It is typically used with the TCP transport since the Unix backend only works on Unix-like systems.

dbus-sharp has stable Windows support.

dbus-sharp-glib has initial, untested Windows support. We are looking for users and developers who wish to work in this area.

Mobile and embedded

Managed D-Bus is a great choice for IPC on handhelds and embedded systems. It is more efficient with CPU and memory resources than many D-Bus language bindings, and offers a much simpler development experience than working directly with the low-level D-Bus API.

Mobile and embedded developers may choose to stick with traditional C D-Bus on their hardware if it does not support a .NET runtime and still benefit from managed D-Bus as a client or server library on developer desktops to interact with the device over the network for automated testing and automation. Managed D-Bus has been successfully deployed as an automation controller for industrial robotics, for example.

DLR and Dynamic CLR languages

Managed D-Bus includes experimental support for dynamic languages, whereby imported D-Bus interfaces are introspected and proxy objects are generated dynamically on use at runtime.

This support can by enabled by adding TypeDefiner.cs to the build if it isn't there already.

This will add a new overload of the Bus.GetObject() method that does not require an interface to be specified. When called with a bus name and object path, it dynamically inspects the remote object, generates semi-anonymous interfaces inferred from the introspection data and returns a System.Object implementing those interfaces. The result is that you can use many D-Bus services with just a couple of lines of code with IronPython etc. and NDesk D-Bus.

IronPython

Examples coming soon.

IronRuby

Examples coming soon.

Comparison with traditional bindings

Managed D-Bus may be a better choice than dbus-python or dbus-ruby depending on the needs, licensing and portability of your application.