Udostępnij za pośrednictwem


Używanie bibliotek języka C/C++ z platformą Xamarin

Omówienie

Platforma Xamarin umożliwia deweloperom tworzenie natywnych dla wielu platform aplikacji mobilnych za pomocą programu Visual Studio. Ogólnie rzecz biorąc, powiązania języka C# są używane do uwidaczniania istniejących składników platformy deweloperom. Jednak czasami aplikacje platformy Xamarin muszą pracować z istniejącymi bazami kodu. Czasami zespoły po prostu nie mają czasu, budżetu ani zasobów do przenoszenia dużych, dobrze przetestowanych i wysoce zoptymalizowanych baz kodu w języku C#.

Programowanie aplikacji mobilnych w języku Visual C++ dla wielu platform umożliwia kompilowanie kodu C/C++ i C# w ramach tego samego rozwiązania, oferując wiele zalet, w tym ujednolicone środowisko debugowania. Firma Microsoft wykorzystała w ten sposób języki C/C++ i Xamarin do dostarczania aplikacji, takich jak Hyperlapse Mobile i Pix Aparat.

Jednak w niektórych przypadkach istnieje potrzeba (lub wymaganie), aby zachować istniejące narzędzia i procesy języka C/C++ oraz zachować oddzielenie kodu biblioteki od aplikacji, traktując bibliotekę tak, jakby była podobna do składnika innej firmy. W takich sytuacjach wyzwaniem jest nie tylko uwidacznianie odpowiednich elementów członkowskich w języku C#, ale zarządzanie biblioteką jako zależność. I, oczywiście, automatyzowanie jak najwięcej tego procesu, jak to możliwe.

W tym wpisie przedstawiono ogólne podejście dla tego scenariusza i przedstawiono prosty przykład.

Tło

Język C/C++ jest uważany za język międzyplatformowy, ale należy zadbać o to, aby kod źródłowy był rzeczywiście międzyplatformowy, używając tylko języka C/C++ obsługiwanego przez wszystkie kompilatory docelowe i zawierające niewiele lub bez warunkowo dołączonej platformy lub kodu specyficznego dla kompilatora.

Ostatecznie kod musi zostać skompilowany i uruchomiony pomyślnie na wszystkich platformach docelowych, dlatego sprowadza się to do podobieństwa między platformami (i kompilatorami). Problemy mogą nadal wynikać z drobnych różnic między kompilatorami i dlatego dokładne testowanie (najlepiej zautomatyzowane) na każdej platformie docelowej staje się coraz ważniejsze.

Podejście wysokiego poziomu

Poniższa ilustracja przedstawia czteroetapowe podejście używane do przekształcania kodu źródłowego C/C++ w wieloplatformową bibliotekę platformy Xamarin udostępnioną za pośrednictwem narzędzia NuGet, a następnie jest używana w aplikacji platformy Xamarin.Forms.

Podejście wysokiego poziomu do używania języka C/C++ z platformą Xamarin

4 etapy to:

  1. Kompilowanie kodu źródłowego C/C++ w bibliotekach natywnych specyficznych dla platformy.
  2. Zawijanie bibliotek natywnych przy użyciu rozwiązania programu Visual Studio.
  3. Pakowanie i wypychanie pakietu NuGet dla otoki .NET.
  4. Korzystanie z pakietu NuGet z aplikacji platformy Xamarin.

Etap 1. Kompilowanie kodu źródłowego C/C++ do bibliotek natywnych specyficznych dla platformy

Celem tego etapu jest utworzenie bibliotek natywnych, które mogą być wywoływane przez otokę języka C#. Może to być istotne w zależności od sytuacji. Wiele narzędzi i procesów, które można przynieść w tym typowym scenariuszu, wykracza poza zakres tego artykułu. Najważniejsze zagadnienia to zachowanie bazy kodu C/C++ zsynchronizowanej z dowolnym natywnym kodem otoki, wystarczającą ilością testów jednostkowych i automatyzacją kompilacji.

Biblioteki w przewodniku zostały utworzone przy użyciu programu Visual Studio Code z towarzyszącym skryptem powłoki. Rozszerzoną wersję tego przewodnika można znaleźć w repozytorium GitHub Mobile CAT, w których omówiono tę część przykładu bardziej szczegółowo. Biblioteki natywne są traktowane jako zależność innej firmy w tym przypadku, jednak ten etap jest ilustrowany dla kontekstu.

Dla uproszczenia przewodnik jest przeznaczony tylko dla podzbioru architektur. W przypadku systemu iOS używa narzędzia lipo do tworzenia pojedynczego pliku binarnego tłuszczu z poszczególnych plików binarnych specyficznych dla architektury. System Android będzie używać dynamicznych plików binarnych z rozszerzeniem .so iOS użyje statycznego pliku binarnego tłuszczu z rozszerzeniem .a.

Etap 2. Zawijanie bibliotek natywnych za pomocą rozwiązania Visual Studio

Następnym etapem jest opakowywanie bibliotek natywnych tak, aby były łatwo używane z platformy .NET. Odbywa się to za pomocą rozwiązania programu Visual Studio z czterema projektami. Udostępniony projekt zawiera wspólny kod. Projekty przeznaczone dla każdego z platform Xamarin.Android, Xamarin.iOS i .NET Standard umożliwiają odwołowanie się do biblioteki w sposób niezależny od platformy.

Otoka używa "przynęty i sztuczki przełącznika", Nie jest to jedyny sposób, ale ułatwia odwołowanie się do biblioteki i pozwala uniknąć konieczności jawnego zarządzania implementacjami specyficznymi dla platformy w samej aplikacji zużywanej. Sztuczka polega zasadniczo na zapewnieniu, że obiekty docelowe (.NET Standard, Android, iOS) współdzielą tę samą przestrzeń nazw, nazwę zestawu i strukturę klas. Ponieważ pakiet NuGet zawsze preferuje bibliotekę specyficzną dla platformy, wersja platformy .NET Standard nigdy nie jest używana w czasie wykonywania.

Większość pracy w tym kroku koncentruje się na używaniu P/Invoke do wywoływania metod biblioteki natywnej i zarządzania odwołaniami do obiektów bazowych. Celem jest uwidocznienie funkcjonalności biblioteki dla użytkownika przy jednoczesnym wyodrębnieniu wszelkich złożoności. Deweloperzy zestawu narzędzi Xamarin.Forms nie muszą mieć wiedzy na temat wewnętrznej pracy biblioteki niezarządzanej. Powinno się wydawać, że używają dowolnej innej zarządzanej biblioteki języka C#.

Ostatecznie dane wyjściowe tego etapu to zestaw bibliotek platformy .NET, jeden na element docelowy wraz z dokumentem nuspec zawierającym informacje wymagane w celu skompilowania pakietu w następnym kroku.

Etap 3. Pakowanie i wypychanie pakietu NuGet dla otoki platformy .NET

Trzeci etap polega na utworzeniu pakietu NuGet przy użyciu artefaktów kompilacji z poprzedniego kroku. Wynikiem tego kroku jest pakiet NuGet, który może być używany z poziomu aplikacji platformy Xamarin. Przewodnik używa katalogu lokalnego, aby służyć jako źródło danych NuGet. W środowisku produkcyjnym ten krok powinien publikować pakiet w publicznym lub prywatnym kanale informacyjnym NuGet i powinien być w pełni zautomatyzowany.

Etap 4. Korzystanie z pakietu NuGet z poziomu aplikacji platformy Xamarin.Forms

Ostatnim krokiem jest odwołanie do pakietu NuGet z aplikacji platformy Xamarin.Forms i użycie go. Wymaga to skonfigurowania kanału informacyjnego NuGet w programie Visual Studio w celu użycia źródła danych zdefiniowanego w poprzednim kroku.

Po skonfigurowaniu kanału informacyjnego należy odwołać się do pakietu z każdego projektu w aplikacji Xamarin.Forms dla wielu platform. "Sztuczka przynęty i przełącznika" zapewnia identyczne interfejsy, więc funkcje biblioteki natywnej można wywołać przy użyciu kodu zdefiniowanego w jednej lokalizacji.

Repozytorium kodu źródłowego zawiera listę dalszych informacji, która zawiera artykuły dotyczące konfigurowania prywatnego kanału informacyjnego NuGet w usłudze Azure DevOps oraz sposobu wypychania pakietu do tego źródła danych. Chociaż wymaga to nieco więcej czasu konfiguracji niż katalog lokalny, ten typ kanału informacyjnego jest lepszy w środowisku programistycznym zespołu.

Bramek

Podane kroki są specyficzne dla Visual Studio dla komputerów Mac, ale struktura działa również w programie Visual Studio 2017.

Wymagania wstępne

Aby wykonać następujące czynności, deweloper będzie potrzebował następujących czynności:

Uwaga

Aby wdrożyć aplikacje na platformie i Telefon, wymagane jest aktywne konto dla deweloperów firmy Apple.

Tworzenie bibliotek natywnych (etap 1)

Funkcja biblioteki natywnej jest oparta na przykładzie z przewodnika: tworzenie i używanie biblioteki statycznej (C++).

Ten przewodnik pomija pierwszy etap, tworząc biblioteki natywne, ponieważ biblioteka jest udostępniana jako zależność innej firmy w tym scenariuszu. Prekompilowane biblioteki natywne są dołączane razem z przykładowym kodem lub można je pobrać bezpośrednio.

Praca z biblioteką natywną

Oryginalny przykład MathFuncsLib zawiera pojedynczą klasę o nazwie MyMathFuncs z następującą definicją:

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

Dodatkowa klasa definiuje funkcje otoki, które umożliwiają użytkownikowi platformy .NET tworzenie, usuwanie i interakcję z bazową klasą natywną MyMathFuncs .

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

Będą to te funkcje otoki, które są używane po stronie platformy Xamarin .

Zawijanie biblioteki natywnej (etap 2)

Ten etap wymaga wstępnie skompilowanych bibliotek opisanych w poprzedniej sekcji.

Tworzenie rozwiązania programu Visual Studio

  1. W Visual Studio dla komputerów Mac kliknij pozycję Nowy projekt (na stronie powitalnej) lub Nowe rozwiązanie (z menu Plik).

  2. W oknie Nowy projekt wybierz pozycję Projekt udostępniony (z biblioteki wieloplatformowej>), a następnie kliknij przycisk Dalej.

  3. Zaktualizuj następujące pola, a następnie kliknij pozycję Utwórz:

    • Nazwa projektu: MathFuncs.Shared
    • Nazwa rozwiązania: MathFuncs
    • Lokalizacja: użyj domyślnej lokalizacji zapisywania (lub wybierz alternatywę)
    • Utwórz projekt w katalogu rozwiązania: ustaw tę opcję na zaznaczoną wartość
  4. W Eksplorator rozwiązań kliknij dwukrotnie projekt MathFuncs.Shared i przejdź do pozycji Main Ustawienia.

  5. Usuń element . Udostępnione z domyślnej przestrzeni nazw tak, aby była ustawiona tylko na MathFuncs , a następnie kliknij przycisk OK.

  6. Otwórz MyClass.cs (utworzoną przez szablon), a następnie zmień nazwę klasy i nazwy pliku na MyMathFuncsWrapper i zmień przestrzeń nazw na MathFuncs.

  7. CONTROL + KLIKNIJ rozwiązanie MathFuncs, a następnie wybierz pozycję Dodaj nowy projekt... z menu Dodaj .

  8. W oknie Nowy projekt wybierz pozycję Biblioteka Standardowa platformy .NET (z poziomu biblioteki wieloplatformowej>), a następnie kliknij przycisk Dalej.

  9. Wybierz pozycję .NET Standard 2.0 , a następnie kliknij przycisk Dalej.

  10. Zaktualizuj następujące pola, a następnie kliknij pozycję Utwórz:

    • Nazwa projektu: MathFuncs.Standard
    • Lokalizacja: użyj tej samej lokalizacji zapisywania co udostępniony projekt
  11. W Eksplorator rozwiązań kliknij dwukrotnie projekt MathFuncs.Standard.

  12. Przejdź do głównej Ustawienia, a następnie zaktualizuj domyślną przestrzeń nazw do funkcji MathFuncs.

  13. Przejdź do ustawień danych wyjściowych, a następnie zaktualizuj nazwę zestawu do funkcji MathFuncs.

  14. Przejdź do ustawień kompilatora, zmień konfiguracjęna Wydanie, ustawiając pozycję Informacje debugowania na Symbole tylko wtedy kliknij przycisk OK.

  15. Usuń Class1.cs/Wprowadzenie z projektu (jeśli jeden z nich został dołączony jako część szablonu).

  16. CONTROL + KLIKNIJ folder Dependencies/References projektu, a następnie wybierz pozycję Edytuj odwołania.

  17. Wybierz pozycję MathFuncs.Shared na karcie Projekty , a następnie kliknij przycisk OK.

  18. Powtórz kroki od 7 do 17 (ignorując krok 9) przy użyciu następujących konfiguracji:

    NAZWA PROJEKTU NAZWA SZABLONU MENU NOWY PROJEKT
    MathFuncs.Android Biblioteka klas Biblioteka systemu Android >
    MathFuncs.iOS Biblioteka powiązań Biblioteka systemu iOS >
  19. W Eksplorator rozwiązań kliknij dwukrotnie projekt MathFuncs.Android, a następnie przejdź do ustawień kompilatora.

  20. Po ustawieniu Konfiguracja na Debuguj edytuj definiuj symbole , aby uwzględnić system Android;.

  21. Zmień konfigurację na Wydanie, a następnie zmodyfikuj definiuj symbole, aby uwzględnić również system Android;.

  22. Powtórz kroki od 19 do 20 dla funkcji MathFuncs.iOS, edytując definicje symboli w celu uwzględnienia systemu iOS; zamiast systemu Android; w obu przypadkach.

  23. Skompiluj rozwiązanie w konfiguracji wydania (CONTROL + COMMAND + B) i sprawdź, czy wszystkie trzy zestawy wyjściowe (Android, iOS, .NET Standard) (w odpowiednich folderach pojemników projektu) mają taką samą nazwę MathFuncs.dll.

Na tym etapie rozwiązanie powinno mieć trzy miejsca docelowe, jeden element dla systemów Android, iOS i .NET Standard oraz udostępniony projekt, do którego odwołuje się każdy z trzech obiektów docelowych. Należy je skonfigurować tak, aby używały tej samej domyślnej przestrzeni nazw i zestawów wyjściowych o tej samej nazwie. Jest to niezbędne do podejścia "przynęty i przełącznika" wymienionego wcześniej.

Dodawanie bibliotek natywnych

Proces dodawania bibliotek natywnych do rozwiązania otoki różni się nieco między systemami Android i iOS.

Dokumentacja natywna dla biblioteki MathFuncs.Android

  1. CONTROL + KLIKNIJprojekt MathFuncs.Android, a następnie wybierz pozycję Nowy folder z menu Dodaj nazwę lib.

  2. Dla każdego interfejsu binarnego aplikacji ABI , CONTROL + KLIKNIJ folder lib, a następnie wybierz pozycję Nowy folder z menu Dodaj, nazewnictwo go po odpowiednim ABI. W takim przypadku:

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

    Uwaga

    Aby uzyskać bardziej szczegółowe omówienie, zobacz temat Architektury i procesory CPU w przewodniku dewelopera NDK, w szczególności sekcję dotyczącą adresowania kodu natywnego w pakietach aplikacji.

  3. Sprawdź strukturę folderów:

    - lib
        - arm64-v8a
        - armeabi-v7a
        - x86
        - x86_64
    
  4. Dodaj odpowiednie biblioteki .so do każdego z folderów ABI na podstawie następującego mapowania:

    arm64-v8a: lib/Android/arm64

    armeabi-v7a: lib/Android/arm

    x86: lib/Android/x86

    x86_64: lib/Android/x86_64

    Uwaga

    Aby dodać pliki, CONTROL + CLICK w folderze reprezentującym odpowiednie ABI, a następnie wybierz pozycję Dodaj pliki... z menu Dodaj . Wybierz odpowiednią bibliotekę (z katalogu PrekompiledLibs ), a następnie kliknij przycisk Otwórz , a następnie kliknij przycisk OK pozostawiając opcję domyślną, aby skopiować plik do katalogu.

  5. Dla każdego pliku .so, CONTROL + CLICK, a następnie wybierz opcję EmbeddedNativeLibrary z menu Akcja kompilacji.

Teraz folder lib powinien wyglądać następująco:

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

Odwołania natywne dla aplikacji MathFuncs.iOS

  1. CONTROL + KLIKNIJprojekt MathFuncs.iOS , a następnie wybierz pozycję Dodaj odwołanie natywne z menu Dodaj .

  2. Wybierz bibliotekę libMathFuncs.a (z biblioteki libs/ios w katalogu PrecompiledLibs), a następnie kliknij pozycję Otwórz

  3. CONTROL + KLIKNIJplik libMathFuncs (w folderze Odwołania natywne, a następnie wybierz opcję Właściwości z menu

  4. Skonfiguruj właściwości odwołania natywnego, aby były zaznaczone (pokazując ikona znacznika) w okienku właściwości:

    • Wymuś obciążenie
    • Czy język C++
    • Łącze inteligentne

    Uwaga

    Użycie typu projektu biblioteki powiązań wraz z natywnym odwołaniem osadza bibliotekę statyczną i umożliwia automatyczne łączenie jej z aplikacją Xamarin.iOS odwołującą się do niej (nawet wtedy, gdy jest dołączana za pośrednictwem pakietu NuGet).

  5. Otwórz ApiDefinition.cs, usuwając kod z komentarzem szablonu (pozostawiając tylko MathFuncs przestrzeń nazw), a następnie wykonaj ten sam krok dla Structs.cs

    Uwaga

    Projekt biblioteki powiązań wymaga tych plików (przy użyciu akcji kompilacji ObjCBindingApiDefinition i ObjCBindingCoreSource ). Napiszemy jednak kod, aby wywołać naszą bibliotekę natywną poza tymi plikami w sposób, który może być udostępniany zarówno dla elementów docelowych bibliotek systemu Android, jak i iOS przy użyciu standardowego P/Invoke.

Pisanie kodu biblioteki zarządzanej

Teraz napisz kod języka C#, aby wywołać bibliotekę natywną. Celem jest ukrycie wszelkich podstawowych złożoności. Użytkownik nie powinien potrzebować żadnej działającej wiedzy na temat wewnętrznych bibliotek natywnych ani koncepcji P/Invoke.

Tworzenie Sejf Handle

  1. CONTROL + KLIKNIJprojekt MathFuncs.Shared , a następnie wybierz pozycję Dodaj plik... z menu Dodaj .

  2. Wybierz pozycję Pusta klasa w oknie Nowy plik, nadaj mu nazwę MyMathFuncs Sejf Handle, a następnie kliknij pozycję Nowy

  3. Zaimplementuj klasę MyMathFuncs Sejf 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;
            }
        }
    }
    

    Uwaga

    Sejf Handle jest preferowanym sposobem pracy z niezarządzanymi zasobami w kodzie zarządzanym. W ten sposób odchodzi wiele standardowy kod związany z krytycznym finalizacją i cyklem życia obiektu. Właściciel tego uchwytu może następnie traktować go jak każdy inny zasób zarządzany i nie będzie musiał zaimplementować pełnego wzorca jednorazowego.

Tworzenie wewnętrznej klasy otoki

  1. Otwórz MyMathFuncsWrapper.cs, zmieniając ją na wewnętrzną klasę statyczną

    namespace MathFuncs
    {
        internal static class MyMathFuncsWrapper
        {
        }
    }
    
  2. W tym samym pliku dodaj następującą instrukcję warunkową do klasy:

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

    Uwaga

    Ustawia wartość stałą DllName na podstawie tego, czy biblioteka jest tworzona dla systemu Android , czy iOS. Dotyczy to różnych konwencji nazewnictwa używanych przez poszczególne platformy, ale także typu biblioteki używanej w tym przypadku. System Android używa biblioteki dynamicznej, dlatego oczekuje nazwy pliku, w tym rozszerzenia. W przypadku systemu iOS jest wymagany element "__Internal", ponieważ używamy biblioteki statycznej.

  3. Dodaj odwołanie do pliku System.Runtime.InteropServices w górnej części pliku MyMathFuncsWrapper.cs

    using System.Runtime.InteropServices;
    
  4. Dodaj metody otoki, aby obsługiwać tworzenie i usuwanie klasy MyMathFuncs :

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

    Uwaga

    Przekazujemy naszą stałą wartość DllName do atrybutu DllImport wraz z programem EntryPoint , który jawnie informuje środowisko uruchomieniowe platformy .NET nazwę funkcji do wywołania w tej bibliotece. Technicznie nie musimy podawać wartości programu EntryPoint , jeśli nazwy metod zarządzanych były identyczne z niezarządzaną wartością. Jeśli go nie podano, nazwa metody zarządzanej zostanie użyta jako program EntryPoint . Lepiej jednak być jawnym.

  5. Dodaj metody otoki, aby umożliwić nam pracę z klasą MyMathFuncs przy użyciu następującego kodu:

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

    Uwaga

    W tym przykładzie używamy prostych typów parametrów. Ponieważ marshalling jest bitową kopią w tym przypadku, nie wymaga dodatkowej pracy w naszej części. Zwróć również uwagę na użycie klasy MyMathFuncs Sejf Handle zamiast standardowej klasy IntPtr. Element IntPtr jest automatycznie mapowany na Sejf Handle w ramach procesu marshallingu.

  6. Sprawdź, czy zakończona klasa MyMathFuncsWrapper wygląda następująco:

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

Ukończenie klasy MyMathFuncs Sejf Handle

  1. Otwórz klasę MyMathFuncs Sejf Handle, przejdź do komentarza zastępczego TODO w metodzie ReleaseHandle:

    // TODO: Release the handle here
    
  2. Zastąp wiersz TODO :

    MyMathFuncsWrapper.DisposeMyMathFuncs(this);
    

Pisanie klasy MyMathFuncs

Teraz, gdy otoka została ukończona, utwórz klasę MyMathFuncs, która będzie zarządzać odwołaniem do niezarządzanego obiektu C++ MyMathFuncs.

  1. CONTROL + KLIKNIJprojekt MathFuncs.Shared , a następnie wybierz pozycję Dodaj plik... z menu Dodaj .

  2. Wybierz pozycję Pusta klasa w oknie Nowy plik , nadaj mu nazwę MyMathFuncs , a następnie kliknij pozycję Nowy

  3. Dodaj następujące elementy członkowskie do klasy MyMathFuncs :

    readonly MyMathFuncsSafeHandle handle;
    
  4. Zaimplementuj konstruktor dla klasy, aby utworzyć i zapisać uchwyt do natywnego obiektu MyMathFuncs po utworzeniu wystąpienia klasy:

    public MyMathFuncs()
    {
        handle = MyMathFuncsWrapper.CreateMyMathFuncs();
    }
    
  5. Zaimplementuj interfejs IDisposable przy użyciu następującego kodu:

    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. Zaimplementuj metody MyMathFuncs przy użyciu klasy MyMathFuncsWrapper , aby wykonać rzeczywistą pracę pod maską, przekazując wskaźnik przechowywany w bazowym niezarządzanym obiekcie. Kod powinien wyglądać następująco:

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

Tworzenie narzędzia nuspec

Aby biblioteka została spakowana i dystrybuowana za pośrednictwem narzędzia NuGet, rozwiązanie wymaga pliku nuspec . Określi to, które z wynikowych zestawów zostaną uwzględnione dla każdej obsługiwanej platformy.

  1. CONTROL + KLIKNIJ rozwiązanie MathFuncs, a następnie wybierz pozycję Dodaj folder rozwiązania z menu Dodaj nazwę SolutionItems.

  2. CONTROL + KLIKNIJfolder SolutionItems , a następnie wybierz pozycję Nowy plik... z menu Dodaj .

  3. Wybierz pozycję Pusty plik XML w oknie Nowy plik, nadaj mu nazwę MathFuncs.nuspec, a następnie kliknij pozycję Nowy.

  4. Zaktualizuj plik MathFuncs.nuspec przy użyciu podstawowych metadanych pakietu, które mają być wyświetlane użytkownikowi NuGet . Na przykład:

    <?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>
    
  5. <files> Dodaj element jako element podrzędny <package> elementu (tuż poniżej <metadata>), identyfikując każdy plik przy użyciu oddzielnego <file> elementu:

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

    Uwaga

    Gdy pakiet jest instalowany w projekcie i gdzie istnieje wiele zestawów określonych przez tę samą nazwę, NuGet skutecznie wybierze zestaw, który jest najbardziej specyficzny dla danej platformy.

  6. <file> Dodaj elementy dla zestawów systemu 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. <file> Dodaj elementy dla zestawów systemu 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. <file> Dodaj elementy dla zestawów 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. Sprawdź manifest narzędzia 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>
    

    Uwaga

    Ten plik określa ścieżki wyjściowe zestawu z kompilacji wydania , więc pamiętaj, aby skompilować rozwiązanie przy użyciu tej konfiguracji.

Na tym etapie rozwiązanie zawiera 3 zestawy .NET i pomocniczy manifest nuspec .

Dystrybucja otoki platformy .NET za pomocą narzędzia NuGet

Następnym krokiem jest spakować i dystrybuować pakiet NuGet, więc może być łatwo używany przez aplikację i zarządzany jako zależność. Zawijanie i zużycie można wykonać w ramach jednego rozwiązania, ale rozpowszechnianie biblioteki za pośrednictwem pomocy NuGet w oddzieleniu i umożliwia niezależne zarządzanie tymi bazami kodu.

Przygotowywanie katalogu pakietów lokalnych

Najprostszą formą kanału informacyjnego NuGet jest katalog lokalny:

  1. W programie Finder przejdź do wygodnego katalogu. Na przykład /Users.
  2. Wybierz pozycję Nowy folder z menu Plik , podając zrozumiałą nazwę, taką jak local-nuget-feed.

Tworzenie pakietu

  1. Ustaw opcję Konfiguracja kompilacji na Wydanie i wykonaj kompilację przy użyciu polecenia + B.

  2. Otwórz terminali zmień katalog na folder zawierający plik nuspec.

  3. W terminalu wykonaj polecenie nuget pack określające plik nuspec, wersję (na przykład 1.0.0) i outputDirectory przy użyciu folderu utworzonego w poprzednim kroku, czyli lokalnego źródła danych nuget. Na przykład:

    nuget pack MathFuncs.nuspec -Version 1.0.0 -OutputDirectory ~/local-nuget-feed
    
  4. Upewnij się , że narzędzie MathFuncs.1.0.0.nupkg zostało utworzone w katalogu local-nuget-feed .

[OPCJONALNIE] Używanie prywatnego źródła danych NuGet z usługą Azure DevOps

Bardziej niezawodną technikę opisano w artykule Wprowadzenie do pakietów NuGet w usłudze Azure DevOps, w którym pokazano, jak utworzyć prywatne źródło danych i wypchnąć pakiet (wygenerowany w poprzednim kroku) do tego źródła danych.

Idealnie jest mieć ten przepływ pracy w pełni zautomatyzowany, na przykład przy użyciu usługi Azure Pipelines. Aby uzyskać więcej informacji, zobacz Rozpoczynanie pracy z usługą Azure Pipelines.

Korzystanie z otoki platformy .NET z poziomu aplikacji platformy Xamarin.Forms

Aby ukończyć przewodnik, utwórz aplikację platformy Xamarin.Forms , aby korzystać z pakietu właśnie opublikowanego w lokalnym kanale informacyjnym NuGet .

Tworzenie projektu platformy Xamarin.Forms

  1. Otwórz nowe wystąpienie Visual Studio dla komputerów Mac. Można to zrobić z poziomu terminalu:

    open -n -a "Visual Studio"
    
  2. W Visual Studio dla komputerów Mac kliknij pozycję Nowy projekt (na stronie powitalnej) lub Nowe rozwiązanie (z menu Plik).

  3. W oknie Nowy projekt wybierz pozycję Pusta aplikacja formularzy (z poziomu aplikacji wieloplatformowej>), a następnie kliknij przycisk Dalej.

  4. Zaktualizuj następujące pola, a następnie kliknij przycisk Dalej:

    • Nazwa aplikacji: MathFuncsApp.
    • Identyfikator organizacji: użyj odwrotnej przestrzeni nazw, na przykład com.{your_org}.
    • Platformy docelowe: użyj wartości domyślnej (docelowych systemów Android i iOS).
    • Kod udostępniony: ustaw tę wartość na .NET Standard (rozwiązanie "Biblioteka udostępniona" jest możliwe, ale poza zakresem tego przewodnika).
  5. Zaktualizuj następujące pola, a następnie kliknij pozycję Utwórz:

    • Nazwa projektu: MathFuncsApp.
    • Nazwa rozwiązania: MathFuncsApp.
    • Lokalizacja: użyj domyślnej lokalizacji zapisywania (lub wybierz alternatywę).
  6. W Eksplorator rozwiązań, CONTROL + CLICK on the target (MathFuncsApp.Android or MathFuncs.iOS) na potrzeby testowania początkowego, a następnie wybierz pozycję Ustaw jako projekt startowy.

  7. Wybierz preferowane urządzenie lub emulator symulatora/.

  8. Uruchom rozwiązanie (COMMAND + RETURN), aby sprawdzić, czy szablonowy projekt platformy Xamarin.Forms kompiluje i działa poprawnie.

    Uwaga

    System iOS (w szczególności symulator) ma tendencję do najszybszego czasu kompilacji/wdrożenia.

Dodawanie lokalnego źródła danych NuGet do konfiguracji narzędzia NuGet

  1. W programie Visual Studio wybierz pozycję Preferencje (z menu programu Visual Studio ).

  2. Wybierz pozycję Źródła w sekcji NuGet , a następnie kliknij przycisk Dodaj.

  3. Zaktualizuj następujące pola, a następnie kliknij pozycję Dodaj źródło:

    • Nazwa: podaj zrozumiałą nazwę, na przykład Local-Packages.
    • Lokalizacja: określ folder local-nuget-feed utworzony w poprzednim kroku.

    Uwaga

    W takim przypadku nie ma potrzeby określania nazwy użytkownika i hasła.

  4. Kliknij przycisk OK.

Odwoływanie się do pakietu

Powtórz następujące kroki dla każdego projektu (MathFuncsApp, MathFuncsApp.Android i MathFuncsApp.iOS).

  1. CONTROL + KLIKNIJ projekt, a następnie wybierz pozycję Dodaj pakiety NuGet... z menu Dodaj .
  2. Wyszukaj ciąg MathFuncs.
  3. Sprawdź, czy wersja pakietu to 1.0.0, a inne szczegóły są wyświetlane zgodnie z oczekiwaniami, takie jak Tytuł i Opis, czyli MathFuncs i Przykładowa biblioteka otoki języka C++.
  4. Wybierz pakiet MathFuncs, a następnie kliknij pozycję Dodaj pakiet.

Korzystanie z funkcji biblioteki

Teraz, wraz z odwołaniem do pakietu MathFuncs w każdym z projektów, funkcje są dostępne dla kodu języka C#.

  1. Otwórz MainPage.xaml.cs z poziomu projektu MathFuncsApp common Xamarin.Forms(przywołyń go zarówno MathFuncsApp.Android, jak i MathFuncsApp.iOS).

  2. Dodaj instrukcje using dla elementu System.Diagnostics i MathFuncs w górnej części pliku:

    using System.Diagnostics;
    using MathFuncs;
    
  3. Zadeklaruj wystąpienie MyMathFuncs klasy na początku MainPage klasy:

    MyMathFuncs myMathFuncs;
    
  4. Zastąpij OnAppearing metody i OnDisappearing z klasy bazowej ContentPage :

    protected override void OnAppearing()
    {
        base.OnAppearing();
    }
    
    protected override void OnDisappearing()
    {
        base.OnDisappearing();
    }
    
  5. Zaktualizuj metodę OnAppearing , aby zainicjować wcześniej zadeklarowaną zmienną myMathFuncs :

    protected override void OnAppearing()
    {
        base.OnAppearing();
        myMathFuncs = new MyMathFuncs();
    }
    
  6. Zaktualizuj metodę , OnDisappearing aby wywołać metodę w metodzie Dispose w metodzie :myMathFuncs

    protected override void OnDisappearing()
    {
        base.OnAppearing();
        myMathFuncs.Dispose();
    }
    
  7. Zaimplementuj metodę prywatną o nazwie TestMathFuncs w następujący sposób:

    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. Na koniec wywołaj metodę TestMathFuncsOnAppearing na końcu metody:

    TestMathFuncs();
    
  9. Uruchom aplikację na każdej platformie docelowej i zweryfikuj dane wyjściowe w okienku danych wyjściowych aplikacji w następujący sposób:

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

    Uwaga

    Jeśli podczas testowania w systemie Android wystąpi błąd "DLLNotFoundException" lub błąd kompilacji w systemie iOS, upewnij się, że architektura procesora CPU używanego urządzenia/emulatora/symulatora jest zgodna z podzestawem, który wybraliśmy do obsługi.

Podsumowanie

W tym artykule wyjaśniono, jak utworzyć aplikację platformy Xamarin.Forms, która korzysta z bibliotek natywnych za pośrednictwem typowej otoki platformy .NET dystrybuowanej za pośrednictwem pakietu NuGet. Przykład przedstawiony w tym przewodniku jest celowo bardzo uproszczony, aby łatwiej zademonstrować podejście. Prawdziwa aplikacja będzie musiała zajmować się złożonościami, takimi jak obsługa wyjątków, wywołania zwrotne, marshaling bardziej złożonych typów i łączenie z innymi bibliotekami zależności. Kluczową kwestią jest proces, w którym ewolucja kodu C++ jest koordynowana i synchronizowana z otoką i aplikacjami klienckimi. Ten proces może się różnić w zależności od tego, czy jeden lub oba te problemy są obowiązkiem jednego zespołu. Tak czy inaczej, automatyzacja jest realną korzyścią. Poniżej przedstawiono niektóre zasoby, które dostarczają dalszych informacji na temat niektórych kluczowych pojęć wraz z odpowiednimi plikami do pobrania.

Pobieranie

Przykłady

Dalsze informacje

Artykuły dotyczące zawartości tego wpisu