Xamarin.iOS でバインディングライブラリを作る
Xamarin では、広告・分析やUIなどの機能を提供するネイティブSDKも使用することができます。.NET用のライブラリだけでなく、ネイティブのライブラリが選択肢に入ることで、より多くの可能性を引き出すことができます。
ネイティブのライブラリを使用するには、バインディングライブラリというものを作ってラップする必要があります。この記事では Xamarin.iOS のネイティブバインディングライブラリを作る方法をご紹介します。
今回は題材として Google Anaytics SDK for iOS v3 を使用しますが、このライブラリは Xamarin Components に存在していてそのまま使用することができます。
バインディングプロジェクトの作成
Xamarin Studio の新規作成画面で iOS の iOS Binding Project を作成します。このとき、プロジェクト名に . を含めないようにしてください。
ライブラリの追加
作成したプロジェクトに、ネイティブバインディングするライブラリを追加します。プロジェクトに対して .a ファイルをドロップします。
LinkWithAttribute の指定
.a ファイルが追加されると、このファイルに対するコードビハインドとして .linkwith.cs ファイルが追加されます。このファイルではこのライブラリに含まれるCPUアーキテクチャや依存するフレームワークやスタティックライブラリなどの情報を指定することができます。
実際に中をのぞくと、アセンブリに対する LinkWithAttribute の指定があることが確認できます。
using System; using MonoTouch.ObjCRuntime; [assembly: LinkWith ("libGoogleAnalyticsServices.a", LinkTarget.ArmV7 | LinkTarget.ArmV7s | LinkTarget.Simulator, ForceLoad = true)]
Google Analytics の場合、添付の Readme.txt に下記のライブラリに依存するとあります。
- CoreData.framework
- SystemConfiguration.framework
- libz.dylib
このうち、.framework で終わるフレームワークは Frameworks、.dylib で終わるライブラリは LinkerFlags で指定します。(IsCxx は C++ を含むライブラリの場合に true で指定するそうです)
using System; using MonoTouch.ObjCRuntime; [assembly: LinkWith ("libGoogleAnalyticsServices.a", LinkTarget.ArmV7 | LinkTarget.ArmV7s | LinkTarget.Simulator, ForceLoad = true, Frameworks = "CoreData SystemConfiguration AdSupport", LinkerFlags = "-lz", IsCxx = true)]
Objective Sharpie を使った定義の生成
次に、このライブラリにあるクラスにアクセスするための C# ラッパーの定義を作成します。自力で P/Invoke を書いていくこともできるのですが、とても骨の折れる作業となってします。そこで、Xamarin が別途提供している Objective Sharpie というツールを使います。このツールを使えばウィザード形式で簡単に定義を生成することができます。
次に、読み込むヘッダファイルを指定します。ヘッダファイルもしくはヘッダファイルの入っているディレクトリを指定します。
あとは、出力されるライブラリのネームスペースを入れます。
Generate をクリックすると、出力先を聞かれますので、一時的に適切な場所に保存します。
書き出されたファイルをテキストエディタで開き、調整しながらプロジェクトに移していきます。
生成された定義の調整
多くの場合、Objective Sharpie で出力された定義は手動での調整が必要です。定義を手動で調整しながら、プロジェクトに書き写していきます。クラスは ApiDefinition.cs、構造体と列挙体は StructsAndEnums.cs へ書き込みます。
以下では問題となるパターンをいくつかご紹介します。
同じ定義が複数出力される
適切なものを1つ選んで書き写します。
列挙体に [unmapped: unexposed: Elaborated] がつく
たとえば、以下のような場合です。
public enum GAILogLevel : [unmapped: unexposed: Elaborated] { None = 0, Error = 1, Warning = 2, Info = 3, Verbose = 4 }
この場合は、適切な型を指定します。
public enum GAILogLevel : uint { None = 0, Error = 1, Warning = 2, Info = 3, Verbose = 4 }
VerifyAttribute がつく
Objective Sharpie が開発者に確認して欲しい所に関しては、VerifyAttribute がつき、この属性が付いている場合ビルドに失敗します。
以下の場合、「Objective-C のメソッドがプロパティっぽかったので、プロパティに変えたけどこれでいいか確認して」という内容になります。
[Static, Export ("sharedInstance"), Verify ("ObjC method massaged into getter property", "/Users/ebina/Downloads/GoogleAnalyticsServicesiOS_3.07/GoogleAnalytics/Library/GAI.h", Line = 97)] GAI SharedInstance { get; }
問題がなければそのまま Verify を消すだけです。
[Static, Export ("sharedInstance")] GAI SharedInstance { get; }
FieldAttribute がビルドエラーになる
FieldAttribute が付いている、ゲッターのみのプロパティがあるとビルドエラーになる場合があります。
[Field ("kGAIUseSecure")] NSString GAIUseSecure { get; }
このとき、エラーメッセージは下記のような、全く関係のない内容でトラブルシューティングが難しい状態となります。
`MonoTouch.Constants' does not contain a definition for `GoogleAnalyticsTouchLibrary'
この場合は、第2引数として "__Internal" をつければひとまずビルドが通ります。
[Field ("kGAIUseSecure", "__Internal")] NSString GAIUseSecure { get; }
## 実際にアプリに組み込んだらエラーになる
バインディングライブラリのビルドが通っても、実際にアプリに組み込んで使用した際にビルドエラーになってしまう場合があります。なぜなら、アプリのビルド時にバインディングライブラリからスタティックライブラリを取り出して、スタティックリンクを行う際に判明する問題があるからです。
Google Analytics の場合、このまま素のプロジェクトに追加してビルドすると sqlite3 系の関数が存在しないというエラーが発生します。(通常のXcodeの場合だとデフォルトで入っているけど、Xamarinだとそのクラスを使わないとリンクされないから...といったところで出ているのではないかと考えています)
error MT5210: Native linking failed, undefined symbol: sqlite3_bind_blob. Please verify that all the necessary frameworks have been referenced and native libraries are properly linked in. error MT5210: Native linking failed, undefined symbol: sqlite3_bind_int. Please verify that all the necessary frameworks have been referenced and native libraries are properly linked in. error MT5210: Native linking failed, undefined symbol: _sqlite3_bind_int64. Please verify that all the necessary frameworks have been referenced and native libraries are properly linked in. (以下略)
この場合ですと、不足しているシンボルをLinkWithに足すことで解消できます。(LinkerFlags のところに -lsqlite3 を追加しています。)
using System; using MonoTouch.ObjCRuntime; [assembly: LinkWith ("libGoogleAnalyticsServices.a", LinkTarget.ArmV7 | LinkTarget.ArmV7s | LinkTarget.Simulator, ForceLoad = true, Frameworks = "CoreData SystemConfiguration AdSupport", LinkerFlags = "-lz -lsqlite3", IsCxx = true)]
おわりに
Xamarin.iOS のネイティブバインディングの作成はだいぶコツが必要ですが、パターンがわかれば Objective Sharpie の利用で簡単にすることができます。
Xamarin でも広告や3Dなど、様々なライブラリを使えるので、是非活用してください。