Xamarin.AndroidでNDKを利用する

JXUG Conference 大阪で発表したXamarin.Android+NDKの、デモ部分の解説記事です。

スライドはこちら。
「Xamarin.Androidでネイティブコードと仲良しになる方法」
http://www.slideshare.net/hIDDENxv/xamarinandroidndk

Xamarin.AndroidでC/C++記述したネイティブコードをプロジェクトに含めて利用するための、シンプルな手順を解説します。
前提は、Xamarin、Android SDK、Android NDK環境が整っていること。
Mac+Xamarin Studioで解説しているので、他の環境の場合は適宜読み替えていただければと。

アジェンダはこちら。
１．NDKビルド環境を整える
２．ビルドしてできたlibsフォルダ以下を、プロジェクトに追加する
３．C#からコールする
４．ビルドターゲットを追加する

##１．NDKビルド環境を整える
まずはAndroidアプリプロジェクトを作成します。
そして、ndk-buildからみて通常のAndroidアプリプロジェクトにみえるように、プロジェクトフォルダ以下の構成を整えます。

ひとつめ。プロジェクトにproject.propertiesファイルを追加。

```properties:project.properties
target=android-19
```

ふたつめ、プロジェクトにjniフォルダを作成し、mkファイルとソースを追加。

```make:Android.mk
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE    := NativeTest
LOCAL_SRC_FILES := Native.cpp

include $(BUILD_SHARED_LIBRARY)
```

```make:Application.mk
APP_ABI := all
```

```cpp:Native.cpp
extern "C"
{
    int add( int a, int b );
}

int add( int a, int b )
{
    return a + b;
}

```
ここまでできたら、ターミナルを開いてビルドしてみます。
プロジェクトフォルダまで移動して、ndk-buildを実行。下記のようなメッセージが表示されれば成功です。

```
Compile++ thumb  : NativeTest <= Native.cpp
SharedLibrary  : libNativeTest.so
Install        : libNativeTest.so => libs/armeabi/libNativeTest.so
Compile++ thumb  : NativeTest <= Native.cpp
SharedLibrary  : libNativeTest.so
Install        : libNativeTest.so => libs/armeabi-v7a/libNativeTest.so
Compile++ mips   : NativeTest <= Native.cpp
SharedLibrary  : libNativeTest.so
Install        : libNativeTest.so => libs/mips/libNativeTest.so
Compile++ x86    : NativeTest <= Native.cpp
SharedLibrary  : libNativeTest.so
Install        : libNativeTest.so => libs/x86/libNativeTest.so
```

##２．ビルドしてできたlibsフォルダ以下を、プロジェクトに追加する
NDKビルドに成功すると、プロジェクトフォルダ下にlibsフォルダが作成されているはず。
libsフォルダには、各プラットフォーム向けのライブラリファイルができているので、必要なぶんだけプロジェクトに追加し、ビルドアクションを「AndroidNativeLibrary」に設定します。
![スクリーンショット 2015-07-17 12.13.39.png](https://qiita-image-store.s3.amazonaws.com/0/69744/6b0b8967-171e-2872-d38e-b4b6aac94535.png)

##３．C#からコールする
このネイティブバイナリをC#コードからコールするには、C#でWindowsアプリを書いていたことのある人にはおなじみの、DllImport属性を使います。

今回はテンプレートで生成されたMainActivityに追記してみます。

```csharp:MainActivity.cs
[DllImport("NativeTest")]
public static extern int add(int a, int b);
```

そのままボタンタッチ時の処理を書き換えて、ネイティブコード実行の確認ができるようにします。

```csharp:MainActivity.cs
button.Click += delegate
{
    var rand = new Random();
    var a = rand.Next(100);
    var b = rand.Next(100);
    button.Text = String.Format("{0} + {1} = {2}", a, b, add(a, b));
};
```

実行するとこのように。無事コールされているのが確認できます。
![run.png](https://qiita-image-store.s3.amazonaws.com/0/69744/789ae2db-64a9-a119-d237-37c26e77b7ee.png)

##４．ビルドターゲットを追加する
ここまでネイティブコードのビルドにはターミナルからndk-buildを実行していましたが、これでは不便。
そこでプロジェクトファイル(csproj)に下記のような`<Target>`タグを追加します。

```xml:*.csproj
<Project>
  <Target Name="BeforeBuild">
    <Exec Command="$(AndroidNdkDirectory)/ndk-build" />
  </Target>
</Project>
```

**(いただいたコメントにより、追記修正しました)**
~~ここまでネイティブコードのビルドにはターミナルからndk-buildを実行していましたが、これでは不便。
というわけで、プロジェクト設定のカスタムコマンドという機能を使って、プロジェクトビルド時にNDKビルドも実行できるよう設定します。~~

~~ここで1点問題。。。
この例では、カスタムコマンドにndk-buildのフルパスを記載しているんですが、チーム開発などには不便ですね。環境設定から値を読み出す方法とかご存知のかたいたらコメントなどいただければありがたいです。~~

##まとめ
これで、Xamarin.AndroidプロジェクトでNDKネイティブコード含めてビルド・実行する環境が整いました。
プロジェクト内に一元管理することで、ビルド・デバッグのテンポがよくなる、リポジトリを分散させなくてすむなどのメリットが生まれるかと。