Wprowadzenie do współdziałania z bibliotekami natywnymi

W tym artykule opisano, jak zacząć korzystać ze współpracy z biblioteką natywną przy użyciu biblioteki Maui.NativeLibraryInterop, aby uprościć konfigurację.

Te instrukcje przedstawiają podstawowe kroki, kluczowe decyzje oraz przykłady pomocnicze dotyczące tworzenia powiązań przy użyciu mechanizmu Native Library Interop. Aby uzyskać więcej wskazówek dotyczących konkretnego interfejsu API i szczegółów implementacji, zapoznaj się z dokumentacją natywnych zestawów SDK i bibliotek, które cię interesują.

Wymagania wstępne

Zainstaluj wymagania wstępne:

Uwaga

Istnieje możliwość zainstalowania zestawu SDK systemu Android i/lub narzędzi wiersza polecenia Xcode w autonomiczny sposób. Jednak instalacja narzędzi wiersza polecenia Xcode jest zwykle obsługiwana za pośrednictwem środowiska Xcode. Podobnie instalacja Android SDK jest zazwyczaj obsługiwana za pomocą Android Studio i/lub rozszerzenia .NET MAUI do VS Code, zgodnie z dokumentacją .NET MAUI Getting Started.

Tworzenie nowego powiązania

Najprostszym sposobem rozpoczęcia tworzenia nowego powiązania jest sklonowanie szablonu w repozytorium Maui.NativeLibraryInterop i wprowadzenie tam modyfikacji. Aby lepiej zrozumieć pełny zakres tego, jak Maui.NativeLibraryInterop jest obecnie skonfigurowane, przeczytaj więcej w dokumentacji — omówieniu.

Konfigurowanie bibliotek powiązań platformy .NET

Szablon zawiera początkowe biblioteki powiązań platformy .NET dla systemów Android i .NET dla systemu iOS.

Zaktualizuj biblioteki powiązań, aby odzwierciedlały platformy docelowe i wersję platformy .NET zgodnie z potrzebami w aplikacji .NET.

Uwaga

Na przykład: Jeśli chcesz utworzyć tylko powiązanie systemu iOS przy użyciu platformy .NET 9, możesz wykonać następujące czynności:

  1. Usuń bibliotekę powiązań systemu Android na stronie template/android/NewBinding.Android.Binding i
  2. Zaktualizuj docelowy framework w template/macios/NewBinding.MaciOS.Binding/NewBinding.MaciOS.Binding.csproj, aby był ustawiony na net9.0-ios.

Skonfiguruj natywne projekty i biblioteki opakowujące

Szablon zawiera również początkowe projekty programu Android Studio i projekty Xcode.

Zaktualizuj projekty natywne, aby odzwierciedlały platformy docelowe i wersje zgodnie z potrzebami w aplikacji .NET, i uwzględnij interesujące biblioteki natywne, wykonując następujące kroki.

Konfiguracja: iOS i Mac Catalyst

Projekt Xcode znajduje się w folderze template/macios/native/NewBinding.

Zaktualizuj projekt Xcode tak, aby odzwierciedlał platformy docelowe i wersje obsługiwane przez aplikację .NET. W projekcie Xcode kliknij framework najwyższego poziomu, a następnie w sekcji Cele > Ogólne:

  1. Dodaj/usuń w razie potrzeby dowolne lokalizacje wsparcia.
  2. Dostosuj wersję systemu iOS zgodnie z potrzebami.

Wdróż bibliotekę natywną dla systemów iOS i/lub MacCatalyst w projekcie Xcode, za pomocą dowolnej metody, która działa najlepiej dla biblioteki i Twoich potrzeb (np. CocoaPods, Swift Menedżer pakietów).

Konfiguracja: Android

Projekt programu Android Studio znajduje się w folderze template/android/native.

Zaktualizuj projekt programu Android Studio, aby odzwierciedlał wersje docelowe obsługiwane w aplikacji .NET.

  1. Przejdź do pliku build.gradle.kts (:app)
  2. W razie potrzeby zaktualizuj wersję compileSdk

Wprowadzenie biblioteki natywnej systemu Android za pomocą narzędzia gradle

  1. Dodaj zależność pakietu w bloku zależności pliku build.gradle.kts (:app).
  2. Dodaj repozytorium do bloku dependencyResolutionManagementrepositories w pliku settings.gradle.kts.
  3. Synchronizuj projekt z plikami gradle (za pomocą przycisku w prawym górnym rogu programu Android Studio).

Tworzenie interfejsu API

Utwórz interfejs API między projektami natywnymi i projektami powiązań platformy .NET, wykonując następujące kroki.

Definicja interfejsu API: iOS i Mac Catalyst

Po stronie natywnej, wprowadź zmiany w template/macios/native/NewBinding/NewBinding/DotnetNewBinding.swift:

  1. Dodaj instrukcję import, aby zaimportować właśnie dodaną bibliotekę natywną.
  2. Napisz interesujące cię definicje interfejsów API biblioteki natywnej.
  3. Upewnij się, że projekt Xcode kompiluje się pomyślnie i że udostępnione API spełniają Twoje oczekiwania.

Po stronie platformy .NET jesteśmy teraz gotowi do współdziałania z biblioteką natywną:

  1. Uruchom polecenie dotnet build z template/macios/NewBinding.MaciOS.Binding, aby sprawdzić, czy wszystko jest poprawnie skonfigurowane i gotowe do działania.
  2. Użyj funkcji objective sharpie, aby wygenerować powiązania języka C# dla aktualizacji interfejsu API swift:
    1. Przejdź do template/macios/NewBinding.MaciOS.Binding/bin/Debug/net9.0-ios/NewBinding.MaciOS.Binding.resources/NewBindingiOS.xcframework/ios-arm64/NewBinding.framework w folderze wyjściowym projektów powiązań systemu MaciOS.
    2. Uruchom sharpie xcode -sdks, aby uzyskać listę prawidłowych wartości docelowego zestawu SDK dla powiązanego polecenia. Wybierz wartość, która jest zgodna z platformą i wersją docelową do użycia z następnym poleceniem, na przykład iphoneos18.0.
    3. Uruchom sharpie bind względem plików nagłówkowych w pliku xcframework utworzonym przez projekt powiązania:
      sharpie bind --output=sharpie-out --namespace=NewBindingMaciOS --sdk=iphoneos18.0 --scope=Headers Headers/NewBinding-Swift.h
      
    4. Zaktualizuj zawartość szablonu/macios/NewBinding.MaciOS.Binding/ApiDefinition.cs, zastępując zawartością template/macios/NewBinding.MaciOS.Binding/bin/Debug/net9.0-ios/NewBinding.MaciOS.Binding.resources/NewBindingiOS.xcframework/ios-arm64/NewBinding.framework/sharpie-out/ApiDefinitions.cs i dostosowując ją zgodnie z potrzebami (np. nazewnictwo).
    5. Uruchom ponownie dotnet build z szablonu/macios/NewBinding.MaciOS.Binding .

Zobacz również dokumentację objective-sharpie, aby dowiedzieć się więcej o tym narzędziu.

Definicja interfejsu API: Android

Po stronie natywnej wprowadź aktualizacje w szablonie /android/native/newbinding/src/main/java/com/example/newbinding/DotnetNewBinding.java:

  1. Dodaj instrukcję import, aby zaimportować właśnie dodaną bibliotekę natywną.
  2. Napisz interesujące cię definicje interfejsów API biblioteki natywnej.
  3. Upewnij się, że projekt w Android Studio kompiluje się bez błędów i że jesteś zadowolony(-a) z interfejsów API.

Po stronie platformy .NET jesteśmy teraz gotowi do współdziałania z biblioteką natywną:

  1. Uruchom polecenie dotnet build z szablonu template/android/NewBinding.Android.Binding, aby sprawdzić, czy wszystko jest poprawnie skonfigurowane i gotowe do działania. (Uwaga: ten krok będzie wymagał zainstalowania zestawu JDK 17)
  2. Aby odwołać się do wszystkich powiązań zależności Androida, dodaj element @(AndroidMavenLibrary) do szablonu/przykładu/mauiSample.csproj dla każdej zależności maven powiązanej w natywnym projekcie Android. Umożliwi to weryfikację zależności języka Java dla projektu i spowoduje utworzenie kolejnych kompilacji w celu wygenerowania ostrzeżeń kompilacji lub błędów dotyczących brakujących zależności. Można rozwiązać te ostrzeżenia/błędy, dodając elementy @(AndroidMavenLibrary) lub @(PackageReference), zgodnie z zaleceniem, aby spełnić łańcuch zależności języka Java dla biblioteki natywnej, którą wiążesz. (Uwaga: zależności Gradle lub Maven często wymagają jawnego wskazania, ponieważ nie są one automatycznie dołączane do bibliotek).
<ItemGroup Condition="$(TargetFramework.Contains('android'))">
    <AndroidMavenLibrary Include="{DependencyGroupId}:{DependencyName}" Version="{DependencyVersion}" Bind="false" />
</ItemGroup>

Aby uzyskać więcej informacji na temat tego procesu, zobacz również dokumentację AndroidMavenLibrary oraz weryfikacji zależności Java.

Uwaga

Możesz zmienić nazwę klasy zastępczej DotnetNewBinding, tak aby lepiej odzwierciedlała natywną bibliotekę, którą ta klasa opakowuje. Aby uzyskać więcej przykładów i wskazówek dotyczących pisania definicji interfejsu API, przeczytaj więcej w poniższej sekcji: Modyfikowanie istniejącego powiązania.

Korzystaj z interfejsów API w aplikacji .NET

Szablon template zawiera przykładową aplikację .NET MAUI w lokalizacji template/sample/MauiSample, która odwołuje się do projektów powiązań dla platformy .NET, dzięki czemu biblioteki natywne są od razu gotowe do użycia!

Jeśli interesuje Cię użycie własnych aplikacji .NET MAUI, .NET for Android, .NET for iOS i/lub .NET for Mac Catalyst, możesz to zrobić, modyfikując pliki projektu aplikacji .NET w celu odwołania się do bibliotek powiązań:

<!-- Reference to MaciOS Binding project -->
<ItemGroup Condition="$(TargetFramework.Contains('ios')) Or $(TargetFramework.Contains('maccatalyst'))">
    <ProjectReference Include="..\..\macios\NewBinding.MaciOS.Binding\NewBinding.MaciOS.Binding.csproj" />
</ItemGroup>

<!-- Reference to Android Binding project -->
<ItemGroup Condition="$(TargetFramework.Contains('android'))">
    <ProjectReference Include="..\..\android\NewBinding.Android.Binding\NewBinding.Android.Binding.csproj" />
</ItemGroup>

Modyfikowanie istniejącego powiązania

Jeśli istniejąca powierzchnia interfejsu API nie uwidacznia potrzebnych funkcji we własnym projekcie, nadszedł czas, aby wprowadzić własne modyfikacje.

Katalizator systemu iOS i Mac

W projekcie Xcode znajdziesz co najmniej jeden plik Swift, który definiuje publiczną powierzchnię interfejsu API dla powiązania. Na przykład metoda register usługi Firebase Messaging jest zdefiniowana następująco:

@objc(MauiFIRMessaging)
public class MauiFIRMessaging : NSObject {

    @objc(register:completion:)
    public static func register(apnsToken: NSData, completion: @escaping (String?, NSError?) -> Void) {
        let data = Data(referencing: apnsToken);
        Messaging.messaging().apnsToken = data
        Messaging.messaging().token(completion: { fid, error in
            completion(fid, error as NSError?)
        })
    }
    // ...
}

Uwaga

Natywne typy API otoki, które będą używane przez powiązanie .NET, muszą być zadeklarowane jako public i oznaczone adnotacją @objc(NameOfType), a metody również muszą być public; mogą też korzystać z podobnych adnotacji @objc(methodName:parameter1:), w których określa się nazwę i parametry, co pomaga wpływać na powiązanie generowane przez Objective Sharpie.

W tej metodzie widać, że publiczny interfejs API używa tylko typów, które platforma .NET dla systemu iOS już obsługuje: NSData, String, NSError i funkcji wywołania zwrotnego.

W projekcie Firebase.MaciOS.Binding plik ApiDefinitions.cs zawiera definicję powiązań dla tego natywnego interfejsu API opakowującego:

using System;
using Foundation;

namespace Firebase
{
    // @interface MauiFIRMessaging : NSObject
    [BaseType (typeof(NSObject))]
    interface MauiFIRMessaging
    {
        [Static]
        [Export ("register:completion:")]
        [Async]
        void Register (NSData apnsToken, Action<string?, NSError?> completion);
        // ...
    }

Załóżmy, że chcesz dodać metodę do wyrejestrowania. Kod Swift będzie wyglądać mniej więcej tak:

@objc(unregister:)
public static func unregister(completion: @escaping (NSError?) -> Void) {
    // need delegate to watch for fcmToken updates
    Messaging.messaging().deleteToken(completion: { error in
        completion(error as NSError?)
    })
}

Druga połowa będzie aktualizować plik ApiDefinitions.cs w projekcie powiązania, aby uwidocznić tę nową metodę. Istnieją dwa sposoby, które można wykonać w tym celu:

  1. Możesz ręcznie dodać wymagany kod
  2. Po utworzeniu projektu powiązania możesz uruchomić narzędzie Objective Sharpie, aby wygenerować plik ApiDefinitions.cs. Możesz spróbować znaleźć odpowiednie zmiany z tego pliku i skopiować je ręcznie lub spróbować skopiować cały plik i przyjrzeć się różnicom, aby znaleźć potrzebną część.

W takim przypadku zmiany w ApiDefinitions.cs będą następujące:

[Static]
[Export("unregister:")]
[Async]
void UnRegister(Action completion);

Po wprowadzeniu tych zmian możesz ponownie skompilować projekt Wiązanie, a nowy interfejs API będzie gotowy do użycia z projektu .NET MAUI.

Uwaga

Projekty powiązań dla platform Mac/iOS nie korzystają z generatorów źródłowych, dlatego system projektu i funkcja IntelliSense mogą nie rozpoznawać nowego interfejsu API, dopóki nie przebudujesz projektu powiązania i nie ponownie załadujesz rozwiązania, aby odwołanie do projektu wskazywało nowszy zestaw. Projekt aplikacji powinien być nadal kompilowany niezależnie od błędów funkcji IntelliSense.

Android

W projekcie programu Android Studio znajdziesz katalog modułu zawierający plik java, który umożliwia określenie publicznego obszaru interfejsu API dla powiązania. Na przykład metoda initialize dla Facebooka jest zdefiniowana następująco:

package com.microsoft.mauifacebook;

import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
import android.util.Log;

import com.facebook.LoggingBehavior;
import com.facebook.appevents.AppEventsLogger;

public class FacebookSdk {

    static AppEventsLogger _logger;

    public static void initialize(Activity activity, Boolean isDebug) {
        Application application = activity.getApplication();

        if (isDebug) {
            com.facebook.FacebookSdk.setIsDebugEnabled(true);
        }

        com.facebook.FacebookSdk.addLoggingBehavior(LoggingBehavior.APP_EVENTS);

        AppEventsLogger.activateApp(application);

        _logger = AppEventsLogger.newLogger(activity);
    }

    // ...
}

W tej metodzie widać, że publiczna powierzchnia interfejsu API używa tylko typów, których platforma .NET dla systemu Android już zna: Activity i Boolean.

W projekcie Facebook.Android.Binding plik Transforms/Metadata.xml zawiera jedynie kod XML opisujący sposób mapowania nazwy pakietu Java (com.microsoft.mauifacebook) na przestrzeń nazw bardziej przyjazną dla języka C# (Facebook). Ogólnie rzecz biorąc, powiązania systemu Android są bardziej "automatyczne" niż mac/iOS w tym momencie i rzadko należy wprowadzać zmiany w tych plikach transformacji.

<metadata>
    <attr path="/api/package[@name='com.microsoft.mauifacebook']" name="managedName">Facebook</attr>
</metadata>

Załóżmy, że chcesz dodać metodę rejestrowania zdarzenia. Kod java będzie wyglądać mniej więcej tak:

public static void logEvent(String eventName) {
    _logger.logEvent(eventName);
}

Dzięki tej prostej zmianie projekt wiązania nie wymaga żadnych aktualizacji pliku Transforms/Metadata.xml ani innych plików. Możesz po prostu przebudować projekt Binding, a nowy interfejs API będzie gotowy do użycia w projekcie .NET MAUI.

Uwaga

Projekty powiązań dla Androida nie używają generatorów źródłowych, dlatego system projektu i mechanizm IntelliSense mogą nie rozpoznawać nowych interfejsów API, dopóki nie skompilujesz ponownie projektu powiązań i nie załadujesz ponownie rozwiązania, tak aby odwołanie do projektu pobrało nowszy zestaw. Projekt aplikacji powinien się nadal kompilować niezależnie od błędów IntelliSense.