バインディングライブラリを作るときの備忘録的なもの
バインディングライブラリとは
Object-cやSwift・javaで書かれているネイティブのライブラリをXamarinでも使えるようにしたライブラリのこと
今回は、Xamarin.iOSでGoogleMapUtilにある「Heatmap」の機能を使いたかったので
「google-maps-ios-utilsリポジトリ」
https://github.com/googlemaps/google-maps-ios-utils/tree/master/src/Heatmap
上記のリポジトのをバインディングしていく。
STEP1: gitからクローンしてくる
git clone git@github.com:googlemaps/google-maps-ios-utils.git
STEP2: 必要な.hファイルと.mファイルを纏める
HeatMapの機能を使うに当たって必要なファイルを1つのフォルダーに纏める
※OSで用意されている奴はこのあとのSTEPでリンクするので含めなくて良いです。
STEP3: Xcodeプロジェクトでスタティックライブラリを生成する(.aファイル)
STEP3-1: Xcodeでスタティックライブラリプロジェクトを新規で作成する
STEP3-2: 抽出したファイルをプロジェクトに追加する
フォルダを右クリックしてAdd Files to <プロジェクト名>を選択する
STEP3-3: 必要なライブラリをリンクさせる
TARGETS → Build Phases のLink Binary With Libraries
各.hファイルと.mファイルのimportされているものを確認しリンクさせる
STEP3-4: .aファイルを生成する
.aファイルを生成するためのMakeFileを作る
XBUILD=/Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild
PROJECT_ROOT=./<YOUR PROJECTNAME>
PROJECT=./<YOUR PROJECTNAME>.xcodeproj
TARGET=<YOUR PROJECTNAME>
all: lib$(TARGET).a
lib$(TARGET)-i386.a:
$(XBUILD) -project $(PROJECT) -target $(TARGET) -sdk iphonesimulator -configuration Release clean build
-mv ./build/Release-iphonesimulator/lib$(TARGET).a $@
lib$(TARGET)-armv7.a:
$(XBUILD) -project $(PROJECT) -target $(TARGET) -sdk iphoneos -arch armv7 -configuration Release clean build
-mv ./build/Release-iphoneos/lib$(TARGET).a $@
lib$(TARGET)-arm64.a:
$(XBUILD) -project $(PROJECT) -target $(TARGET) -sdk iphoneos -arch arm64 -configuration Release clean build
-mv ./build/Release-iphoneos/lib$(TARGET).a $@
lib$(TARGET).a: lib$(TARGET)-i386.a lib$(TARGET)-armv7.a lib$(TARGET)-arm64.a
xcrun -sdk iphoneos lipo -create -output $@ $^
clean:
-rm -f *.a *.dll
次にMakefileのあるディレクトリでターミナル上からmakeコマンドを実行する
実行に成功すると以下のようにファイルが生成される。
STEP4 バインディングライブラリプロジェクトを作る
STEP3-5で生成した.aファイルを追加するためにVisualStudioにてバインディングライブラリプロジェクトを生成する。
STEP4-1: .aファイルをプロジェクトに追加する
生成した.aファイルをプロジェクトに追加する。 ←(arm64とか付いていないプロジェクト名だけの.aファイル)
STEP4-2: .aファイルの中のlinkwith.csファイルを編集する
linkwith.csにはライブラリに含まれるCPUアーキテクチャ・依存するフレームワークを記述し、
指定することができる。
- CPUのアーキテクチャーの指定
LinkTarget.ArmV7 | LinkTarget.ArmV7s | LinkTarget.Simulator,
の3つを指定すると良いとのこと。
- 必要なフレームワークを記述する。 (Githubのreadmeやheaderファイルを確認し必要なフレームワークを確認する)
Frameworks = "GoogleMapsBase, GoogleMaps, GoogleMapsCore, Security,UiKit, CoreLocation, Foundation"
- ライブラリのリンクを記述する。
.dylibで終わるライブラリはここで指定する
LinkerFlags = "-lz",
- IsCxxについて
これはC++を含むライブラリがある時だけtrueにすれば良い。
using ObjCRuntime;
[assembly: LinkWith("libGoogleMapiOSHeatmapUtility.a",
LinkTarget.ArmV7 | LinkTarget.ArmV7s | LinkTarget.Simulator,
SmartLink = true,
ForceLoad = true,
Frameworks = " GoogleMapsBase GoogleMaps GoogleMapsCore Security, UiKit, CoreLocation, Foundation",
LinkerFlags = "-lz",
IsCxx = true)]
STEP4-3: API定義ファイルとAPIに必要な構造体と列挙体のファイルを作成する
Xamarinの公式で出ているバインディングツール、「Objective Sharpie」を使って2つのファイルを生成する。
(公式ページ)
https://developer.xamarin.com/guides/cross-platform/macios/binding/objective-sharpie/
STEP2で必要な.hファイルが入っているフォルダーを作ってあると思うので(作っていなかったら作ってください。)そのフォルダーのディレクトリーにターミナルで移動する。
移動したら、次のコマンドをターミナルで実行する。
sharpie bind --output=GoogleMapiOSHeatmapUtility --namespace=GoogleMapiOSHeatmapUtility --sdk=iphoneos11.1 --verbose ./GoogleMapiOSHeatmapUtility/*.h
実行後、ApiDefinitions.csとStructsAndEnums.csが生成される。
中身を開いてみると何万行にもなる定義が書かれていているので、気合いで必要な部分だけ残し
不要なものを削る。
※[Verify]みたいなのがたくさん出てきてエラーになりますがこれは削除すれば問題ないそうで。
↓
@tanaさんの記事を見ながらやると良さそう Xamarin Objective Sharpie ApiDefinitionsのエラー集
※ StackOverflowであってないような英語スキルで質問を投げてみたら、
新しいバージョンになってから起こったことらしく、
「必要なものは最後らへんにあるのでそこだけ残してあげればいいよ。 あと cocoapodsで配布されているのであれば cocoapodsを使用して作った方が楽だよ」的な答が返ってきた。
自分が質問したStackOverflowのページ
https://stackoverflow.com/questions/47710739/xamarin-object-sharpie-obj-c-library-binding-why-output-huge-file/47747957#47747957
※@Jinshichiさんの記事によると --scope オプションをつけると巨大ファイルが生成されないらしいが自分はまだ試していない
今回の場合、GoogleMaps SDK for iOS(Xamarin)に含まれているメソッドや構造体・列挙体のもあるので、それも削ってあげる。
STEP4-4: GoogleMaps SDK for iOS(Xamarin)に型などを合わせる
当たり前かもしれないが、本体の方で使われている型に合わせないと使い物にならないので、
ApiDefinitions.csとStructsAndEnums.csの中身を適切に変更してあげる。
(例)
GoogleMaps SDK for iOS(Xamarin)側
[Register ("GMSPath", true)]
public class Path : NSObject, INSCopying, INativeObject, IDisposable, INSMutableCopying
{
//
// Static Fields
//
[CompilerGenerated]
private static readonly IntPtr class_ptr;
//
// Static Properties
//
[CompilerGenerated]
public static Path GetPath {
[Export ("path")]
get;
}
バインドしてきたUtilityLibrary側(一部)
// extern BOOL GMSGeometryIsLocationOnPathTolerance (CLLocationCoordinate2D point, GMSPath * _Nonnull path, BOOL geodesic, CLLocationDistance tolerance);
[DllImport ("__Internal")]
[Verify (PlatformInvoke)]
static extern bool GMSGeometryIsLocationOnPathTolerance (CLLocationCoordinate2D point, GMSPath path, bool geodesic, double tolerance);
本体側では GMSPath
→ Path
となっているので
GMSPathになっている箇所を直してあげる。
他にも違う型で同じところがあったら同様に直す。
STEP4-5: (謎)Enumがあるとコンパイルエラーになる
ApiDefintionsの方にenumが出力されるがそのままビルドすると Error CS0101 The namespace '' already contains a definition for~~~~ と、すでに定義されてるよ?というエラーが出るが同じenumを定義してもいないのに発生する。
### Alternate workaround (that works on both Mac and Windows)
Move the `StructsAndEnums.cs` file to a separate small iOS class library project, set the build action of the file to "Compile", and then reference that class library project from the binding project.
対処法としては別プロジェクトに分けてやる
STEP4-6: dllを生成する
dll生成はいたって簡単で、バインディングライブラリプロジェクトをビルドすれば、
プロジェクト内のbinフォルダーの (Debug or release)内にプロジェクト名.dllが生成される。
STEP5: 実際にアプリに組み込んでみる
-
iOSの参照に生成したdllを追加する。
- 参照を右クリックし参照の編集を開き.NETアセンブリタグを開く。
- 生成した<プロジェクト名>.dllにチェックを入れる
あとは、Googleの公式のリファレンスをC#風に置き換えればOK
(公式リファレンス) 尚、日本語版は更新されていない模様)
https://developers.google.com/maps/documentation/ios-sdk/utility/heatmap
バインディングする際に参考にさせていただいたサイト
mattsuDev’s blog - XamarinでNativeライブラリを使用する (iOS編)
Xamarin.iOS でバインディングライブラリを作る
Binding Objective-C
Xamarin Objective Sharpie ApiDefinitionsのエラー集
Xamarin.iOSにObjective-Cライブラリをバインディングする際にハマったこと