{"id":1917,"date":"2015-12-11T22:59:58","date_gmt":"2015-12-11T22:59:58","guid":{"rendered":"http:\/\/blog.rastersoft.com\/?p=1917"},"modified":"2015-12-11T22:59:58","modified_gmt":"2015-12-11T22:59:58","slug":"usando-dbus-desde-lenguaje-c-y-javascript","status":"publish","type":"post","link":"https:\/\/blog.rastersoft.com\/?p=1917","title":{"rendered":"Usando DBus desde lenguaje C y JavaScript"},"content":{"rendered":"<p>Trabajar con <a href=\"http:\/\/www.freedesktop.org\/wiki\/Software\/dbus\/\" target=\"_blank\">DBus<\/a> desde <a href=\"https:\/\/www.python.org\/\" target=\"_blank\">Python<\/a> o <a href=\"https:\/\/wiki.gnome.org\/Projects\/Vala\" target=\"_blank\">Vala<\/a> es muy sencillo: esconden la complejidad del protocolo de manera que uno tiene la ilusi\u00f3n de estar haciendo una llamada local. Sin embargo, hist\u00f3ricamente, trabajar con DBus desde C ha sido considerado un pe\u00f1azo de dimensiones colosales. Sin embargo, desde la llegada de la biblioteca <a href=\"http:\/\/www.freedesktop.org\/software\/gstreamer-sdk\/data\/docs\/latest\/gio\/ch30.html\" target=\"_blank\">GDBus<\/a>, que integra DBus dentro de GIO la cosa se ha simplificado bastante, sobre todo en aquellos casos en los que simplemente queremos llamar de manera s\u00edncrona a un m\u00e9todo remoto.<\/p>\n<p>Debido a algunos cambios que estuve haciendo en <a href=\"http:\/\/www.rastersoft.com\/programas\/panther_launcher_es.html\" target=\"_blank\">Panther Launcher<\/a>, necesit\u00e9 poder hacer precisamente eso: llamar a un m\u00e9todo remoto mediante DBus desde C, para implementarlo dentro del applet para <a href=\"https:\/\/wiki.gnome.org\/Projects\/GnomeFlashback\" target=\"_blank\">Gnome Flashback<\/a>. Dado que no encontr\u00e9 documentaci\u00f3n sencilla para este detalle concreto (esto es, sin usar entre medias un bucle de eventos), voy a comentar como lo hice, por si le es \u00fatil a alguien m\u00e1s.<\/p>\n<p>Lo primero que necesitamos para poder llamar a un m\u00e9todo remoto mediante DBus es la interfaz correspondiente donde se define dicho m\u00e9todo. Esta interfaz se puede obtener de manera muy sencilla mediante <strong>dbus-send<\/strong> y las capacidades de introspecci\u00f3n de DBus:<\/p>\n<div class=\"mycode\">\n<pre class=\"mycode\">dbus-send --session --type=method_call --print-reply --dest=com.rastersoft.panther.remotecontrol \/com\/rastersoft\/panther\/remotecontrol org.freedesktop.DBus.Introspectable.Introspect<\/pre>\n<\/div>\n<p>En este ejemplo obtenemos las interfaces disponibles en el objeto \u00a0<em>\/com\/rastersoft\/panther\/remotecontrol<\/em>, del servicio \u00a0<em>com.rastersoft.panther.remotecontrol<\/em>. El resultado es \u00e9ste:<\/p>\n<div class=\"mycode\">\n<pre class=\"mycode\">freedesktop.DBus.Introspectable.Introspect\r\nmethod return time=1449871353.404951 sender=:1.1222 -&gt; destination=:1.1246 serial=104 reply_serial=2\r\n string \"&lt;!DOCTYPE node PUBLIC \"-\/\/freedesktop\/\/DTD D-BUS Object Introspection 1.0\/\/EN\"\r\n \"http:\/\/www.freedesktop.org\/standards\/dbus\/1.0\/introspect.dtd\"&gt;\r\n&lt;!-- GDBus 2.46.2 --&gt;\r\n&lt;node&gt;\r\n &lt;interface name=\"org.freedesktop.DBus.Properties\"&gt;\r\n   &lt;method name=\"Get\"&gt;\r\n     &lt;arg type=\"s\" name=\"interface_name\" direction=\"in\"\/&gt;\r\n     &lt;arg type=\"s\" name=\"property_name\" direction=\"in\"\/&gt;\r\n     &lt;arg type=\"v\" name=\"value\" direction=\"out\"\/&gt;\r\n   &lt;\/method&gt;\r\n   &lt;method name=\"GetAll\"&gt;\r\n     &lt;arg type=\"s\" name=\"interface_name\" direction=\"in\"\/&gt;\r\n     &lt;arg type=\"a{sv}\" name=\"properties\" direction=\"out\"\/&gt;\r\n   &lt;\/method&gt;\r\n   &lt;method name=\"Set\"&gt;\r\n     &lt;arg type=\"s\" name=\"interface_name\" direction=\"in\"\/&gt;\r\n     &lt;arg type=\"s\" name=\"property_name\" direction=\"in\"\/&gt;\r\n     &lt;arg type=\"v\" name=\"value\" direction=\"in\"\/&gt;\r\n   &lt;\/method&gt;\r\n   &lt;signal name=\"PropertiesChanged\"&gt;\r\n     &lt;arg type=\"s\" name=\"interface_name\"\/&gt;\r\n     &lt;arg type=\"a{sv}\" name=\"changed_properties\"\/&gt;\r\n     &lt;arg type=\"as\" name=\"invalidated_properties\"\/&gt;\r\n   &lt;\/signal&gt;\r\n &lt;\/interface&gt;\r\n &lt;interface name=\"org.freedesktop.DBus.Introspectable\"&gt;\r\n   &lt;method name=\"Introspect\"&gt;\r\n     &lt;arg type=\"s\" name=\"xml_data\" direction=\"out\"\/&gt;\r\n   &lt;\/method&gt;\r\n &lt;\/interface&gt;\r\n &lt;interface name=\"org.freedesktop.DBus.Peer\"&gt;\r\n   &lt;method name=\"Ping\"\/&gt;\r\n   &lt;method name=\"GetMachineId\"&gt;\r\n     &lt;arg type=\"s\" name=\"machine_uuid\" direction=\"out\"\/&gt;\r\n   &lt;\/method&gt;\r\n &lt;\/interface&gt;\r\n &lt;interface name=\"com.rastersoft.panther.remotecontrol\"&gt;\r\n   &lt;method name=\"DoPing\"&gt;\r\n     &lt;arg type=\"i\" name=\"v\" direction=\"in\"\/&gt;\r\n     &lt;arg type=\"i\" name=\"result\" direction=\"out\"\/&gt;\r\n   &lt;\/method&gt;\r\n   &lt;method name=\"DoShow\"&gt;\r\n   &lt;\/method&gt;\r\n &lt;\/interface&gt;\r\n&lt;\/node&gt;\r\n<\/pre>\n<\/div>\n<p>Vemos que aparecen varias interfaces, dentro de cada una varios m\u00e9todos, y dentro de cada m\u00e9todo puede haber cero o m\u00e1s par\u00e1metros, y cero o m\u00e1s valores devueltos. Para nuestros prop\u00f3sitos no vamos a necesitar todo, sino s\u00f3lo la interfaz\u00a0<em>com.rastersoft.panther.remotecontrol<\/em>, as\u00ed que eliminaremos el resto de entradas (y las l\u00edneas superiores descriptivas, s\u00f3lo queremos el XML), y nos quedar\u00e1 esto:<\/p>\n<div class=\"mycode\">\n<pre class=\"mycode\">&lt;node&gt;\r\n &lt;interface name=\"com.rastersoft.panther.remotecontrol\"&gt;\r\n   &lt;method name=\"DoPing\"&gt;\r\n     &lt;arg type=\"i\" name=\"v\" direction=\"in\"\/&gt;\r\n     &lt;arg type=\"i\" name=\"result\" direction=\"out\"\/&gt;\r\n   &lt;\/method&gt;\r\n   &lt;method name=\"DoShow\"&gt;\r\n   &lt;\/method&gt;\r\n &lt;\/interface&gt;\r\n&lt;\/node&gt;<\/pre>\n<\/div>\n<p>Este fichero XML es el que describe las llamadas que vamos a implementar.<\/p>\n<p>Si queremos llamar a alguno de estos m\u00e9todos desde JavaScript (por ejemplo, para hacer una llamada desde una extensi\u00f3n de <a href=\"https:\/\/wiki.gnome.org\/Projects\/GnomeShell\" target=\"_blank\">Gnome Shell<\/a>), s\u00f3lo necesitaremos hacer lo siguiente:<\/p>\n<div class=\"mycode\">\n<pre class=\"mycode\">const Gio = imports.gi.Gio;\r\n\r\nconst MyIface = '&lt;node&gt;\\\r\n &lt;interface name=\"com.rastersoft.panther.remotecontrol\"&gt;\\\r\n  &lt;method name=\"DoShow\" \/&gt;\\\r\n  &lt;method name=\"DoPing\" &gt;\\\r\n   &lt;arg name=\"n\" direction=\"in\" type=\"i\"\/&gt;\\\r\n   &lt;arg name=\"response\" direction=\"out\" type=\"i\"\/&gt;\\\r\n  &lt;\/method&gt;\\\r\n &lt;\/interface&gt;\\\r\n&lt;\/node&gt;';\r\n\r\nconst MyProxy = Gio.DBusProxy.makeProxyWrapper(MyIface);\r\n\r\nlet instance = new MyProxy(Gio.DBus.session, 'com.rastersoft.panther.remotecontrol','\/com\/rastersoft\/panther\/remotecontrol');\r\ninstance.DoShowSync();\r\ninstance.DoPingSync(0);\r\n<\/pre>\n<\/div>\n<p>Primero importamos Gio para tener acceso a GDBus. Despu\u00e9s insertamos el XML con la interfaz en una variable, y creamos un Proxy DBus con ella. Finalmente, cada vez que queramos acceder a un objeto (en este caso\u00a0<em>\/com\/rastersoft\/panther\/remotecontrol<\/em>) de un servicio DBus (en este caso<em>\u00a0com.rastersoft.panther.remotecontrol<\/em>), s\u00f3lo tenemos que crear una instancia del proxy anterior, el cual nos permitir\u00e1 llamar a los m\u00e9todos definidos en la interfaz.<\/p>\n<p>Cabe recalcar que aqu\u00ed estoy llamando a los m\u00e9todos con su nombre terminado en <em>Sync<\/em>. Eso significa que la llamada ser\u00e1 bloqueante, y no retornar\u00e1 hasta que se reciba la respuesta del otro extremo. Es posible hacer llamadas as\u00edncronas, pero no lo he investigado y no voy a entrar ah\u00ed.<\/p>\n<p>Hasta aqu\u00ed JavaScript, que es la parte m\u00e1s sencilla. Ahora llega el turno de como hacer esto mismo desde C.<\/p>\n<p>GDbus tiene un generador de c\u00f3digo que nos simplifica el trabajo. Para usarlo basta con grabar en un fichero el XML anterior (por ejemplo, <em>remotecontrol.xml<\/em>) y llamar a\u00a0<em>gdbus-codegen<\/em> para que construya el c\u00f3digo necesario para poder llamar a dichos m\u00e9todos:<\/p>\n<div class=\"mycode\">\n<pre class=\"mycode\">gdbus-codegen --c-generate-object-manager --generate-c-code dbus remotecontrol.xml<\/pre>\n<\/div>\n<p>Este comando crear\u00e1 dos ficheros: dbus.c y dbus.h (se utiliza el nombre indicado en <em>&#8211;generate-c-code<\/em>) a partir del fichero\u00a0<em>remotecontrol.xml<\/em>. Existen algunos par\u00e1metros extra, como\u00a0<em>&#8211;c-namespace<\/em>, que permite especificar un prefijo para todas las funciones que se generen, y as\u00ed evitar choques de nombres.<\/p>\n<p>En dichos ficheros tendremos un mont\u00f3n de c\u00f3digo ya escrito, y es precisamente este c\u00f3digo el que nos simplifica el proceso, pues no tendremos que escribirlo nosotros. Una vez que lo tenemos, s\u00f3lo hemos de llamarlo as\u00ed:<\/p>\n<div class=\"mycode\">\n<pre class=\"mycode\">#include \"dbus.h\"\r\n\r\nGError *error = NULL;\r\n\r\nComRastersoftPantherRemotecontrol *proxy;\r\n\r\nproxy = com_rastersoft_panther_remotecontrol_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,\r\n                                                  G_DBUS_PROXY_FLAGS_NONE,\r\n                                                  \"com.rastersoft.panther.remotecontrol\",\r\n                                                  \"\/com\/rastersoft\/panther\/remotecontrol\",\r\n                                                  NULL, \/* GCancellable *\/\r\n                                                  &amp;error);\r\nif (proxy != NULL) {\r\n    error = NULL;\r\n    retval = com_rastersoft_panther_remotecontrol_call_do_show_sync(proxy,NULL,&amp;error);\r\n\r\n    ...\r\n\r\n    error = NULL;\r\n    gint value;\r\n    retval = com_rastersoft_panther_remotecontrol_call_do_ping_sync(proxy,0,&amp;value,NULL,&amp;error);\r\n}<\/pre>\n<\/div>\n<p>Tras incluir el fichero\u00a0<em>dbus.h<\/em> para disponer de las llamadas, lo primero que hacemos es crear un puntero de tipo\u00a0<em>GError<\/em> para recibir las excepciones que se produzcan.<\/p>\n<p>A continuaci\u00f3n creamos de manera s\u00edncrona un proxy de la interfaz concreta que queremos utilizar. Vemos que lo estamos haciendo para el bus de sesi\u00f3n, para el objeto \/<em>com\/rastersoft\/panther\/remotecontrol<\/em>\u00a0del servicio com.rastersoft.panther.remotecontrol. Es fundamental que el puntero\u00a0<em>GError<\/em> est\u00e9 inicializado a <strong>NULL<\/strong>, pues de no hacerlo la llamada fallar\u00e1.<\/p>\n<p>Y una vez que tenemos dicho proxy ya podemos llamar a los m\u00e9todos remotos. Vemos que cada llamada est\u00e1 formada por el nombre de la interfaz, m\u00e1s\u00a0<em>_<\/em><em>call_<\/em>, m\u00e1s el nombre del m\u00e9todo, y en este caso, como la llamada queremos que se bloquee hasta que llegue la respuesta, termina en\u00a0<em>_sync<\/em>. El primer par\u00e1metro es el proxy, y los dos \u00faltimos un puntero a la funci\u00f3n para cancelar la llamada (que ponemos a NULL para no complicarnos la vida) y un puntero a\u00a0<em>GError<\/em>, que tambi\u00e9n debe estar inicializado a NULL antes de llamar a la funci\u00f3n.<\/p>\n<p>Entre medias se introducen, en el mismo orden en que est\u00e1n definidos en el XML, los par\u00e1metros de entrada (que se pasan por valor) y punteros para los par\u00e1metros de salida.<\/p>\n<p>Para compilarlo hay que utilizar\u00a0<em>pkg-config gio-2.0 gio-unix-2.0<\/em> para que se a\u00f1adan las cabeceras y bibliotecas necesarias.<\/p>\n<p>Y ya est\u00e1, con esto podemos por fin llamar de manera sencilla un m\u00e9todo DBus desde lenguaje C.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Trabajar con DBus desde Python o Vala es muy sencillo: esconden la complejidad del protocolo de manera que uno tiene la ilusi\u00f3n de estar haciendo una llamada local. Sin embargo, hist\u00f3ricamente, trabajar con DBus desde C ha sido considerado un pe\u00f1azo de dimensiones colosales. Sin embargo, desde la llegada de la biblioteca GDBus, que integra &hellip; <a href=\"https:\/\/blog.rastersoft.com\/?p=1917\" class=\"more-link\">Seguir leyendo <span class=\"screen-reader-text\">Usando DBus desde lenguaje C y JavaScript<\/span> <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5,6,7],"tags":[],"class_list":["post-1917","post","type-post","status-publish","format-standard","hentry","category-programacion","category-trucos","category-tutoriales"],"_links":{"self":[{"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/posts\/1917","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1917"}],"version-history":[{"count":8,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/posts\/1917\/revisions"}],"predecessor-version":[{"id":1926,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/posts\/1917\/revisions\/1926"}],"wp:attachment":[{"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1917"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1917"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1917"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}