Partager via


Utiliser des bibliothèques C/C++ avec Xamarin

Vue d’ensemble

Xamarin permet aux développeurs de créer des applications mobiles natives multiplateformes avec Visual Studio. En règle générale, les liaisons C# sont utilisées pour exposer les composants de plateforme existants aux développeurs. Toutefois, il arrive que les applications Xamarin fonctionnent avec des bases de code existantes. Parfois, les équipes n’ont pas le temps, le budget ou les ressources pour porter une base de code volumineuse, bien testée et hautement optimisée en C#.

Visual C++ pour le développement mobile multiplateforme permet de créer le code C/C++ et C# dans le cadre de la même solution, offrant de nombreux avantages, notamment une expérience de débogage unifiée. Microsoft a utilisé C/C++ et Xamarin de cette façon pour fournir des applications telles que Hyperlapse Mobile et Pix Camera.

Toutefois, dans certains cas, il existe un désir (ou une exigence) de conserver les outils et processus C/C++ existants et de conserver le code de bibliothèque découplé de l’application, en traitant la bibliothèque comme si elle était similaire à un composant tiers. Dans ces situations, le défi n’est pas seulement d’exposer les membres pertinents à C# mais de gérer la bibliothèque en tant que dépendance. Et bien sûr, automatiser autant de ce processus que possible.

Ce billet décrit une approche générale pour ce scénario et décrit un exemple simple.

Background

C/C++ est considéré comme un langage multiplateforme, mais il convient de veiller à ce que le code source soit effectivement multiplateforme, en utilisant uniquement C/C++ pris en charge par tous les compilateurs cibles et contenant peu ou pas de code spécifique à la plateforme ou au compilateur inclus conditionnellement.

En fin de compte, le code doit compiler et s’exécuter correctement sur toutes les plateformes cibles, ce qui se résume à la commonalité entre les plateformes (et les compilateurs) ciblés. Les problèmes peuvent toujours provenir de différences mineures entre les compilateurs et de tests approfondis (de préférence automatisés) sur chaque plateforme cible devient de plus en plus important.

Approche de haut niveau

L’illustration ci-dessous représente l’approche en quatre étapes utilisée pour transformer le code source C/C++ en bibliothèque Xamarin multiplateforme partagée via NuGet, puis utilisée dans une application Xamarin.Forms.

Approche générale pour l’utilisation de C/C++ avec Xamarin

Les 4 étapes sont les suivantes :

  1. Compilation du code source C/C++ dans des bibliothèques natives spécifiques à la plateforme.
  2. Encapsuler les bibliothèques natives avec une solution Visual Studio.
  3. Empaquetage et envoi (push) d’un package NuGet pour le wrapper .NET.
  4. Utilisation du package NuGet à partir d’une application Xamarin.

Étape 1 : Compilation du code source C/C++ dans des bibliothèques natives spécifiques à la plateforme

L’objectif de cette étape est de créer des bibliothèques natives qui peuvent être appelées par le wrapper C#. Cela peut ou non être pertinent en fonction de votre situation. Les nombreux outils et processus qui peuvent être mis à jour dans ce scénario commun dépassent la portée de cet article. Les considérations clés sont de maintenir la base de code C/C++ synchronisée avec n’importe quel code wrapper natif, des tests unitaires suffisants et une automatisation de build.

Les bibliothèques de la procédure pas à pas ont été créées à l’aide de Visual Studio Code avec un script shell associé. Vous trouverez une version étendue de cette procédure pas à pas dans le référentiel GitHub de cat mobile qui traite de cette partie de l’exemple plus en détail. Les bibliothèques natives sont traitées comme une dépendance tierce dans ce cas, mais cette étape est illustrée pour le contexte.

Par souci de simplicité, la procédure pas à pas cible uniquement un sous-ensemble d’architectures. Pour iOS, il utilise l’utilitaire lipo pour créer un seul binaire gras à partir des fichiers binaires spécifiques à l’architecture individuelle. Android utilisera des fichiers binaires dynamiques avec une extension .so et iOS utilisera un binaire fat statique avec une extension .a.

Étape 2 : Habillage des bibliothèques natives avec une solution Visual Studio

La phase suivante consiste à encapsuler les bibliothèques natives afin qu’elles soient facilement utilisées à partir de .NET. Cette opération s’effectue avec une solution Visual Studio avec quatre projets. Un projet partagé contient le code commun. Les projets ciblant chacun de Xamarin.Android, Xamarin.iOS et .NET Standard permettent à la bibliothèque d’être référencée de manière indépendante de la plateforme.

Le wrapper utilise « l’appât et changer d’astuce », Ce n’est pas la seule façon, mais il facilite la référence de la bibliothèque et évite la nécessité de gérer explicitement des implémentations spécifiques à la plateforme au sein de l’application consommatrice elle-même. L’astuce consiste essentiellement à s’assurer que les cibles (.NET Standard, Android, iOS) partagent le même espace de noms, le même nom d’assembly et la même structure de classe. Étant donné que NuGet préfère toujours une bibliothèque spécifique à la plateforme, la version .NET Standard n’est jamais utilisée au moment de l’exécution.

La plupart du travail de cette étape se concentre sur l’utilisation de P/Invoke pour appeler les méthodes de bibliothèque natives et gérer les références aux objets sous-jacents. L’objectif est d’exposer les fonctionnalités de la bibliothèque au consommateur tout en extrayant toute complexité. Les développeurs Xamarin.Forms n’ont pas besoin d’avoir des connaissances sur les fonctionnements internes de la bibliothèque non managée. Il doit se sentir comme s’ils utilisent n’importe quelle autre bibliothèque C# managée.

En fin de compte, la sortie de cette étape est un ensemble de bibliothèques .NET, une par cible, ainsi qu’un document nuspec qui contient les informations requises pour générer le package à l’étape suivante.

Étape 3 : Empaquetage et envoi (push) d’un package NuGet pour le wrapper .NET

La troisième étape consiste à créer un package NuGet à l’aide des artefacts de build de l’étape précédente. Le résultat de cette étape est un package NuGet qui peut être consommé à partir d’une application Xamarin. La procédure pas à pas utilise un répertoire local pour servir de flux NuGet. En production, cette étape doit publier un package sur un flux NuGet public ou privé et doit être entièrement automatisé.

Étape 4 : Utilisation du package NuGet à partir d’une application Xamarin.Forms

La dernière étape consiste à référencer et à utiliser le package NuGet à partir d’une application Xamarin.Forms. Cela nécessite la configuration du flux NuGet dans Visual Studio pour utiliser le flux défini à l’étape précédente.

Une fois le flux configuré, le package doit être référencé à partir de chaque projet dans l’application Xamarin.Forms multiplateforme. « L’astuce d’appât-and-switch » fournit des interfaces identiques, de sorte que la fonctionnalité de bibliothèque native peut être appelée à l’aide du code défini dans un emplacement unique.

Le référentiel de code source contient une liste de lectures supplémentaires qui incluent des articles sur la configuration d’un flux NuGet privé sur Azure DevOps et la façon d’envoyer (push) le package à ce flux. Même si vous avez besoin d’un peu plus de temps de configuration qu’un répertoire local, ce type de flux est préférable dans un environnement de développement d’équipe.

Procédure pas à pas

Les étapes fournies sont spécifiques à Visual Studio pour Mac, mais la structure fonctionne également dans Visual Studio 2017.

Prérequis

Pour suivre le suivi, le développeur a besoin des éléments suivants :

Remarque

Un compte de développeur Apple actif est requis pour déployer des applications sur un i Téléphone.

Création des bibliothèques natives (étape 1)

La fonctionnalité de bibliothèque native est basée sur l’exemple de procédure pas à pas : création et utilisation d’une bibliothèque statique (C++).

Cette procédure pas à pas ignore la première étape, en créant les bibliothèques natives, car la bibliothèque est fournie en tant que dépendance tierce dans ce scénario. Les bibliothèques natives précompilées sont incluses en même temps que l’exemple de code ou peuvent être téléchargées directement.

Utilisation de la bibliothèque native

L’exemple MathFuncsLib d’origine inclut une classe unique appelée MyMathFuncs avec la définition suivante :

namespace MathFuncs
{
    class MyMathFuncs
    {
    public:
        double Add(double a, double b);
        double Subtract(double a, double b);
        double Multiply(double a, double b);
        double Divide(double a, double b);
    };
}

Une classe supplémentaire définit des fonctions wrapper qui permettent à un consommateur .NET de créer, de supprimer et d’interagir avec la classe native MyMathFuncs sous-jacente.

#include "MyMathFuncs.h"
using namespace MathFuncs;

extern "C" {
    MyMathFuncs* CreateMyMathFuncsClass();
    void DisposeMyMathFuncsClass(MyMathFuncs* ptr);
    double MyMathFuncsAdd(MyMathFuncs *ptr, double a, double b);
    double MyMathFuncsSubtract(MyMathFuncs *ptr, double a, double b);
    double MyMathFuncsMultiply(MyMathFuncs *ptr, double a, double b);
    double MyMathFuncsDivide(MyMathFuncs *ptr, double a, double b);
}

Il s’agit de ces fonctions wrapper utilisées côté Xamarin .

Habillage de la bibliothèque native (étape 2)

Cette étape nécessite les bibliothèques précompilées décrites dans la section précédente.

Création de la solution Visual Studio

  1. Dans Visual Studio pour Mac, cliquez sur Nouveau projet (à partir de la page d’accueil) ou Nouvelle solution (dans le menu Fichier).

  2. Dans la fenêtre Nouveau projet, choisissez Projet partagé (à partir de la bibliothèque multiplateforme>), puis cliquez sur Suivant.

  3. Mettez à jour les champs suivants, puis cliquez sur Créer :

    • Nom du projet : MathFuncs.Shared
    • Nom de la solution : MathFuncs
    • Emplacement : utilisez l’emplacement d’enregistrement par défaut (ou choisissez une alternative)
    • Créer un projet dans le répertoire de la solution : définissez-le sur case activée ed
  4. À partir de Explorateur de solutions, double-cliquez sur le projet MathFuncs.Shared et accédez à Main Paramètres.

  5. Supprimer . Partagé à partir de l’espace de noms par défaut afin qu’il soit défini sur MathFuncs uniquement, puis cliquez sur OK.

  6. Ouvrez MyClass.cs (créé par le modèle), puis renommez la classe et le nom de fichier en MyMathFuncsWrapper et remplacez l’espace de noms par MathFuncs.

  7. CONTROL + CLICK sur la solution MathFuncs, puis choisissez Ajouter un nouveau projet... dans le menu Ajouter .

  8. Dans la fenêtre Nouveau projet, choisissez la bibliothèque .NET Standard (à partir de la bibliothèque multiplateforme>), puis cliquez sur Suivant.

  9. Choisissez .NET Standard 2.0 , puis cliquez sur Suivant.

  10. Mettez à jour les champs suivants, puis cliquez sur Créer :

    • Nom du projet : MathFuncs.Standard
    • Emplacement : Utilisez le même emplacement d’enregistrement que le projet partagé
  11. À partir de Explorateur de solutions, double-cliquez sur le projet MathFuncs.Standard.

  12. Accédez à Main Paramètres, puis mettez à jour l’espace de noms par défaut sur MathFuncs.

  13. Accédez aux paramètres de sortie, puis mettez à jour le nom de l’assembly sur MathFuncs.

  14. Accédez aux paramètres du compilateur, remplacez la configuration par Mise en production, définissez les informations de débogage sur Symboles uniquement, puis cliquez sur OK.

  15. Supprimez Class1.cs/Prise en main du projet (si l’un de ces éléments a été inclus dans le modèle).

  16. CONTROL + CLICK sur le dossier Dépendances/Références du projet, puis choisissez Modifier les références.

  17. Sélectionnez MathFuncs.Shared dans l’onglet Projets , puis cliquez sur OK.

  18. Répétez les étapes 7-17 (en ignorant l’étape 9) à l’aide des configurations suivantes :

    NOM DU PROJET NOM DU MODÈLE MENU NOUVEAU PROJET
    MathFuncs.Android Bibliothèque de classes Bibliothèque Android >
    MathFuncs.iOS Bibliothèque de liaisons Bibliothèque iOS >
  19. À partir de Explorateur de solutions, double-cliquez sur le projet MathFuncs.Android, puis accédez aux paramètres du compilateur.

  20. Avec la configuration définie sur Débogage, modifiez Définir des symboles pour inclure Android ;.

  21. Modifiez la configuration en version, puis modifiez Définir des symboles pour inclure Android ;.

  22. Répétez les étapes 19-20 pour MathFuncs.iOS, en modifiant Définir des symboles pour inclure iOS ; au lieu d’Android ; dans les deux cas.

  23. Générez la solution dans la configuration release (CONTROL + COMMAND + B) et vérifiez que les trois assemblys de sortie (Android, iOS, .NET Standard) (dans les dossiers de compartiment de projet respectifs) partagent le même nom MathFuncs.dll.

À ce stade, la solution doit avoir trois cibles, une pièce pour Android, iOS et .NET Standard, et un projet partagé référencé par chacune des trois cibles. Ceux-ci doivent être configurés pour utiliser le même espace de noms et les mêmes assemblys de sortie par défaut portant le même nom. Cela est nécessaire pour l’approche « appât et commutateur » mentionnée précédemment.

Ajout des bibliothèques natives

Le processus d’ajout des bibliothèques natives à la solution wrapper varie légèrement entre Android et iOS.

Références natives pour MathFuncs.Android

  1. CONTROL + CLICK sur le projet MathFuncs.Android , puis choisissez Nouveau dossier dans le menu Ajouter un nom de bibliothèque.

  2. Pour chaque ABI (interface binaire d’application), CONTROL + CLICK sur le dossier lib, puis choisissez Nouveau dossier dans le menu Ajouter, en le nommant après cette ABI respective. Dans ce cas :

    • arm64-v8a
    • armeabi-v7a
    • x86
    • x86_64

    Remarque

    Pour obtenir une vue d’ensemble plus détaillée, consultez la rubrique Architectures et PROCESSEURs du guide du développeur NDK, en particulier la section sur l’adressage du code natif dans les packages d’application.

  3. Vérifiez la structure de dossiers :

    - lib
        - arm64-v8a
        - armeabi-v7a
        - x86
        - x86_64
    
  4. Ajoutez les bibliothèques .so correspondantes à chacun des dossiers ABI en fonction du mappage suivant :

    arm64-v8a : lib/Android/arm64

    armeabi-v7a : lib/Android/arm

    x86 : lib/Android/x86

    x86_64 : lib/Android/x86_64

    Remarque

    Pour ajouter des fichiers, CONTROL + CLICK sur le dossier représentant l’ABI respectif, puis choisissez Ajouter des fichiers... dans le menu Ajouter. Choisissez la bibliothèque appropriée (dans le répertoire PrecompiledLibs ), puis cliquez sur Ouvrir , puis cliquez sur OK en laissant l’option par défaut pour copier le fichier dans le répertoire.

  5. Pour chacun des fichiers .so , CONTROL + CLICK , choisissez l’option EmbeddedNativeLibrary dans le menu Action de génération.

Le dossier lib doit maintenant apparaître comme suit :

- lib
    - arm64-v8a
        - libMathFuncs.so
    - armeabi-v7a
        - libMathFuncs.so
    - x86
        - libMathFuncs.so
    - x86_64
        - libMathFuncs.so

Références natives pour MathFuncs.iOS

  1. CONTROL + CLICK sur le projet MathFuncs.iOS , puis choisissez Ajouter une référence native dans le menu Ajouter .

  2. Choisissez la bibliothèque libMathFuncs.a (à partir de libs/ios sous le répertoire PrecompiledLibs ), puis cliquez sur Ouvrir

  3. CONTROL + CLICK sur le fichier libMathFuncs (dans le dossier Références natives , puis choisissez l’option Propriétés dans le menu

  4. Configurez les propriétés de référence native afin qu’elles soient case activée ed (montrant une icône de graduation) dans le panneau Propriétés :

    • Forcer la charge
    • C++ est-il
    • Smart Link

    Remarque

    L’utilisation d’un type de projet de bibliothèque de liaison ainsi qu’une référence native incorpore la bibliothèque statique et lui permet d’être automatiquement liée à l’application Xamarin.iOS qui la référence (même lorsqu’elle est incluse via un package NuGet).

  5. Ouvrez ApiDefinition.cs, supprimez le code commenté avec modèle (en laissant uniquement l’espace MathFuncs de noms), puis effectuez la même étape pour Structs.cs

    Remarque

    Un projet de bibliothèque de liaison nécessite ces fichiers (avec les actions de génération ObjCBindingApiDefinition et ObjCBindingCoreSource ) afin de générer. Toutefois, nous allons écrire le code pour appeler notre bibliothèque native, en dehors de ces fichiers d’une manière qui peut être partagée entre les cibles de bibliothèque Android et iOS à l’aide de P/Invoke standard.

Écriture du code de la bibliothèque managée

À présent, écrivez le code C# pour appeler la bibliothèque native. L’objectif est de masquer toute complexité sous-jacente. Le consommateur ne doit pas avoir besoin d’une connaissance fonctionnelle des concepts internes de la bibliothèque native ou des concepts P/Invoke.

Création d’un Coffre Handle

  1. CONTROL + CLICK sur le projet MathFuncs.Shared , puis choisissez Ajouter un fichier... dans le menu Ajouter .

  2. Choisissez Classe vide dans la fenêtre Nouveau fichier, nommez-la MyMathFuncs Coffre Handle, puis cliquez sur Nouveau

  3. Implémentez la classe MyMathFuncs Coffre Handle :

    using System;
    using Microsoft.Win32.SafeHandles;
    
    namespace MathFuncs
    {
        internal class MyMathFuncsSafeHandle : SafeHandleZeroOrMinusOneIsInvalid
        {
            public MyMathFuncsSafeHandle() : base(true) { }
    
            public IntPtr Ptr => handle;
    
            protected override bool ReleaseHandle()
            {
                // TODO: Release the handle here
                return true;
            }
        }
    }
    

    Remarque

    Un Coffre Handle est le moyen préféré d’utiliser des ressources non managées dans du code managé. Cela extrait un grand nombre de code réutilisables liés à la finalisation critique et au cycle de vie des objets. Le propriétaire de ce handle peut par la suite le traiter comme n’importe quelle autre ressource managée et n’aura pas besoin d’implémenter le modèle jetable complet.

Création de la classe wrapper interne

  1. Ouvrez MyMathFuncsWrapper.cs, en le remplaçant par une classe statique interne

    namespace MathFuncs
    {
        internal static class MyMathFuncsWrapper
        {
        }
    }
    
  2. Dans le même fichier, ajoutez l’instruction conditionnelle suivante à la classe :

    #if Android
        const string DllName = "libMathFuncs.so";
    #else
        const string DllName = "__Internal";
    #endif
    

    Remarque

    Cela définit la valeur constante DllName en fonction de la création de la bibliothèque pour Android ou iOS. Il s’agit de traiter les différentes conventions d’affectation de noms utilisées par chaque plateforme respective, mais également le type de bibliothèque utilisé dans ce cas. Android utilise une bibliothèque dynamique et attend donc un nom de fichier incluant l’extension. Pour iOS, « __Internal » est nécessaire, car nous utilisons une bibliothèque statique.

  3. Ajouter une référence à System.Runtime.InteropServices en haut du fichier MyMathFuncsWrapper.cs

    using System.Runtime.InteropServices;
    
  4. Ajoutez les méthodes wrapper pour gérer la création et la suppression de la classe MyMathFuncs :

    [DllImport(DllName, EntryPoint = "CreateMyMathFuncsClass")]
    internal static extern MyMathFuncsSafeHandle CreateMyMathFuncs();
    
    [DllImport(DllName, EntryPoint = "DisposeMyMathFuncsClass")]
    internal static extern void DisposeMyMathFuncs(MyMathFuncsSafeHandle ptr);
    

    Remarque

    Nous transmettons notre dllName constant à l’attribut DllImport ainsi qu’à EntryPoint qui indique explicitement au runtime .NET le nom de la fonction à appeler dans cette bibliothèque. Techniquement, nous n’avons pas besoin de fournir la valeur EntryPoint si nos noms de méthode managée étaient identiques à ceux non gérés. Si l’un n’est pas fourni, le nom de la méthode managée est utilisé comme EntryPoint à la place. Toutefois, il est préférable d’être explicite.

  5. Ajoutez les méthodes wrapper pour nous permettre d’utiliser la classe MyMathFuncs à l’aide du code suivant :

    [DllImport(DllName, EntryPoint = "MyMathFuncsAdd")]
    internal static extern double Add(MyMathFuncsSafeHandle ptr, double a, double b);
    
    [DllImport(DllName, EntryPoint = "MyMathFuncsSubtract")]
    internal static extern double Subtract(MyMathFuncsSafeHandle ptr, double a, double b);
    
    [DllImport(DllName, EntryPoint = "MyMathFuncsMultiply")]
    internal static extern double Multiply(MyMathFuncsSafeHandle ptr, double a, double b);
    
    [DllImport(DllName, EntryPoint = "MyMathFuncsDivide")]
    internal static extern double Divide(MyMathFuncsSafeHandle ptr, double a, double b);
    

    Remarque

    Nous utilisons des types simples pour les paramètres de cet exemple. Étant donné que le marshalling est une copie au niveau du bit dans ce cas, il ne nécessite aucun travail supplémentaire de notre part. Notez également l’utilisation de la classe MyMathFuncs Coffre Handle au lieu de la classe IntPtr standard. IntPtr est automatiquement mappé au Coffre Handle dans le cadre du processus de marshaling.

  6. Vérifiez que la classe MyMathFuncsWrapper terminée apparaît comme suit :

    using System.Runtime.InteropServices;
    
    namespace MathFuncs
    {
        internal static class MyMathFuncsWrapper
        {
            #if Android
                const string DllName = "libMathFuncs.so";
            #else
                const string DllName = "__Internal";
            #endif
    
            [DllImport(DllName, EntryPoint = "CreateMyMathFuncsClass")]
            internal static extern MyMathFuncsSafeHandle CreateMyMathFuncs();
    
            [DllImport(DllName, EntryPoint = "DisposeMyMathFuncsClass")]
            internal static extern void DisposeMyMathFuncs(MyMathFuncsSafeHandle ptr);
    
            [DllImport(DllName, EntryPoint = "MyMathFuncsAdd")]
            internal static extern double Add(MyMathFuncsSafeHandle ptr, double a, double b);
    
            [DllImport(DllName, EntryPoint = "MyMathFuncsSubtract")]
            internal static extern double Subtract(MyMathFuncsSafeHandle ptr, double a, double b);
    
            [DllImport(DllName, EntryPoint = "MyMathFuncsMultiply")]
            internal static extern double Multiply(MyMathFuncsSafeHandle ptr, double a, double b);
    
            [DllImport(DllName, EntryPoint = "MyMathFuncsDivide")]
            internal static extern double Divide(MyMathFuncsSafeHandle ptr, double a, double b);
        }
    }
    

Fin de la classe MyMathFuncs Coffre Handle

  1. Ouvrez la classe MyMathFuncs Coffre Handle, accédez au commentaire TODO de l’espace réservé dans la méthode ReleaseHandle :

    // TODO: Release the handle here
    
  2. Remplacez la ligne TODO :

    MyMathFuncsWrapper.DisposeMyMathFuncs(this);
    

Écriture de la classe MyMathFuncs

Maintenant que le wrapper est terminé, créez une classe MyMathFuncs qui gérera la référence à l’objet C++ MyMathFuncs non managé.

  1. CONTROL + CLICK sur le projet MathFuncs.Shared , puis choisissez Ajouter un fichier... dans le menu Ajouter .

  2. Choisissez Classe vide dans la fenêtre Nouveau fichier, nommez-la MyMathFuncs, puis cliquez sur Nouveau

  3. Ajoutez les membres suivants à la classe MyMathFuncs :

    readonly MyMathFuncsSafeHandle handle;
    
  4. Implémentez le constructeur pour la classe afin qu’elle crée et stocke un handle dans l’objet MyMathFuncs natif lorsque la classe est instanciée :

    public MyMathFuncs()
    {
        handle = MyMathFuncsWrapper.CreateMyMathFuncs();
    }
    
  5. Implémentez l’interface IDisposable à l’aide du code suivant :

    public class MyMathFuncs : IDisposable
    {
        ...
    
        protected virtual void Dispose(bool disposing)
        {
            if (handle != null && !handle.IsInvalid)
                handle.Dispose();
        }
    
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    
        // ...
    }
    
  6. Implémentez les méthodes MyMathFuncs à l’aide de la classe MyMathFuncsWrapper pour effectuer le travail réel sous le capot en passant le pointeur que nous avons stocké dans l’objet non managé sous-jacent. Le code devrait être le suivant :

    public double Add(double a, double b)
    {
        return MyMathFuncsWrapper.Add(handle, a, b);
    }
    
    public double Subtract(double a, double b)
    {
        return MyMathFuncsWrapper.Subtract(handle, a, b);
    }
    
    public double Multiply(double a, double b)
    {
        return MyMathFuncsWrapper.Multiply(handle, a, b);
    }
    
    public double Divide(double a, double b)
    {
        return MyMathFuncsWrapper.Divide(handle, a, b);
    }
    

Création de la nuspec

Pour que la bibliothèque soit empaquetée et distribuée via NuGet, la solution a besoin d’un fichier nuspec . Cela permet d’identifier les assemblys résultants qui seront inclus pour chaque plateforme prise en charge.

  1. CONTROL + CLICK sur la solution MathFuncs, puis choisissez Ajouter un dossier de solution dans le menu Ajouter un nom à SolutionItems.

  2. CONTROL + CLICK dans le dossier SolutionItems , puis choisissez Nouveau fichier... dans le menu Ajouter .

  3. Choisissez Fichier XML vide dans la fenêtre Nouveau fichier, nommez-le MathFuncs.nuspec, puis cliquez sur Nouveau.

  4. Mettez à jour MathFuncs.nuspec avec les métadonnées de package de base à afficher pour le consommateur NuGet . Par exemple :

    <?xml version="1.0"?>
    <package>
        <metadata>
            <id>MathFuncs</id>
            <version>$version$</version>
            <authors>Microsoft Mobile Customer Advisory Team</authors>
            <description>Sample C++ Wrapper Library</description>
            <requireLicenseAcceptance>false</requireLicenseAcceptance>
            <copyright>Copyright 2018</copyright>
        </metadata>
    </package>
    

    Remarque

    Pour plus d’informations sur le schéma utilisé pour ce manifeste, consultez le document de référence nuspec.

  5. Ajoutez un <files> élément en tant qu’enfant de l’élément <package> (juste en dessous <metadata>), identifiant chaque fichier avec un élément distinct <file> :

    <files>
    
        <!-- Android -->
    
        <!-- iOS -->
    
        <!-- netstandard2.0 -->
    
    </files>
    

    Remarque

    Lorsqu’un package est installé dans un projet et qu’il existe plusieurs assemblys spécifiés par le même nom, NuGet choisit efficacement l’assembly le plus spécifique à la plateforme donnée.

  6. Ajoutez les <file> éléments pour les assemblys Android :

    <file src="MathFuncs.Android/bin/Release/MathFuncs.dll" target="lib/MonoAndroid81/MathFuncs.dll" />
    <file src="MathFuncs.Android/bin/Release/MathFuncs.pdb" target="lib/MonoAndroid81/MathFuncs.pdb" />
    
  7. Ajoutez les <file> éléments pour les assemblys iOS :

    <file src="MathFuncs.iOS/bin/Release/MathFuncs.dll" target="lib/Xamarin.iOS10/MathFuncs.dll" />
    <file src="MathFuncs.iOS/bin/Release/MathFuncs.pdb" target="lib/Xamarin.iOS10/MathFuncs.pdb" />
    
  8. Ajoutez les <file> éléments pour les assemblys netstandard2.0 :

    <file src="MathFuncs.Standard/bin/Release/netstandard2.0/MathFuncs.dll" target="lib/netstandard2.0/MathFuncs.dll" />
    <file src="MathFuncs.Standard/bin/Release/netstandard2.0/MathFuncs.pdb" target="lib/netstandard2.0/MathFuncs.pdb" />
    
  9. Vérifiez le manifeste nuspec :

    <?xml version="1.0"?>
    <package>
    <metadata>
        <id>MathFuncs</id>
        <version>$version$</version>
        <authors>Microsoft Mobile Customer Advisory Team</authors>
        <description>Sample C++ Wrapper Library</description>
        <requireLicenseAcceptance>false</requireLicenseAcceptance>
        <copyright>Copyright 2018</copyright>
    </metadata>
    <files>
    
        <!-- Android -->
        <file src="MathFuncs.Android/bin/Release/MathFuncs.dll" target="lib/MonoAndroid81/MathFuncs.dll" />
        <file src="MathFuncs.Android/bin/Release/MathFuncs.pdb" target="lib/MonoAndroid81/MathFuncs.pdb" />
    
        <!-- iOS -->
        <file src="MathFuncs.iOS/bin/Release/MathFuncs.dll" target="lib/Xamarin.iOS10/MathFuncs.dll" />
        <file src="MathFuncs.iOS/bin/Release/MathFuncs.pdb" target="lib/Xamarin.iOS10/MathFuncs.pdb" />
    
        <!-- netstandard2.0 -->
        <file src="MathFuncs.Standard/bin/Release/netstandard2.0/MathFuncs.dll" target="lib/netstandard2.0/MathFuncs.dll" />
        <file src="MathFuncs.Standard/bin/Release/netstandard2.0/MathFuncs.pdb" target="lib/netstandard2.0/MathFuncs.pdb" />
    
    </files>
    </package>
    

    Remarque

    Ce fichier spécifie les chemins de sortie d’assembly d’une build Release . Veillez donc à générer la solution à l’aide de cette configuration.

À ce stade, la solution contient 3 assemblys .NET et un manifeste nuspec de prise en charge.

Distribution du wrapper .NET avec NuGet

L’étape suivante consiste à empaqueter et à distribuer le package NuGet afin qu’il puisse être facilement consommé par l’application et géré en tant que dépendance. L’habillage et la consommation peuvent tous être effectués au sein d’une seule solution, mais la distribution de la bibliothèque via des aides NuGet dans le découplage et nous permet de gérer ces bases de code indépendamment.

Préparation d’un répertoire de packages locaux

La forme la plus simple du flux NuGet est un répertoire local :

  1. Dans Finder, accédez à un répertoire pratique. Par exemple, /Users.
  2. Choisissez Nouveau dossier dans le menu Fichier , en fournissant un nom explicite tel que local-nuget-feed.

Création du package

  1. Définissez la configuration de build sur Release et exécutez une build à l’aide de COMMAND + B.

  2. Ouvrez Terminal et remplacez le répertoire par le dossier contenant le fichier nuspec .

  3. Dans Terminal, exécutez la commande nuget pack spécifiant le fichier nuspec, la version (par exemple, 1.0.0) et le OutputDirectory à l’aide du dossier créé à l’étape précédente, autrement dit, local-nuget-feed. Par exemple :

    nuget pack MathFuncs.nuspec -Version 1.0.0 -OutputDirectory ~/local-nuget-feed
    
  4. Vérifiez que MathFuncs.1.0.0.nupkg a été créé dans le répertoire local-nuget-feed.

[FACULTATIF] Utilisation d’un flux NuGet privé avec Azure DevOps

Une technique plus robuste est décrite dans Prise en main des packages NuGet dans Azure DevOps, qui montre comment créer un flux privé et envoyer (généré à l’étape précédente) le package vers ce flux.

Il est idéal pour que ce flux de travail soit entièrement automatisé, par exemple à l’aide d’Azure Pipelines. Pour plus d’informations, consultez Prise en main d’Azure Pipelines.

Utilisation du wrapper .NET à partir d’une application Xamarin.Forms

Pour effectuer la procédure pas à pas, créez une application Xamarin.Forms pour utiliser le package qui vient d’être publié sur le flux NuGet local.

Création du projet Xamarin.Forms

  1. Ouvrez une nouvelle instance de Visual Studio pour Mac. Cette opération peut être effectuée à partir du terminal :

    open -n -a "Visual Studio"
    
  2. Dans Visual Studio pour Mac, cliquez sur Nouveau projet (à partir de la page d’accueil) ou Nouvelle solution (dans le menu Fichier).

  3. Dans la fenêtre Nouveau projet, choisissez Application Formulaires vides (à partir de l’application multiplateforme>), puis cliquez sur Suivant.

  4. Mettez à jour les champs suivants, puis cliquez sur Suivant :

    • Nom de l’application : MathFuncsApp.
    • Identificateur de l’organisation : utilisez un espace de noms inverse, par exemple com .{your_org}.
    • Plateformes cibles : utilisez la valeur par défaut (cibles Android et iOS).
    • Code partagé : définissez ce paramètre sur .NET Standard (une solution « Bibliothèque partagée » est possible, mais au-delà de l’étendue de cette procédure pas à pas).
  5. Mettez à jour les champs suivants, puis cliquez sur Créer :

    • Nom du projet : MathFuncsApp.
    • Nom de la solution : MathFuncsApp.
    • Emplacement : utilisez l’emplacement d’enregistrement par défaut (ou choisissez une alternative).
  6. Dans Explorateur de solutions, CONTROL + CLICK sur la cible (MathFuncsApp.Android ou MathFuncs.iOS) pour les tests initiaux, puis choisissez Définir en tant que projet de démarrage.

  7. Choisissez l’appareil ou l’émulateur de simulateur/préféré.

  8. Exécutez la solution (COMMAND + RETURN) pour vérifier que le projet Xamarin.Forms basé sur un modèle génère et s’exécute correctement.

    Remarque

    IOS (en particulier le simulateur) a tendance à avoir le temps de génération/déploiement le plus rapide.

Ajout du flux NuGet local à la configuration NuGet

  1. Dans Visual Studio, choisissez Préférences (dans le menu Visual Studio ).

  2. Choisissez Sources sous la section NuGet , puis cliquez sur Ajouter.

  3. Mettez à jour les champs suivants, puis cliquez sur Ajouter une source :

    • Nom : indiquez un nom explicite, par exemple, Local-Packages.
    • Emplacement : spécifiez le dossier local-nuget-feed créé à l’étape précédente.

    Remarque

    Dans ce cas, il n’est pas nécessaire de spécifier un nom d’utilisateur et un mot de passe.

  4. Cliquez sur OK.

Référencement du package

Répétez les étapes suivantes pour chaque projet (MathFuncsApp, MathFuncsApp.Android et MathFuncsApp.iOS).

  1. CONTROL + CLICK sur le projet, puis choisissez Ajouter des packages NuGet... dans le menu Ajouter .
  2. Recherchez MathFuncs.
  3. Vérifiez que la version du package est 1.0.0 et que les autres détails apparaissent comme prévu, tels que le titre et la description, c’est-à-dire MathFuncs et l’exemple de bibliothèque wrapper C++.
  4. Sélectionnez le package MathFuncs , puis cliquez sur Ajouter un package.

Utilisation des fonctions de bibliothèque

À présent, avec une référence au package MathFuncs dans chacun des projets, les fonctions sont disponibles pour le code C#.

  1. Ouvrez MainPage.xaml.cs à partir du projet Xamarin.Formscommun MathFuncsApp (référencé par MathFuncsApp.Android et MathFuncsApp.iOS).

  2. Ajoutez des instructions using pour System.Diagnostics et MathFuncs en haut du fichier :

    using System.Diagnostics;
    using MathFuncs;
    
  3. Déclarez une instance de la MyMathFuncs classe en haut de la MainPage classe :

    MyMathFuncs myMathFuncs;
    
  4. Remplacez les méthodes et OnDisappearing les OnAppearing méthodes de la ContentPage classe de base :

    protected override void OnAppearing()
    {
        base.OnAppearing();
    }
    
    protected override void OnDisappearing()
    {
        base.OnDisappearing();
    }
    
  5. Mettez à jour la OnAppearing méthode pour initialiser la myMathFuncs variable déclarée précédemment :

    protected override void OnAppearing()
    {
        base.OnAppearing();
        myMathFuncs = new MyMathFuncs();
    }
    
  6. Mettez à jour la OnDisappearing méthode pour appeler la Dispose méthode sur myMathFuncs:

    protected override void OnDisappearing()
    {
        base.OnAppearing();
        myMathFuncs.Dispose();
    }
    
  7. Implémentez une méthode privée appelée TestMathFuncs comme suit :

    private void TestMathFuncs()
    {
        var numberA = 1;
        var numberB = 2;
    
        // Test Add function
        var addResult = myMathFuncs.Add(numberA, numberB);
    
        // Test Subtract function
        var subtractResult = myMathFuncs.Subtract(numberA, numberB);
    
        // Test Multiply function
        var multiplyResult = myMathFuncs.Multiply(numberA, numberB);
    
        // Test Divide function
        var divideResult = myMathFuncs.Divide(numberA, numberB);
    
        // Output results
        Debug.WriteLine($"{numberA} + {numberB} = {addResult}");
        Debug.WriteLine($"{numberA} - {numberB} = {subtractResult}");
        Debug.WriteLine($"{numberA} * {numberB} = {multiplyResult}");
        Debug.WriteLine($"{numberA} / {numberB} = {divideResult}");
    }
    
  8. Enfin, appelez TestMathFuncs à la fin de la OnAppearing méthode :

    TestMathFuncs();
    
  9. Exécutez l’application sur chaque plateforme cible et validez la sortie dans le panneau Sortie de l’application comme suit :

    1 + 2 = 3
    1 - 2 = -1
    1 * 2 = 2
    1 / 2 = 0.5
    

    Remarque

    Si vous rencontrez une « DLLNotFoundException » lors du test sur Android ou une erreur de build sur iOS, veillez à case activée que l’architecture du processeur de l’appareil/émulateur/simulateur que vous utilisez est compatible avec le sous-ensemble que nous avons choisi de prendre en charge.

Résumé

Cet article a expliqué comment créer une application Xamarin.Forms qui utilise des bibliothèques natives via un wrapper .NET commun distribué via un package NuGet. L’exemple fourni dans cette procédure pas à pas est intentionnellement très simpliste pour illustrer plus facilement l’approche. Une application réelle devra gérer des complexités telles que la gestion des exceptions, les rappels, le marshaling de types plus complexes et la liaison avec d’autres bibliothèques de dépendances. Un élément clé est le processus par lequel l’évolution du code C++ est coordonnée et synchronisée avec le wrapper et les applications clientes. Ce processus peut varier selon que l’une ou les deux de ces préoccupations sont la responsabilité d’une seule équipe. De toute façon, l’automatisation est un véritable avantage. Voici quelques ressources qui fournissent des lectures plus approfondies sur certains des concepts clés, ainsi que les téléchargements pertinents.

Téléchargements

Exemples

Pour aller plus loin

Articles relatifs au contenu de ce billet