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


旧暦ですら2021年を迎えてしまいましたが
AndroidでD言語でDXライブラリを使ってD言語くんを降臨させました
この記事はリンク先の記事にインスパイアされています

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

Android Studioの準備

公式ページから落として
公式ガイドに従って入れるだけですね
依存ライブラリなしで動きましたが気になる人は入れたほうが無難かと思います

LDCの準備

公式のreleaseページから各ファイルをダウンロード
執筆時点では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を使う

公式ガイドに従って入れますが
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で書く
元コードからの変更は最小限にしてます
D言語バインドとして拙作の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

モデルをこちらから拝借

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(全環境用のバイナリが入ってます)ができます

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