Utiliser la librairie C# sous Linux et macOS avec Mono

Utiliser la librairie C# sous Linux et macOS avec Mono

Récemment nous avons publié deux applications en C# qui utilisent les modules Yoctopuce: Yocto-Visualization et Yocto-Discovery. Grâce à Mono, on arrive à exécuter ces deux applications sous Windows, macOS et Linux (Intel et ARM). Cela nous a pris quelques essais pour que Mono accepte de d'utiliser notre librairie. Du coup, on s'est dit que cela intéresserait probablement d'autres utilisateurs...



Dans cet article, nous allons expliquer comment utiliser notre librairie de programmation C# avec Mono. Mais il ne s’agit pas d'un tutoriel sur Mono. Nous partons du principe que vous savez écrire une application simple en C# et que vous arrivez à la faire fonctionner sous Windows, Linux, et macOS à l'aide de Mono. De la même manière, si vous n'avez jamais utilisé nos modules, nous vous conseillons de commencer par notre série de tutoriels, en particulier l'article "Comment débuter en C# avec des modules Yoctopuce".


Les problèmes de portabilité


Dans la plupart des cas, utiliser une application C# .NET sous Linux ou macOS n'est pas un problème car le code est interprété par une machine virtuelle et non pas directement part le CPU. Sous Windows, c'est l’environnement .NET qui implémente cette machine virtuelle, alors que sous Linux et macOS c'est Mono s'en charge.

Par exemple le code suivant peut être effectué sur toutes les plateformes:

static void Main(string[] args)
{
    Console.WriteLine("Hello world");
}



Alors pourquoi est-ce plus compliqué avec notre librairie C#?
Parce que la librairie Yoctopuce est divisée en deux couches : une couche haut niveau implémente les objets Yoctopuce (YModules, YTemperature, etc.) et une couche bas niveau gère les communications avec le matériel. La couche haut niveau est écrite en C# et est donc portable. C'est-à-dire que le code généré par le compilateur peut être exécuté directement par n'importe quelle machine virtuelle C# (Windows ou Mono).

La couche bas niveau, appelée "yapi", fonctionne différemment. C'est une librairie dynamique écrite en C qui interagit directement avec l'OS. Bien que ce code puisse être compilé pour toutes les plateformes qui nous intéressent, le code binaire généré n'est pas portable. La version Windows 32 bits ne fonctionne que sous Windows 32 bits, la version Linux ARM bits ne fonctionne que sur Linux ARM, etc... C'est pour cette raison que dans le sous-répertoire /Source/dll de notre librairie, vous trouvez plusieurs versions de cette librairie dynamique.

A l’exécution, le programme qui utilise notre librairie va détecter l'OS et le CPU de la machine, et charger la bonne version de la librairie dynamique.

Concrètement, le "problème" de portabilité de notre librairie se résume donc à copier la bonne version de la librairie dans le bon répertoire pour que la machine virtuelle .NET puisse la trouver.

Un exemple


Pour cet article, nous allons réaliser une application en ligne de commande très basique. L'application va simplement lister tous les modules connectés aux ports USB de la machine courante.

Le code est extrêmement simple. On initialise d'API et on itère sur tous les modules présents:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp1
{
  class Program
  {
    static void Main(string[] args)
    {
      YModule m;
      string errmsg = "";

      if (YAPI.RegisterHub("usb", ref errmsg) != YAPI.SUCCESS) {
          Console.WriteLine("RegisterHub error: " + errmsg);
          Environment.Exit(0);
      }

      Console.WriteLine("Device list");
      m = YModule.FirstModule();
      while (m != null) {
          Console.WriteLine(m.get_serialNumber() +
                        " (" + m.get_productName() + ")");
          m = m.nextModule();
      }
      YAPI.FreeAPI();
    }
  }
}



Pour compiler ce code, il faut ajouter le fichier yocto_api.cs au projet qui implémente les objets YAPI et YModules. Sans rien faire de plus, le projet peut être compilé. Mais si on lance l’exécutable, l'appel à YAPI.RegisterHub va retourner une erreur: "Unable to load yapi.dll".

Pour que le programme fonctionne correctement, il faut copier dans le répertoire de l’exécutable la version de la librairie dynamique correspondant à la plateforme en cours.

Windows 32 bits


La version Windows 32 bits de la librairie dynamique yapi est le fichier yapi.dll qui se trouve dans le répertoire /Sources/dll de notre librairie.

Il faut la copier dans le répertoire de l’exécutable. On peut soit le faire à la main, soit ajouter la dll au projet et indiquer à Visual Studio de la copier lors de la compilation grâce à propriété "Copy to Output Directory".

Il faut copier la dll vers l'exécutable
Il faut copier la dll vers l'exécutable



Windows 64 bits


La version Windows 64 bits de la librairie dynamique yapi est le fichier yapi.dll qui se trouve dans le répertoire /Sources/dll/amd64 de notre librairie. Pour que la machine virtuelle C# trouve cette librairie, il faut la placer dans un sous-répertoire amd64.

De nouveau, on peut soit copier le fichier à la main, soit le faire directement depuis Visual Studio. Attention, pour que la dll se retrouve dans un sous-répertoire "amd64", il faut tout d'abord créer ce sous-répertoire dans le panneau "Solutions Explorer".

La dll doit être ajoutée dans un sous-répertoire amd64
La dll doit être ajoutée dans un sous-répertoire amd64



Ensuite, il faut ajouter la dll dans ce sous-répertoire et la valeur "Copy always" pour la propriété "Copy to Output Directory".

Notez qu'en ajoutant la dll 32 bits et 64 bits à votre projet, vous pouvez maintenant générer une application "Any CPU" qui peut être exécutée sur n'importe quel Windows. Lors de l’exécution, la librairie va automatiquement prendre la bonne version de la dll.

Installer Mono


Pour exécuter notre application sous Linux ou macOS, il faut en premier lieu installer Mono. La procédure est relativement simple. Sous macOS, il faut downloader et lancer l’installeur Mono. Sous Linux, la plupart du temps il est possible de l'installer directement à l'aide de la commande suivante:

sudo apt-get install mono-complete



Si le package n'est pas disponible dans votre distribution, le site web de Mono explique comment l'installer.

Exécuter l’application sous Linux et macOS


Bien qu'il soit possible de compiler l'application sous Linux ou macOS, l'expérience nous a montré qu'il est bien plus simple de toujours compiler l'application sous Windows et de copier le répertoire de l’exécutable sur les autres OS. Pour exécuter l'application, on utilise la commande suivante:

$ mono ConsoleApp1.exe




Copier les librairies dynamiques


Comme nous n'avons pas encore copié les versions Linux ou macOS de la librairie dynamique yapi, le programme va retourner le message d'erreur "Unable to load yapi.dll".

yocto@linux-laptop:/tmp/bin/Release$ mono ConsoleApp1.exe RegisterHub error: Unable to load yapi.dll (yapi) yocto@linux-laptop:/tmp/bin/Release$



Il faut copier les quatre versions Linux et/ou les deux versions macOS de la librairie dynamique yapi:

  1. libyapi-i386.so pour Linux Intel 32 bits
  2. libyapi-amd64.so pour Linux Intel 64 bits
  3. libyapi-armhf.so pour Linux ARM 32 bits
  4. libyapi-aarch64.so pour Linux ARM 64 bits
  5. libyapi32.dylib pour macOS 32 bits
  6. libyapi.dylib pour macOS 64 bits


Ces fichiers doivent être copiés dans le même répertoire que l’exécutable.

Mise à jour: Depuis la version 1.10.34639 de la librairie C#, il n'est plus nécessaire de modifier le fichier .config et d'ajouter les entrées dllmap.

Mais contrairement à Windows, il y a une étape supplémentaire: il faut modifier le fichier ConsoleApp1.exe.config et ajouter un nœud <dllmap> pour chaque version de la librairie yapi dans la section <configuration>.

Voici le fichier final de notre exemple:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup>
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
    </startup>
    <dllmap dll="yapi" os="linux" cpu="x86" target="libyapi-i386.so"/>
    <dllmap dll="yapi" os="linux" cpu="x86-64" target="libyapi-amd64.so"/>
    <dllmap dll="yapi" os="linux" cpu="arm" target="libyapi-armhf.so"/>
    <dllmap dll="yapi" os="osx" wordsize="32" target="libyapi32.dylib"/>
    <dllmap dll="yapi" os="osx" wordsize="64" target="libyapi.dylib"/>
</configuration>



Comme vous pouvez le voir, il y a une entrée par plateforme, les attributs os, cpu et wordsize permettant d'indiquer à Mono quelle librairie utiliser pour chaque plateforme.


Et voilà, vous avez maintenant une même application qui fonctionne sur une machine Windows (32 ou 64 bits), sous Linux 32 ou 64 bits, sur un Raspberry Pi, et sous macOS.

Au final, si l'on résume la liste des fichiers nécessaires, on a:

  • ConsoleApp1.exe : l’exécutable C#
  • ConsoleApp1.exe.config : le fichier de config avec les entrées dllmap
  • yapi.dll : la couche bas niveau pour Windows 32 bits
  • amd64/yapi.dll : la couche bas niveau pour Windows 64 bits
  • libyapi-i386.so : la couche bas niveau pour Linux 32 bits
  • libyapi-amd64.so : la couche bas niveau pour Linux 64 bits
  • libyapi-armhf.so : la couche bas niveau pour Linux sous ARM
  • libyapi-aarch64.so : la couche bas niveau pour Linux sous ARM 64 bits
  • libyapi.dylib : la couche bas niveau pour macOS (64 bits)
  • libyapi32.dylib : la couche bas niveau pour macOS (32 bits)


Le code source de l'exemple


Le code source de ce programme est disponible sur Github: https://github.com/yoctopuce-examples/MultiPlatformCSharp.

Conclusion et remarques


Bien que le fait d'ajouter les nœuds dllmap dans le fichier de configuration ne dérange pas l’exécution sous Windows, il n'est pas possible de configurer Visual Studio pour les ajouter automatiquement. En effet, lors de la compilation, il va vérifier ce fichier et générer une erreur si un tag "non certifié Microsoft" est présent. Cela veut dire qu'il faudra manuellement patcher ce fichier après chaque compilation.

Vous avez peut-être remarqué que nous avons une version 32 bits de la librairie yapi pour macOS, malgré qu'Apple ait annoncé la fin du support 32 bits dans les futures versions de macOS. La raison est qu'actuellement le support pour les applications graphiques Winform ne fonctionnent pas sur la version 64 bit de Mono. Actuellement, la seule solution pour exécuter une application graphique Winform sous mac est d'utiliser la version 32 bits.

En conclusion, malgré quelques bugs et limitations par-ci par-là, Mono permet de réaliser relativement facilement une application graphique qui fonctionne sur les trois principaux OS et sur un Raspberry Pi. En tout cas, sans Mono nous n’aurions probablement pas eu les ressources pour coder trois versions de nos applications Yocto-Visualization et Yocto-Discovery.

Commenter aucun commentaire Retour au blog












Yoctopuce, get your stuff connected.