Skip to content
Toru Hisai edited this page Sep 1, 2012 · 16 revisions

Visual C++ と Xcode

まずは Visual C++ か Xcode のプロジェクトに取り込む方法を示す。

基本的には、単にソースコードをプロジェクトに足し、本体のプログラムのどこかで Lua のインタプリタを作成して初期化するだけである。

Visual C++

  • スクリーンショット

Lua のソースコード一式を、プロジェクトに足す。 .c のサフィックスのついたファイルはソースファイルとして、 .h のサフィックスのついたファイルはヘッダファイルとして足せば良い。 ただし、main 関数を含む lua.c、luac.c だけは、 ここでは必要ないので除外する。

main 関数

  • ヘッダファイルのインクルード
  • lua_Stete の作成
  • ライブラリのロード
  • スクリプトを文字列として渡して評価する

Xcode

  • Visual C++ と同じ。

C API とスタック

スタック

C と Lua の間で関数を呼び出すとき、必要な情報は、スタックを使って受け渡しする。

  • スタックの絵

基本的に、関数と引数をスタックに積んで、この状態で、lua_call を呼び出すだけである。

void lua_call (lua_State *L, int nargs, int nresults);

ここに nargs は引数の数、nresults は期待する戻り値の数である。

しかし、理屈は簡単だが、実際の操作はかなり煩雑になるので、以下では例をみながら説明する。

例:単純な呼び出し

print "Hello Lua!" に相当する関数呼び出しをするには、次のような手順を実行する。

  • print というシンボルを引く
  • "Hello Lua!" という文字列をスタックに積む
  • lua_call を呼び出す

これを C で書くと次のようになる。

lua_getglobal(L, "print");
lua_pushstring(L, "Hello Lua!");
lua_call(L, 1, 0);

lua_getglobal は、グローバルに定義された変数の値を取得する関数である。取得した値は再びスタックにプッシュされる。print は関数を格納したグローバルな変数なので、この関数がスタックにプッシュされることになる。

いま、スタックは次の図のような内容になっている。

    +-------------
  1 | print 関数
    +-------------

次に lua_pushstring で文字列をスタックにプッシュしている。

    +-------------
  2 | "Hello Lua!"
    +-------------
  1 | print 関数
    +-------------

最後に lua_call を呼び出す。lua_call は呼び出したい関数に渡す引数の個数と、関数から受け取る戻り値の個数を指定する。

void lua_call (lua_State *L, int nargs, int nresults);

ここでは、nargs に 1、nresults に 0 を指定している。lua_call を実行すると、引数と関数はスタックから取り除かれる。従って、最終的にはスタックの内容は空っぽになる。そして関数の副作用として画面に Hello Lua! と出力されるはずである。

複雑な例:リファレンスから

Lua のリファレンスマニュアルの lua_call の項(http://www.lua.org/manual/5.2/manual.html#lua_call)から例を引いてみよう。

Lua で次のように書く内容を考える。

a = f("how", t.x, 14)

よく見ると、第 2 引数は t というテーブルの x というキーを参照していることに注意すること。まず、目指すべきスタックの状態は、次のようになる。

    +-------------
  4 | 14
    +-------------
  3 | t.x の値
    +-------------
  2 | "how"
    +-------------
  1 | f 関数
    +-------------

このなかで厄介なのは t.x の部分である。まずは、他の項目は脇へ置いて、この値をスタックへプッシュすることを考える。まずマニュアルから当該部分に例示されたコードを見てみよう。以下ではコメントも含めてコードを引用する。

lua_getglobal(L, "t");                    /* table to be indexed */
lua_getfield(L, -1, "x");        /* push result of t.x (2nd arg) */
lua_remove(L, -2);                  /* remove 't' from the stack */

まず最初に t というテーブルを取得するために、lua_getglobal() を呼び出す。すると、t という変数に結びつけられた値(ここではテーブル)がスタックにプッシュされる。

         +-------------
  1 (-1) | t テーブル
         +-------------

次に、テーブルから x という名前のキーに割り当てられた値をとりだす。これは lua_getfield() という関数を使う。この関数に与える第 2 引数は、スタックの中のテーブルのインデックスである。いま、テーブルはスタックのトップに積まれているので、インデックスは -1 となる。この結果、t.x の値がスタックにプッシュされる。

         +-------------
  2 (-1) | t.x の値
         +-------------
  1 (-2) | t テーブル
         +-------------

ここで、元のテーブルはもう要らないので、lua_remove() で削除する。

void lua_remove (lua_State *L, int index);

いま、当該のテーブルはスタックのトップから数えて 2 番目にあるので、インデックスとして -2 を指定する。なお、lua_remove() は、スタックの途中から値を削除した場合、隙間を埋めるようにそれより上に積まれている値が順次下にシフトする。

         +-------------
  1 (-1) | t.x の値
         +-------------

これで t.x の値をスタックにプッシュすることができた。

さて、ここまで、スタックのインデックスにはトップから数えたマイナスの値を使っていたが、これはどうしてだろうか? テーブルが入っているインデックスは 1 だと分かっていれば、lua_getfield() の時も lua_remove() の時も、両方インデックスに 1 を指定すれば良いのに。

しかし実際のスタック操作では、空っぽの状態のスタックを扱うことはあまりないので、スタックの底から数えるよりもトップから数えた方が間違いがないのだ。このことをみるために、今度は実際のスタックに近い内容を考える。

いま、スタックには f という関数と "how" という文字列をプッシュするとする。

lua_getglobal(L, "f");                  /* function to be called */
lua_pushstring(L, "how");                        /* 1st argument */

         +-------------
  2 (-1) | "how"
         +-------------
  1 (-2) | f 関数
         +-------------

ここで、lua_getglobal(L, "t") を呼び出すと、スタックは次のようになる。

         +-------------
  3 (-1) | t テーブル
         +-------------
  2 (-2) | "how"
         +-------------
  1 (-3) | f 関数
         +-------------

テーブルのインデックスは 3 になった。でも、スタックのトップは -1 というインデックスで参照できるので、lua_getfield(L, -1, "x") という関数呼び出しは、うまく動作する。

         +-------------
  4 (-1) | t.x の値
         +-------------
  3 (-2) | t テーブル
         +-------------
  2 (-3) | "how"
         +-------------
  1 (-4) | f 関数
         +-------------

次の lua_remove(L, -2) も同様である。

         +-------------
  3 (-1) | t.x の値
         +-------------
  2 (-2) | "how"
         +-------------
  1 (-3) | f 関数
         +-------------

最後に整数の 14 をプッシュする。

lua_pushinteger(L, 14);                          /* 3rd argument */

lua_pushinteger() は整数をプッシュする関数である。ただし、Lua の内部では数値はすべて lua_Numberdouble)に変換される。これでようやく当初の目的を達成した。

         +-------------
  4 (-1) | 14
         +-------------
  3 (-2) | t.x の値
         +-------------
  2 (-3) | "how"
         +-------------
  1 (-4) | f 関数
         +-------------

さてここで、f という関数は引数を 3 個とり値を 1 個返すので、次のようにして lua_call() を呼び出す。

lua_call(L, 3, 1);     /* call 'f' with 3 arguments and 1 result */

lua_call() は、関数とその引数をスタックから消去し、代わりに関数からの戻り値をスタックにプッシュする。

         +-------------
  1 (-1) | 返り値
         +-------------

最後にもう一仕事残っている。

a = f("how", t.x, 14)

この Lua のコードと同等の動作をするために、最後に a というグローバル変数に値を代入する。

lua_setglobal(L, "a");                         /* set global 'a' */

lua_setglobal() はスタックのトップから値を取り出し(ポップ)、その値を引数で与えた名前の変数に代入する。

void lua_setglobal (lua_State *L, const char *name);

そして最終的にスタックは空っぽになる。

返り値の受け取りと調整

Lua 関数からの戻り値は、再びスタックに積まれる。いくつの値を戻すかは、呼び出し側が決めることになっているので、スタックに積まれたデータの数が期待と異なることはない。また、関数が実際に返した値の数と、この期待する返り値の数が異なっていた場合は、多値の代入と同じように 調整(adjustment) が行われる。 [http://www.lua.org/manual/5.2/manual.html#3.3.3]

エラー処理

Lua の関数の実行中にエラーが発生したときに、そのエラーを C 側で捕捉するには lua_pcall() という関数を使う。

int lua_pcall (lua_State *L, int nargs, int nresults, int msgh);

nargsnresults の意味は lua_pcall と同じで、最後の引数として msgh という値をとるところが違う。この引数には、エラーメッセージを処理する関数のインデックスを指定することができるが、特に特別な処理をする必要がなくて単にエラーが捕捉できさえすれば良いという場合は 0 を指定する。

また lua_call は値を返さないのに比べ、lua_pcall は正常に関数が実行されれば 0 を、途中でエラーが発生すれば 0 以外の値を返す。エラーが発生して 0 以外の値を返すときには、同時にスタックのトップにエラーメッセージもプッシュするので、C の呼び出し側はこのメッセージを読み取れば良い。

msgh の正確な意味と、lua_pcall の返り値の意味について詳しくはマニュアルを参照。 http://www.lua.org/manual/5.2/manual.html#lua_pcall

最も手軽な例は、次のようになる。

if (lua_pcall(L, 1, 0, 0)) {
    NSLog(@"Lua Error: %s", lua_tostring(L, -1));
}

lua_pcall() が 0 以外の値を返す時はエラーメッセージの文字列がスタックのトップにあるので、これを lua_tostring() を使って取得し、ログに出力している。

Clone this wiki locally