Compartilhar via


Usar bibliotecas C/C++ com o Xamarin

Visão geral

O Xamarin permite que os desenvolvedores criem aplicativos móveis nativos de plataforma cruzada com o Visual Studio. Geralmente, as associações C# são usadas para expor componentes de plataforma existentes aos desenvolvedores. No entanto, há momentos em que os aplicativos Xamarin precisam trabalhar com bases de código existentes. Às vezes, as equipes simplesmente não têm tempo, orçamento ou recursos para portar uma base de código grande, bem testada e altamente otimizada para C#.

O Visual C++ para desenvolvimento móvel entre plataformas permite que o código C/C++ e C# seja criado como parte da mesma solução, oferecendo muitas vantagens, incluindo uma experiência de depuração unificada. A Microsoft usou C/C++ e Xamarin dessa forma para entregar aplicativos como Hyperlapse Mobile e Pix Camera.

No entanto, em alguns casos, há um desejo (ou requisito) de manter as ferramentas e processos C/C++ existentes no lugar e manter o código da biblioteca desacoplado do aplicativo, tratando a biblioteca como se fosse semelhante a um componente de terceiros. Nessas situações, o desafio não é apenas expor os membros relevantes ao C#, mas gerenciar a biblioteca como uma dependência. E, claro, automatizar o máximo possível desse processo.

Este post descreve uma abordagem de alto nível para esse cenário e apresenta um exemplo simples.

Tela de fundo

C/C++ é considerada uma linguagem multiplataforma, mas muito cuidado deve ser tomado para garantir que o código-fonte seja realmente multiplataforma, usando apenas C/C++ suportado por todos os compiladores de destino e contendo pouca ou nenhuma plataforma condicionalmente incluída ou código específico do compilador.

Em última análise, o código deve ser compilado e executado com sucesso em todas as plataformas de destino, portanto, isso se resume à semelhança entre as plataformas (e compiladores) que estão sendo direcionadas. Os problemas ainda podem surgir de pequenas diferenças entre os compiladores e, portanto, testes completos (de preferência automatizados) em cada plataforma de destino se tornam cada vez mais importantes.

Abordagem de alto nível

A ilustração abaixo representa a abordagem de quatro estágios usada para transformar o código-fonte C/C++ em uma biblioteca Xamarin de plataforma cruzada que é compartilhada via NuGet e, em seguida, é consumida em um aplicativo Xamarin.Forms.

Abordagem de alto nível para usar C/C++ com Xamarin

As 4 etapas são:

  1. Compilando o código-fonte C/C++ em bibliotecas nativas específicas da plataforma.
  2. Encapsulando as bibliotecas nativas com uma solução do Visual Studio.
  3. Empacotando e enviando um pacote NuGet para o wrapper .NET.
  4. Consumindo o pacote NuGet de um aplicativo Xamarin.

Estágio 1: Compilando o código-fonte C/C++ em bibliotecas nativas específicas da plataforma

O objetivo desta etapa é criar bibliotecas nativas que podem ser chamadas pelo wrapper C#. Isso pode ou não ser relevante, dependendo da sua situação. As muitas ferramentas e processos que podem ser trazidos à tona nesse cenário comum estão além do escopo deste artigo. As principais considerações são manter a base de código C/C++ sincronizada com qualquer código wrapper nativo, testes de unidade suficientes e automação de compilação.

As bibliotecas no passo a passo foram criadas usando o Visual Studio Code com um script de shell acompanhante. Uma versão estendida deste passo a passo pode ser encontrada no repositório GitHub do CAT móvel que discute essa parte do exemplo com mais profundidade. As bibliotecas nativas estão sendo tratadas como uma dependência de terceiros neste caso, no entanto, este estágio é ilustrado para o contexto.

Para simplificar, o passo a passo destina-se apenas a um subconjunto de arquiteturas. Para iOS, ele usa o utilitário lipo para criar um único binário gordo a partir dos binários específicos da arquitetura individual. O Android usará binários dinâmicos com uma extensão .so e o iOS usará um binário fat estático com uma extensão .a.

Estágio 2: Encapsulando as bibliotecas nativas com uma solução do Visual Studio

O próximo estágio é encapsular as bibliotecas nativas para que elas sejam facilmente usadas a partir do .NET. Isso é feito com uma solução do Visual Studio com quatro projetos. Um projeto compartilhado contém o código comum. Projetos direcionados a cada um dos Xamarin.Android, Xamarin.iOS e .NET Standard permitem que a biblioteca seja referenciada de maneira independente de plataforma.

O invólucro usa "a isca e o truque de trocar". Essa não é a única maneira, mas facilita a referência à biblioteca e evita a necessidade de gerenciar explicitamente implementações específicas da plataforma dentro do próprio aplicativo de consumo. O truque é essencialmente garantir que os destinos (.NET Standard, Android, iOS) compartilhem o mesmo namespace, nome de assembly e estrutura de classe. Como o NuGet sempre prefere uma biblioteca específica da plataforma, a versão do .NET Standard nunca é usada em tempo de execução.

A maior parte do trabalho nesta etapa se concentrará no uso de P/Invoke para chamar os métodos de biblioteca nativa e no gerenciamento das referências aos objetos subjacentes. O objetivo é expor a funcionalidade da biblioteca ao consumidor, abstraindo qualquer complexidade. Os desenvolvedores do Xamarin.Forms não precisam ter conhecimento prático sobre o funcionamento interno da biblioteca não gerenciada. Deve parecer que eles estão usando qualquer outra biblioteca C# gerenciada.

Em última análise, a saída desse estágio é um conjunto de bibliotecas .NET, uma por destino, juntamente com um documento nuspec que contém as informações necessárias para compilar o pacote na próxima etapa.

Estágio 3: Empacotando e enviando um pacote NuGet para o wrapper .NET

O terceiro estágio é a criação de um pacote NuGet usando os artefatos de compilação da etapa anterior. O resultado desta etapa é um pacote NuGet que pode ser consumido de um aplicativo Xamarin. O passo a passo usa um diretório local para servir como o feed NuGet. Na produção, essa etapa deve publicar um pacote em um feed NuGet público ou privado e deve ser totalmente automatizada.

Estágio 4: Consumindo o pacote NuGet de um aplicativo Xamarin.Forms

A etapa final é fazer referência e usar o pacote NuGet de um aplicativo Xamarin.Forms. Isso requer a configuração do feed NuGet no Visual Studio para usar o feed definido na etapa anterior.

Depois que o feed é configurado, o pacote precisa ser referenciado de cada projeto no aplicativo Xamarin.Forms de plataforma cruzada. O 'truque de isca e comutação' fornece interfaces idênticas, para que a funcionalidade da biblioteca nativa possa ser chamada usando código definido em um único local.

O repositório de código-fonte contém uma lista de leituras adicionais que inclui artigos sobre como configurar um feed NuGet privado no Azure DevOps e como enviar o pacote por push para esse feed. Embora exija um pouco mais de tempo de configuração do que um diretório local, esse tipo de feed é melhor em um ambiente de desenvolvimento de equipe.

Passo a passo

As etapas fornecidas são específicas do Visual Studio para Mac, mas a estrutura também funciona no Visual Studio 2017 .

Pré-requisitos

Para acompanhar, o desenvolvedor precisará:

Observação

É necessária uma Conta de Programador Apple ativa para implementar aplicações num iPhone.

Criando as bibliotecas nativas (Estágio 1)

A funcionalidade de biblioteca nativa é baseada no exemplo de Passo a passo: Criando e usando uma biblioteca estática (C++).

Este passo a passo ignora o primeiro estágio, criando as bibliotecas nativas, já que a biblioteca é fornecida como uma dependência de terceiros nesse cenário. As bibliotecas nativas pré-compiladas são incluídas junto com o código de exemplo ou podem ser baixadas diretamente.

Trabalhando com a biblioteca nativa

O exemplo MathFuncsLib original inclui uma única classe chamada MyMathFuncs com a seguinte definição:

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

Uma classe adicional define funções de wrapper que permitem que um consumidor .NET crie, descarte e interaja com a classe nativa MyMathFuncs subjacente.

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

Serão essas funções de wrapper que serão usadas no lado do Xamarin .

Encapsulando a biblioteca nativa (Estágio 2)

Este estágio requer as bibliotecas pré-compiladas descritas na seção anterior.

Criando a solução do Visual Studio

  1. No Visual Studio para Mac, clique em Novo Projeto (na Página de Boas-vindas) ou Nova Solução (no menu Arquivo ).

  2. Na janela Novo Projeto, escolha Projeto Compartilhado (de dentro da Biblioteca Multiplataforma>) e clique em Avançar.

  3. Atualize os seguintes campos e clique em Criar:

    • Nome do projeto: MathFuncs.Shared
    • Nome da solução: MathFuncs
    • Local: use o local de salvamento padrão (ou escolha uma alternativa)
    • Criar um projeto dentro do diretório da solução: defina isso como marcado
  4. No Gerenciador de Soluções, clique duas vezes no projeto MathFuncs.Shared e navegue até Configurações principais.

  5. Remover . Compartilhado a partir do namespace padrão para que ele seja definido como MathFuncs somente e, em seguida, clique em OK.

  6. Abra MyClass.cs (criado pelo modelo), renomeie a classe e o nome do arquivo para MyMathFuncsWrapper e altere o namespace para MathFuncs.

  7. CONTROL + CLIQUE na solução MathFuncs, em seguida, escolha Adicionar novo projeto...no menu Adicionar.

  8. Na janela Novo Projeto, escolha .NET Standard Library (de dentro da Biblioteca Multiplataforma>) e clique em Avançar.

  9. Escolha .NET Standard 2.0 e clique em Avançar.

  10. Atualize os seguintes campos e clique em Criar:

    • Nome do projeto: MathFuncs.Standard
    • Local: use o mesmo local de salvamento do projeto compartilhado
  11. No Gerenciador de Soluções, clique duas vezes no projeto MathFuncs.Standard.

  12. Navegue até Configurações principais e atualize o namespace padrão para MathFuncs.

  13. Navegue até as configurações de Saída e atualize o nome do assembly para MathFuncs.

  14. Navegue até as configurações do compilador, altere a configuração para liberar, definindo informações de depuração como Somente símbolos e clique em OK.

  15. Exclua Class1.cs/Introdução do projeto (se um deles tiver sido incluído como parte do modelo).

  16. CONTROL + CLICK na pasta Dependências/Referências do projeto e escolha Editar referências.

  17. Selecione MathFuncs.Shared na guia Projetos e clique em OK.

  18. Repita as etapas 7 a 17 (ignorando a etapa 9) usando as seguintes configurações:

    NOME DO PROJETO NOME DO MODELO MENU NOVO PROJETO
    MathFuncs.Android Biblioteca de Classes Biblioteca Android >
    MathFuncs.iOS Biblioteca de vinculação Biblioteca do iOS >
  19. No Gerenciador de Soluções, clique duas vezes no projeto MathFuncs.Android e navegue até as configurações do compilador.

  20. Com a Configuração definida como Depurar, edite Definir símbolos para incluir o Android;.

  21. Altere a Configuração para Liberar e, em seguida, edite Definir símbolos para incluir também o Android;.

  22. Repita as etapas 19 a 20 para MathFuncs.iOS, editando Definir símbolos para incluir iOS, em vez de Android, em ambos os casos.

  23. Crie a solução na configuração Release (CONTROL + COMMAND + B) e valide se todos os três assemblies de saída (Android, iOS, .NET Standard) (nas respectivas pastas bin do projeto) compartilham o mesmo nome MathFuncs.dll.

Neste estágio, a solução deve ter três destinos, um para Android, iOS e .NET Standard e um projeto compartilhado que é referenciado por cada um dos três destinos. Eles devem ser configurados para usar o mesmo namespace padrão e assemblies de saída com o mesmo nome. Isso é necessário para a abordagem de "isca e interruptor" mencionada anteriormente.

Adicionando as bibliotecas nativas

O processo de adicionar as bibliotecas nativas à solução wrapper varia ligeiramente entre Android e iOS.

Referências nativas para MathFuncs.Android

  1. CONTROL + CLICK no projeto MathFuncs.Android e, em seguida, escolha Nova pasta no menu Adicionar nomeando-o lib.

  2. Para cada ABI (Application Binary Interface), CONTROL + CLICK na pasta lib e, em seguida, escolha Nova pasta no menu Adicionar, nomeando-a após o respectivo ABI. Nesse caso:

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

    Observação

    Para obter uma visão geral mais detalhada, consulte o tópico Arquiteturas e CPUs do guia do desenvolvedor NDK, especificamente a seção sobre como abordar código nativo em pacotes de aplicativos.

  3. Verifique a estrutura de pastas:

    - lib
        - arm64-v8a
        - armeabi-v7a
        - x86
        - x86_64
    
  4. Adicione as bibliotecas .so correspondentes a cada uma das pastas ABI com base no seguinte mapeamento:

    arm64-v8a: lib/Android/arm64

    armeabi-v7a: lib / Android / braço

    x86: lib/Android/x86

    x86_64: lib/Android/x86_64

    Observação

    Para adicionar arquivos, CONTROL + CLIQUE na pasta que representa o respectivo ABI e escolha Adicionar arquivos nomenu Adicionar . Escolha a biblioteca apropriada (no diretório PrecompiledLibs), clique em Abrir e, em seguida, clique em OK deixando a opção padrão para Copiar o arquivo para o diretório.

  5. Para cada um dos arquivos .so , CONTROL + CLICK e escolha a opção EmbeddedNativeLibrary no menu Build Action .

Agora a pasta lib deve aparecer da seguinte maneira:

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

Referências nativas para MathFuncs.iOS

  1. CONTROL + CLIQUE no projeto MathFuncs.iOS e escolha Adicionar referência nativa no menu Adicionar .

  2. Escolha a biblioteca libMathFuncs.a (de libs/ios no diretório PrecompiledLibs ) e clique em Abrir

  3. CONTROL + CLIQUE no arquivo libMathFuncs (dentro da pasta Referências Nativas , em seguida, escolha a opção Propriedades no menu

  4. Configure as propriedades de Referência Nativa para que elas sejam marcadas (mostrando um ícone de tick) no Painel de Propriedades:

    • Forçar Carga
    • É C++
    • Link Inteligente

    Observação

    O uso de um tipo de projeto de biblioteca de vinculação junto com uma referência nativa incorpora a biblioteca estática e permite que ela seja vinculada automaticamente ao aplicativo Xamarin.iOS que faz referência a ela (mesmo quando ela é incluída por meio de um pacote NuGet).

  5. Abra ApiDefinition.cs, exclua o código comentado do modelo (deixando apenas o MathFuncs namespace) e execute a mesma etapa para Structs.cs

    Observação

    Um projeto de biblioteca de vinculação requer esses arquivos (com as ações de compilação ObjCBindingApiDefinition e ObjCBindingCoreSource ) para compilar. No entanto, escreveremos o código, para chamar nossa biblioteca nativa, fora desses arquivos de uma forma que possa ser compartilhada entre os destinos da biblioteca Android e iOS usando P/Invoke padrão.

Escrevendo o código da biblioteca gerenciada

Agora, escreva o código C# para chamar a biblioteca nativa. O objetivo é ocultar qualquer complexidade subjacente. O consumidor não deve precisar de nenhum conhecimento prático dos internos da biblioteca nativa ou dos conceitos P/Invoke.

Criando um SafeHandle

  1. CONTROL + CLIQUE no projeto MathFuncs.Shared e escolha Adicionar arquivo...no menu Adicionar.

  2. Escolha Classe vazia na janela Novo arquivo , nomeie-a MyMathFuncsSafeHandle e clique em Novo

  3. Implemente a classe MyMathFuncsSafeHandle :

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

    Observação

    Um SafeHandle é a maneira preferida de trabalhar com recursos não gerenciados em código gerenciado. Isso abstrai muito código clichê relacionado à finalização crítica e ao ciclo de vida do objeto. O proprietário desse identificador pode tratá-lo subsequentemente como qualquer outro recurso gerenciado e não precisará implementar o padrão descartável completo.

Criando a classe wrapper interna

  1. Abra MyMathFuncsWrapper.cs, alterando-o para uma classe estática interna

    namespace MathFuncs
    {
        internal static class MyMathFuncsWrapper
        {
        }
    }
    
  2. No mesmo arquivo, adicione a seguinte instrução condicional à classe:

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

    Observação

    Isso define o valor da constante DllName com base em se a biblioteca está sendo criada para Android ou iOS. Isso é para abordar as diferentes convenções de nomenclatura usadas por cada respectiva plataforma, mas também o tipo de biblioteca que está sendo usada neste caso. O Android está usando uma biblioteca dinâmica e, portanto, espera um nome de arquivo incluindo extensão. Para iOS, '__Internal' é necessário, pois estamos usando uma biblioteca estática.

  3. Adicione uma referência a System.Runtime.InteropServices na parte superior do arquivo MyMathFuncsWrapper.cs

    using System.Runtime.InteropServices;
    
  4. Adicione os métodos wrapper para lidar com a criação e o descarte da classe MyMathFuncs :

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

    Observação

    Estamos passando nossa constante DllName para o atributo DllImport junto com o EntryPoint que informa explicitamente ao tempo de execução do .NET o nome da função a ser chamada dentro dessa biblioteca. Tecnicamente, não precisamos fornecer o valor EntryPoint se nossos nomes de método gerenciado forem idênticos ao não gerenciado. Se um não for fornecido, o nome do método gerenciado será usado como EntryPoint. No entanto, é melhor ser explícito.

  5. Adicione os métodos wrapper para nos permitir trabalhar com a classe MyMathFuncs usando o seguinte código:

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

    Observação

    Estamos usando tipos simples para os parâmetros neste exemplo. Uma vez que o marshalling é uma cópia bitwise, neste caso, não requer nenhum trabalho adicional da nossa parte. Observe também o uso da classe MyMathFuncsSafeHandle em vez do IntPtr padrão. O IntPtr é mapeado automaticamente para o SafeHandle como parte do processo de empacotamento.

  6. Verifique se a classe MyMathFuncsWrapper concluída aparece como abaixo:

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

Concluindo a classe MyMathFuncsSafeHandle

  1. Abra a classe MyMathFuncsSafeHandle, navegue até o comentário TODO do espaço reservado dentro do método ReleaseHandle:

    // TODO: Release the handle here
    
  2. Substitua a linha TODO :

    MyMathFuncsWrapper.DisposeMyMathFuncs(this);
    

Escrevendo a classe MyMathFuncs

Agora que o wrapper está concluído, crie uma classe MyMathFuncs que gerenciará a referência ao objeto MyMathFuncs C++ não gerenciado.

  1. CONTROL + CLIQUE no projeto MathFuncs.Shared e escolha Adicionar arquivo...no menu Adicionar.

  2. Escolha Classe vazia na janela Novo arquivo , nomeie-a MyMathFuncs e clique em Novo

  3. Adicione os seguintes membros à classe MyMathFuncs :

    readonly MyMathFuncsSafeHandle handle;
    
  4. Implemente o construtor para a classe para que ele crie e armazene um identificador para o objeto MyMathFuncs nativo quando a classe é instanciada:

    public MyMathFuncs()
    {
        handle = MyMathFuncsWrapper.CreateMyMathFuncs();
    }
    
  5. Implemente a interface IDisposable usando o seguinte código:

    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. Implemente os métodos MyMathFuncs usando a classe MyMathFuncsWrapper para executar o trabalho real sob o capô passando o ponteiro que armazenamos para o objeto não gerenciado subjacente. O código deve ser o seguinte:

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

Criando o nuspec

Para ter a biblioteca empacotada e distribuída via NuGet, a solução precisa de um arquivo nuspec . Isso identificará quais dos assemblies resultantes serão incluídos para cada plataforma suportada.

  1. CONTROL + CLICK na solução MathFuncs, em seguida, escolha Adicionar pasta de solução no menu Adicionar nomeando-o SolutionItems.

  2. CONTROL + CLIQUE na pasta SolutionItems e escolha Novo arquivo...no menu Adicionar.

  3. Escolha Arquivo XML vazio na janela Novo arquivo , nomeie-o como MathFuncs.nuspec e clique em Novo.

  4. Atualize o MathFuncs.nuspec com os metadados básicos do pacote a serem exibidos para o consumidor do NuGet . Por exemplo:

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

    Observação

    Consulte o documento de referência nuspec para obter mais detalhes sobre o esquema usado para este manifesto.

  5. Adicione um <files> elemento como filho do <package> elemento (logo abaixo <metadata>), identificando cada arquivo com um elemento separado <file> :

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

    Observação

    Quando um pacote é instalado em um projeto e há vários assemblies especificados pelo mesmo nome, o NuGet escolhe efetivamente o assembly mais específico para determinada plataforma.

  6. Adicione os <file> elementos para os assemblies 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. Adicione os <file> elementos para os assemblies do 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. Adicione os <file> elementos para os assemblies 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. Verifique o 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>
    

    Observação

    Esse arquivo especifica os caminhos de saída do assembly de uma compilação Release , portanto, certifique-se de compilar a solução usando essa configuração.

Neste ponto, a solução contém 3 assemblies .NET e um manifesto nuspec de suporte.

Distribuindo o wrapper .NET com o NuGet

A próxima etapa é empacotar e distribuir o pacote NuGet para que ele possa ser facilmente consumido pelo aplicativo e gerenciado como uma dependência. O empacotamento e o consumo poderiam ser feitos dentro de uma única solução, mas a distribuição da biblioteca via NuGet ajuda no desacoplamento e nos permite gerenciar essas bases de código de forma independente.

Preparando um diretório de pacotes locais

A forma mais simples de feed NuGet é um diretório local:

  1. No Finder, navegue até um diretório conveniente. Por exemplo, /Users.
  2. Escolha Nova pasta no menu Arquivo, fornecendo um nome significativo, como local-nuget-feed.

Como criar o pacote

  1. Defina a Configuração de compilação como Release e execute uma compilação usando COMMAND + B.

  2. Abra o Terminal e altere o diretório para a pasta que contém o arquivo nuspec .

  3. No Terminal, execute o comando nuget pack especificando o arquivo nuspec , a Versão (por exemplo, 1.0.0) e o OutputDirectory usando a pasta criada na etapa anterior, ou seja, local-nuget-feed. Por exemplo:

    nuget pack MathFuncs.nuspec -Version 1.0.0 -OutputDirectory ~/local-nuget-feed
    
  4. Confirme se MathFuncs.1.0.0.nupkg foi criado no diretório local-nuget-feed .

[OPCIONAL] Usando um feed NuGet privado com o Azure DevOps

Uma técnica mais robusta é descrita em Introdução aos pacotes NuGet no Azure DevOps, que mostra como criar um feed privado e enviar o pacote (gerado na etapa anterior) para esse feed.

É ideal ter esse fluxo de trabalho totalmente automatizado, por exemplo, usando o Azure Pipelines. Para obter mais informações, consulte Introdução ao Azure Pipelines.

Consumindo o wrapper .NET de um aplicativo Xamarin.Forms

Para concluir o passo a passo, crie um aplicativo Xamarin.Forms para consumir o pacote recém-publicado no feed NuGet local.

Criando o projeto Xamarin.Forms

  1. Abra uma nova instância do Visual Studio para Mac. Isso pode ser feito a partir do Terminal:

    open -n -a "Visual Studio"
    
  2. No Visual Studio para Mac, clique em Novo Projeto (na Página de Boas-vindas) ou Nova Solução (no menu Arquivo ).

  3. Na janela Novo Projeto, escolha Aplicativo de Formulários em Branco (de dentro do Aplicativo Multiplataforma>) e clique em Avançar.

  4. Atualize os seguintes campos e clique em Avançar:

    • Nome do aplicativo: MathFuncsApp.
    • Identificador da Organização: use um namespace reverso, por exemplo, com.{your_org}.
    • Plataformas de destino: use o padrão (destinos Android e iOS).
    • Código compartilhado: defina isso como .NET Standard (uma solução de "Biblioteca Compartilhada" é possível, mas além do escopo deste passo a passo).
  5. Atualize os seguintes campos e clique em Criar:

    • Nome do projeto: MathFuncsApp.
    • Nome da solução: MathFuncsApp.
    • Local: use o local de salvamento padrão (ou escolha uma alternativa).
  6. No Gerenciador de Soluções, CONTROL + CLIQUE no destino (MathFuncsApp.Android ou MathFuncs.iOS) para o teste inicial e escolha Definir como projeto de inicialização.

  7. Escolha o dispositivo preferido ou emulador de simulador/.

  8. Execute a solução (COMMAND + RETURN) para validar se o projeto Xamarin.Forms modelo é compilado e executado corretamente.

    Observação

    O iOS (especificamente o simulador) tende a ter o tempo de compilação/implantação mais rápido.

Adicionando o feed NuGet local à configuração do NuGet

  1. No Visual Studio, escolha Preferências (no menu do Visual Studio ).

  2. Escolha Códigos-fonte na seção NuGet e clique em Adicionar.

  3. Atualize os seguintes campos e clique em Adicionar Origem:

    • Nome: forneça um nome significativo, por exemplo, Local-Packages.
    • Local: especifique a pasta local-nuget-feed criada na etapa anterior.

    Observação

    Neste caso, não há necessidade de especificar um Nome de Usuário e Senha.

  4. Clique em OK.

Referenciando o pacote

Repita as etapas a seguir para cada projeto (MathFuncsApp, MathFuncsApp.Android e MathFuncsApp.iOS).

  1. CONTROL + CLICK no projeto e escolha Adicionar pacotes NuGet...no menu Adicionar .
  2. Procure por MathFuncs.
  3. Verifique se a versão do pacote é 1.0.0 e os outros detalhes aparecem conforme o esperado, como o título e a descrição, ou seja, MathFuncs e Sample C++ Wrapper Library.
  4. Selecione o pacote MathFuncs e clique em Adicionar Pacote.

Usando as funções da biblioteca

Agora, com uma referência ao pacote MathFuncs em cada um dos projetos, as funções estão disponíveis para o código C#.

  1. Abra MainPage.xaml.cs de dentro do projeto Xamarin.Formscomum do MathFuncsApp (referenciado por MathFuncsApp.Android e MathFuncsApp.iOS).

  2. Adicione instruções using para System.Diagnostics e MathFuncs na parte superior do arquivo:

    using System.Diagnostics;
    using MathFuncs;
    
  3. Declare uma instância da MyMathFuncs classe na parte superior da MainPage classe:

    MyMathFuncs myMathFuncs;
    
  4. Substitua os OnAppearing métodos e OnDisappearing da ContentPage classe base:

    protected override void OnAppearing()
    {
        base.OnAppearing();
    }
    
    protected override void OnDisappearing()
    {
        base.OnDisappearing();
    }
    
  5. Atualize o OnAppearing método para inicializar a variável declarada myMathFuncs anteriormente:

    protected override void OnAppearing()
    {
        base.OnAppearing();
        myMathFuncs = new MyMathFuncs();
    }
    
  6. Atualize o OnDisappearing método para chamar o Dispose método em myMathFuncs:

    protected override void OnDisappearing()
    {
        base.OnAppearing();
        myMathFuncs.Dispose();
    }
    
  7. Implemente um método privado chamado TestMathFuncs da seguinte maneira:

    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. Por fim, chame TestMathFuncs no final do OnAppearing método:

    TestMathFuncs();
    
  9. Execute o aplicativo em cada plataforma de destino e valide a saída no Application Output Pad aparece da seguinte maneira:

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

    Observação

    Se você encontrar um 'DLLNotFoundException' ao testar no Android, ou um erro de compilação no iOS, certifique-se de verificar se a arquitetura da CPU do dispositivo/emulador/simulador que você está usando é compatível com o subconjunto que escolhemos suportar.

Resumo

Este artigo explicou como criar um aplicativo Xamarin.Forms que usa bibliotecas nativas por meio de um wrapper .NET comum distribuído por meio de um pacote NuGet. O exemplo fornecido neste passo a passo é intencionalmente muito simplista para demonstrar mais facilmente a abordagem. Um aplicativo real terá que lidar com complexidades, como tratamento de exceções, retornos de chamada, empacotamento de tipos mais complexos e vinculação com outras bibliotecas de dependência. Uma consideração importante é o processo pelo qual a evolução do código C++ é coordenada e sincronizada com o wrapper e os aplicativos cliente. Esse processo pode variar dependendo se uma ou ambas as preocupações são de responsabilidade de uma única equipe. De qualquer forma, a automação é um benefício real. Abaixo estão alguns recursos que fornecem leitura adicional sobre alguns dos principais conceitos, juntamente com os downloads relevantes.

Downloads

Exemplos

Leitura Adicional

Artigos relacionados ao conteúdo deste post