コンピュータを楽しもう!!

今、自分が面白くていろいろやってみたことを書き綴りたいと思います。連絡先はtarosa.yでgmail.comです。

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側は元気が出たときに考えよう。