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
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
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
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
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
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
289 Net::DBus::Tutorial for details of other tutorials, and Net::DBus for
290 API documentation
291
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)