URL: https://linuxfr.org/news/communiquer-avec-d-bus-en-java-avec-jnidbus Title: Communiquer avec D-Bus en Java avec JNIDBus Authors: pulkomandy SeekDaSky, Davy Defaud, Ysabeau đ§¶, claudex, NĂżco et BAud Date: 2019ćčŽ09æ16æ„T14:07:53+02:00 License: CC By-SA Tags: dbus, java et jnidbus Score: 30 Avec mes collĂšgues chez [Viveris](http://viveris.fr), on sâest dit quâon aimerait bien faire plus de logiciel libre. On a donc montĂ© un « groupe _opensource_ » 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. Il y a quelques mois je vous prĂ©sentais [QTestFramework](https://linuxfr.org/users/pulkomandy/journaux/un-harnais-de-test-pour-qt), depuis on a Ă©galement pu contribuer au [dissecteur 0MQ pour Wireshark](https://github.com/whitequark/zmtp-wireshark) et [un outil pour le _boundary scan_ JTAG](https://github.com/viveris/jtag-boundary-scanner). On vient de publier il y a quelques jours une bibliothĂšque Java pour communiquer en D-Bus. ---- [Projets opensource chez Viveris](https://opensource.viveris.fr) [JNIDBus sur GitHub](https://github.com/viveris/jnidbus) [D-Bus sur WikipĂ©dia](https://fr.wikipedia.org/wiki/D-Bus) ---- Contexte ======== D-Bus ---- 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. JNI ---- JNI (_Java Native Interface_) 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 _wrappers_ 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. Solutions existantes et leurs limitations ----------------------------------------- Freedesktop propose [DBus-Java](https://www.freedesktop.org/wiki/Software/DBusBindings/), 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. Il existe bien [une version mise Ă jour](https://github.com/hypfvieh/dbus-java) 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. JNIDBus ======= Historique ---------- 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. 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 (_pool_) de fils dâexĂ©cution qui complexifiait encore le logiciel. 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. Afin de ne pas rĂ©implĂ©menter le protocole D-Bus nous voulions utiliser la bibliothĂšque `libdbus-1`. 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 (_bindings_) Ă Ă©crire. 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. Utilisation ----------- La base de JNIDBus est la sĂ©rialisation dâobjet Java. La signature du message est dĂ©crite dans une annotation. *Exemple pour un message contenant une chaĂźne de caractĂšres et un entier :* ```java @DBusType( /* Pour plus dâinfo sur le format de la signature, rĂ©fĂ©rez vous * Ă la documentation D-Bus */ signature = "si", /* Donne le nom des propriĂ©tĂ©s contenant les donnĂ©es du message, * dans notre cas la chaĂźne de caractĂšres est contenue dans la * propriĂ©tĂ© nommĂ©e « string » et lâentier dans le champs « integer » */ fields = {"string","integer"} ) public class StringMessage extends Message { /* Les champs seront accĂ©dĂ©s au travers de ses setters et * getters qui devront respecter la convention "setXxx"/"getXxx" */ private String string; private int integer public String getString() { ... } public void setString(String string) { ... } public int getInteger() { ... } public void setInteger(int string) { ... } } ``` Lâappel de mĂ©thodes distantes est transparent grĂące a lâutilisation de `proxy` 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 `PendingCall` auquel on doit attacher un _listener_ pour ĂȘtre notifiĂ© de lâarrivĂ©e du rĂ©sultat. JNIDBus permet Ă©galement dâexposer des mĂ©thodes distante par le biais de `handlers`. Un _handler_ 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 _handlers_ 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 _handlers_ gĂšrent un type de retour asynchrone (`Promise`). *Exemple dâun* handler *pour un signal et un appel :* ```java @Handler( /* pour plus dâinformation rĂ©fĂ©rez vous Ă la documentation D-Bus */ path = "/some/object/path", interfaceName = "some.dbus.interface" ) public class SomeHandler extends GenericHandler { @HandlerMethod( //le nom exposĂ© a D-Bus peut ĂȘtre diffĂ©rent du nom de la mĂ©thode Java member = "someSignal", type = HandlerType.SIGNAL ) //Ici notre signal nâa aucun paramĂštre, on utilise donc le singleton EmptyMessage public void someSignal(Message.EmptyMessage emptyMessage) { ... } @HandlerMethod( member = "stringSignal", type = HandlerType.METHOD ) public SomeOutput someCall(SomeInput input) { ... } } ``` *Comment enregistrer le handler auprĂšs de D-Bus :* ```java //connection au bus Dbus receiver = new Dbus(BusType.SESSION,"my.bus.name"); //instanciation SomeHandler handler = new SomeHandler(); //ajout, JNIDBus lancera une exception si le handler nâest pas valide this.receiver.addHandler(handler); ``` 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 `handlers` qui facilitent la mise en place de cette suspension : ```kotlin //il faut enregistrer la classe qui se chargera dâinvoquer les mĂ©thodes qui suspendent KotlinMethodInvocator.registerKotlinInvocator() @Handler(path = "...", interfaceName = "...") class CallHandler : KotlinGenericHandler() { @HandlerMethod(member = "suspendingCall", type = MemberType.METHOD) suspend fun suspendingCall(emptyMessage: Message.EmptyMessage): SingleStringMessage { //coroutines are awesome delay(2000) return SingleStringMessage().apply { string = "test" } } } ```