はじめに
この記事はだいぶ前(2012年)にココログの「キリーの日本語プログラミング」に掲載したコラムですが、再編集してこちらにも投稿します。今となっては古い話ではありますが、OSによってプログラム言語(AndroidではJava)が決め打ちされている環境で規定と異なるもの(Mind)を走らせたことの記録としてお読みください。
AndroidでMindをどうやって動かすか(2)
MindではMコードがオブジェクトコードの実体であるため、当然だがMコードファイル(.mco)の扱いが重要となる。
しかし Android OS から見た狭義のプログラムは JavaのクラスファイルとC(JNI)の共有ライブラリの2つであり当然だがMコードは関与されない。そのためファイルの設置や読み出しは自前で行う必要がある。
パッケージング
まず設置については、パッケージ(.apk)の中にMコードファイルを含めておき、アプリ起動時にそれが自動展開されるのが良いのだが、調べたところこの目的に合致したのが assets/ ディレクトリだった。(アンドロイドてはWindowsやLinuxなどと異なり、好きなところにファイルを置くことはできず、その点は不自由である)
assets/ ディレクトリは元々、画像や音楽ファイルを置くような用途を想定されているらしく、Read Onlyなアクセスとなるが、Mコードへは書き込みが無いため丁度良い。またMindで使うUnicodeの変換テーブルなどのデータファイルもここに置くことにした。
読み出しについて
読み出しについては、MコードのロードはC管轄なのでCで読めると良かったのだがどうも困難なようで(Cから普通のファイルのようにはアクセスできない!)、仕方なくJavaで読み出したあと、Cの初期化関数呼び出し時に渡すようにした。
開発時のディレクトリ構成
アプリをmakeする時のディレクトリ構成(プロジェクトフォルダ内)は以下のようになる。(アプリが走行するときのディレクトリ構成とは異なる)
MindSample/
|
|-- AndroidManifest.xml
|
|-- assets ←プログラムからロードするファイルはここ
| |-- bin
| | |-- euc-uni-encode.bin ←Unicodeテーブル
| | `-- uni-euc-decode.bin
| `-- mco
| `-- main.mco ←Mコードファイル(Mindプログラム本体)
|
|-- bin
| |-- MindSample-debug.apk
| ~略~
| |-- classes
| | `-- jp
| `-- co
| `-- scriptslab
| `-- MindSample
| |-- MindSampleActivity.class
|
|-- jni ←Mindのカーネルはここへ
| |-- 0ker.c
| |-- 1ker.c
| |-- 2ker0.c
| |-- 2ker1.c
| ~略~
| |-- sources
| `-- unixdep.c
|
|-- libs ←カーネルの生成場所
| `-- armeabi
| `-- libdllker.so
|
|-- mind4andr ←Mind記述のプログラム
| |-- andrlib ←ランタイムライブラリ(2)
| | |-- andr_basicoutput.src
| | |-- andr_init.src
| | ~略~
| | `-- obj
| |-- app ←ユーザ記述のMindプログラム
| | |-- sample.src
| | `-- obj
| |-- file ←ランタイムライブラリ(1)
| | |-- asm_osdep.src
| | |-- asmequ.src
| | ~略~
| | `-- obj
| `-- lib
| |--
| `--
|
|-- obj
| `-- local
| `-- armeabi
| |-- libdllker.so
| `-- objs
| `-- dllker
| |-- dllker.o
| |-- dllker.o.d
| |-- osfunc.o
| `-- osfunc.o.d
|
|-- res
| |-- layout ←res/layout/main.xml は置かない
| `-- values
| `-- strings.xml
|
`-- src
`-- jp
`-- co
`-- scriptslab
`-- MindSample
`-- MindSampleActivity.java
assets/ フォルダ
assets/ 以下では、サブディレクトリ mco/ にMコードを格納し、bin/ にはUnicode変換用のテーブルを置いた。ここに置いておけばビルド時に .apk に含めてもらえる。
jni/ フォルダ
jni/ フォルダにはMindのC記述部(以下、単にカーネルと言うことにする)を格納している。カーネルのオブジェクトファイル群は obj/ 配下に格納され、そのうちメインのオブジェクトである libdllker.so がJava側ビルド時に libs/ に複写されこれが最終的な .apk に含められるようだ。
libs/armeabi/ という名称から分かるように、ARM系CPU専用となっているため他のCPUでは走行できない点に注意が必要だ。他のCPUも別のカーネルを用意すれば良いのだと思うが今のところはARM向けということで進むことにする。(編集注:後にGooglePlayの要請で64bitのバイナリも必要となったのだが別のフォルダとなる)
JNIとMindカーネルとの関係
(ここはQiita投稿のため加筆した部分です)
ここで JNI(Java Native Interface)という機構とMindのカーネル(C記述部)との関係について説明する。JNI はAndroid専用というわけではなく(私が知らなかっただけだが)一般的にJavaが備えているものらしい。Wikipediaでは以下のように解説されている。
Java Native Interface (JNI) は、Javaプラットフォームにおいて、Javaで記述されたプログラムと、他のプログラミング言語(たとえばCやC++など)で書かれた、実際のCPU上で動作するコード(ネイティブコード)とを連携するためのインタフェース仕様である。Java言語からネイティブコードを利用するためのABIと、逆にネイティブコードからJavaバイトコードを動作させるためにバーチャルマシン (VM) を利用するためのAPIの2つから成る。
C/C++で書いたプログラムはJavaから見ると共有ライブラリの扱いとなる。凝った仕組みでもあるのかと思ったら意外と普通である。共有ライブラリのファイル拡張子も .so で、Linuxでおなじみのもの。
共有ライブラリはアプリ起動の冒頭でJava記述により明にロードして使う。Javaのコードは以下のようにしている。
// JNIライブラリのロード //
static
{
String dllname = "dllker_" + MYCLASSNAME;
System.loadLibrary( dllname );
}
一方、Mindの側(Mindのカーネル部)から見ると、自身を共有ライブラリとして作っておき、Javaから「ロードしてもらう/内部にある関数を呼んでもらう」形となる。幸い、Mindは Version 7の時から自身を共有ライブラリ(WindowsではDLLと呼ぶ)として走らせるライブラリ形態があったのでそれをベースにして作成した。
「自身を共有ライブラリとする」がどういうことなのかと言うと、おなじみの「メインとは」の定義が無いことである。実は表向き「メイン」は有るのだが共有ライブラリの仕様上、ロード時に初期化目的で呼ばれる関数があり、Mindではそれを「メイン」にしているに過ぎない。(たぶんほかのプログラム言語ではそれは main() の名前ではないように思うのだがよく知らない)
さて、JNIがらみて大きな問題があった。
Javaが共有ライブラリ(Mindではカーネル)内の関数を呼び出す際に関数名に厳しい規則がある。
たとえばスタートアップ関数は次のような名前となる。
<-------A------> <---B--> <---C--> <----D--->
Java_jp_co_scriptslab_TestDate_TestDate_dllStartup()
とても長い名前だが規則だから守らないといけない。
上記で A は開発者(私ではなくアプリケーション作成者)のドメインである。ドメインはインターネットのそれと似ているが違っていても良く、要するに世界で一意に開発者を特定できるようにするためのもの。
B はアプリケーションのクラス名、C はそのアクティビティ名、 D でようやく本来の意味の関数名となる(これは私が勝手に命名したもの)。
補足すると、まず「アプリケーションのクラス名」は要するにプログラム名のこと。
次に「アクティビティ名」はそのプログラムに対応する画面の名前で画面遷移があるなら複数の名前を持つ。
結果的に、共有ライブラリ内にある関数でJavaから呼ばれるものについては、その関数名が世界で一意になる・・という壮大なネーミング・ルールである。
Mindを移植するに当たって最大の問題がここにあった。
どういうことかと言うと、Mindの開発者である私はMindの利用者であるユーザがどのようなドメインを使いどのようなアプリケーション名を使い、どのようなアクティビティ名を使うかまったく予想できないので、本来であれば固定的な(たとえば libdllker.so という)ファイル1つで提供できるはずのカーネル、つまりCのソース群を「動的に作らざるを得ない」ことだった。
ユーザがMindのソースを1つコンパイルまたは再コンパイルするだけなのに、Mindのカーネルのコンパイルからやる必要がある。うんざりするような思いだった。
複雑なのはそれだけではない。
Androidのアプリのほとんどは複数の画面を持っているが、Androidでは画面ごとに(一般に言う)プログラムが独立している(パッケージとしては1つだが中はそうなる)。前記の解説で言うと「C」にはバリエーションが存在することになる。つまり画面ごとにJavaから呼んでもらう関数を複数用意する必要がある。結果的にカーネルは画面ごとに複数用意することになる。
たとえば私が作った薬の飲み忘れ防止アプリではカーネル内にJavaから呼ばれるCソースとして次のようなものがある。
dllker_AlarmService.c
dllker_BootReceiverMind.c
dllker_NomiwasureBoushi.c
上記で一番下がメインのアクティビティ向け(主画面なのでアプリ名そのもの)、他はサービス向けのものである。都合3種のソースを用意する。
これらをインクルードするようなカーネル複数をコンパイルすると以下のようなオブジェクトが生成される。
/home/andr/android-projects/NomiwasureBoushi/obj/local/armeabi/
libdllker_AlarmService.so
libdllker_BootReceiverMind.so
libdllker_NomiwasureBoushi.so
上記の .so ファイル1つ1つがMindのカーネルだから、1つのアプリなのに複数のカーネルを持つことが分かる。
Androidの開発環境は誰でもダウンロードして設定可能とは言え、ユーザ開発者がJavaとCとMindのコンパイラ3種を駆使してプログラムを開発する姿はMindが目指す世界とはだいぶ違うため、当初からAndroid向けMindはクラウド方式にしたいと思っていた。それはそうなのだが、今書いたような理由でもっとクラウドにするしかしない状況になったと言える。とてもではないがこんな複雑なものをユーザにおこなってもらうわけにはいかない。
mind4andr/ フォルダ
mind4andr/ フォルダへはMind記述のプログラムを格納する。
ライブラリの順位は低いほうから file → andrlib → app である。app/内にユーザ記述のMindソースを置くことにしたがこのあたりはこの先変更するかも知れない。ソケット(通信)・グラフィック・その他のためにさらに別ディレクトリが必要になると思うが追々やっていくことにする。
res/ フォルダ
res/ フォルダでは、Androidのプログラミングで必ず登場する res/layout/main.xml(レイアウトを記述するファイル)はMindでの開発では使わない。レイアウトはすべてプログラム記述とするためである。これに関係するが、書籍やWebサイトでの多くのサンプルプログラムはレイアウトを main.xml に記述しており、プログラム記述のものを探すのに苦労した。
つづく
最近、Mindユーザーの@mylifewithviolinさんがMindコンパイラの内部に興味を持たれているようなので参考情報として投稿してみました。本記事はしばらくつづきます。次は(ステップ2/9)。