Cette semaine, nous allons voir comment intégrer notre librairie de programmation C# dans une application .NET MAUI. Ce nouveau framework promet le saint Graal pour tout développeur: écrire une seule application que l'on puisse utiliser sur n'importe quelle plateforme. Regardons ce qu'il en est.
.NET MAUI (Multi-platform App UI) est un framework open-source développé par Microsoft qui permet de créer des applications natives multiplateformes (Windows, macOS, Android, iOS) en utilisant C# et XAML. Ce framework espère réussir là où de nombreux autres frameworks ont échoué par le passé, comme JavaFX, QT, Xamarin, etc. Notez l'absence étonnante du support Linux, qui, de notre point de vue est vraiment dommageable.
Mobile vs Desktop
Ce framework est basé sur .NET 6 et donc notre libraire C# fonctionne sans problème, mais il y a quand même une grosse limitation. Notre librairie ne fonctionne pas sous Android et iOS.
Notre librairie C# est composée de 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 supportée par toutes les versions de .NET 6 et .NET MAUI.
La couche bas niveau, appelée "yapi", fonctionne différemment. C'est une librairie dynamique écrite en C qui interagit directement avec l'OS. Cette couche n'est pas portable et nécessite une version par plateforme supportée. Lors du premier appel à la librairie, le code C# de la couche haut niveau va détecter l'OS et l'architecture de la machine et charger la bonne version de la couche "yapi".
Cette couche est disponible pour les architectures/OS suivantes:
- Windows Intel 32 bits
- Windows Intel 64 bits
- Windows Arm 64 bits
- Linux i386
- Linux x64
- Linux armhf (ARM 32 bits)
- Linux aarch64 (ARM 64 bits)
- macOS 64 bits
Notre librairie C# fonctionne correctement uniquement sur ces plates-formes car Android et iOS ne fournissent pas d'API pour accéder directement aux ports USB. Si vous essayez de l'utiliser sur iOS, par exemple, la librairie retournera une erreur. C'est pour cette raison qu'il n'est pas possible d'utiliser notre librairie dans une application .NET MAUI Android ou iOS.
Malgré ces limitations, voyons comment exploiter .NET MAUI pour une application desktop.
Une app desktop
Nous allons réaliser une petite application qui permet de lister les numéros de série des modules Yoctopuce connectés par USB ou sur un YoctoHub distant.
Il faut commencer par installer Visual Studio 2022 en sélectionnant bien l'option ".NET Multi-platforme App UI developement" lors de l'installation.
On peut ensuite créer un projet ".NET MAUI App". Visual Studio crée un projet complet avec un chablon d'application.
Il faut ensuite inclure notre librairie de programmation au projet. La procédure est identique à ce qui est expliqué dans notre tutoriel C#. C'est-à-dire qu'il faut ajouter au projet les fichiers C# des classes que l'on va utiliser ainsi que les différentes librairies "yapi".
Pour cet exemple nous allons utiliser uniquement les classes YAPI et YModules qui sont définies dans le fichier yocto_api.cs. Pour la couche "yapi", il faut inclure les versions Windows 32 bits (yapi.dll), Windows 64 bits (amd64/yapi.dll), Windows ARM (arm64/yapi.dll) et macOS (libyapi.dylib). Pour ces 4 fichiers, il ne faut pas oublier de changer la propriété "Copy to Output Directory" à "Copy always".
Une application MAUI utilise XAML pour définir l'interface utilisateur. XAML est un langage déclaratif basé sur XML qui est aussi utilisé avec le framwork UWP. Ce langage n'est pas très intuitif, en particulier le système de "binding", mais, en s'inspirant du code de base et de la documentation de Microsoft, on arrive à créer notre interface.
Le contenu de MainPAge.xaml:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Prog_MAUI.MainPage">
<Grid
RowDefinitions="100,auto,auto,*"
ColumnDefinitions=".25*,.75*"
Padding="10"
RowSpacing="20"
ColumnSpacing="20">
<Image Source="logo.png" />
<Label
VerticalTextAlignment="Center"
Grid.Column="1"
FontSize="42">
Yoctopuce Inventory in Maui
</Label>
<Button
Clicked="OnUpdateClicked"
Grid.Row="2"
Text="Perform Inventory" />
<Entry
x:Name="TargetURL"
BindingContext=""
Grid.Row="2"
Grid.Column="1"
Placeholder="Hub URL" />
<CollectionView Grid.Row="3"
Grid.ColumnSpan="2"
ItemsSource="{Binding Inventory}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="0,5">
<Border Padding="5">
<Label Text="{Binding .}"></Label>
</Border>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Grid>
</ContentPage>
Les éléments à retenir sont le bouton qui va appeler la méthode OnUpdateClicked, le champ texte TargetURL qui permet d'enter l'URL du YoctoHub, et la CollectionView qui va afficher la liste de numéro de série en utilisant l'attribut Inventory de notre code.
Une fois l'interface définie, il reste à écrire la logique de l'application. Tout le code se trouve dans la méthode OnUpdateClicked qui est appelée par le bouton.
Le contenu de MainPAge.xaml.cs:
{
public string lastURL = "";
public ObservableCollection<string> Inventory { get; }
= new ObservableCollection<string>();
public MainPage()
{
InitializeComponent();
BindingContext = this;
}
private void OnUpdateClicked(object? sender, EventArgs e)
{
string url = TargetURL.Text;
// check if we need to update the urls
if (url != lastURL) {
if (lastURL != "") {
YAPI.UnregisterHub(lastURL);
}
string errmsg = "";
int res = YAPI.RegisterHub(url, ref errmsg);
if (res != YAPI.SUCCESS) {
DisplayAlert("Erreur", errmsg, "OK");
return;
}
lastURL = url;
}
Inventory.Clear();
YModule module = YModule.FirstModule();
while (module != null) {
Inventory.Add(module.get_serialNumber());
module = module.nextModule();
}
}
}
On commence par récupérer l'URL entrée par l'utilisateur, si elle est changée, on met à jour la librairie avec les méthodes YAPI.UnregisterHub et YAPI.RegisterHub. Ensuite on énumère les modules détectés à l'aide de la classe YModule et on met à jour la propriété Inventory qui est utilisée par l'interface.
Une fois ces modifications faites, on peut tester l'application et vérifier que tout fonctionne correctement:

Voilà l'application qui fonctionne sous Windows
Sous macOS
Pour tester l'application sous macOS, il faut copier le projet sur un mac qui a .NET 9 SKD et MAUI installés. Le SDK est disponible directement sur le site de Microsoft. Une fois installé, on peut utiliser la commande suivante pour installer MAUI:
sudo dotnet workload install maui
Sous macOS, l’accès aux périphériques USB et aux fonctionnalités réseau nécessite des autorisations spécifiques. Pour obtenir ces permission, il faut ajouter les clés suivantes au fichier Platforms/MacCatalyst/Entitlements.plist du projet :
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
Ces entrées permettent respectivement :
- com.apple.security.device.usb : l’accès aux périphériques USB (pour détecter les modules Yoctopuce connectés localement).
- com.apple.security.network.client et com.apple.security.network.server : les communications réseau (pour interagir avec un YoctoHub distant).
Une fois ces deux étapes réalisées, vous pouvez tester l’application en exécutant la commande :
dotnet run --framework net9.0-maccatlyst

Voilà l'application qui fonctionne sous macOS
Le code source
Le code source de cette application est désormais inclus dans notre librairie C#. Au lieu de partir d'une page blanche, vous pouvez ouvrir le projet qui se trouve dans le répertoire Examples/Prog-MAUI et partir d'un projet fonctionnel.
Conclusion
En résumé, .NET MAUI permet de réaliser des applications multiplateformes, mais il n'est pas possible d'utiliser notre librairie sous les version Android et iOS. On trouve aussi dommage qu'il manque le support Linux. Ça aurait été le premier framework a fournir une solution pour créer une application native qui fonctionne sur le trois OS depuis la fin de Mono.
