Objective-C のバインディングの概要

"バインド プロセスのしくみの詳細"

Xamarin で使用する Objective-C ライブラリをバインドするには、次の 3 つの手順を行います。

  1. .NET でどのようにネイティブ API が公開されるか、また、それがどのように基になる Objective-C にマップされるかを記述するために、C# "API 定義" を記述します。 これは、interface のような標準 C# 構成体と、さまざまなバインド属性を使用して行われます (この簡単な例を参照してください)。

  2. C# で "API 定義" を記述したら、それをコンパイルして "バインド" アセンブリを生成します。 これは、コマンド ラインで、または Visual Studio for Mac または Visual Studio のバインド プロジェクトを使用して実行できます。

  3. その後、その "バインド" アセンブリが Xamarin アプリケーション プロジェクトに追加されます。これにより、定義した API を使用してネイティブ機能にアクセスできます。 バインド プロジェクトは、アプリケーション プロジェクトから完全に分離されています。

    Note

    手順 1 は、Objective Sharpie を使用して自動化できます。 これにより、Objective-C API が調べられ、提案の C# "API 定義" が生成されます。Objective Sharpie によって作成されたファイルをカスタマイズし、それをバインド プロジェクト(またはコマンド ライン)で使用して、バインド アセンブリを作成できます。 Objective Sharpie は、それ自体でバインドを作成するのではなく、より大きなプロセスのオプションの一部にすぎません。

また、バインドを記述するのに役立つそのしくみの技術的な詳細を参照することもできます。

コマンド ライン バインド

Xamarin.iOS の btouch-native (または Xamarin.Mac を使用している場合は bmac-native) を使って、直接バインドを構築することができます。 これは、手動で (または Objective Sharpie を使用して) 作成した C# API 定義 をコマンド ライン ツール (iOS の場合はbtouch-native、Mac の場合は bmac-native) に渡すことによって機能します。

これらのツールを呼び出すための一般的な構文は次のとおりです。

# 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

上記のコマンドによって、現在のディレクトリに cocos2d.dll ファイルが生成されます。これには、お使いのプロジェクトで使用できる完全にバインドされたライブラリが含まれます。 これは、バインド プロジェクト(下記参照) を使用する場合に Visual Studio for Mac がバインドの作成に使用するツールです。

バインド プロジェクト

バインド プロジェクトは、Visual Studio for Mac または Visual Studio (Visual Studio は iOS バインドのみをサポートします) で作成することができ、(コマンド ラインを使用する場合と比べて) バインド の API 定義の編集と構築が容易になります。

この使用開始ガイドに従って、バインド プロジェクトを作成および使用してバインドを生成する方法を確認してください。

Objective Sharpie

Objective Sharpie は、もう 1 つの別のコマンド ライン ツールであり、バインドの作成の初期段階に役立ちます。 これは、それ自体でバインドを作成するのではなく、ターゲットのネイティブ ライブラリの API 定義を生成する最初の手順を自動化します。

ネイティブ ライブラリ、ネイティブ フレームワーク、CocoaPods を解析して、バインドに組み込むことができる API 定義にする方法については、Objective Sharpie に関するドキュメントを参照してください。

バインドのしくみ

[Register] 属性、[Export] 属性、手動 Objective-C セレクターの呼び出しを一緒に使用して、新しい (以前にバインドされていない) Objective-C 型を手動でバインドすることができます。

最初に、バインドする型を見つけます。 説明 (と簡潔さ) のために、NSEnumerator 型をバインドします (これは既に Foundation.NSEnumerator にバインドされています。以下の実装は単なる例です)。

次に、C# 型を作成する必要があります。 これを名前空間に配置したい場合があります。Objective-C は名前空間をサポートしていないため、Xamarin.iOS によって Objective-C ランタイムに登録される型名を、[Register] 属性を使用して変更する必要があります。 C# 型は Foundation.NSObject から継承する必要もあります。

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

3 番目に、Objective-C のドキュメントを確認して、使用するセレクターごとに ObjCRuntime.Selector インスタンスを作成します。 これらをクラス本体内に配置します。

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

4 番目に、使用する型ではコンストラクターを提供する必要があります。 コンストラクターの呼び出しを基底クラスのコンストラクターにチェーンする "必要があります"。 [Export] 属性を使用すると、Objective-C コードで、指定したセレクター名を含むコンストラクターを呼び出すことができます。

[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)
{
}

5 番目に、手順 3 で宣言した各セレクターのメソッドを指定します。 これらは、objc_msgSend() を使用して、ネイティブ オブジェクトのセレクターを呼び出します。 IntPtr を適切に型指定された NSObject (サブ) 型に変換するために Runtime.GetNSObject() が使用されていることに注意してください。 このメソッドを Objective-C コードから呼び出せるようにするには、メンバーを仮想にする "必要があります"。

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

すべてを組み合わせると次のようになります。

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