tag:linuxfr.org,2005:/tags/jnidbus/publicLinuxFr.org : les contenus étiquetés avec « jnidbus »2019-10-07T21:24:53+02:00/favicon.pngtag:linuxfr.org,2005:News/394422019-09-24T13:24:02+02:002019-09-26T13:54:58+02:00Communiquer avec D-Bus en Java avec JNIDBusLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><p>Avec mes collègues chez <a href="http://viveris.fr">Viveris</a>, on s’est dit qu’on aimerait bien faire plus de logiciel libre. On a donc monté un « groupe <em>opensource</em> » dont le but est d’identifier les projets pour lesquels on peut publier tout ou une partie du code sous licence libre, et aussi de contribuer aux outils et bibliothèques qu’on utilise le plus.</p>
<p>Il y a quelques mois je vous présentais <a href="//linuxfr.org/users/pulkomandy/journaux/un-harnais-de-test-pour-qt">QTestFramework</a>, depuis on a également pu contribuer au <a href="https://github.com/whitequark/zmtp-wireshark">dissecteur 0MQ pour Wireshark</a> et <a href="https://github.com/viveris/jtag-boundary-scanner">un outil pour le <em>boundary scan</em> JTAG</a>.</p>
<p>On vient de publier il y a quelques jours une bibliothèque Java pour communiquer en D-Bus.</p>
</div><ul><li>lien nᵒ 1 : <a title="https://opensource.viveris.fr" hreflang="en" href="https://linuxfr.org/redirect/104857">Projets opensource chez Viveris</a></li><li>lien nᵒ 2 : <a title="https://github.com/viveris/jnidbus" hreflang="en" href="https://linuxfr.org/redirect/104858">JNIDBus sur GitHub</a></li><li>lien nᵒ 3 : <a title="https://fr.wikipedia.org/wiki/D-Bus" hreflang="fr" href="https://linuxfr.org/redirect/104859">D-Bus sur Wikipédia</a></li></ul><div><h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li>
<a href="#toc-contexte">Contexte</a><ul>
<li><a href="#toc-d-bus">D-Bus</a></li>
<li><a href="#toc-jni">JNI</a></li>
<li><a href="#toc-solutions-existantes-et-leurs-limitations">Solutions existantes et leurs limitations</a></li>
</ul>
</li>
<li>
<a href="#toc-jnidbus">JNIDBus</a><ul>
<li><a href="#toc-historique">Historique</a></li>
<li><a href="#toc-utilisation">Utilisation</a></li>
</ul>
</li>
</ul>
<h2 id="toc-contexte">Contexte</h2>
<h3 id="toc-d-bus">D-Bus</h3>
<p>D-Bus est un système de communication inter-processus utilisé sous GNU/Linux. Le projet a été lancé par des développeurs de Red Hat au sein de Freedesktop. Il a été intégré dans GNOME 2 et KDE 4, et aujourd’hui il est utilisé par de très nombreux composants d’un système GNU/Linux : systemd, NetworkManager et PulseAudio, par exemple. Il y a même une implémentation dans le noyau Linux lui‑même.</p>
<h3 id="toc-jni">JNI</h3>
<p>JNI (<em>Java Native Interface</em>) est une API qui permet d’interfacer du code tournant dans une machine virtuelle Java avec du code natif. Cela nécessite d’écrire (en C ou C++) des <em>wrappers</em> qui vont manipuler la pile de la JVM pour récupérer les arguments et pousser les valeurs de retour, et éventuellement accéder aux objets Java manipulés. Les méthodes ainsi implémentées peuvent ensuite être appelées depuis le code Java de façon transparente.</p>
<h3 id="toc-solutions-existantes-et-leurs-limitations">Solutions existantes et leurs limitations</h3>
<p>Freedesktop propose <a href="https://www.freedesktop.org/wiki/Software/DBusBindings/">DBus-Java</a>, mais il n’y a pas eu de version publiée depuis 2009. La dernière version a besoin de Java 7 pour fonctionner. De plus, cette bibliothèque implémente le protocole D-Bus en Java, ce qui risque de poser des problèmes d’interopérabilité avec l’implémentation en C.</p>
<p>Il existe bien <a href="https://github.com/hypfvieh/dbus-java">une version mise à jour</a> de la bibliothèque qui corrige au moins le premier problème, cependant l’API n’utilise pas les nouvelles fonctionnalités de Java et c’est bien dommage.</p>
<h2 id="toc-jnidbus">JNIDBus</h2>
<h3 id="toc-historique">Historique</h3>
<p>Dans le cadre d’une migration d’un de nos logiciels depuis Java 7, nous avons découvert que DBus-Java ne prenait pas en charge les versions plus récentes. Nous aurions pu nous contenter d’une mise à jour de cette implémentation, mais il y avait beaucoup de code à reprendre dedans et de toute façon, l’API ne nous convenait pas.</p>
<p>En effet, dbus-java représente les messages D-Bus par des « tuples » génériques, ce qui est assez peu pratique à utiliser et rend le code illisible. De plus, les API sont bloquantes et cela nous contraignait à utiliser une réserve (<em>pool</em>) de fils d’exécution qui complexifiait encore le logiciel.</p>
<p>L’ensemble des ces défauts et l’absence d’alternative viable nous ont poussé à développer notre propre alternative, en essayant de répondre a toutes les problématiques.</p>
<p>Afin de ne pas réimplémenter le protocole D-Bus nous voulions utiliser la bibliothèque <code>libdbus-1</code>. L’écosystème Java possède deux manières d’appeler du code natif : JNI et JNA, ce dernier étant écarté pour des raisons de performances et de complexité des bibliothèques de liaison (<em>bindings</em>) à écrire.</p>
<p>Enfin, la dernière contrainte était de réduire le code natif au strict minimum, afin de limiter la complexité de ce dernier qui est très difficile à tester unitairement.</p>
<h3 id="toc-utilisation">Utilisation</h3>
<p>La base de JNIDBus est la sérialisation d’objet Java. La signature du message est décrite dans une annotation.</p>
<p><em>Exemple pour un message contenant une chaîne de caractères et un entier :</em></p>
<pre><code class="java"><span class="nd">@DBusType</span><span class="o">(</span>
<span class="cm">/* Pour plus d’info sur le format de la signature, référez vous</span>
<span class="cm"> * à la documentation D-Bus</span>
<span class="cm"> */</span>
<span class="n">signature</span> <span class="o">=</span> <span class="s">"si"</span><span class="o">,</span>
<span class="cm">/* Donne le nom des propriétés contenant les données du message,</span>
<span class="cm"> * dans notre cas la chaîne de caractères est contenue dans la</span>
<span class="cm"> * propriété nommée « string » et l’entier dans le champs « integer »</span>
<span class="cm"> */</span>
<span class="n">fields</span> <span class="o">=</span> <span class="o">{</span><span class="s">"string"</span><span class="o">,</span><span class="s">"integer"</span><span class="o">}</span>
<span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">StringMessage</span> <span class="kd">extends</span> <span class="n">Message</span> <span class="o">{</span>
<span class="cm">/* Les champs seront accédés au travers de ses setters et</span>
<span class="cm"> * getters qui devront respecter la convention "setXxx"/"getXxx"</span>
<span class="cm"> */</span>
<span class="kd">private</span> <span class="n">String</span> <span class="n">string</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">int</span> <span class="n">integer</span>
<span class="kd">public</span> <span class="n">String</span> <span class="nf">getString</span><span class="o">()</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setString</span><span class="o">(</span><span class="n">String</span> <span class="n">string</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span>
<span class="kd">public</span> <span class="kt">int</span> <span class="nf">getInteger</span><span class="o">()</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setInteger</span><span class="o">(</span><span class="kt">int</span> <span class="n">string</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span>
<span class="o">}</span></code></pre>
<p>L’appel de méthodes distantes est transparent grâce a l’utilisation de <code>proxy</code> Java, il suffit de décrire l’interface de l’objet et de donner le nom de son bus pour pouvoir l’appeler. Toute méthode distante retourne un <code>PendingCall</code> auquel on doit attacher un <em>listener</em> pour être notifié de l’arrivée du résultat.</p>
<p>JNIDBus permet également d’exposer des méthodes distante par le biais de <code>handlers</code>. Un <em>handler</em> est simplement une classe annotée décrivant et implémentant les méthodes distantes, les signatures sont inférées grâce aux types d’entrées et de sorties. Les <em>handlers</em> seront exécutés dans la boucle d’évènements, il est donc primordial d’éviter tout appel bloquant ou toute tâche lourde. Afin de tout de même pouvoir effectuer ces tâches lourdes, les <em>handlers</em> gèrent un type de retour asynchrone (<code>Promise</code>).</p>
<p><em>Exemple d’un</em> handler <em>pour un signal et un appel :</em></p>
<pre><code class="java"><span class="nd">@Handler</span><span class="o">(</span>
<span class="cm">/* pour plus d’information référez vous à la documentation D-Bus</span>
<span class="cm"> */</span>
<span class="n">path</span> <span class="o">=</span> <span class="s">"/some/object/path"</span><span class="o">,</span>
<span class="n">interfaceName</span> <span class="o">=</span> <span class="s">"some.dbus.interface"</span>
<span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">SomeHandler</span> <span class="kd">extends</span> <span class="n">GenericHandler</span> <span class="o">{</span>
<span class="nd">@HandlerMethod</span><span class="o">(</span>
<span class="c1">//le nom exposé a D-Bus peut être différent du nom de la méthode Java</span>
<span class="n">member</span> <span class="o">=</span> <span class="s">"someSignal"</span><span class="o">,</span>
<span class="n">type</span> <span class="o">=</span> <span class="n">HandlerType</span><span class="o">.</span><span class="na">SIGNAL</span>
<span class="o">)</span>
<span class="c1">//Ici notre signal n’a aucun paramètre, on utilise donc le singleton EmptyMessage</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">someSignal</span><span class="o">(</span><span class="n">Message</span><span class="o">.</span><span class="na">EmptyMessage</span> <span class="n">emptyMessage</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span>
<span class="nd">@HandlerMethod</span><span class="o">(</span>
<span class="n">member</span> <span class="o">=</span> <span class="s">"stringSignal"</span><span class="o">,</span>
<span class="n">type</span> <span class="o">=</span> <span class="n">HandlerType</span><span class="o">.</span><span class="na">METHOD</span>
<span class="o">)</span>
<span class="kd">public</span> <span class="n">SomeOutput</span> <span class="nf">someCall</span><span class="o">(</span><span class="n">SomeInput</span> <span class="n">input</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span>
<span class="o">}</span></code></pre>
<p><em>Comment enregistrer le handler auprès de D-Bus :</em></p>
<pre><code class="java"><span class="c1">//connection au bus</span>
<span class="n">Dbus</span> <span class="n">receiver</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Dbus</span><span class="o">(</span><span class="n">BusType</span><span class="o">.</span><span class="na">SESSION</span><span class="o">,</span><span class="s">"my.bus.name"</span><span class="o">);</span>
<span class="c1">//instanciation</span>
<span class="n">SomeHandler</span> <span class="n">handler</span> <span class="o">=</span> <span class="k">new</span> <span class="n">SomeHandler</span><span class="o">();</span>
<span class="c1">//ajout, JNIDBus lancera une exception si le handler n’est pas valide</span>
<span class="k">this</span><span class="o">.</span><span class="na">receiver</span><span class="o">.</span><span class="na">addHandler</span><span class="o">(</span><span class="n">handler</span><span class="o">);</span></code></pre>
<p>Le langage Kotlin est pris en charge par le biais d’un artefact Gradle supplémentaire définissant des extensions qui suspendent l’appel de fonction et offrant la possibilité d’avoir des <code>handlers</code> qui facilitent la mise en place de cette suspension :</p>
<pre><code class="kotlin"><span class="c1">//il faut enregistrer la classe qui se chargera d’invoquer les méthodes qui suspendent</span>
<span class="n">KotlinMethodInvocator</span><span class="p">.</span><span class="n">registerKotlinInvocator</span><span class="p">()</span>
<span class="n">@Handler</span><span class="p">(</span><span class="n">path</span> <span class="p">=</span> <span class="s">"..."</span><span class="p">,</span> <span class="n">interfaceName</span> <span class="p">=</span> <span class="s">"..."</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">CallHandler</span> <span class="p">:</span> <span class="n">KotlinGenericHandler</span><span class="p">()</span> <span class="p">{</span>
<span class="n">@HandlerMethod</span><span class="p">(</span><span class="n">member</span> <span class="p">=</span> <span class="s">"suspendingCall"</span><span class="p">,</span> <span class="n">type</span> <span class="p">=</span> <span class="n">MemberType</span><span class="p">.</span><span class="n">METHOD</span><span class="p">)</span>
<span class="n">suspend</span> <span class="k">fun</span> <span class="nf">suspendingCall</span><span class="p">(</span><span class="n">emptyMessage</span><span class="p">:</span> <span class="n">Message</span><span class="p">.</span><span class="n">EmptyMessage</span><span class="p">):</span> <span class="n">SingleStringMessage</span> <span class="p">{</span>
<span class="c1">//coroutines are awesome</span>
<span class="n">delay</span><span class="p">(</span><span class="m">2000</span><span class="p">)</span>
<span class="k">return</span> <span class="n">SingleStringMessage</span><span class="p">().</span><span class="n">apply</span> <span class="p">{</span> <span class="n">string</span> <span class="p">=</span> <span class="s">"test"</span> <span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre>
</div><div><a href="https://linuxfr.org/news/communiquer-avec-d-bus-en-java-avec-jnidbus.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/118126/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/communiquer-avec-d-bus-en-java-avec-jnidbus#comments">ouvrir dans le navigateur</a>
</p>
pulkomandySeekDaSkyDavy DefaudYsabeau 🧶 🧦claudexNÿcoBAudhttps://linuxfr.org/nodes/118126/comments.atom