1Net::DBus::Tutorial::ExUpsoerrtiCnognOtbrjiebcuttse(d3N)Peetr:l:DDBoucsu:m:eTnuttaotriioanl::ExportingObjects(3)
2
3
4

NAME

6       Net::DBus::Tutorial::ExportingObjects - tutorials on providing a DBus
7       service
8

DESCRIPTION

10       This document provides a tutorial on providing a DBus service using the
11       Perl Net::DBus application bindings. This examples in this document
12       will be based on the code from the Music::Player distribution, which is
13       a simple DBus service providing a music track player.
14

CREATING AN OBJECT

16       The first step in creating an object is to create a new package which
17       inherits from Net::DBus::Object. The Music::Player::Manager object
18       provides an API for managing the collection of music player backends
19       for different track types. To start with, lets create the skeleton of
20       the package & its constructor. The constructor of the super type,
21       Net::DBus::Object expects to be given to parameters, a handle to the
22       Net::DBus::Service owning the object, and a path under which the object
23       shall be exported. Since the manager class is intended to be a
24       singleton object, we can hard code the path to it within the
25       constructor:
26
27         package Music::Player::Manager;
28
29         use base qw(Net::DBus::Object);
30
31         sub new {
32             my $class = shift;
33             my $service = shift;
34             my $self = $class->SUPER::new($service, "/music/player/manager");
35
36             bless $self, $class;
37
38             return $self;
39         }
40
41         1;
42
43       Now, as mentioned, the manager with handle a number of different player
44       backends. So we need to provide methods for registering new backends,
45       and querying for backends capable of playing a particular file type. So
46       modifying the above code we add a hash table in the constructor, to
47       store the backends:
48
49         sub new {
50             my $class = shift;
51             my $service = shift;
52             my $self = $class->SUPER::new($service, "/music/player/manager");
53
54             $self->{backends} = {};
55
56             bless $self, $class;
57
58             return $self;
59         }
60
61       And now a method to register a new backend. This takes a Perl module
62       name and uses it to instantiate a backend. Since the backends are also
63       going to be DBus objects, we need to pass in a reference to the service
64       we are attached to, along with a path under which to register the
65       backend. We use the "get_service" method to retreieve a reference to
66       the service the manager is attached to, and attach the player backend
67       to this same service: When a method on DBus object is invoked, the
68       first parameter is the object reference ($self), and the remainder are
69       the parameters provided to the method call. Thus writing a method
70       implementation on a DBUs is really no different to normal object
71       oriented Perl (cf perltoot):
72
73         sub register_backend {
74             my $self = shift;
75             my $name = shift;
76             my $module = shift;
77
78             eval "use $module";
79             if ($@) {
80                 die "cannot load backend $module: $@" ;
81             }
82
83             $self->{backends}->{$name} = $module->new($self->get_service,
84                                                       "/music/player/backend/$name");
85         }
86
87       Looking at this one might wonder what happens if the "die" method is
88       triggered. In such a scenario, rather than terminating the service
89       process, the error will be caught and propagated back to the remote
90       caller to deal with.
91
92       The player backends provide a method "get_track_types" which returns an
93       array reference of the music track types they support. We can use this
94       method to provide an API to allow easy retrieval of a backend for a
95       particular track type. This method will return a path with which the
96       backend object can be accessed
97
98         sub find_backend {
99             my $self = shift;
100             my $extension = shift;
101
102             foreach my $name (keys %{$self->{backends}}) {
103                my $backend = $self->{backends}->{$name};
104                foreach my $type (@{$backend->get_track_types}) {
105                   if ($type eq $extension) {
106                       return $backend->get_object_path;
107                   }
108                }
109             }
110
111             die "no backend for type $extension";
112         }
113
114       Lets take a quick moment to consider how this method would be used to
115       play a music track. If you've not already done so, refresh your memory
116       from Net::DBus::Tutorial::UsingObjects. Now, we have an MP3 file which
117       we wish to play, so we search for the path to a backend, then retrieve
118       the object for it, and play the track:
119
120         ...get the music player service...
121         # Ask for a path to a player for mp3 files
122         my $path = $service->find_backend("mp3");
123         # $path now contains '/music/player/backend/mpg123'
124         # and we can get the backend object
125         my $backend = $service->get_object($path);
126         # and finally play the track
127         $backend->play("/vol/music/beck/guero/09-scarecrow.mp3");
128

PROVIDING INTROSPECTION DATA

130       The code above is a complete working object, ready to be registered
131       with a service, and since the parameters and return values for the two
132       methods are both simple strings we could stop there. In some cases,
133       however, one might want to be more specific about data types expected
134       for parameters, for example signed vs unsigned integers. Adding
135       explicit data typing also makes interaction with other programming
136       languages more reliable. Providing explicit data type definitions for
137       exported method is known in the DBus world as "Introspection", and it
138       makes life much more reliable for users of one's service whom may be
139       using a strongly typed language such as C.
140
141       The first step in providing introspection data for a DBus object in
142       Perl, is to specify the name of the interface provided by the object.
143       This is typically a period separated string, by convention containing
144       the domain name of the application as its first component. Since most
145       Perl modules end up living on CPAN, one might use "org.cpan" as the
146       first component, followed by the package name of the module (replacing
147       :: with .), eg "org.cpan.music.player.manager". If it is not planned to
148       host the module on CPAN, a personal/project domain might be used eg
149       "com.berrange.music.player.manager". The interface for an object is
150       defined by loading the Net::DBus::Exporter module, providing the
151       interface as its first parameter. So the earlier code example would be
152       modified to look like:
153
154         package Music::Player::Manager;
155
156         use base qw(Net::DBus);
157         use Net::DBus::Exporter qw(com.berrange.music.player.manager)
158
159       Next up, it is necessary to provide data types for the parameters and
160       return values of the methods. The Net::DBus::Exporter module provides a
161       method "dbus_method" for this purpose, which takes three parameter, the
162       name of the method being exported, an array reference of parameter
163       types, and an array reference of return types (the latter can be
164       omitted if there are no return values). This can be called at any point
165       in the module's code, but by convention it is preferable to associate
166       calls to "dbus_method" with the actual method implementation, thus:
167
168         dbus_method("register_backend", ["string", "string"]);
169         sub register_backend {
170             my $self = shift;
171             my $name = shift;
172             my $module = shift;
173
174             .. snipped rest of method body ...
175         }
176
177       And, thus:
178
179         dbus_method("find_backend", ["string"], ["string"])
180         sub find_backend {
181             my $self = shift;
182             my $extension = shift;
183             ... snip method body...
184         }
185

DEFINING A SERVICE

187       Now that the objects have been written, it is time to define a service.
188       A service is nothing more than a well known name for a given API
189       contract. A contract can be thought of as a definition of a list of
190       object paths, and the corresponding interfaces they provide. So,
191       someone else could come along a provide an alternate music player
192       implementation using the Python or QT bindings for DBus, and if they
193       provided the same set of object paths & interfaces, they could
194       justifiably register the same service on the bus.
195
196       The Net::DBus::Service module provides the means to register a service.
197       Its constructor expects a reference to the bus object (an instance of
198       Net::DBus), along with the name of the service.  As with interface
199       names, the first component of a service name is usually derived from a
200       domain name, and then suffixed with the name of the application, in our
201       example forming "org.cpan.Music.Player".  While some objects will be
202       created on the fly during execution of the application, others are
203       created upon initial startup. The music player manager object created
204       earlier in this tutorial is an example of the latter. It is typical to
205       instantiate and register these objects in the constructor for the
206       service. Thus a service object for the music player application would
207       look like:
208
209           package Music::Player;
210
211           use base qw(Net::DBus::Service);
212
213           sub new {
214               my $class = shift;
215               my $bus = shift;
216               my $self = $class->SUPER::new($bus, "org.cpan.music.player");
217
218               bless $self, $class;
219
220               $self->{manager} = Music::Player::Manager->new($self);
221
222               return $self;
223           }
224
225       The Net::DBus::Service automatically provides one special object to all
226       services, under the path "/org/freedesktop/DBus/Exporter".  This object
227       implements the "org.freedesktop.DBus.Exporter" interface which has a
228       method "ListObject". This enables clients to determine a list of all
229       objects exported within a service. While not functionally necessary for
230       most applications, it is none-the-less a useful tool for developers
231       debugging applications, or wondering what a service provides.
232

CONNECTING TO THE BUS

234       The final step in getting our service up and running is to connect it
235       to the bus. This brings up an interesting conundrum, does one export
236       the service on the system bus (shared by all users & processes on the
237       machine), or the session bus (one per user logged into a machine). In
238       some cases the answer, with only one of the two buses conceptually
239       making sense. In other cases, however, both the session & system bus
240       are valid.  In the former one would use the "session" or <system>
241       methods on Net::DBus to get a handle to the desired bus, while in the
242       latter case, the "find" method would be used. This applies a heuristic
243       to determine the correct bus based on execution environment. In the
244       case of the music player, either bus is relevant, so the code to
245       connect the service to the bus would look like:
246
247          use Net::DBus;
248
249          my $bus = Net::DBus->find;
250          my $player = Music::Player->new($bus);
251
252       With the service attached to the bus, it is merely necessary to run the
253       main event processing loop to listen out for & handle incoming DBus
254       messages. So the above code is modified to start a simple reactor:
255
256          use Net::DBus;
257          use Net::DBus::Reactor;
258
259          my $bus = Net::DBus->find;
260          my $player = Music::Player->new($bus);
261
262          Net::DBus::Reactor->main->run;
263
264          exit 0;
265
266       Saving this code into a script "/usr/bin/music-player.pl", coding is
267       complete and the service ready for use by clients on the bus.
268

SERVICE ACTIVATION

270       One might now wonder how best to start the service, particularly if it
271       is a service capable of running on both the system and session buses.
272       DBus has the answer in the concept of "activation". What happens is
273       that when a client on the bus attempts to call a method, or register a
274       signal handler against, a service not currently running, it will first
275       try and start the service. Service's which wish to participate in this
276       process merely need stick a simple service definition file into the
277       directory "/usr/share/dbus-1/services". The file should be named to
278       match the service name, with the file extension ".service" appended.
279       eg, "/usr/share/dbus-1/services/org.cpan.music.player.service" The file
280       contains two keys, first the name of the service, and second the name
281       of the executable used to run the service, or in this case the Perl
282       script. So, for our simple service the data file would contain:
283
284         [D-BUS Service]
285         Name=org.cpan.music.player
286         Exec=/usr/bin/music-player.pl
287

SEE ALSO

289       Net::DBus::Tutorial for details of other tutorials, and Net::DBus for
290       API documentation
291

AUTHORS

293       Daniel Berrange <dan@berrange.com>
294
296       Copyright (C) 2005 Daniel P. Berrange
297
298
299
300perl v5.34.0                      2021-N0e7t-:2:2DBus::Tutorial::ExportingObjects(3)
Impressum