Übersicht über Objective-C-Bindungen

Details zur Funktionsweise des Bindungsprozesses

Die Bindung einer Objective-C-Bibliothek zur Verwendung mit Xamarin erfolgt in drei Schritten:

  1. Schreiben Sie eine „API-Definition“ in C#, um die Offenlegung der nativen API in .NET und deren Zuordnung im zugrunde liegenden Objective-C zu beschreiben. Dies geschieht unter Verwendung von C#-Standardkonstrukten wie interface und verschiedenen Bindungsattributen (siehe dieses einfache Beispiel).

  2. Nachdem Sie die „API-Definition“ in C# geschrieben haben, kompilieren Sie sie, um eine „Bindungsassembly“ zu generieren. Die Kompilierung kann über die Befehlszeile oder mithilfe eines Bindungsprojekts in Visual Studio für Mac oder Visual Studio erfolgen.

  3. Diese „Bindungsassembly“ wird anschließend Ihrem Xamarin-Anwendungsprojekt hinzugefügt, damit Sie über die definierte API auf die native Funktionalität zugreifen können. Das Bindungsprojekt ist vollständig getrennt von Ihren Anwendungsprojekten.

    Hinweis

    Schritt 1 kann mithilfe von Objective Sharpie automatisiert werden. Dieses Tool untersucht die Objective-C-API und generiert eine vorgeschlagene C#-API-Definition. Sie können die von Objective Sharpie erstellten Dateien anpassen und sie in einem Bindungsprojekt (oder in der Befehlszeile) verwenden, um Ihre Bindungsassembly zu erstellen. Objective Sharpie selbst erstellt keine Bindungen, dies ist lediglich ein optionaler Bestandteil des übergeordneten Prozesses.

Sie können sich über weitere technische Details zur Funktionsweise informieren, um Unterstützung beim Schreiben von Bindungen zu erhalten.

Befehlszeilenbindungen

Sie können btouch-native für Xamarin.iOS (oder bmac-native bei Verwendung von Xamarin.Mac) verwenden, um Bindungen direkt zu erstellen. Hierbei werden die manuell (oder mit Objective Sharpie) erstellten C#-API-Definitionen an das Befehlszeilentool (btouch-native für iOS oder bmac-native für Mac) übergeben.

Die allgemeine Syntax zum Aufrufen dieser Tools lautet folgendermaßen:

# Use this for Xamarin.iOS:
bash$ /Developer/MonoTouch/usr/bin/btouch-native -e cocos2d.cs -s:enums.cs -x:extensions.cs
# Use this for Xamarin.Mac:
bash$ bmac-native -e cocos2d.cs -s:enums.cs -x:extensions.cs

Der obige Befehl erzeugt die Datei cocos2d.dll im aktuellen Verzeichnis, und sie enthält die vollständig gebundene Bibliothek, die Sie in Ihrem Projekt verwenden können. Dies ist das Tool, mit dem Visual Studio für Mac Ihre Bindungen erstellt, wenn Sie ein Bindungsprojekt verwenden (siehe Beschreibung unten).

Bindungsprojekt

Ein Bindungsprojekt kann in Visual Studio für Mac oder Visual Studio (Visual Studio unterstützt nur iOS-Bindungen) erstellt werden und vereinfacht das Bearbeiten und Kompilieren von API-Definitionen für Bindungen (im Gegensatz zur Verwendung der Befehlszeile).

Dieser Leitfaden für die ersten Schritte zeigt, wie Sie ein Bindungsprojekt erstellen und verwenden, um eine Bindung zu erzeugen.

Objektive Sharpie

Objective Sharpie ist ein weiteres, separates Befehlszeilentool, das Unterstützung in den ersten Phasen der Erstellung einer Bindung bietet. Das Tool selbst erstellt keine Bindungen, sondern automatisiert den ersten Schritt zum Generieren einer API-Definition für die native Zielbibliothek.

In der Objective Sharpie-Dokumentation erfahren Sie, wie Sie native Bibliotheken, native Frameworks und CocoaPods durch Parsen in API-Definitionen umwandeln, die in Bindungen kompiliert werden können.

Funktionsweise von Bindungen

Es ist möglich, die Attribute [Register] und [Export] sowie einen manuellen Objective-C-Selektoraufruf gemeinsam zu verwenden, um neue (bisher ungebundene) Objective-C-Typen manuell zu binden.

Im ersten Schritt ermitteln Sie einen Typ, den Sie binden möchten. Zur Erläuterung (und der Einfachheit halber) binden wir den Typ NSEnumerator (der bereits in Foundation.NSEnumerator gebunden wurde; die nachstehende Implementierung dient lediglich als Beispiel).

Im zweiten Schritt muss der C#-Typ erstellt werden. Die Platzierung erfolgt vorzugsweise in einem Namespace. Da Objective-C aber keine Namespaces unterstützt, müssen wir mit dem [Register]-Attribut den Typnamen ändern, den Xamarin.iOS bei der Objective-C-Runtime registriert. Der C#-Typ muss außerdem von Foundation.NSObject erben:

namespace Example.Binding {
    [Register("NSEnumerator")]
    class NSEnumerator : NSObject
    {
        // see steps 3-5
    }
}

Im dritten Schritt konsultieren wir die Objective-C-Dokumentation und erstellen ObjCRuntime.Selector-Instanzen für jeden Selektor, der verwendet werden soll. Platzieren Sie diese innerhalb des Klassentexts:

static Selector selInit       = new Selector("init");
static Selector selAllObjects = new Selector("allObjects");
static Selector selNextObject = new Selector("nextObject");

Im vierten Schritt muss Ihr Typ Konstruktoren bereitstellen. Sie müssen Ihren Konstruktoraufruf mit dem Basisklassenkonstruktor verketten. Das [Export]-Attribut erlaubt es dem Objective-C-Code, die Konstruktoren mit dem angegebenen Selektornamen aufzurufen:

[Export("init")]
public NSEnumerator()
    : base(NSObjectFlag.Empty)
{
    Handle = Messaging.IntPtr_objc_msgSend(this.Handle, selInit.Handle);
}
// This constructor must be present so that Xamarin.iOS
// can create instances of your type from Objective-C code.
public NSEnumerator(IntPtr handle)
    : base(handle)
{
}

Im fünften Schritt geben Sie Methoden für jeden in Schritt 3 deklarierten Selektor an. Diese verwenden objc_msgSend() zum Aufruf des Selektors für das native Objekt. Beachten Sie die Verwendung von Runtime.GetNSObject() zum Konvertieren von IntPtr in ein NSObject eines geeigneten Typs (Untertyps). Wenn die Methode vom Objective-C-Code aus aufrufbar sein soll, muss es sich um einen virtuellen Member handeln.

[Export("nextObject")]
public virtual NSObject NextObject()
{
    return Runtime.GetNSObject(
        Messaging.IntPtr_objc_msgSend(this.Handle, selNextObject.Handle));
}
// Note that for properties, [Export] goes on the get/set method:
public virtual NSArray AllObjects {
    [Export("allObjects")]
    get {
        return (NSArray) Runtime.GetNSObject(
            Messaging.IntPtr_objc_msgSend(this.Handle, selAllObjects.Handle));
    }
}

Zusammenfassung:

using System;
using Foundation;
using ObjCRuntime;

namespace Example.Binding {
    [Register("NSEnumerator")]
    class NSEnumerator : NSObject
    {
        static Selector selInit       = new Selector("init");
        static Selector selAllObjects = new Selector("allObjects");
        static Selector selNextObject = new Selector("nextObject");

        [Export("init")]
        public NSEnumerator()
            : base(NSObjectFlag.Empty)
        {
            Handle = Messaging.IntPtr_objc_msgSend(this.Handle,
                selInit.Handle);
        }

        public NSEnumerator(IntPtr handle)
            : base(handle)
        {
        }

        [Export("nextObject")]
        public virtual NSObject NextObject()
        {
            return Runtime.GetNSObject(
                Messaging.IntPtr_objc_msgSend(this.Handle,
                    selNextObject.Handle));
        }

        // Note that for properties, [Export] goes on the get/set method:
        public virtual NSArray AllObjects {
            [Export("allObjects")]
            get {
                return (NSArray) Runtime.GetNSObject(
                    Messaging.IntPtr_objc_msgSend(this.Handle,
                        selAllObjects.Handle));
            }
        }
    }
}