466 lines
14 KiB
HTML
466 lines
14 KiB
HTML
|
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
|
<HTML><HEAD><TITLE>Man page of Net::DBus::Tutorial::ExportingObjects</TITLE>
|
|
</HEAD><BODY>
|
|
<H1>Net::DBus::Tutorial::ExportingObjects</H1>
|
|
Section: User Contributed Perl Documentation (3pm)<BR>Updated: 2019-12-26<BR><A HREF="#index">Index</A>
|
|
<A HREF="/cgi-bin/man/man2html">Return to Main Contents</A><HR>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<A NAME="lbAB"> </A>
|
|
<H2>NAME</H2>
|
|
|
|
Net::DBus::Tutorial::ExportingObjects - tutorials on providing a DBus service
|
|
<A NAME="lbAC"> </A>
|
|
<H2>DESCRIPTION</H2>
|
|
|
|
|
|
|
|
This document provides a tutorial on providing a DBus service using the
|
|
Perl Net::DBus application bindings. This examples in this document
|
|
will be based on the code from the Music::Player distribution, which
|
|
is a simple DBus service providing a music track player.
|
|
<A NAME="lbAD"> </A>
|
|
<H2>CREATING AN OBJECT</H2>
|
|
|
|
|
|
|
|
The first step in creating an object is to create a new package
|
|
which inherits from Net::DBus::Object. The Music::Player::Manager
|
|
object provides an <FONT SIZE="-1">API</FONT> for managing the collection of music player
|
|
backends for different track types. To start with, lets create the
|
|
skeleton of the package & its constructor. The constructor of the
|
|
super type, Net::DBus::Object expects to be given to parameters,
|
|
a handle to the Net::DBus::Service owning the object, and a path
|
|
under which the object shall be exported. Since the manager class is
|
|
intended to be a singleton object, we can hard code the path to it
|
|
within the constructor:
|
|
<P>
|
|
|
|
|
|
|
|
<PRE>
|
|
package Music::Player::Manager;
|
|
|
|
use base qw(Net::DBus::Object);
|
|
|
|
sub new {
|
|
my $class = shift;
|
|
my $service = shift;
|
|
my $self = $class->SUPER::new($service, "/music/player/manager");
|
|
|
|
bless $self, $class;
|
|
|
|
return $self;
|
|
}
|
|
|
|
1;
|
|
|
|
</PRE>
|
|
|
|
|
|
<P>
|
|
|
|
Now, as mentioned, the manager with handle a number of different
|
|
player backends. So we need to provide methods for registering
|
|
new backends, and querying for backends capable of playing a
|
|
particular file type. So modifying the above code we add a hash
|
|
table in the constructor, to store the backends:
|
|
<P>
|
|
|
|
|
|
|
|
<PRE>
|
|
sub new {
|
|
my $class = shift;
|
|
my $service = shift;
|
|
my $self = $class->SUPER::new($service, "/music/player/manager");
|
|
|
|
$self->{backends} = {};
|
|
|
|
bless $self, $class;
|
|
|
|
return $self;
|
|
}
|
|
|
|
</PRE>
|
|
|
|
|
|
<P>
|
|
|
|
And now a method to register a new backend. This takes a Perl
|
|
module name and uses it to instantiate a backend. Since the
|
|
backends are also going to be DBus objects, we need to pass
|
|
in a reference to the service we are attached to, along with
|
|
a path under which to register the backend. We use the <TT>"get_service"</TT>
|
|
method to retreieve a reference to the service the manager is
|
|
attached to, and attach the player backend to this same service:
|
|
When a method on DBus object is invoked, the first parameter is
|
|
the object reference (<TT>$self</TT>), and the remainder are the
|
|
parameters provided to the method call. Thus writing a method
|
|
implementation on a DBUs is really no different to normal object
|
|
oriented Perl (cf perltoot):
|
|
<P>
|
|
|
|
|
|
|
|
<PRE>
|
|
sub register_backend {
|
|
my $self = shift;
|
|
my $name = shift;
|
|
my $module = shift;
|
|
|
|
eval "use $module";
|
|
if ($@) {
|
|
die "cannot load backend $module: $@" ;
|
|
}
|
|
|
|
$self->{backends}->{$name} = $module->new($self->get_service,
|
|
"/music/player/backend/$name");
|
|
}
|
|
|
|
</PRE>
|
|
|
|
|
|
<P>
|
|
|
|
Looking at this one might wonder what happens if the <TT>"die"</TT>
|
|
method is triggered. In such a scenario, rather than terminating
|
|
the service process, the error will be caught and propagated back
|
|
to the remote caller to deal with.
|
|
<P>
|
|
|
|
The player backends provide a method <TT>"get_track_types"</TT> which returns
|
|
an array reference of the music track types they support. We can use
|
|
this method to provide an <FONT SIZE="-1">API</FONT> to allow easy retrieval of a backend
|
|
for a particular track type. This method will return a path with which
|
|
the backend object can be accessed
|
|
<P>
|
|
|
|
|
|
|
|
<PRE>
|
|
sub find_backend {
|
|
my $self = shift;
|
|
my $extension = shift;
|
|
|
|
foreach my $name (keys %{$self->{backends}}) {
|
|
my $backend = $self->{backends}->{$name};
|
|
foreach my $type (@{$backend->get_track_types}) {
|
|
if ($type eq $extension) {
|
|
return $backend->get_object_path;
|
|
}
|
|
}
|
|
}
|
|
|
|
die "no backend for type $extension";
|
|
}
|
|
|
|
</PRE>
|
|
|
|
|
|
<P>
|
|
|
|
Lets take a quick moment to consider how this method would be used to
|
|
play a music track. If you've not already done so, refresh your memory
|
|
from Net::DBus::Tutorial::UsingObjects. Now, we have an <FONT SIZE="-1">MP3</FONT> file
|
|
which we wish to play, so we search for the path to a backend, then
|
|
retrieve the object for it, and play the track:
|
|
<P>
|
|
|
|
|
|
|
|
<PRE>
|
|
...get the music player service...
|
|
# Ask for a path to a player for mp3 files
|
|
my $path = $service->find_backend("mp3");
|
|
# $path now contains '/music/player/backend/mpg123'
|
|
# and we can get the backend object
|
|
my $backend = $service->get_object($path);
|
|
# and finally play the track
|
|
$backend->play("/vol/music/beck/guero/09-scarecrow.mp3");
|
|
|
|
</PRE>
|
|
|
|
|
|
<A NAME="lbAE"> </A>
|
|
<H2>PROVIDING INTROSPECTION DATA</H2>
|
|
|
|
|
|
|
|
The code above is a complete working object, ready to be registered with
|
|
a service, and since the parameters and return values for the two methods
|
|
are both simple strings we could stop there. In some cases, however, one
|
|
might want to be more specific about data types expected for parameters,
|
|
for example signed vs unsigned integers. Adding explicit data typing also
|
|
makes interaction with other programming languages more reliable. Providing
|
|
explicit data type definitions for exported method is known in the DBus world
|
|
as <TT>"Introspection"</TT>, and it makes life much more reliable for users of one's
|
|
service whom may be using a strongly typed language such as C.
|
|
<P>
|
|
|
|
The first step in providing introspection data for a DBus object in Perl, is
|
|
to specify the name of the interface provided by the object. This is typically
|
|
a period separated string, by convention containing the domain name of the
|
|
application as its first component. Since most Perl modules end up living on
|
|
<FONT SIZE="-1">CPAN,</FONT> one might use <TT>"org.cpan"</TT> as the first component, followed by the package
|
|
name of the module (replacing :: with .), eg <TT>"org.cpan.music.player.manager"</TT>. If it is
|
|
not planned to host the module on <FONT SIZE="-1">CPAN,</FONT> a personal/project domain might be
|
|
used eg <TT>"com.berrange.music.player.manager"</TT>. The interface for an object is defined
|
|
by loading the Net::DBus::Exporter module, providing the interface as its
|
|
first parameter. So the earlier code example would be modified to look like:
|
|
<P>
|
|
|
|
|
|
|
|
<PRE>
|
|
package Music::Player::Manager;
|
|
|
|
use base qw(Net::DBus);
|
|
use Net::DBus::Exporter qw(com.berrange.music.player.manager)
|
|
|
|
</PRE>
|
|
|
|
|
|
<P>
|
|
|
|
Next up, it is necessary to provide data types for the parameters and return
|
|
values of the methods. The Net::DBus::Exporter module provides a method
|
|
<TT>"dbus_method"</TT> for this purpose, which takes three parameter, the name of the
|
|
method being exported, an array reference of parameter types, and an array
|
|
reference of return types (the latter can be omitted if there are no return
|
|
values). This can be called at any point in the module's code, but by convention
|
|
it is preferable to associate calls to <TT>"dbus_method"</TT> with the actual method
|
|
implementation, thus:
|
|
<P>
|
|
|
|
|
|
|
|
<PRE>
|
|
dbus_method("register_backend", ["string", "string"]);
|
|
sub register_backend {
|
|
my $self = shift;
|
|
my $name = shift;
|
|
my $module = shift;
|
|
|
|
.. snipped rest of method body ...
|
|
}
|
|
|
|
</PRE>
|
|
|
|
|
|
<P>
|
|
|
|
And, thus:
|
|
<P>
|
|
|
|
|
|
|
|
<PRE>
|
|
dbus_method("find_backend", ["string"], ["string"])
|
|
sub find_backend {
|
|
my $self = shift;
|
|
my $extension = shift;
|
|
... snip method body...
|
|
}
|
|
|
|
</PRE>
|
|
|
|
|
|
<A NAME="lbAF"> </A>
|
|
<H2>DEFINING A SERVICE</H2>
|
|
|
|
|
|
|
|
Now that the objects have been written, it is time to define
|
|
a service. A service is nothing more than a well known name
|
|
for a given <FONT SIZE="-1">API</FONT> contract. A contract can be thought of as a
|
|
definition of a list of object paths, and the corresponding
|
|
interfaces they provide. So, someone else could come along a
|
|
provide an alternate music player implementation using the
|
|
Python or <FONT SIZE="-1">QT</FONT> bindings for DBus, and if they provided the same
|
|
set of object paths & interfaces, they could justifiably register
|
|
the same service on the bus.
|
|
<P>
|
|
|
|
The Net::DBus::Service module provides the means to register
|
|
a service. Its constructor expects a reference to the bus object
|
|
(an instance of Net::DBus), along with the name of the service.
|
|
As with interface names, the first component of a service name is
|
|
usually derived from a domain name, and then suffixed with the
|
|
name of the application, in our example forming <TT>"org.cpan.Music.Player"</TT>.
|
|
While some objects will be created on the fly during execution
|
|
of the application, others are created upon initial startup. The
|
|
music player manager object created earlier in this tutorial is
|
|
an example of the latter. It is typical to instantiate and register
|
|
these objects in the constructor for the service. Thus a service
|
|
object for the music player application would look like:
|
|
<P>
|
|
|
|
|
|
|
|
<PRE>
|
|
package Music::Player;
|
|
|
|
use base qw(Net::DBus::Service);
|
|
|
|
sub new {
|
|
my $class = shift;
|
|
my $bus = shift;
|
|
my $self = $class->SUPER::new($bus, "org.cpan.music.player");
|
|
|
|
bless $self, $class;
|
|
|
|
$self->{manager} = Music::Player::Manager->new($self);
|
|
|
|
return $self;
|
|
}
|
|
|
|
</PRE>
|
|
|
|
|
|
<P>
|
|
|
|
The Net::DBus::Service automatically provides one special
|
|
object to all services, under the path <TT>"/org/freedesktop/DBus/Exporter"</TT>.
|
|
This object implements the <TT>"org.freedesktop.DBus.Exporter"</TT> interface
|
|
which has a method <TT>"ListObject"</TT>. This enables clients to determine
|
|
a list of all objects exported within a service. While not functionally
|
|
necessary for most applications, it is none-the-less a useful tool for
|
|
developers debugging applications, or wondering what a service provides.
|
|
<A NAME="lbAG"> </A>
|
|
<H2>CONNECTING TO THE BUS</H2>
|
|
|
|
|
|
|
|
The final step in getting our service up and running is to connect it
|
|
to the bus. This brings up an interesting conundrum, does one export
|
|
the service on the system bus (shared by all users & processes on the
|
|
machine), or the session bus (one per user logged into a machine). In
|
|
some cases the answer, with only one of the two buses conceptually making
|
|
sense. In other cases, however, both the session & system bus are valid.
|
|
In the former one would use the <TT>"session"</TT> or <system> methods on Net::DBus
|
|
to get a handle to the desired bus, while in the latter case, the <TT>"find"</TT>
|
|
method would be used. This applies a heuristic to determine the correct
|
|
bus based on execution environment. In the case of the music player, either
|
|
bus is relevant, so the code to connect the service to the bus would look
|
|
like:
|
|
<P>
|
|
|
|
|
|
|
|
<PRE>
|
|
use Net::DBus;
|
|
|
|
my $bus = Net::DBus->find;
|
|
my $player = Music::Player->new($bus);
|
|
|
|
</PRE>
|
|
|
|
|
|
<P>
|
|
|
|
With the service attached to the bus, it is merely necessary to run
|
|
the main event processing loop to listen out for & handle incoming
|
|
DBus messages. So the above code is modified to start a simple reactor:
|
|
<P>
|
|
|
|
|
|
|
|
<PRE>
|
|
use Net::DBus;
|
|
use Net::DBus::Reactor;
|
|
|
|
my $bus = Net::DBus->find;
|
|
my $player = Music::Player->new($bus);
|
|
|
|
Net::DBus::Reactor->main->run;
|
|
|
|
exit 0;
|
|
|
|
</PRE>
|
|
|
|
|
|
<P>
|
|
|
|
Saving this code into a script <TT>"/usr/bin/music-player.pl"</TT>, coding
|
|
is complete and the service ready for use by clients on the bus.
|
|
<A NAME="lbAH"> </A>
|
|
<H2>SERVICE ACTIVATION</H2>
|
|
|
|
|
|
|
|
One might now wonder how best to start the service, particularly
|
|
if it is a service capable of running on
|
|
both the system and session buses. DBus has the answer in the
|
|
concept of <TT>"activation"</TT>. What happens is that when a client
|
|
on the bus attempts to call a method, or register a signal
|
|
handler against, a service not currently running, it will first
|
|
try and start the service. Service's which wish to participate
|
|
in this process merely need stick a simple service definition
|
|
file into the directory <TT>"/usr/share/dbus-1/services"</TT>. The file
|
|
should be named to match the service name, with the file extension
|
|
<TT>".service"</TT> appended. eg, <TT>"/usr/share/dbus-1/services/org.cpan.music.player.service"</TT>
|
|
The file contains two keys, first the name of the service, and
|
|
second the name of the executable used to run the service, or in
|
|
this case the Perl script. So, for our simple service the data
|
|
file would contain:
|
|
<P>
|
|
|
|
|
|
|
|
<PRE>
|
|
[D-BUS Service]
|
|
Name=org.cpan.music.player
|
|
Exec=/usr/bin/music-player.pl
|
|
|
|
</PRE>
|
|
|
|
|
|
<A NAME="lbAI"> </A>
|
|
<H2>SEE ALSO</H2>
|
|
|
|
|
|
|
|
Net::DBus::Tutorial for details of other tutorials, and
|
|
Net::DBus for <FONT SIZE="-1">API</FONT> documentation
|
|
<A NAME="lbAJ"> </A>
|
|
<H2>AUTHORS</H2>
|
|
|
|
|
|
|
|
Daniel Berrange <<A HREF="mailto:dan@berrange.com">dan@berrange.com</A>>
|
|
<A NAME="lbAK"> </A>
|
|
<H2>COPYRIGHT</H2>
|
|
|
|
|
|
|
|
Copyright (C) 2005 Daniel P. Berrange
|
|
<P>
|
|
|
|
<HR>
|
|
<A NAME="index"> </A><H2>Index</H2>
|
|
<DL>
|
|
<DT id="1"><A HREF="#lbAB">NAME</A><DD>
|
|
<DT id="2"><A HREF="#lbAC">DESCRIPTION</A><DD>
|
|
<DT id="3"><A HREF="#lbAD">CREATING AN OBJECT</A><DD>
|
|
<DT id="4"><A HREF="#lbAE">PROVIDING INTROSPECTION DATA</A><DD>
|
|
<DT id="5"><A HREF="#lbAF">DEFINING A SERVICE</A><DD>
|
|
<DT id="6"><A HREF="#lbAG">CONNECTING TO THE BUS</A><DD>
|
|
<DT id="7"><A HREF="#lbAH">SERVICE ACTIVATION</A><DD>
|
|
<DT id="8"><A HREF="#lbAI">SEE ALSO</A><DD>
|
|
<DT id="9"><A HREF="#lbAJ">AUTHORS</A><DD>
|
|
<DT id="10"><A HREF="#lbAK">COPYRIGHT</A><DD>
|
|
</DL>
|
|
<HR>
|
|
This document was created by
|
|
<A HREF="/cgi-bin/man/man2html">man2html</A>,
|
|
using the manual pages.<BR>
|
|
Time: 00:05:49 GMT, March 31, 2021
|
|
</BODY>
|
|
</HTML>
|