C++幼女先輩

プログラミング成分多め

久々のAndroid開発(いきなりndkでOpenGL)序章

私には出来ませんって 断り続けたものの お客さんがかんたんに許してくれるわけでもなく
3回ほど断ったけど 結局 やってやんよ! というしか無かった Android開発
もちろん NDAの範囲で〜 の話だけど
具体的なことはかけないのだが NDKでAndroidの開発である

過去の栄光

こんなもの捨ててしまえばいいのだ
ただ、知識は捨てる必要はない。先人の7年前の私の話をしよう
7〜8年前に、わたしは某社の社長から特命を受けた
AndroidiOSに両対応したゲームエンジンを作れと
当時はAndroidは2.xで iPhone3 とかの時代だった
OpenGLESは1であったし、なんといっても私は AndroidiPhoneも持ってない
当然 スマホの開発はしたことないし、OpenGLも触ったことがなかった
そんな状況ではあるが、社長はR&Dとして期日も設けず、好きに開発してくれと
結局それから毎日 鬼のように働き3週間でプロトタイプを作成した
その時にとった戦略は、メインライブラリをC++で作り
AndroidにはJNIで、Objective-CにはそのままC++で 呼び出す事だった
最初に面倒なAndroid対応を行った
JNIも初めてで、意外とJavaとの呼び出しに罠が多く苦労した(String関連のメモリの所有者、GC、リフレクションなど)
OpenGLは初めてだけど すぐに理解できた
今の私だと、あの速度で理解出来たのか不明 いや、きっとやればできるはずだ・・
Objective-CC++がそのまま動くので何も問題なくおわった
開発中にGLES2.0が出たので、両対応したり
今考えても3週間で誰の助けもなくよく終わったと思う

その後、Javaの側をつくったり、GLES2.0のレジスタにおさまらないスキンメッシュをCPU計算して
SIMDNEONを使ったり
色んな部分をマルチスレッド化したり
Javaへ例外をやり取りしたり、RTTIに対応したり、本来非対応なものを、ライブラリ無理やり入れて動かした
また、NativeActivityが出たので対応したり

今回の話

あれから7、8年たち、色々と状況変わっているだろうが、結局いつもと同じく やるしかない
自由を手に入れるには 案件を片付けるしかない
まずは環境構築だ

環境

以前はWindowsで vs-androidを使った
これはVisualStudioにプラグインとしてのせて、VisualStudioにツールチェーンを構築してAPK作って転送する
とっても素晴らしいものであった
何が素晴らしいかって、clangでビルド出来て、C++11が使え(本来使えないが無理やり使えるようにツールチェーン作った)
Boostもちゃんと無理やり入れた
そして C++のビルドが早く、Javaを変更しなければそちらのビルドが走らず高速で実行できる
また、C++部分にブレークポイントうってデバッグもでき、控えめに言って最高であった

それに加え、コマンドラインでのビルド(リリース、自動ビルド用)と
EclipseでのNDKビルド環境を作った
Eclipseビルドは C++部分の自動ビルドが非常に遅く、バックグラウンドでCMakeでビルドするほうが早く
そのようなシステムを作ったが、vs-androidよりビルドは遅いし、そのごJavaコンパイルも入り
C++部分のデバッグもVisualStudio様に比べて弱く 非常に使いにくかった
もちろん Java部分の開発にはとてもパワフルだった

ので、C++部分の開発(全体の98%)は vs-androidにて
Java部分(2%)は Eclipseで開発
というスタイルであった

今回は、WindowsPCもMacPCも持っているものの、できればMacにすべて開発移したかったけど
vs-androidの便利さ考えると 悩むところ

ってことで、とりあえず AndroidStudioでのNDK開発を検証してみる

AndroidStudio、NDKなど環境を整え 新しいプロジェクトの作成

f:id:murasame-labo:20180516025235p:plain
すばらしい! 最初から C++14まで選べて、Exception、RTTIも標準でできる
これだけでテンションあがる

生成コード

まず、ビルドがGradleになっている。使ったことないけど 評判は良い
前つかってたときは Antで、Mavienがいいよって中国の技術者に教わって Maven対応したんだけど
とりあえずGradleの沼に入るのは今ではない

C++側のビルドはCMakeだ。何も文句ない

Java側のソース

package com.murasame_lab.cstudio1;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Example of a call to a native method
        TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
}

C++

#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring

JNICALL
Java_com_murasame_1lab_cstudio1_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

最初らしく、C++コードから "Hello from C++" の文字を返し
それをJavaで画面に表示している
NewStringUTF() は、メモリJavaが破棄してくれるんだっけ?
そのあたりは 8年前に一通り調べたけど
正直記憶がない。3週間で一夜漬けしたので かんたんに思い出すことは難しいだろう
もう一度JNI周りは調べ直すしかない
記憶はないが 私にはコードという記録がある
いつか思い出すはずだ

ちなみに Static関数でライブラリのLoadをするのは、一つのお作法のようだ
8年前に色々な方法でベストを探したけど、最もかんたんにかけて問題がないのがこの方法であった

とりあえず小さなコードにおいては、AndroidStudioでブレークポイントもかかったし、高速にビルド出来た
そのため、今回はAndroidStudioでもいけるんじゃないかと 期待している