mirror of
				https://gitlab.gnome.org/GNOME/glib.git
				synced 2025-10-31 08:22:16 +01:00 
			
		
		
		
	Allow constructing a GDBusProxy for well-known names as discussed here http://mail.gnome.org/archives/gtk-devel-list/2009-October/msg00075.html including test cases. Make it possible to create a GDBusProxy for a GBusType instead of a GDBusConnection. This requires G_BUS_TYPE_NONE so add that too. Nuke g_bus_watch_proxy() since one can now more or less use GDBusProxy for this. Port gdbus-example-watch-proxy to this new API and include this example in the GDBusProxy doc page. Also nuke the GType parameter from the GDBusProxy constructors as requested here: https://bugzilla.gnome.org/show_bug.cgi?id=621229 Also update the porting guide and other API docs for this change. Also fix a bug in the signal dispatching code so each subscriber only get notified once, not N times, for the same signal. Also add a test case for this. https://bugzilla.gnome.org/show_bug.cgi?id=621213 Signed-off-by: David Zeuthen <davidz@redhat.com>
		
			
				
	
	
		
			341 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			XML
		
	
	
	
	
	
			
		
		
	
	
			341 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			XML
		
	
	
	
	
	
| <chapter>
 | |
|   <title>Migrating to GDBus</title>
 | |
| 
 | |
|   <section>
 | |
|     <title>Conceptual differences</title>
 | |
| 
 | |
|     <para>
 | |
|       The central concepts of D-Bus are modelled in a very similar way
 | |
|       in dbus-glib and GDBus. Both have a objects representing connections,
 | |
|       proxies and method invocations. But there are some important
 | |
|       differences:
 | |
|       <itemizedlist>
 | |
|         <listitem><para>
 | |
|           dbus-glib uses libdbus, GDBus doesn't. Instead, it relies on GIO
 | |
|           streams as transport layer, and has its own implementation for the
 | |
|           the D-Bus connection setup and authentication. Apart from using
 | |
|           streams as transport, avoiding libdbus also lets GDBus avoid some
 | |
|           thorny multithreading issues.
 | |
|         </para></listitem>
 | |
|         <listitem><para>
 | |
|           dbus-glib uses the GObject type system for method arguments and
 | |
|           return values, including a homegrown container specialization
 | |
|           mechanism. GDBus relies uses the #GVariant type system which is
 | |
|           explicitly designed to match D-Bus types.
 | |
|         </para></listitem>
 | |
|         <listitem><para>
 | |
|           The typical way to export an object in dbus-glib involves generating
 | |
|           glue code from XML introspection data using <command>dbus-binding-tool</command>. GDBus does not (yet?) use code generation; you are expected to
 | |
|           embed the introspection data in your application code.
 | |
|         </para></listitem>
 | |
|       </itemizedlist>
 | |
|     </para>
 | |
|   </section>
 | |
| 
 | |
|   <section>
 | |
|     <title>API comparison</title>
 | |
| 
 | |
|     <table id="dbus-glib-vs-gdbus">
 | |
|       <title>dbus-glib APIs and their GDBus counterparts</title>
 | |
|       <tgroup cols="2">
 | |
|         <thead>
 | |
|            <row><entry>dbus-glib</entry><entry>GDBus</entry></row>
 | |
|         </thead>
 | |
|         <tbody>
 | |
|           <row><entry>#DBusGConnection</entry><entry>#GDBusConnection</entry></row>
 | |
|           <row><entry>#DBusGProxy</entry><entry>#GDBusProxy</entry></row>
 | |
|           <row><entry>#DBusGMethodInvocation</entry><entry>#GDBusMethodInvocation</entry></row>
 | |
|           <row><entry>dbus_g_bus_get()</entry><entry>g_bus_get_sync(), also see
 | |
|                g_bus_get()</entry></row>
 | |
|           <row><entry>dbus_g_proxy_new_for_name()</entry><entry>g_dbus_proxy_new_sync() and
 | |
|                g_dbus_proxy_new_for_bus_sync(), also see g_dbus_proxy_new()</entry></row>
 | |
|           <row><entry>dbus_g_proxy_add_signal()</entry><entry>not needed, use the generic #GDBusProxy::g-signal</entry></row>
 | |
|           <row><entry>dbus_g_proxy_connect_signal()</entry><entry>use g_signal_connect() with #GDBusProxy::g-signal</entry></row>
 | |
|           <row><entry>dbus_g_connection_register_g_object()</entry><entry>g_dbus_connection_register_object()</entry></row>
 | |
|           <row><entry>dbus_g_connection_unregister_g_object()</entry><entry>g_dbus_connection_unregister_object()</entry></row>
 | |
|           <row><entry>dbus_g_object_type_install_info()</entry><entry>introspection data is installed while registering
 | |
|                an object, see g_dbus_connection_register_object()</entry></row>
 | |
|           <row><entry>dbus_g_proxy_begin_call()</entry><entry>g_dbus_proxy_call()</entry></row>
 | |
|           <row><entry>dbus_g_proxy_end_call()</entry><entry>g_dbus_proxy_call_finish()</entry></row>
 | |
|           <row><entry>dbus_g_proxy_call()</entry><entry>g_dbus_proxy_call_sync()</entry></row>
 | |
|           <row><entry>dbus_g_error_domain_register()</entry><entry>g_dbus_error_register_error_domain()</entry></row>
 | |
|           <row><entry>dbus_g_error_has_name()</entry><entry>no direct equivalent, see g_dbus_error_get_remote_error()</entry></row>
 | |
|           <row><entry>dbus_g_method_return()</entry><entry>g_dbus_method_invocation_return_value()</entry></row>
 | |
|           <row><entry>dbus_g_method_return_error()</entry><entry>g_dbus_method_invocation_return_error() and variants</entry></row>
 | |
|           <row><entry>dbus_g_method_get_sender()</entry><entry>g_dbus_method_invocation_get_sender()</entry></row>
 | |
|         </tbody>
 | |
|       </tgroup>
 | |
|     </table>
 | |
|   </section>
 | |
| 
 | |
|   <section>
 | |
|     <title>Owning bus names</title>
 | |
|     <para>
 | |
|       Using dbus-glib, you typically call RequestName manually
 | |
|       to own a name, like in the following excerpt:
 | |
|       <informalexample><programlisting><![CDATA[
 | |
|   error = NULL;
 | |
|   res = dbus_g_proxy_call (system_bus_proxy,
 | |
|                            "RequestName",
 | |
|                            &error,
 | |
|                            G_TYPE_STRING, NAME_TO_CLAIM,
 | |
|                            G_TYPE_UINT,   DBUS_NAME_FLAG_ALLOW_REPLACEMENT,
 | |
|                            G_TYPE_INVALID,
 | |
|                            G_TYPE_UINT,   &result,
 | |
|                            G_TYPE_INVALID);
 | |
|   if (!res)
 | |
|     {
 | |
|       if (error != NULL)
 | |
|         {
 | |
|           g_warning ("Failed to acquire %s: %s",
 | |
|                      NAME_TO_CLAIM, error->message);
 | |
|           g_error_free (error);
 | |
|         }
 | |
|       else
 | |
|         {
 | |
|           g_warning ("Failed to acquire %s", NAME_TO_CLAIM);
 | |
|         }
 | |
|       goto out;
 | |
|     }
 | |
| 
 | |
|   if (result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
 | |
|     {
 | |
|       if (error != NULL)
 | |
|         {
 | |
|           g_warning ("Failed to acquire %s: %s",
 | |
|                      NAME_TO_CLAIM, error->message);
 | |
|           g_error_free (error);
 | |
|         }
 | |
|       else
 | |
|         {
 | |
|           g_warning ("Failed to acquire %s", NAME_TO_CLAIM);
 | |
|         }
 | |
|       exit (1);
 | |
|     }
 | |
| 
 | |
|   dbus_g_proxy_add_signal (system_bus_proxy, "NameLost",
 | |
|                            G_TYPE_STRING, G_TYPE_INVALID);
 | |
|   dbus_g_proxy_connect_signal (system_bus_proxy, "NameLost",
 | |
|                                G_CALLBACK (on_name_lost), NULL, NULL);
 | |
| 
 | |
|   /* further setup ... */
 | |
| ]]>
 | |
|       </programlisting></informalexample>
 | |
|     </para>
 | |
|     <para>
 | |
|     While you can do things this way with GDBus too, using
 | |
|     g_dbus_proxy_call_sync(), it is much nicer to use the high-level API
 | |
|     for this:
 | |
|     <informalexample><programlisting><![CDATA[
 | |
| static void
 | |
| on_name_acquired (GDBusConnection *connection,
 | |
|                   const gchar     *name,
 | |
|                   gpointer         user_data)
 | |
| {
 | |
|   /* further setup ... */
 | |
| }
 | |
| 
 | |
| /* ... */
 | |
| 
 | |
|   owner_id = g_bus_own_name (G_BUS_TYPE_SYSTEM,
 | |
|                              NAME_TO_CLAIM,
 | |
|                              G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT,
 | |
|                              on_bus_acquired,
 | |
|                              on_name_acquired,
 | |
|                              on_name_lost,
 | |
|                              NULL,
 | |
|                              NULL);
 | |
| 
 | |
|   g_main_loop_run (loop);
 | |
| 
 | |
|   g_bus_unown_name (owner_id);
 | |
| ]]>
 | |
|     </programlisting></informalexample>
 | |
|     Note that g_bus_own_name() works asynchronously and requires
 | |
|     you to enter your mainloop to await the on_name_aquired()
 | |
|     callback. Also note that in order to avoid race conditions (e.g.
 | |
|     when your service is activated by a method call), you have to export
 | |
|     your manager object <emphasis>before</emphasis> acquiring the
 | |
|     name. The on_bus_acquired() callback is the right place to do
 | |
|     such preparations.
 | |
|     </para>
 | |
|   </section>
 | |
| 
 | |
|   <section>
 | |
|     <title>Creating proxies for well-known names</title>
 | |
|     <para>
 | |
|       dbus-glib lets you create proxy objects for well-known names, like the
 | |
|       following example:
 | |
|       <informalexample><programlisting><![CDATA[
 | |
|   proxy = dbus_g_proxy_new_for_name (system_bus_connection,
 | |
|                                      "org.freedesktop.Accounts",
 | |
|                                      "/org/freedesktop/Accounts",
 | |
|                                      "org.freedesktop.Accounts");
 | |
|       ]]>
 | |
|       </programlisting></informalexample>
 | |
|       For a #DBusGProxy constructed like this, method calls will be sent to
 | |
|       the current owner of the name, and that owner can change over time.
 | |
|     </para>
 | |
|     <para>
 | |
|       The same can be achieved with #GDBusProxy:
 | |
|       <informalexample><programlisting><![CDATA[
 | |
|   error = NULL;
 | |
|   proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
 | |
|                                          G_DBUS_PROXY_FLAGS_NONE,
 | |
|                                          NULL, /* GDBusInterfaceInfo */
 | |
|                                          "org.freedesktop.Accounts",
 | |
|                                          "/org/freedesktop/Accounts",
 | |
|                                          "org.freedesktop.Accounts",
 | |
|                                          NULL, /* GCancellable */
 | |
|                                          &error);
 | |
|       ]]>
 | |
|       </programlisting></informalexample>
 | |
|       For an added layer of safety, you can specify what D-Bus
 | |
|       interface the proxy is expected to conform to by using the
 | |
|       #GDBusInterfaceInfo type.
 | |
|     </para>
 | |
|     <para>
 | |
|       Additionally, #GDBusProxy loads, caches and tracks changes to
 | |
|       the D-Bus properties on the remote object. It also sets up match
 | |
|       rules so D-Bus signals from the remote object are delivered
 | |
|       locally.
 | |
|     </para>
 | |
|   </section>
 | |
|   <section>
 | |
|     <title>Client-side GObject bindings</title>
 | |
| 
 | |
|     <para>
 | |
|       dbus-glib comes with <command>dbus-binding-tool</command>, which
 | |
|       can produce somewhat nice client-side wrappers for a D-Bus interface.
 | |
|       GDBus does not have code-generation at this point, but #GDBusProxy
 | |
|       is designed to allow the creating of client-side wrappers by
 | |
|       subclassing #GDBusProxy.
 | |
|     </para>
 | |
|     <para>
 | |
|       For an example of a #GDBusProxy-derived class that wraps a D-Bus
 | |
|       interface in a type-safe way, see <xref
 | |
|       linkend="gdbus-example-proxy-subclass"/>. The comparison is as
 | |
|       follows:
 | |
|       <table id="gdbus-example-type-safe-proxy">
 | |
|         <title>Wrapping the org.freedesktop.Accounts.User D-Bus interface in the AccountUser GObject type</title>
 | |
|         <tgroup cols="2">
 | |
|           <thead>
 | |
|             <row><entry>D-Bus concept</entry><entry>GObject concept</entry></row>
 | |
|           </thead>
 | |
|           <tbody>
 | |
|             <row>
 | |
|               <entry>AutomaticLogin property</entry>
 | |
|               <entry>
 | |
|                 <para><literal>AccountsUser:automatic-login</literal> GObject property</para>
 | |
|                 <para>C getter: accounts_user_get_automatic_login()</para>
 | |
|                 <para>Watch changes via the <literal>notify::automatic-login</literal> signal</para>
 | |
|               </entry>
 | |
|             </row>
 | |
|             <row>
 | |
|               <entry>RealName property</entry>
 | |
|               <entry>
 | |
|                 <para><literal>AccountsUser:real-name</literal> GObject property</para>
 | |
|                 <para>C getter: accounts_user_get_real_name()</para>
 | |
|                 <para>Watch changes via the <literal>notify::real-name signal</literal></para>
 | |
|               </entry>
 | |
|             </row>
 | |
|             <row>
 | |
|               <entry>UserName property</entry>
 | |
|               <entry>
 | |
|                 <para><literal>AccountsUser:user-name</literal> GObject property</para>
 | |
|                 <para>C getter: accounts_user_get_user_name()</para>
 | |
|                 <para>Watch changes via the <literal>notify::user-name</literal> signal</para>
 | |
|               </entry>
 | |
|             </row>
 | |
|             <row>
 | |
|               <entry>Changed signal</entry>
 | |
|               <entry>
 | |
|                 <para><literal>AccountsUser::changed</literal> GObject signal</para>
 | |
|                 <para>Watch via e.g. g_signal_connect()</para>
 | |
|               </entry>
 | |
|             </row>
 | |
|             <row>
 | |
|               <entry>Frobnicate method</entry>
 | |
|               <entry>
 | |
|                 <para>Use accounts_user_frobnicate() + accounts_user_frobnicate_finish() or accounts_user_frobnicate_sync() to invoke</para>
 | |
|               </entry>
 | |
|             </row>
 | |
|           </tbody>
 | |
|         </tgroup>
 | |
|       </table>
 | |
|     </para>
 | |
|     <example id="gdbus-example-proxy-subclass"><title>GDBusProxy subclass example</title><programlisting><xi:include xmlns:xi="http://www.w3.org/2001/XInclude" parse="text" href="../../../../gio/tests/gdbus-example-proxy-subclass.c"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting></example>
 | |
|   </section>
 | |
| 
 | |
|   <section>
 | |
|     <title>Exporting objects</title>
 | |
| 
 | |
|     <para>
 | |
|       With dbus-glib, exporting an object over D-Bus works by generating
 | |
|       a bunch of glue code from your introspection XML with
 | |
|       <command>dbus-binding-tool</command>. The glue code gets included in
 | |
|       your source, and you need to call
 | |
|       <informalexample><programlisting>
 | |
|   dbus_g_object_type_install_info (TYPE_MYOBJECT,
 | |
|                                    &dbus_glib_myobject_object_info);
 | |
|       </programlisting></informalexample>
 | |
|       in your class_init() function to tell dbus-glib about your type.
 | |
|       To actually export an instance, you call
 | |
|       <informalexample><programlisting>
 | |
|   dbus_g_connection_register_g_object (system_bus_connection,
 | |
|                                        my_object_path,
 | |
|                                        G_OBJECT (my_object));
 | |
|       </programlisting></informalexample>
 | |
| 
 | |
|     </para>
 | |
|     <para>
 | |
|       The GDBus way of exporting an object works by embedding the
 | |
|       introspection XML in the source, creating introspection data
 | |
|       structures from it with g_dbus_node_info_new_for_xml(), and
 | |
|       passing that along when you register the object:
 | |
|       <informalexample><programlisting><![CDATA[
 | |
| 
 | |
|   static const gchar introspection_xml[] =
 | |
|     "<node>"
 | |
|     "  <interface name='org.gtk.GDBus.TestPeerInterface'>"
 | |
|     "    <method name='HelloWorld'>"
 | |
|     "      <arg type='s' name='greeting' direction='in'/>"
 | |
|     "      <arg type='s' name='response' direction='out'/>"
 | |
|     "    </method>"
 | |
|     "  </interface>"
 | |
|     "</node>";
 | |
| 
 | |
|   /* parse introspection data */
 | |
|   introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
 | |
| 
 | |
|   /
 | |
|   id = g_dbus_connection_register_object (connection,
 | |
|                                           "/org/gtk/GDBus/TestObject",
 | |
|                                           "org.gtk.GDBus.TestPeerInterface",
 | |
|                                           introspection_data->interfaces[0],
 | |
|                                           &interface_vtable,
 | |
|                                           NULL,  /* user_data */
 | |
|                                           NULL,  /* user_data_free_func */
 | |
|                                           NULL); /* GError** */
 | |
| 
 | |
| ]]>
 | |
|     </programlisting></informalexample>
 | |
|     </para>
 | |
|     <para>
 | |
|       The actual implementation of the exported object is done by specifying
 | |
|       a #GDBusInterfaceVTable that has method_call(), get_property() and
 | |
|       set_property() methods. There is no direct support beyond that for
 | |
|       exporting #GObjects, so there is quite a bit of manual work involved,
 | |
|       as you can see in the following example.
 | |
|     </para>
 | |
|     <para>
 | |
|       Since the VTable methods don't have any direct #GObject support, we
 | |
|       pass the exported object as @user_data. Also note that we have to handle
 | |
|       the emission of the PropertiesChanged signal ourselves, by connecting
 | |
|       to ::notify.
 | |
|     </para>
 | |
|      <example id="gdbus-export"><title>Exporting a GObject</title><programlisting><xi:include xmlns:xi="http://www.w3.org/2001/XInclude" parse="text" href="../../../../gio/tests/gdbus-example-export.c"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting></example>
 | |
|   </section>
 | |
| 
 | |
| </chapter>
 |