luacのAndroidへの移植
luaスクリプトを中間コードに変換する方法は、以前書いたloadfile()を用いれば可能です(下記)が、変換時にデバッグ情報も付加されるので、デバッグ情報が無い中間コードを生成したいと思って、単純にluac.cをAndroidに移植しようと試みました。
string.dump(assert(loadfile("test.lua")))
でも、途中で疲れました。中途半端ですが、防備録として書いておきたいと思います。
もともと、最初はndkを用いて、luacをビルドして、luaridaから下記のように使えば簡単だと考えて、luacをndkで作ったのですが、実行プログラムは/data/以下に置かなければダメのようで、いろいろと面倒そうでやめました。
os.execute("./luac -s -o /sdcard/luarida/test.luac /sdcard/luarida/test.lua")
そうか、luarida.apkの中にsoの形でなくて、luac実行モジュールを直接埋め込む方法があればいいのか。あるのかな?
javaからluacを呼び出す
当然ながらndkを使います。適当にプロジェクトを作って、luacを呼び出す関数を書きました。中間コードに変換したいファイル名をsrcに入れて、出力したいファイル名をdistに入れてluacStart()を呼び出します。
public void startLuac(){ String src="/sdcard/luarida/test.lua"; String dist="/sdcard/luarida/test.luac"; int ret = luacStart( src, dist ); Log.i("ret=",String.valueOf(ret)); } /* implementend by libplasma.so */ public native int luacStart(String src, String dist); static { System.loadLibrary("luac_module"); }
モジュール名はluac_moduleです。ndk-buildすると、libluac_module.soが出来上がります。
Android.mk
Android.mkの中身を書きます。
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := luac_module LOCAL_SRC_FILES := main.c \ lua/luac.c lua/lauxlib.c lua/ldo.c lua/lfunc.c \ lua/lmem.c lua/lobject.c lua/lstring.c lua/lundump.c \ lua/lopcodes.c lua/ldebug.c lua/lgc.c lua/lparser.c \ lua/lstate.c lua/ltable.c lua/ltm.c lua/lvm.c \ lua/lzio.c lua/llex.c lua/lapi.c lua/lcode.c \ lua/print.c lua/ldump.c LOCAL_LDLIBS := -llog include $(BUILD_SHARED_LIBRARY)
luac.h
必要無いですがluac.hです。
#include <android/log.h> #define LOG_TAG "Luac" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
main.c
main.cです。javaにintを返します。関数名のJava_com_momoonga_luarida_luac_LuacActivity_luacStartは、Java+パッケージ名+呼び出しているclass名+ndkの関数名です。
#include <stdlib.h> #include <jni.h> jint Java_com_momoonga_luarida_luac_LuacActivity_luacStart( JNIEnv* env, jobject obj, jstring src, jstring dist ) { char *argv[5]; argv[0] = (char*)"luac"; argv[1] = (char*)"-s"; argv[2] = (char*)"-o"; argv[3] = (char*)(*env)->GetStringUTFChars(env, dist, NULL); argv[4] = (char*)(*env)->GetStringUTFChars(env, src, NULL); int ret = main( 5, argv ); (*env)->ReleaseStringUTFChars(env, dist, argv[3]); (*env)->ReleaseStringUTFChars(env, src, argv[4]); return( ret ); }
引数の前2つは自動的に決まっています。その後はjava側からの引数になります。やっていることは超単純で、コマンドライン引数を作っているだけです。"-s"がデバッグ情報を入れないオプションです。DumpState.strip=1になるオプションです。"-o"は出力ファイル名の指定です。そして、luacのmain()を呼び出します。結果としてこう書いているのと同じです。
luac -s -o /sdcard/luarida/test.luac /sdcard/luarida/test.lua
luac.cの変更
上記変更で万事うまく行くと思ったのですが、甘かったです。luacの中で処理がエラーになると、exit(EXIT_FAILURE);で処理を抜けているのです。確かに普通はそうしますよね。ですが、これでは処理がjavaに戻らずに終了しちゃうのです。仕方ないので、exitさせずにロ〜ングジャンプして戻ってきてもらうことにしました。正確にsource追っていないので、メモリリークしているかもしれません。ごめんなさい。
ということで、luac.cのmain()を下記のように書き直しました。
int main(int argc, char* argv[]) { lua_State* L; struct Smain s; // int i=doargs(argc,argv); int i; int trycatch = setjmp( EnvData ); //try{} if( trycatch==0 ){ i=doargs(argc,argv); argc-=i; argv+=i; if (argc<=0) usage("no input files given"); L=lua_open(); if (L==NULL) fatal("not enough memory for state"); s.argc=argc; s.argv=argv; if (lua_cpcall(L,pmain,&s)!=0) fatal(lua_tostring(L,-1)); lua_close(L); return EXIT_SUCCESS; } //catch{} else if(trycatch==1){ return EXIT_FAILURE; } //finally{} { } return EXIT_SUCCESS; }
ヘッダに setjmp.h を追加しました。
#include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <setjmp.h>
グローバル変数に EnvData を追加しました。
jmp_buf EnvData; //setjump用
fatal と cannot と usage を変更しました。
static void fatal(const char* message) { fprintf(stderr,"%s: %s\n",progname,message); // exit(EXIT_FAILURE); longjmp( EnvData, 1 ); //setjmp()に飛ぶ } static void cannot(const char* what) { fprintf(stderr,"%s: cannot %s %s: %s\n",progname,what,output,strerror(errno)); // exit(EXIT_FAILURE); longjmp( EnvData, 1 ); //setjmp()に飛ぶ } static void usage(const char* message) { if (*message=='-') fprintf(stderr,"%s: unrecognized option " LUA_QS "\n",progname,message); else fprintf(stderr,"%s: %s\n",progname,message); fprintf(stderr, "usage: %s [options] [filenames].\n" "Available options are:\n" " - process stdin\n" " -l list\n" " -o name output to file " LUA_QL("name") " (default is \"%s\")\n" " -p parse only\n" " -s strip debug information\n" " -v show version information\n" " -- stop handling options\n", progname,Output); // exit(EXIT_FAILURE); longjmp( EnvData, 1 ); //setjmp()に飛ぶ }
doargs も変更しました。doargsの変更は exit(EXIT_SUCCESS)なので、longjmp( EnvData, 2 )としています。この場合は、else if(trycatch==1)にも掛からないので、return EXIT_SUCCESS;します。
luac.cの変更箇所は、これだけです。
static int doargs(int argc, char* argv[]) { int i; int version=0; if (argv[0]!=NULL && *argv[0]!=0) progname=argv[0]; for (i=1; i<argc; i++) { if (*argv[i]!='-') /* end of options; keep it */ break; else if (IS("--")) /* end of options; skip it */ { ++i; if (version) ++version; break; } else if (IS("-")) /* end of options; use stdin */ break; else if (IS("-l")) /* list */ ++listing; else if (IS("-o")) /* output file */ { output=argv[++i]; if (output==NULL || *output==0) usage(LUA_QL("-o") " needs argument"); if (IS("-")) output=NULL; } else if (IS("-p")) /* parse only */ dumping=0; else if (IS("-s")) /* strip debug information */ stripping=1; else if (IS("-v")) /* show version */ ++version; else /* unknown option */ usage(argv[i]); } if (i==argc && (listing || !dumping)) { dumping=0; argv[--i]=Output; } if (version) { printf("%s %s\n",LUA_RELEASE,LUA_COPYRIGHT); if (version==argc-1){ // exit(EXIT_SUCCESS); longjmp( EnvData, 2 ); //setjmp()に飛ぶ } } return i; }
ldo.cの変更
Luaのエラー処理はここでthrowしていますが、ここで処理できないエラーは exit(EXIT_FAILURE)していました。あらら、ということで、ここにも longjmp を入れました。
extern jmp_buf EnvData; //setjump用 void luaD_throw (lua_State *L, int errcode) { if (L->errorJmp) { L->errorJmp->status = errcode; LUAI_THROW(L, L->errorJmp); } else { L->status = cast_byte(errcode); if (G(L)->panic) { resetstack(L, errcode); lua_unlock(L); G(L)->panic(L); } // exit(EXIT_FAILURE); longjmp( EnvData, 1 ); //setjmp()に飛ぶ } }
以上で、luac関連のsource変更終了です。
これで、ndk-buildすれば、ビルドが通って万事OKです。←locale.h 絡みの話はパス。
ここで、力尽きました。Android側は元気が出たときに考えよう。