Condividi tramite


Usare librerie C/C++ con Xamarin

Panoramica

Xamarin consente agli sviluppatori di creare app per dispositivi mobili native multipiattaforma con Visual Studio. In genere, le associazioni C# vengono usate per esporre i componenti della piattaforma esistenti agli sviluppatori. Tuttavia, in alcuni casi, le app Xamarin devono usare codebase esistenti. A volte i team non hanno semplicemente tempo, budget o risorse per convertire una codebase di grandi dimensioni, ben testata e altamente ottimizzata in C#.

Visual C++ per lo sviluppo per dispositivi mobili multipiattaforma consente di compilare il codice C/C++ e C# come parte della stessa soluzione, offrendo molti vantaggi, tra cui un'esperienza di debug unificata. Microsoft ha usato C/C++ e Xamarin in questo modo per distribuire app come Hyperlapse Mobile e Pix Fotocamera.

In alcuni casi, tuttavia, esiste un desiderio (o requisito) di mantenere attivi gli strumenti e i processi C/C++ esistenti e di mantenere il codice di libreria separato dall'applicazione, trattando la libreria come se fosse simile a un componente di terze parti. In queste situazioni, la sfida non è solo l'esposizione dei membri pertinenti a C# ma la gestione della libreria come dipendenza. E, naturalmente, automatizzare la maggior parte di questo processo possibile.

Questo post descrive un approccio generale per questo scenario e illustra un semplice esempio.

Background

C/C++ è considerato un linguaggio multipiattaforma, ma è necessario prestare particolare attenzione per garantire che il codice sorgente sia effettivamente multipiattaforma, usando solo C/C++ supportato da tutti i compilatori di destinazione e contenente poco o nessun codice specifico del compilatore o della piattaforma incluso in modo condizionale.

In definitiva, il codice deve essere compilato ed eseguito correttamente in tutte le piattaforme di destinazione, pertanto ciò si riduce alla commonality tra le piattaforme (e i compilatori) di destinazione. I problemi possono comunque derivare da piccole differenze tra i compilatori e quindi i test approfonditi (preferibilmente automatizzati) in ogni piattaforma di destinazione diventano sempre più importanti.

Approccio generale

La figura seguente rappresenta l'approccio a quattro fasi usato per trasformare il codice sorgente C/C++ in una libreria Xamarin multipiattaforma condivisa tramite NuGet e quindi viene usata in un'app Xamarin.Forms.

Approccio generale per l'uso di C/C++ con Xamarin

Le 4 fasi sono:

  1. Compilazione del codice sorgente C/C++ in librerie native specifiche della piattaforma.
  2. Wrapping delle librerie native con una soluzione di Visual Studio.
  3. Compressione e push di un pacchetto NuGet per il wrapper .NET.
  4. Uso del pacchetto NuGet da un'app Xamarin.

Fase 1: Compilazione del codice sorgente C/C++ in librerie native specifiche della piattaforma

L'obiettivo di questa fase è creare librerie native che possono essere chiamate dal wrapper C#. Questo può essere o meno rilevante a seconda della situazione. I numerosi strumenti e processi che possono essere portati a termine in questo scenario comune esulano dall'ambito di questo articolo. Le considerazioni chiave tengono sincronizzata la codebase C/C++ con qualsiasi codice wrapper nativo, unit test sufficienti e automazione della compilazione.

Le librerie nella procedura dettagliata sono state create usando Visual Studio Code con uno script della shell associato. Una versione estesa di questa procedura dettagliata è disponibile nel repository GitHub per dispositivi mobili CAT che illustra questa parte dell'esempio in modo più approfondito. Le librerie native vengono considerate come una dipendenza di terze parti in questo caso, tuttavia questa fase viene illustrata per il contesto.

Per semplicità, la procedura dettagliata è destinata solo a un subset di architetture. Per iOS, usa l'utilità lipo per creare un singolo binario grasso dai singoli file binari specifici dell'architettura. Android userà file binari dinamici con estensione so e iOS userà un file binario fat statico con estensione .a.

Fase 2: Wrapping delle librerie native con una soluzione di Visual Studio

La fase successiva consiste nell'eseguire il wrapping delle librerie native in modo che vengano usate facilmente da .NET. Questa operazione viene eseguita con una soluzione di Visual Studio con quattro progetti. Un progetto condiviso contiene il codice comune. I progetti destinati a ogni Xamarin.Android, Xamarin.iOS e .NET Standard consentono di fare riferimento alla libreria in modo indipendente dalla piattaforma.

Il wrapper usa "l'esca e il cambio di trucco", Questo non è l'unico modo, ma semplifica il riferimento alla libreria ed evita la necessità di gestire in modo esplicito le implementazioni specifiche della piattaforma all'interno dell'applicazione che usa. Il trucco consiste essenzialmente nel garantire che le destinazioni (.NET Standard, Android, iOS) condividono lo stesso spazio dei nomi, nome dell'assembly e struttura di classi. Poiché NuGet preferisce sempre una libreria specifica della piattaforma, la versione di .NET Standard non viene mai usata in fase di esecuzione.

La maggior parte delle operazioni di questo passaggio si concentrerà sull'uso di P/Invoke per chiamare i metodi della libreria nativa e sulla gestione dei riferimenti agli oggetti sottostanti. L'obiettivo è esporre la funzionalità della libreria al consumer astraendo qualsiasi complessità. Gli sviluppatori di Xamarin.Forms non devono avere conoscenze sul funzionamento interno della libreria non gestita. Dovrebbe sembrare che usino qualsiasi altra libreria C# gestita.

In definitiva, l'output di questa fase è un set di librerie .NET, una per destinazione, insieme a un documento nuspec che contiene le informazioni necessarie per compilare il pacchetto nel passaggio successivo.

Fase 3: Compressione e push di un pacchetto NuGet per il wrapper .NET

La terza fase consiste nel creare un pacchetto NuGet usando gli artefatti di compilazione del passaggio precedente. Il risultato di questo passaggio è un pacchetto NuGet che può essere usato da un'app Xamarin. La procedura dettagliata usa una directory locale per fungere da feed NuGet. Nell'ambiente di produzione, questo passaggio deve pubblicare un pacchetto in un feed NuGet pubblico o privato e deve essere completamente automatizzato.

Fase 4: Uso del pacchetto NuGet da un'app Xamarin.Forms

Il passaggio finale consiste nel fare riferimento e usare il pacchetto NuGet da un'app Xamarin.Forms. Ciò richiede la configurazione del feed NuGet in Visual Studio per usare il feed definito nel passaggio precedente.

Dopo aver configurato il feed, è necessario fare riferimento al pacchetto da ogni progetto nell'app Xamarin.Forms multipiattaforma. 'The bait-and-switch trick' fornisce interfacce identiche, quindi la funzionalità della libreria nativa può essere chiamata usando il codice definito in un'unica posizione.

Il repository del codice sorgente contiene un elenco di altre informazioni che includono articoli su come configurare un feed NuGet privato in Azure DevOps e su come eseguire il push del pacchetto in tale feed. Anche se è necessario un po ' di tempo di configurazione più di una directory locale, questo tipo di feed è migliore in un ambiente di sviluppo del team.

Procedura dettagliata

I passaggi forniti sono specifici per Visual Studio per Mac, ma la struttura funziona anche in Visual Studio 2017.

Prerequisiti

Per seguire la procedura, lo sviluppatore dovrà:

Nota

Per distribuire le app in un i Telefono è necessario un account sviluppatore Apple attivo.

Creazione delle librerie native (fase 1)

La funzionalità della libreria nativa si basa sull'esempio di Procedura dettagliata: Creazione e uso di una libreria statica (C++).

Questa procedura dettagliata ignora la prima fase, creando le librerie native, poiché la libreria viene fornita come dipendenza di terze parti in questo scenario. Le librerie native precompilate sono incluse insieme al codice di esempio o possono essere scaricate direttamente.

Uso della libreria nativa

L'esempio mathFuncsLib originale include una singola classe denominata MyMathFuncs con la definizione seguente:

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);
    };
}

Una classe aggiuntiva definisce le funzioni wrapper che consentono a un consumer .NET di creare, eliminare e interagire con la classe nativa MyMathFuncs sottostante.

#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);
}

Queste funzioni wrapper verranno usate sul lato Xamarin .

Wrapping della libreria nativa (fase 2)

Questa fase richiede le librerie precompilate descritte nella sezione precedente.

Creazione della soluzione Visual Studio

  1. In Visual Studio per Mac fare clic su Nuovo progetto (dalla pagina iniziale) o Su nuova soluzione (dal menu File).

  2. Nella finestra Nuovo progetto scegliere Progetto condiviso (dall'interno della libreria multipiattaforma>) e quindi fare clic su Avanti.

  3. Aggiornare i campi seguenti e quindi fare clic su Crea:

    • Nome progetto: MathFuncs.Shared
    • Nome soluzione: MathFuncs
    • Percorso: usare il percorso di salvataggio predefinito (o scegliere un'alternativa)
    • Creare un progetto all'interno della directory della soluzione: impostare questa opzione su selezionata
  4. Da Esplora soluzioni fare doppio clic sul progetto MathFuncs.Shared e passare a Impostazioni principale.

  5. Rimuovere . Condiviso dallo spazio dei nomi predefinito in modo che sia impostato solo su MathFuncs , quindi fare clic su OK.

  6. Aprire MyClass.cs (creato dal modello), quindi rinominare sia la classe che il nome file in MyMathFuncsWrapper e modificare lo spazio dei nomi in MathFuncs.

  7. CTRL + CLIC sulla soluzione MathFuncs, quindi scegliere Aggiungi nuovo progetto... dal menu Aggiungi .

  8. Nella finestra Nuovo progetto scegliere Libreria .NET Standard (dalla libreria multipiattaforma>) e quindi fare clic su Avanti.

  9. Scegliere .NET Standard 2.0 e quindi fare clic su Avanti.

  10. Aggiornare i campi seguenti e quindi fare clic su Crea:

    • Nome progetto: MathFuncs.Standard
    • Percorso: usare la stessa posizione di salvataggio del progetto condiviso
  11. Da Esplora soluzioni fare doppio clic sul progetto MathFuncs.Standard.

  12. Passare a Impostazioni principale, quindi aggiornare spazio dei nomi predefinito a MathFuncs.

  13. Passare alle impostazioni di output e quindi aggiornare Il nome dell'assembly in MathFuncs.

  14. Passare alle impostazioni del compilatore , impostare Configurazionesu Rilascio, impostare Informazioni di debug su Simboli solo e quindi fare clic su OK.

  15. Eliminare Class1.cs/Introduzione dal progetto (se una di queste è stata inclusa come parte del modello).

  16. CTRL + CLIC sulla cartella Dipendenze/Riferimenti del progetto, quindi scegliere Modifica riferimenti.

  17. Selezionare MathFuncs.Shared nella scheda Progetti e quindi fare clic su OK.

  18. Ripetere i passaggi da 7 a 17 (ignorando il passaggio 9) usando le configurazioni seguenti:

    NOME PROGETTO NOME MODELLO MENU NUOVO PROGETTO
    MathFuncs.Android Libreria di classi Libreria Android >
    MathFuncs.iOS Libreria di binding Libreria iOS >
  19. Da Esplora soluzioni fare doppio clic sul progetto MathFuncs.Android e quindi passare alle impostazioni del compilatore.

  20. Con l'opzione Configurazione impostata su Debug, modificare Definisci simboli per includere Android;.

  21. Modificare Configurazione in Rilascio, quindi modificare Definisci simboli per includere anche Android;.

  22. Ripetere i passaggi da 19 a 20 per MathFuncs.iOS, modificando Definisci simboli per includere iOS, anziché Android , in entrambi i casi.

  23. Compilare la soluzione in Configurazione versione (CONTROL + COMMAND + B) e verificare che tutti e tre gli assembly di output (Android, iOS, .NET Standard) (nelle rispettive cartelle bin del progetto) condivida lo stesso nome MathFuncs.dll.

In questa fase, la soluzione deve avere tre destinazioni, una per Android, iOS e .NET Standard e un progetto condiviso a cui fanno riferimento ognuna delle tre destinazioni. Questi devono essere configurati in modo da usare gli stessi assembly di output e dello spazio dei nomi predefiniti con lo stesso nome. Questo è necessario per l'approccio "esca e switch" menzionato in precedenza.

Aggiunta delle librerie native

Il processo di aggiunta delle librerie native alla soluzione wrapper varia leggermente tra Android e iOS.

Riferimenti nativi per MathFuncs.Android

  1. CTRL + CLIC sul progetto MathFuncs.Android , quindi scegliere Nuova cartella dal menu Aggiungi denominandolo lib.

  2. Per ogni ABI (Application Binary Interface), CONTROL + CLICK sulla cartella lib , quindi scegliere Nuova cartella dal menu Aggiungi , assegnandogli un nome dopo il rispettivo ABI. In questo caso:

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

    Nota

    Per una panoramica più dettagliata, vedere l'argomento Architetture e CPU della guida per sviluppatori NDK, in particolare la sezione relativa all'indirizzamento del codice nativo nei pacchetti dell'app.

  3. Verificare la struttura di cartelle:

    - lib
        - arm64-v8a
        - armeabi-v7a
        - x86
        - x86_64
    
  4. Aggiungere le librerie .so corrispondenti a ognuna delle cartelle ABI in base al mapping seguente:

    arm64-v8a: lib/Android/arm64

    armeabi-v7a: lib/Android/arm

    x86: lib/Android/x86

    x86_64: lib/Android/x86_64

    Nota

    Per aggiungere file, CTRL + CLIC sulla cartella che rappresenta il rispettivo ABI, quindi scegliere Aggiungi file dalmenu Aggiungi . Scegliere la libreria appropriata (dalla directory PrecompiledLibs) e quindi fare clic su Apri e quindi fare clic su OK lasciando l'opzione predefinita per Copiare il file nella directory.

  5. Per ognuno dei file con estensione so , CTRL + CLIC e quindi scegliere l'opzione EmbeddedNativeLibrary dal menu Azione di compilazione.

La cartella lib dovrebbe ora essere visualizzata nel modo seguente:

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

Riferimenti nativi per MathFuncs.iOS

  1. CTRL + CLIC sul progetto MathFuncs.iOS , quindi scegliere Aggiungi riferimento nativo dal menu Aggiungi .

  2. Scegliere la libreria libMathFuncs.a (da libs/ios nella directory PrecompiledLibs ) e quindi fare clic su Apri

  3. CTRL + CLIC sul file libMathFuncs (all'interno della cartella Riferimenti nativi, quindi scegliere l'opzione Proprietà dal menu

  4. Configurare le proprietà di riferimento nativo in modo che vengano controllate (mostrando un'icona di graduazione) nel riquadro Proprietà :

    • Forzare il caricamento
    • Is C++
    • Collegamento intelligente

    Nota

    L'uso di un tipo di progetto della libreria di binding insieme a un riferimento nativo incorpora la libreria statica e consente di collegarlo automaticamente all'app Xamarin.iOS che vi fa riferimento (anche quando viene inclusa tramite un pacchetto NuGet).

  5. Aprire ApiDefinition.cs, eliminare il codice commentato basato su modelli (lasciando solo lo spazio dei MathFuncs nomi), quindi eseguire lo stesso passaggio per Structs.cs

    Nota

    Un progetto di libreria di binding richiede questi file (con le azioni di compilazione ObjCBindingApiDefinition e ObjCBindingCoreSource ) per la compilazione. Tuttavia, il codice verrà scritto per chiamare la libreria nativa, all'esterno di questi file in modo che possa essere condiviso tra le destinazioni della libreria Android e iOS usando P/Invoke standard.

Scrittura del codice della libreria gestita

Scrivere ora il codice C# per chiamare la libreria nativa. L'obiettivo è nascondere qualsiasi complessità sottostante. Il consumer non deve avere alcuna conoscenza funzionante degli elementi interni della libreria nativa o dei concetti di P/Invoke.

Creazione di un Cassaforte Handle

  1. CTRL + CLIC sul progetto MathFuncs.Shared , quindi scegliere Aggiungi file dalmenu Aggiungi .

  2. Scegliere Classe vuota nella finestra Nuovo file, denominarla MyMathFuncs Cassaforte Handle e quindi fare clic su Nuovo

  3. Implementare la classe MyMathFuncs Cassaforte 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;
            }
        }
    }
    

    Nota

    Un Cassaforte Handle è il modo migliore per lavorare con risorse non gestite nel codice gestito. Questo elimina un sacco di codice boilerplate correlato alla finalizzazione critica e al ciclo di vita degli oggetti. Il proprietario di questo handle può successivamente trattarlo come qualsiasi altra risorsa gestita e non dovrà implementare il modello eliminabile completo.

Creazione della classe wrapper interna

  1. Aprire MyMathFuncsWrapper.cs, passando a una classe statica interna

    namespace MathFuncs
    {
        internal static class MyMathFuncsWrapper
        {
        }
    }
    
  2. Nello stesso file aggiungere l'istruzione condizionale seguente alla classe :

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

    Nota

    Imposta il valore della costante DllName in base al fatto che la libreria venga compilata per Android o iOS. Questo consiste nell'affrontare le diverse convenzioni di denominazione usate da ogni rispettiva piattaforma, ma anche il tipo di libreria in uso in questo caso. Android usa una libreria dinamica e quindi prevede un nome file incluso l'estensione. Per iOS è necessario "__Internal" perché si usa una libreria statica.

  3. Aggiungere un riferimento a System.Runtime.InteropServices nella parte superiore del file MyMathFuncsWrapper.cs

    using System.Runtime.InteropServices;
    
  4. Aggiungere i metodi wrapper per gestire la creazione e l'eliminazione della classe MyMathFuncs :

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

    Nota

    Si passa la costante DllName all'attributo DllImport insieme a EntryPoint che indica in modo esplicito al runtime .NET il nome della funzione da chiamare all'interno della libreria. Tecnicamente, non è necessario fornire il valore EntryPoint se i nomi dei metodi gestiti sono identici a quello non gestito. Se non ne viene specificato uno, il nome del metodo gestito verrà usato come EntryPoint . Tuttavia, è meglio essere espliciti.

  5. Aggiungere i metodi wrapper per consentire l'uso della classe MyMathFuncs usando il codice seguente:

    [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);
    

    Nota

    In questo esempio vengono usati tipi semplici per i parametri. Poiché il marshalling è una copia bit per bit in questo caso non richiede alcun lavoro aggiuntivo da parte nostra. Si noti anche l'uso della classe MyMathFuncs Cassaforte Handle anziché di IntPtr standard. Il mapping di IntPtr viene eseguito automaticamente al Cassaforte Handle come parte del processo di marshalling.

  6. Verificare che la classe MyMathFuncsWrapper completata sia visualizzata come indicato di seguito:

    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);
        }
    }
    

Completamento della classe MyMathFuncs Cassaforte Handle

  1. Aprire la classe MyMathFuncs Cassaforte Handle, passare al commento TODO segnaposto all'interno del metodo ReleaseHandle:

    // TODO: Release the handle here
    
  2. Sostituire la riga TODO :

    MyMathFuncsWrapper.DisposeMyMathFuncs(this);
    

Scrittura della classe MyMathFuncs

Ora che il wrapper è completo, creare una classe MyMathFuncs che gestirà il riferimento all'oggetto MyMathFuncs C++ non gestito.

  1. CTRL + CLIC sul progetto MathFuncs.Shared , quindi scegliere Aggiungi file dalmenu Aggiungi .

  2. Scegliere Classe vuota nella finestra Nuovo file, denominarla MyMathFuncs e quindi fare clic su Nuovo

  3. Aggiungere i membri seguenti alla classe MyMathFuncs :

    readonly MyMathFuncsSafeHandle handle;
    
  4. Implementare il costruttore per la classe in modo che crei e archivii un handle per l'oggetto MyMathFuncs nativo quando viene creata un'istanza della classe:

    public MyMathFuncs()
    {
        handle = MyMathFuncsWrapper.CreateMyMathFuncs();
    }
    
  5. Implementare l'interfaccia IDisposable usando il codice seguente:

    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. Implementare i metodi MyMathFuncs usando la classe MyMathFuncsWrapper per eseguire il lavoro reale sotto le quinte passando il puntatore archiviato all'oggetto non gestito sottostante. Il codice dovrebbe essere il seguente:

    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);
    }
    

Creazione di nuspec

Per avere la libreria in pacchetto e distribuita tramite NuGet, la soluzione necessita di un file nuspec . In questo modo verrà identificato quale degli assembly risultanti verrà incluso per ogni piattaforma supportata.

  1. CTRL + CLIC sulla soluzione MathFuncs, quindi scegliere Aggiungi cartella soluzione dal menu Aggiungi assegnando un nome SolutionItems.

  2. CTRL + CLIC sulla cartella SolutionItems , quindi scegliere Nuovo file dalmenu Aggiungi .

  3. Scegliere File XML vuoto nella finestra Nuovo file , denominarlo MathFuncs.nuspec e quindi fare clic su Nuovo.

  4. Aggiornare MathFuncs.nuspec con i metadati di base del pacchetto da visualizzare al consumer NuGet . Ad esempio:

    <?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>
    

    Nota

    Per altri dettagli sullo schema usato per questo manifesto, vedere il documento di riferimento nuspec.

  5. Aggiungere un <files> elemento come elemento figlio dell'elemento <package> (appena sotto <metadata>), identificando ogni file con un elemento separato <file> :

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

    Nota

    Quando un pacchetto viene installato in un progetto e in cui sono presenti più assembly specificati con lo stesso nome, NuGet sceglierà effettivamente l'assembly più specifico per la piattaforma specificata.

  6. Aggiungere gli <file> elementi per gli assembly 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. Aggiungere gli <file> elementi per gli assembly 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. Aggiungere gli <file> elementi per gli assembly 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. Verificare il manifesto 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>
    

    Nota

    Questo file specifica i percorsi di output dell'assembly di una build di rilascio , quindi assicurarsi di compilare la soluzione usando tale configurazione.

A questo punto, la soluzione contiene 3 assembly .NET e un manifesto nuspec di supporto.

Distribuzione del wrapper .NET con NuGet

Il passaggio successivo consiste nel creare un pacchetto e distribuire il pacchetto NuGet in modo che possa essere facilmente usato dall'app e gestito come dipendenza. Il wrapping e il consumo possono essere eseguiti tutti all'interno di una singola soluzione, ma distribuendo la libreria tramite Strumenti NuGet in disaccoppiamento e consente di gestire queste codebase in modo indipendente.

Preparazione di una directory dei pacchetti locali

La forma più semplice del feed NuGet è una directory locale:

  1. In Finder passare a una directory comoda. Ad esempio, /Users.
  2. Scegliere Nuova cartella dal menu File , specificando un nome significativo, ad esempio local-nuget-feed.

Creazione del pacchetto

  1. Impostare Build Configuration (Configurazione compilazione) su Release (Rilascio) ed eseguire una compilazione usando COMMAND + B.

  2. Aprire Terminale e passare alla cartella contenente il file nuspec .

  3. In Terminale eseguire il comando nuget pack specificando il file nuspec, la versione (ad esempio, 1.0.0) e outputDirectory usando la cartella creata nel passaggio precedente, ovvero local-nuget-feed. Ad esempio:

    nuget pack MathFuncs.nuspec -Version 1.0.0 -OutputDirectory ~/local-nuget-feed
    
  4. Verificare che MathFuncs.1.0.0.nupkg sia stato creato nella directory local-nuget-feed .

[FACOLTATIVO] Uso di un feed NuGet privato con Azure DevOps

Una tecnica più affidabile è descritta in Introduzione ai pacchetti NuGet in Azure DevOps, che illustra come creare un feed privato ed eseguire il push del pacchetto (generato nel passaggio precedente) in tale feed.

È ideale avere questo flusso di lavoro completamente automatizzato, ad esempio usando Azure Pipelines. Per altre informazioni, vedere Introduzione ad Azure Pipelines.

Uso del wrapper .NET da un'app Xamarin.Forms

Per completare la procedura dettagliata, creare un'app Xamarin.Forms per usare il pacchetto appena pubblicato nel feed NuGet locale.

Creazione del progetto Xamarin.Forms

  1. Aprire una nuova istanza di Visual Studio per Mac. Questa operazione può essere eseguita dal terminale:

    open -n -a "Visual Studio"
    
  2. In Visual Studio per Mac fare clic su Nuovo progetto (dalla pagina iniziale) o Su nuova soluzione (dal menu File).

  3. Nella finestra Nuovo progetto scegliere App Moduli vuoti (dall'interno dell'app multipiattaforma>) e quindi fare clic su Avanti.

  4. Aggiornare i campi seguenti e quindi fare clic su Avanti:

    • Nome app: MathFuncsApp.
    • Identificatore organizzazione: usare uno spazio dei nomi inverso, ad esempio com.{your_org}.
    • Piattaforme di destinazione: usare il valore predefinito (destinazioni Android e iOS).
    • Codice condiviso: impostare questa opzione su .NET Standard (è possibile una soluzione "Libreria condivisa", ma oltre l'ambito di questa procedura dettagliata).
  5. Aggiornare i campi seguenti e quindi fare clic su Crea:

    • Nome progetto: MathFuncsApp.
    • Nome soluzione: MathFuncsApp.
    • Percorso: usare il percorso di salvataggio predefinito (o scegliere un'alternativa).
  6. In Esplora soluzioni, CTRL + CLIC sulla destinazione (MathFuncsApp.Android o MathFuncs.iOS) per i test iniziali, quindi scegliere Imposta come progetto di avvio.

  7. Scegliere il dispositivo o l'emulatore del simulatore/ preferito.

  8. Eseguire la soluzione (COMMAND + RETURN) per verificare che il progetto Xamarin.Forms basato su modelli venga compilato ed eseguito correttamente.

    Nota

    iOS (in particolare il simulatore) tende a avere il tempo di compilazione/distribuzione più veloce.

Aggiunta del feed NuGet locale alla configurazione di NuGet

  1. In Visual Studio scegliere Preferenze dal menu di Visual Studio .

  2. Scegliere Origini nella sezione NuGet e quindi fare clic su Aggiungi.

  3. Aggiornare i campi seguenti e quindi fare clic su Aggiungi origine:

    • Nome: specificare un nome significativo, ad esempio Local-Packages.
    • Percorso: specificare la cartella local-nuget-feed creata nel passaggio precedente.

    Nota

    In questo caso non è necessario specificare un nome utente e una password.

  4. Fare clic su OK.

Riferimento al pacchetto

Ripetere i passaggi seguenti per ogni progetto (MathFuncsApp, MathFuncsApp.Android e MathFuncsApp.iOS).

  1. CTRL + CLIC sul progetto, quindi scegliere Aggiungi pacchetti NuGet dalmenu Aggiungi .
  2. Cercare MathFuncs.
  3. Verificare che la versione del pacchetto sia 1.0.0 e gli altri dettagli vengano visualizzati come previsto, ad esempio Titolo e Descrizione, ovvero MathFuncs e Libreria wrapper C++ di esempio.
  4. Selezionare il pacchetto MathFuncs , quindi fare clic su Aggiungi pacchetto.

Uso delle funzioni di libreria

Ora, con un riferimento al pacchetto MathFuncs in ognuno dei progetti, le funzioni sono disponibili per il codice C#.

  1. Aprire MainPage.xaml.cs dall'interno del progetto Xamarin.Formscomune MathFuncsApp (a cui fanno riferimento sia MathFuncsApp.Android che MathFuncsApp.iOS).

  2. Aggiungere istruzioni using per System.Diagnostics e MathFuncs all'inizio del file:

    using System.Diagnostics;
    using MathFuncs;
    
  3. Dichiarare un'istanza della MyMathFuncs classe all'inizio della MainPage classe :

    MyMathFuncs myMathFuncs;
    
  4. Eseguire l'override dei OnAppearing metodi e OnDisappearing dalla ContentPage classe base:

    protected override void OnAppearing()
    {
        base.OnAppearing();
    }
    
    protected override void OnDisappearing()
    {
        base.OnDisappearing();
    }
    
  5. Aggiornare il OnAppearing metodo per inizializzare la myMathFuncs variabile dichiarata in precedenza:

    protected override void OnAppearing()
    {
        base.OnAppearing();
        myMathFuncs = new MyMathFuncs();
    }
    
  6. Aggiornare il OnDisappearing metodo per chiamare il Dispose metodo in myMathFuncs:

    protected override void OnDisappearing()
    {
        base.OnAppearing();
        myMathFuncs.Dispose();
    }
    
  7. Implementare un metodo privato denominato TestMathFuncs come indicato di seguito:

    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. Infine, chiamare TestMathFuncs alla fine del OnAppearing metodo:

    TestMathFuncs();
    
  9. Eseguire l'app in ogni piattaforma di destinazione e convalidare l'output nel riquadro di output dell'applicazione viene visualizzato come segue:

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

    Nota

    Se si verifica un 'DLLNotFoundException' durante il test in Android o un errore di compilazione in iOS, assicurarsi di verificare che l'architettura della CPU dell'emulatore/dispositivo/simulatore in uso sia compatibile con il subset scelto per il supporto.

Riepilogo

Questo articolo ha illustrato come creare un'app Xamarin.Forms che usa librerie native tramite un wrapper .NET comune distribuito tramite un pacchetto NuGet. L'esempio fornito in questa procedura dettagliata è intenzionalmente molto semplice per illustrare più facilmente l'approccio. Un'applicazione reale dovrà gestire complessità come la gestione delle eccezioni, i callback, il marshalling di tipi più complessi e il collegamento ad altre librerie di dipendenze. Una considerazione fondamentale è il processo in base al quale l'evoluzione del codice C++ è coordinata e sincronizzata con le applicazioni wrapper e client. Questo processo può variare a seconda che una o entrambe le preoccupazioni siano responsabilità di un singolo team. In entrambi i casi, l'automazione è un vantaggio reale. Di seguito sono riportate alcune risorse che forniscono ulteriori informazioni su alcuni dei concetti chiave insieme ai download pertinenti.

Download

Esempi

Altre informazioni

Articoli relativi al contenuto di questo post