D言語とDxLibでAndroid上にD言語くんを出現させる

旧暦ですら2021年を迎えてしまいましたが
Androidで[D言語でDXライブラリを使ってD言語くんを降臨](https://qiita.com/yasei_no_otoko/items/e717ef09dce98330cde5)させました
この記事はリンク先の記事にインスパイアされています

なお世界に同じ環境でやる人がいるか微妙ですがLinux上(Xubuntu 20.04)で開発しています


## Android Studioの準備

[公式ページ](https://developer.android.com/studio)から落として
[公式ガイド](https://developer.android.com/studio/install?hl=ja#linux)に従って入れるだけですね
依存ライブラリなしで動きましたが気になる人は入れたほうが無難かと思います

## LDCの準備

[公式のreleaseページ](https://github.com/ldc-developers/ldc/releases/)から各ファイルをダウンロード
執筆時点では1.25.0を使いました（ちんたら執筆してたら正式版が出た）

- ldc2-1.25.0-linux-x86_64.tar.xz (コンパイラ)
- ldc2-1.25.0-android-aarch64.tar.xz (x86_64/aarch64向けライブラリ)
- ldc2-1.25.0-android-armv7a.tar.xz (i686/armv7a向けライブラリ)

ダウンロードして展開して中身を使いやすいように移動

```
export LDC2VER=1.25.0

wget https://github.com/ldc-developers/ldc/releases/download/v$LDC2VER/ldc2-$LDC2VER-linux-x86_64.tar.xz
wget https://github.com/ldc-developers/ldc/releases/download/v$LDC2VER/ldc2-$LDC2VER-android-aarch64.tar.xz
wget https://github.com/ldc-developers/ldc/releases/download/v$LDC2VER/ldc2-$LDC2VER-android-armv7a.tar.xz

tar xf ldc2-$LDC2VER-linux-x86_64.tar.xz
tar xf ldc2-$LDC2VER-android-aarch64.tar.xz
tar xf ldc2-$LDC2VER-android-armv7a.tar.xz

mv ldc2-$LDC2VER-android-aarch64/lib-x86_64 ldc2-$LDC2VER-linux-x86_64/lib-android_x86_64
mv ldc2-$LDC2VER-android-aarch64/lib ldc2-$LDC2VER-linux-x86_64/lib-android_arm64-v8a
mv ldc2-$LDC2VER-android-armv7a/lib-i686 ldc2-$LDC2VER-linux-x86_64/lib-android_x86
mv ldc2-$LDC2VER-android-armv7a/lib ldc2-$LDC2VER-linux-x86_64/lib-android_armeabi-v7a
```

ここまでやったらldcを適当にPATHに通しておいてください

```
export PATH=$PATH:path/to/ldc2-$LDC2VER-linux-x86_64/bin
#例: echo "export PATH=\$PATH:`pwd`/ldc2-$LDC2VER-linux-x86_64/bin" >> ~/.bashrc ; source ~/.bashrc
```

続いてldc2.confを修正しますが、NDKパスを確定させたいので次章の「Android StudioでDxLibを使う」
を先に読んでNDKのインストールを行ってパスを確認してください

--------

NDKのパスがわかったらldc2.confを修正します

```
NDKPATH=path-to-ndk-toolchain
#例: NDKPATH=$HOME/Android/Sdk/ndk/21.1.6352462

cat >> ldc2-$LDC2VER-linux-x86_64/etc/ldc2.conf << EOF

"x86_64-.*-linux-android":
{
    switches = [
        "-defaultlib=phobos2-ldc,druntime-ldc",
        "-link-defaultlib-shared=false",
        "-gcc=${NDKPATH}/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android21-clang",
    ];
    lib-dirs = [
        "%%ldcbinarypath%%/../lib-android_x86_64",
    ];
    rpath = "";
};

"i686-.*-linux-android":
{
    switches = [
        "-defaultlib=phobos2-ldc,druntime-ldc",
        "-link-defaultlib-shared=false",
        "-gcc=${NDKPATH}/toolchains/llvm/prebuilt/linux-x86_64/bin/i686-linux-android21-clang",
    ];
    lib-dirs = [
        "%%ldcbinarypath%%/../lib-android_x86",
    ];
    rpath = "";
};

"aarch64-.*-linux-android":
{
    switches = [
        "-defaultlib=phobos2-ldc,druntime-ldc",
        "-link-defaultlib-shared=false",
        "-gcc=${NDKPATH}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang",
    ];
    lib-dirs = [
        "%%ldcbinarypath%%/../lib-android_arm64-v8a",
    ];
    rpath = "";
};

"armv7a-.*-linux-android":
{
    switches = [
        "-defaultlib=phobos2-ldc,druntime-ldc",
        "-link-defaultlib-shared=false",
        "-gcc=${NDKPATH}/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi21-clang",
    ];
    lib-dirs = [
        "%%ldcbinarypath%%/../lib-android_armeabi-v7a",
    ];
    rpath = "";
};
EOF
```

## Android StudioでDxLibを使う

[公式ガイド](https://dxlib.xsrv.jp/use/dxuse_android_studio_4_0.html)に従って入れますが
Linuxだとzipが文字化けするので少し注意が必要です

```
$ unzip -l -O cp932 DxLib_Android3_22a.zip  # 確認したければ
$ unzip -O cp932 DxLib_Android3_22a.zip
```

なお`-O CHARSET`オプションはディストリビューションによってはなかったり(Arch Linuxとか)するので
その時はがんばってください

2章が終わってNDKが入ったら前章のNDKの設定をldc2.confに入れてください

他変更は以下

3.1
手で触りたくない人向け

```
cd <path-to-project> (以下同or続けて)
sed -i app/src/main/AndroidManifest.xml \
-e "s#android:theme\(.*\)>#android:theme\1#" \
-e 's#        <activity android:name=".MainActivity">#\
        android:hasCode="false">\n\
        <activity android:name="android.app.NativeActivity">\n\
            <meta-data android:name="android.app.lib_name"\n\
                android:value="native-lib" />#'
```

3.2
手で触りたくない人向け
link_directoriesはよしなにいじってください

```
DXLIB_LIBPATH_PREFIX="path/to/プロジェクトに追加すべきファイル_Android用/"
PROJECT_LIBPATH_PREFIX="path/to/yourproject/app/src/main/cpp/samplelibdir/"
LDC2_LIBPATH_PREFIX="path/to/ldc2-$LDC2VER-linux-x86_64/lib-android_"

例:
DXLIB_LIBPATH_PREFIX="$HOME/Downloads/DxLib_Android/プロジェクトに追加すべきファイル_Android用/"
PROJECT_LIBPATH_PREFIX="$HOME/AndroidStudioProjects/test/app/src/main/cpp/samplelibdir/"
LDC2_LIBPATH_PREFIX="$HOME/Downloads/ldc2-$LDC2VER-linux-x86_64/lib-android_"

sed -i app/src/main/cpp/CMakeLists.txt \
-e "/^add_library/i include_directories( ${DXLIB_LIBPATH_PREFIX}\${ANDROID_ABI} )\n\
link_directories( ${DXLIB_LIBPATH_PREFIX}\${ANDROID_ABI}\n\
        ${PROJECT_LIBPATH_PREFIX}\${ANDROID_ABI}\n\
        ${LDC2_LIBPATH_PREFIX}\${ANDROID_ABI} )\n\
" \
-e "s#\${log-lib} )#`for l in {' ',\\${log-lib},\
android,GLESv1_CM,EGL,GLESv2,OpenSLES,m,sample,phobos2-ldc,druntime-ldc,DxLib,DxUseCLib,\
jpeg,png,zlib,tiff,theora_static,vorbis_static,vorbisfile_static,ogg_static,bullet,'opus )'};\
 do echo -n '                       '$l'\n'; done`#"
```

4
native-lib.cppはDのコードを呼び出す機能だけ搭載しています

```
cat > app/src/main/cpp/native-lib.cpp << 'EOF'
extern "C" void myDmain();
void android_main(){
    myDmain();
}
EOF
```

Dのランタイム特有？の問題としてリンカをbfdにしてあげないとクラッシュするっぽいためbuild.gradleを変更
また、ldcのライブラリがr21のSDKでコンパイルされているため対象API Levelを21(=Android 5.0以上が必要)に設定

```
sed -i app/build.gradle -e 's/cppFlags ""/cppFlags "-Wl,-fuse-ld=bfd"/'
sed -i app/build.gradle -e 's/minSdkVersion 16/minSdkVersion 21/'
```

本体コードをDで書く
[元コード](https://qiita.com/yasei_no_otoko/items/e717ef09dce98330cde5#%E3%82%B3%E3%83%BC%E3%83%89%E3%82%92%E6%9B%B8%E3%81%8F)からの変更は最小限にしてます
D言語バインドとして拙作の[dxlib-d](https://github.com/dragoon2014/dxlib-d)を使用しています

```
cat > app/src/main/cpp/sample.d << 'EOF'
module main;
import dxlib;
import std.string;
//import std.file;

extern(C) int __android_log_write(int, const(char)*, const(char)*);

// この辺もう少しきれいにしたい
alias extern(C) int function(char[][] args) MainFunc;
//extern(C) int _d_run_main(int argc, char** argv, MainFunc mainFunc);
extern(C) int _d_run_main2(char[][] args, size_t totalArgsLength, MainFunc mainFunc);
extern(C) int myDmain(){
    return _d_run_main2(null, 0, &myDmain2);
}

extern(C) int myDmain2(char[][] args){
    main_();
    return 0;
}

// mainのままだとldcがセグフォした
void main_()
{
    int count, x, y;

    try
    {
        int mHandle;
        //ChangeWindowMode(TRUE);

        if (DxLib_Init() < 0 )
        {
            throw new Exception("DXライブラリの初期化に失敗");
        }

        if ((mHandle = MV1LoadModel("Mr_D.pmd")) == -1)
        {
            throw new Exception("モデルの読み込みに失敗");
        }

        // なんかこう書かないとVECTORがうまく使えなかった
        auto camera = VECTOR(0.0f, 10.0f, -20.0f);
        auto target = VECTOR(0.0f, 10.0f, 0.0f);

        SetBackgroundColor(0, 0, 255);
        ClearDrawScreen();
        SetCameraNearFar(1.0f, 150.0f);
        SetCameraPositionAndTarget_UpVecY(camera, target);

        // ここvmdファイルが必要？
        /+ if (MV1AttachAnim(mHandle, 0, -1, FALSE) == -1)
        {
            throw new Exception("アニメーションの再生に失敗");
        }
        MV1SetAttachAnimTime(mHandle, 0, 0.0f); +/

        if (MV1DrawModel(mHandle) == -1)
        {
            throw new Exception("モデルの描画に失敗");
        }

        WaitKey();
    }
    catch( Exception e )
    {
        //std.file.write( "error.log", e.msg );
        __android_log_write(6, "d-catched", e.msg.toStringz);
        //throw e;
    }
    finally
    {
        DxLib_End();
    }
}
EOF
```

各環境用にライブラリをビルド

```
mkdir -p app/src/main/cpp/samplelibdir/{arm64-v8a,armeabi-v7a,x86,x86_64}

pushd app/src/main/cpp/samplelibdir/x86_64
ldc2 -mtriple=x86_64--linux-android -c ../../sample -I ~/dxlib-d/ && rm -f libsample.a && ar rcs libsample.a sample.o
popd
pushd app/src/main/cpp/samplelibdir/arm64-v8a
ldc2 -mtriple=aarch64--linux-android -c ../../sample -I ~/dxlib-d/ && rm -f libsample.a && ar rcs libsample.a sample.o
popd
pushd app/src/main/cpp/samplelibdir/x86
ldc2 -mtriple=i686--linux-android -c ../../sample -I ~/dxlib-d/ && rm -f libsample.a && ar rcs libsample.a sample.o
popd
pushd app/src/main/cpp/samplelibdir/armeabi-v7a
ldc2 -mtriple=armv7a--linux-androideabi -c ../../sample -I ~/dxlib-d/ && rm -f libsample.a && ar rcs libsample.a sample.o
popd
```

Androidの場合、各種追加ファイルはassetsディレクトリに配置します

```
mkdir -p app/src/main/assets
```

[モデルをこちらから拝借](https://onedrive.live.com/?cid=F7E340CFC6666A27&id=F7E340CFC6666A27%21574)

Mr_D.pmdをapp/src/main/assetsに配置
同じくテクスチャですがファイル名が違うようでTex_D.pngをTex_D.bmp(一応変換した)にして保存

あとはAndroid Studio上でAVD Managerから適当な(Android 5.0+必須でx86_64推奨)エミュレータを起動しつつ
Run→Run 'app'で実験(起動してるAVDと同じアーキテクチャのバイナリだけ入ってます)
問題なさそうならMake Module '〜.app'でapk(全環境用のバイナリが入ってます)ができます

![Screenshot_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51482/7a4aa182-38d5-0cb1-740b-ff8dce4991ed.png)

native-lib.cppの機能をD側で用意してso作成してパッケージ化すればD言語(とAndroidのビルドコマンド)の知識のみでAndroidアプリ開発ができそうですね！（疲れた）