1Net::DBus::Tutorial::ExUpsoerrtiCnognOtbrjiebcuttse(d3N)Peetr:l:DDBoucsu:m:eTnuttaotriioanl::ExportingObjects(3)
2
3
4
6 Net::DBus::Tutorial::ExportingObjects - tutorials on providing a DBus
7 service
8
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
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 pro‐
18 vides an API for managing the collection of music player backends for
19 different track types. To start with, lets create the skeleton of the
20 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 single‐
24 ton object, we can hard code the path to it within the constructor:
25
26 package Music::Player::Manager;
27
28 use base qw(Net::DBus);
29
30 sub new {
31 my $class = shift;
32 my $service = shift;
33 my $self = $class->SUPER::new($service, "/music/player/manager");
34
35 bless $self, $class;
36
37 return $self;
38 }
39
40 1;
41
42 Now, as mentioned, the manager with handle a number of different player
43 backends. So we need to provide methods for registering new backends,
44 and querying for backends capable of playing a particular file type. So
45 modifying the above code we add a hash table in the constructor, to
46 store the backends:
47
48 sub new {
49 my $class = shift;
50 my $service = shift;
51 my $self = $class->SUPER::new($service, "/music/player/manager");
52
53 $self->{backends} = {};
54
55 bless $self, $class;
56
57 return $self;
58 }
59
60 And now a method to register a new backend. This takes a Perl module
61 name and uses it to instantiate a backend. Since the backends are also
62 going to be DBus objects, we need to pass in a reference to the service
63 we are attached to, along with a path under which to register the back‐
64 end. We use the "get_service" method to retreieve a reference to the
65 service the manager is attached to, and attach the player backend to
66 this same service: When a method on DBus object is invoked, the first
67 parameter is the object reference ($self), and the remainder are the
68 parameters provided to the method call. Thus writing a method implemen‐
69 tation on a DBUs is really no different to normal object oriented Perl
70 (cf perltoot):
71
72 sub register_backend {
73 my $self = shift;
74 my $name = shift;
75 my $module = shift;
76
77 eval "use $module";
78 if ($@) {
79 die "cannot load backend $module: $@" ;
80 }
81
82 $self->{backends} = $module->new($self->get_service,
83 "/music/player/backend/$name");
84 }
85
86 Looking at this one might wonder what happens if the "die" method is
87 triggered. In such a scenario, rather than terminating the service
88 process, the error will be caught and propagated back to the remote
89 caller to deal with.
90
91 The player backends provide a method "get_track_types" which returns an
92 array reference of the music track types they support. We can use this
93 method to provide an API to allow easy retrieval of a backend for a
94 particular track type. This method will return a path with which the
95 backend object can be accessed
96
97 sub find_backend {
98 my $self = shift;
99 my $extension = shift;
100
101 foreach my $name (keys %{$self->{backends}}) {
102 my $backend = $self->{backends}->{$name};
103 foreach my $type (@{$backend->get_track_types}) {
104 if ($type eq $extension) {
105 return $backend->get_object_path;
106 }
107 }
108 }
109
110 die "no backend for type $extension";
111 }
112
113 Lets take a quick moment to consider how this method would be used to
114 play a music track. If you've not already done so, refresh your memory
115 from Net::DBus::Tutorial::UsingObjects. Now, we have an MP3 file which
116 we wish to play, so we search for the path to a backend, then retrieve
117 the object for it, and play the track:
118
119 ...get the music player service...
120 # Ask for a path to a player for mp3 files
121 my $path = $service->find_backend("mp3");
122 # $path now contains '/music/player/backend/mpg123'
123 # and we can get the backend object
124 my $backend = $service->get_object($path);
125 # and finally play the track
126 $backend->play("/vol/music/beck/guero/09-scarecrow.mp3");
127
129 The code above is a complete working object, ready to be registered
130 with a service, and since the parameters and return values for the two
131 methods are both simple strings we could stop there. In some cases,
132 however, one might want to be more specific about data types expected
133 for parameters, for example signed vs unsigned integers. Adding
134 explicit data typing also makes interaction with other programming lan‐
135 guages more reliable. Providing explicit data type defintions for
136 exported method is known in the DBus world as "Introspection", and it
137 makes life much more reliable for users of one's service whom may be
138 using a strongly typed language such as C.
139
140 The first step in providing introspection data for a DBus object in
141 Perl, is to specify the name of the interface provided by the object.
142 This is typically a period separated string, by convention containing
143 the domain name of the application as its first component. Since most
144 Perl modules end up living on CPAN, one might use "org.cpan" as the
145 first component, followed by the package name of the module (replacing
146 :: with .), eg "org.cpan.music.player.manager". If it is not planned to
147 host the module on CPAN, a personal/project domain might be used eg
148 "com.berrange.music.player.manager". The interface for an object is
149 defined by loading the Net::DBus::Exporter module, providing the inter‐
150 face as its first parameter. So the earlier code example would be modi‐
151 fied to look like:
152
153 package Music::Player::Manager;
154
155 use base qw(Net::DBus);
156 use Net::DBus::Exporter qw(com.berrange.music.player.manager)
157
158 Next up, it is neccessary to provide data types for the parameters and
159 return values of the methods. The Net::DBus::Exporter module provides a
160 method "dbus_method" for this purpose, which takes three parameter, the
161 name of the method being exported, an array reference of parameter
162 types, and an array reference of return types (the latter can be omit‐
163 ted if there are no return values). This can be called at any point in
164 the module's code, but by convention it is preferrable to associate
165 calls to "dbus_method" with the actual method implementation, thus:
166
167 dbus_method("register_backend", ["string", "string"]);
168 sub register_backend {
169 my $self = shift;
170 my $name = shift;
171 my $module = shift;
172
173 .. snipped rest of method body ...
174 }
175
176 And, thus:
177
178 dbus_method("find_backend", ["string"], ["string"])
179 sub find_backend {
180 my $self = shift;
181 my $extension = shift;
182 ... snip method body...
183 }
184
186 Now that the objects have been written, it is time to define a service.
187 A service is nothing more than a well known name for a given API con‐
188 tract. A contract can be thought of as a definition of a list of object
189 paths, and the corresponding interfaces they provide. So, someone else
190 could come along a provide an alternate music player implementation
191 using the Python or QT bindings for DBus, and if they provided the same
192 set of object paths & interfaces, they could justifiably register the
193 same service on the bus.
194
195 The Net::DBus::Service module provides the means to register a service.
196 Its constructor expects a reference to the bus object (an instance of
197 Net::DBus), along with the name of the service. As with interface
198 names, the first component of a service name is usually derived from a
199 domain name, and then suffixed with the name of the application, in our
200 example forming "org.cpan.Music.Player". While some objects will be
201 created on the fly during execution of the application, others are cre‐
202 ated upon initial startup. The music player manager object created ear‐
203 lier in this tutorial is an example of the latter. It is typical to
204 instantiate and register these objects in the constructor for the ser‐
205 vice. Thus a service object for the music player application would look
206 like:
207
208 package Music::Player;
209
210 use base qw(Net::DBus::Service);
211
212 sub new {
213 my $class = shift;
214 my $bus = shift;
215 my $self = $class->SUPER::new($bus, "org.cpan.music.player");
216
217 bless $self, $class;
218
219 $self->{manager} = Music::Player::Manager->new($self);
220
221 return $self;
222 }
223
224 The Net::DBus::Service automatically provides one special object to all
225 services, under the path "/org/freedesktop/DBus/Exporter". This object
226 implements the "org.freedesktop.DBus.Exporter" interface which has a
227 method "ListObject". This enables clients to determine a list of all
228 objects exported within a service. While not functionally neccessary
229 for most applications, it is none-the-less a useful tool for developers
230 debugging applications, or wondering what a service provides.
231
233 The final step in getting our service up and running is to connect it
234 to the bus. This brings up an interesting conundrum, does one export
235 the service on the system bus (shared by all users & processes on the
236 machine), or the session bus (one per user logged into a machine). In
237 some cases the answer, with only one of the two buses conceptually mak‐
238 ing sense. In other cases, however, both the session & system bus are
239 valid. In the former one would use the "session" or <system> methods
240 on Net::DBus to get a handle to the desired bus, while in the latter
241 case, the "find" method would be used. This applies a heuristic to
242 determine the correct bus based on execution environment. In the case
243 of the music player, either bus is relevant, so the code to connect the
244 service to the bus would look like:
245
246 use Net::DBus;
247
248 my $bus = Net::DBus->find;
249 my $player = Music::Player->new($bus);
250
251 With the service attached to the bus, it is merely neccessary to run
252 the main event processing loop to listen out for & handle incoming DBus
253 messages. So the above code is modified to start a simple reactor:
254
255 use Net::DBus;
256 use Net::DBus::Reactor;
257
258 my $bus = Net::DBus->find;
259 my $player = Music::Player->new($bus);
260
261 Net::DBus::Reactor->main->run;
262
263 exit 0;
264
265 Saving this code into a script "/usr/bin/music-player.pl", coding is
266 complete and the service ready for use by clients on the bus.
267
269 One might now wonder how best to start the service, particularly if it
270 is a service capable of running on both the system and session buses.
271 DBus has the answer in the concept of "activation". What happens is
272 that when a client on the bus attempts to call a method, or register a
273 signal handler against, a service not currently running, it will first
274 try and start the service. Service's which wish to participate in this
275 process merely need stick a simple service definition file into the
276 directoy "/usr/share/dbus-1/services". The file should be named to
277 match the service name, with the file extension ".service" appended.
278 eg, "/usr/share/dbus-1/services/org.cpan.music.player.service" The file
279 contains two keys, first the name of the service, and second the name
280 of the executable used to run the service, or in this case the Perl
281 script. So, for our simple service the data file would contain:
282
283 [D-BUS Service]
284 Name=org.cpan.music.player
285 Exec=/usr/bin/music-player.pl
286
288 Net::DBus::Tutorial for details of other tutorials, and Net::DBus for
289 API documentation
290
292 Daniel Berrange <dan@berrange.com>
293
295 Copyright (C) 2005 Daniel P. Berrange
296
297
298
299perl v5.8.8 2008-N0e2t-:2:0DBus::Tutorial::ExportingObjects(3)