第6章 C言語との連携

LuaとC言語を連携させるためには,スタックの仕組みを理解しておかなければなりません. LuaからC言語側にデータを渡す,またはC言語側からLuaにデータを渡す場合, Luaスタックと呼ばれるものを使用します.

スタックとは

01

スタック(Stack)は上図で表すようなデータ構造です. データは上から順に積み上げられていきます. スタックにデータを積み上げる動作を プッシュ(Push)するといいます. 下図はスタックに値10をプッシュしている様子を 表しています.

02

またスタックの上部のデータを一つ削除する動作を ポップ(Pop)するといいます. 下図はスタックから値をポップしている様子を表しています.

03

Luaの初期化

次のコードはC言語からLuaを利用するためのプログラムです. このプログラムはLuaを初期化したあとは何もしません.

#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"

int main (void)
{
    lua_State* L = luaL_newstate();
    //ここに処理を書く
    lua_close(L);
    return 0;
}

Luaを呼び出すためには3つのヘッダファイル及びLuaのdllが必要です. lua_StateはLuaインタプリタの状態全体を保持する不透明な構造体です. luaL_newstate()関数を呼び出すとLuaの新しい状態を作り出し,Luaが利用可能な 状態となります. Luaを利用するためには必ずこの関数を呼び出すこととなります. lua_State構造体であるLの中にはLuaインタプリタの現在の状態が保管されています. LuaをC言語側から利用する際には必ず利用します. またLuaを終了する場合はlua_close()関数を呼び出してください. この関数を呼び出すことでステート内で使われていたすべての動的メモリを解放します.

Luaスタックへ値をプッシュする

Luaスタックへ値をプッシュする場合,lua_push***関数を呼び出します. ***の部分にはデータ型が入ります. 例えばboolean型をプッシュする場合はbooleanが,number型をプッシュする場合はnumberが入ります.

lua_pushboolean(L, 1);
lua_pushnumber(L, 10.5);
lua_pushinteger(L, 3);
lua_pushstring(L, "Hello world");
lua_pushnil(L);

Lua関係の関数は殆どの場合,第1引数にlua_Stateを渡す必要があります.

#include <stdio.h>
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"

void dumpStack(lua_State* L)
{
  int i;
  //スタックに積まれている数を取得する
  int stackSize = lua_gettop(L);
  for( i = stackSize; i >= 1; i-- ) {
    int type = lua_type(L, i);
    printf("Stack[%2d-%10s] : ", i, lua_typename(L,type) );

    switch( type ) {
    case LUA_TNUMBER:
      //number型
      printf("%f", lua_tonumber(L, i) );
      break;
    case LUA_TBOOLEAN:
      //boolean型
      if( lua_toboolean(L, i) ) {
        printf("true");
      }else{
        printf("false");
      }
      break;
    case LUA_TSTRING:
      //string型
      printf("%s", lua_tostring(L, i) );
      break;
    case LUA_TNIL:
      //nil
      break;
    default:
      //その他の型
      printf("%s", lua_typename(L, type));
      break;
    }
    printf("¥n");
  }
  printf("¥n");
}

int main (void)
{
  lua_State* L = luaL_newstate();

  lua_pushboolean(L, 1); //trueをpush
  dumpStack(L);
  lua_pushnumber(L, 10.5); //10.5をpush
  dumpStack(L);
  lua_pushinteger(L, 3); //3をpush
  dumpStack(L);
  lua_pushnil(L); //nilをpush
  dumpStack(L);
  lua_pushstring(L, "Hello world"); //hello worldをpush
  dumpStack(L);
  lua_close(L);
  return 0;
}

ここではLuaスタックの状態を見るためdumpStack関数を作成しました. この関数内の処理は現段階では理解しなくて結構です. スタックの一番下は1番目となっています. 値をプッシュするごとに2番目,3番目と積み重ねられていきます. dumpStack関数はスタックの位置,スタックに格納されているデータ型,及び データの中身を表示します. ただし,データの中身を表示できるのは,number,boolean,string,nilに限ります.

実行結果

Stack[ 3-    number] : 3.000000
Stack[ 2-    number] : 10.500000
Stack[ 1-   boolean] : true

Stack[ 2-    number] : 10.500000
Stack[ 1-   boolean] : true

Stack[ 4-    string] : Hello world
Stack[ 3-       nil] :
Stack[ 2-    number] : 10.500000
Stack[ 1-   boolean] : true

Stack[ 2-    number] : 10.500000
Stack[ 1-   boolean] : true

Luaスタックの操作

スタック操作系の関数を紹介します.

関数一覧

int lua_gettop(lua_State* L);
void lua_settop(lua_State* L, int index);
void lua_pushvalue(lua_State* L, int index);
void lua_remove(lua_State* L, int index);
void lua_insert(lua_State* L, int index);
void lua_replace(lua_State* L, int index);

lua_gettop関数はスタックに積まれている数を取得します(図1).

01.png
図1

lua_settop関数はスタックに積まれている値の数を変更します(図2). スタックの値を削除(ポップ)するのに使われています.

02.png 図2

indexには変更後のスタックの数を指定します. ただし,マイナスのindexは上から積まれたスタックの数を表しています(図3).

03.png

図3

lua_settop(L, -1); //一番上のスタック

は一番上のスタックまで残すという意味です. つまりこの操作をしてもスタックには変化がありません. スタックを操作する時にはマイナスのindexを指定する方法がよく使われます.

スタックを空にするにはindexに0を指定します.

lua_settop(L, 0); //スタックをすべて削除

スタックの値をポップする場合はlua_pop関数を利用しましたが(図4), この関数は実はlua_settop関数のマクロとなっています.

#define lua_pop(L, n)  lua_settop(L, -(n) - 1)

04.png 図4

lua_pushvalue関数は指定したindexの値をコピーしてスタックにプッシュします(図5).

05.png 図5

lua_remove関数は指定したindexの値を削除します(図6). 要素を削除したら,指定したindexの上に乗っていた要素をずらして隙間を埋めます.

06.png 図6

lua_insert関数はスタックの一番上にある要素を指定したindexに挿入します(図7).

07.png 図7

lua_replace関数はスタックの一番上の要素を指定したindexに置き換えます(図8).

08.png 図8

スタック操作系の関数のサンプルコードをお見せします.

#include <stdio.h>

#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"

void dumpStack(lua_State* L)
{
    ()
}

int main (void)
{
    lua_State* L = luaL_newstate();

    lua_pushnumber(L, 10); //10 push
    lua_pushnumber(L, 20); //20 push
    lua_pushnumber(L, 30); //30 push
    lua_pushnumber(L, 40); //40 push
    dumpStack(L);

    //スタックの要素数取得
    printf("現在のスタックの数 : %d\n", lua_gettop(L));

    //上から3つ目の要素をコピーしてスタックに積む
    printf("pushvalue -3\n");
    lua_pushvalue(L, -3);
    dumpStack(L);

    //上から3つ目の要素を削除する
    printf("remove -3\n");
    lua_remove(L, -3);
    dumpStack(L);

    //スタックトップの値を2番目に挿入する
    printf("insert 2\n");
    lua_insert(L, 2);
    dumpStack(L);

    //スタックトップの値を2番目の要素とする
    printf("replace 2\n");
    lua_replace(L, 2);
    dumpStack(L);

    lua_close(L);
    return 0;
}

実行結果

Stack[ 4-    number] : 40.000000
Stack[ 3-    number] : 30.000000
Stack[ 2-    number] : 20.000000
Stack[ 1-    number] : 10.000000

現在のスタックの数 : 4
pushvalue -3
Stack[ 5-    number] : 20.000000
Stack[ 4-    number] : 40.000000
Stack[ 3-    number] : 30.000000
Stack[ 2-    number] : 20.000000
Stack[ 1-    number] : 10.000000

remove -3
Stack[ 4-    number] : 20.000000
Stack[ 3-    number] : 40.000000
Stack[ 2-    number] : 20.000000
Stack[ 1-    number] : 10.000000

insert 2
Stack[ 4-    number] : 40.000000
Stack[ 3-    number] : 20.000000
Stack[ 2-    number] : 20.000000
Stack[ 1-    number] : 10.000000

replace 2
Stack[ 3-    number] : 20.000000
Stack[ 2-    number] : 40.000000
Stack[ 1-    number] : 10.000000

Luaスタックのサイズ

Luaスタックに値を積むことができる数には上限があります. スタックサイズの上限を超えて値を積むとスタックオーバーフローとなってしまいます. 通常はLUA_MINSTACK個(20に設定されている)だけスタックに値を積むことができます. 普通に使う分にはスタックの容量を気にする必要はないですが,もし大量の値を積みたい場合は注意が必要です.

次のサンプルコードはスタックオーバーフローが発生する例です.

#include <stdio.h>

#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"

int main (void)
{
    //このコードはエラーが発生する
    int i=0;
    lua_State* L = luaL_newstate();

    for(i=0; i < 100; i++) {
        lua_pushnumber(L, i + 1);
    }
    lua_close(L);
    return 0;
}

スタックの容量を増やすにはlua_checkstack関数を使用します.

int lua_checkstack (lua_State* L, int extra)

この関数はスタックにextra個の値の空きがあることを保障するための関数です. つまりスタックの容量を伸ばすための関数です. extra個だけ値を積むことができることが保障できればtrue(=1)を返し,そうでなければfalse(=0)を返します.

先ほどのエラーが発生したコードにlua_checkstack関数を加えると正常に動作します.

#include <stdio.h>

#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"

void dumpStack(lua_State* L)
{
    ()
}

int main (void)
{
    int i=0;
    lua_State* L = luaL_newstate();

    if( lua_checkstack(L, 100) == 0 ) {
        printf("スタックの容量が確保できませんでした\n");
        return 1;
    }

    for(i=0; i < 100; i++) {
        lua_pushnumber(L, i + 1);
    }
    dumpStack(L);
    lua_close(L);
    return 0;
}

実行結果

Stack[100-    number] : 100.000000
Stack[99-    number] : 99.000000
    ()
Stack[ 3-    number] : 3.000000
Stack[ 2-    number] : 2.000000
Stack[ 1-    number] : 1.000000

Luaスタックの値の参照

スタックに積まれている値の型を見るにはlua_type関数を使用します.

int lua_type (lua_State* L, int index)
この関数は指定したindexの型を返します(下表).
lua_typeが返す型はlua.h内で定義されています. 有効でないindexが渡された場合はLUA_TNONEを戻り値として返します.

Table: lua_type関数の戻り値

*型* *戻り値*
nil LUA_TNIL
number LUA_TNUMBER
boolean LUA_TBOOLEAN
string LUA_TSTRING
table LUA_TTABLE
function LUA_TFUNCTION
userdata LUA_TUSERDATA
thread LUA_TTHREAD
lightuserdata LUA_TLIGHTUSERDATA

また,指定した型かどうかを判定するlua_is***関数も存在しています.***の部分には型が入ります.

int lua_is*** (lua_State* L, int index)

例えばindexで指定したスタックの要素がbooleanかを判定したい場合は

int lua_isboolean (lua_State* L, int index)

とします.返却値がtrue,つまり1であればbooleanであり,false,つまり0が返されればboolean型ではないということになります. これらの関数は実はlua_type関数を利用したマクロとなっています.

また,指定したindexの要素を指定した型で取得する関数lua_to***関数も存在します. ***の部分には型が入ります.

int lua_to*** (lua_State* L, int index)

Luaスタック上のテーブルの格納と参照

Luaスタック上のテーブルの値を書き換えたり参照したりすることもできます.

void lua_settable (lua_State *L, int index);
void lua_setfield (lua_State *L, int index, const char *k);
void lua_gettable (lua_State *L, int index);
void lua_getfield (lua_State *L, int index, const char *k);

スタック上に存在しているテーブルにキーと値を格納するには lua_settable関数かlua_setfield関数を使用します.

lua_settable関数はスタックトップには格納する値を, スタックトップの一つ下にはキーとなる値を入れておき, これらのペアをindexで指定したテーブルに格納します.

また,スタックに積まれていたキーと値はスタック上から削除されます.

|10|

図:lua_settable関数

lua_setfield関数はキーを関数側から指定できます. スタックトップには格納したい値を積んでおきます.

この関数はlua_settable関数とは違い, キーには文字列しか指定できません(図).

|11|

図:lua_setfield関数

lua_gettable関数はindexで指定したテーブルからキーと対になっている値を取得しスタックに積みます.
キーはスタックトップに積んでおきます(図).

|12|

図:lua_gettable関数

lua_getfield関数はキーを関数側から指定できます.
この関数もlua_gettable関数とは違い,キーには文字列しか指定できません(図).

|13|

図:lua_getfield関数

テーブルはC言語側から作成することができます.
空のテーブルを作成したい場合はlua_newtable関数を呼び出します.

|09|

図:lua_createtable関数

void lua_newtable (lua_State *L);

この関数は実はlua_createtable関数のマクロとなっています.

void lua_createtable (lua_State *L, int narr, int nrec);
引数narrには配列要素の個数を,nrecには非配列要素の個数を指定します.
この個数を指定するとあらかじめ割り当て済みの空間が与えられます. 要はテーブルがたくさん要素を持つことがあらかじめ分かっている場合に役にたつ関数です. lua_newtable(L)はlua_createtable(L, 0, 0)とマクロで定義されています. 通常テーブルを作る際はlua_newtable関数で十分です.

以上の関数を使ったサンプルコードをお見せします.

#include <stdio.h>

#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"

void dumpStack(lua_State* L)
{
    ()
}

int main (void)
{
    int i=0;
    lua_State* L = luaL_newstate();

    lua_newtable(L); //新しいtableの作成
    lua_pushnumber(L, 10); //key
    lua_pushstring(L, "hello"); //value
    dumpStack(L);

    // table[10] = "hello" を実行
    lua_settable(L, -3);
    dumpStack(L);

    //value
    lua_pushstring(L, "world");
        //table["key"] = "world" を実行
    lua_setfield(L, -2, "key");
    dumpStack(L);

    lua_pushnumber(L, 10);
    //table[10]の値を取り出しスタックに積む
    lua_gettable(L, -2);
    dumpStack(L);

    //table["key"]の値を取り出しスタックに積む
    lua_getfield(L, -2, "key");
    dumpStack(L);

    lua_close(L);
    return 0;
}

実行結果

Stack[ 3-    string] : hello
Stack[ 2-    number] : 10.000000
Stack[ 1-     table] : table

Stack[ 1-     table] : table

Stack[ 1-     table] : table

Stack[ 2-    string] : hello
Stack[ 1-     table] : table

Stack[ 3-    string] : world
Stack[ 2-    string] : hello
Stack[ 1-     table] : table

CからLuaを呼び出す

この節ではC言語側からLuaの変数や関数を呼び出す方法を説明していきます.

Luaファイルを読み込む

まずはC言語側からLuaで書かれたコードを読み込んでみましょう. 次のようなLuaソースコードを作成したとします. このコードのファイル名はsample.luaです.

--sample.lua

--グローバル変数

NAME = "HELLOWORLD"
SIZE = 640

このLuaファイルを読み込むためのC言語コードは以下のようになります.

#include <stdio.h>

#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"

int main (void)
{
    //Luaを開く
    lua_State* L = luaL_newstate();
    //Luaの標準関数を使用できる状態にする
    luaL_openlibs(L);
    //Luaファイルsample.luaを読み込む
    if( luaL_loadfile(L, "sample.lua") || lua_pcall(L, 0, 0, 0) ) {
        printf("sample.luaを開けませんでした\n");
                printf("error : %s\n", lua_tostring(L, -1) );
        return 1;
    }
    //ここに何か処理を書く

    lua_close(L);
    return 0;
}

このプログラムはsample.luaを読み込むだけで何も処理は行いません.

ここで新たに3つの新しい関数が登場しました. まず,luaL_openlibs関数ですが,この関数はLua内で標準関数が利用できるようにするものです. この関数を呼び出さないとLua内でprint関数などが使用できません.

luaL_loadfile関数ですが,次のような定義となっています.

int luaL_loadfile (lua_State *L, const char *filename);

filenameには開きたいLuaファイルを指定します. この関数はファイルが読み込めなかった場合(ファイルが存在しない,文法が間違っていた等),LUA_ERRFILEを返します.

lua_pcall関数ですが,この関数は本来Luaの関数を呼ぶためのものです. ここでは使い方を詳しく説明しませんが,loadfileを行った後ファイルを実行する役目を果たしています.

luaL_openlibs関数及びlua_pcall関数は成功すると0を返します. この戻り値を使ってファイルが正しく読み込めたかどうかを判定できます. また,エラーが発生した場合,エラーの詳細をスタックに積んでくれます. よってスタックトップの値を参照すればエラーの原因を特定できます.

では,試しにluaL_loadfile関数の引数にsample.luaではなく存在しないファイルtest.luaを指定してみましょう. 結果は以下のようになります.

実行結果

sample.luaを開けませんでした
error : cannot open test.lua: No such file or directory

またLuaファイルの2行目に適当な文法の文字列を挿入してみます. 挿入した文字列は

if a-b=c then end

です.

実行結果

sample.luaを開けませんでした
error : sample.lua:2: 'then' expected near '='

Luaのグローバル変数を取得する

前節で登場したsample.luaから変数NAMEとSIZEの値を取得してみましょう. NAMEは文字列で取得し,SIZEはNUMBERで取得します.

#include <stdio.h>

#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"

int main (void)
{
    //Luaを開く
    lua_State* L = luaL_newstate();
    //Luaの標準関数を使用できる状態にする
    luaL_openlibs(L);
    //Luaファイルsample.luaを読み込む
    if( luaL_loadfile(L, "sample.lua") || lua_pcall(L, 0, 0, 0) ) {
        printf("sample.luaを開けませんでした\n");
        printf("error : %s\n", lua_tostring(L, -1) );
        return 1;
    }

    //PATHを取得しスタックに積む
    lua_getglobal(L, "NAME");
    //SIZEを取得しスタックに積む
    lua_getglobal(L, "SIZE");

    if( !lua_isstring(L, -2) || !lua_isnumber(L, -1) ) {
        printf("正しく値が取得できませんでした\n");
        return 1;
    }
    printf("NAME : %s\n", lua_tostring(L, -2));
    printf("SIZE : %d\n", lua_tointeger(L, -1));

    lua_close(L);
    return 0;
}

実行結果

NAME : HELLOWORLD

SIZE : 640

lua_getglobal関数はグローバル変数を取得する変数で以下のように定義されています.

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

nameには取得したいグローバル変数の変数名を指定します. 実はこの関数はlua_gettable関数のマクロとなっています.

#define lua_getglobal(L,s)  lua_getfield(L, LUA_GLOBALSINDEX, s)

LUA_GLOBALSINDEXはLuaのグローバル変数がある領域テーブルを指しています.

さて,上記のサンプルですがスタックにはNAMEとSIZEが残ったままになっています. 実際のプログラムではNAME,SIZEを使用したあとはきちんとポップしておきましょう.

Lua関数を呼び出す

C言語側からLuaの関数を呼び出してみましょう. 次のようなadd関数をLuaで作成したとします. ファイル名はsample.luaです.

-sample.lua

function add(x, y)
    print("x : ".. x .. " y : " .. y .. "を受け取りました")
    return x + y
end

add関数は二つの引数x,yを受け取り,その合計値を返却します. ではC言語側のサンプルコードをお見せします.

#include <stdio.h>

#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"

int main (void)
{
    int x = 10, y = 5;

    //Luaを開く
    lua_State* L = luaL_newstate();
    //Luaの標準関数を使用できる状態にする
    luaL_openlibs(L);
    //Luaファイルsample.luaを読み込む
    if( luaL_loadfile(L, "sample.lua") || lua_pcall(L, 0, 0, 0) ) {
        printf("sample.luaを開けませんでした\n");
        printf("error : %s\n", lua_tostring(L, -1) );
        return 1;
    }
    //add関数をスタックに積む
    lua_getglobal(L, "add");
    //第1引数x
    lua_pushnumber(L, x);
    //第2引数y
    lua_pushnumber(L, y);

    //add(x, y)を呼び出す 引数2個,戻り値1個
    if(lua_pcall(L, 2, 1, 0) != 0) {
        printf("関数呼び出し失敗\n");
        printf("error : %s\n", lua_tostring(L, -1) );
        return 1;
    }
    if( lua_isnumber(L, -1) ) {
        printf("結果 : %d\n", lua_tointeger(L, -1) );
        lua_pop(L,1); //戻り値をポップ
    }

    lua_close(L);
    return 0;
}

実行結果

x : 10 y : 5を受け取りました

結果 : 15

ここで登場した関数がlua_pcall関数です.

14

図:lua_pcall関数

この関数は次のように定義されています.

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

nargsには関数に渡す引数の数を,nresultsは関数から受け取る戻り値の数を指定します.errfuncは通常は0のままでかまいません.

さて,関数を呼ぶには次のような手順が必要です. まず,始めに呼び出したい関数を取得しスタックに積みます. 次にその関数に渡す引数を順番どおりに積んでいきます. つまり第1引数は一番初めにプッシュしなければなりません. そしてlua_pcallを呼び出すとその関数を実行します. 関数を呼び出した後は,スタックに積まれていた関数,及び引数はスタック上から削除されます. 関数からの戻り値がある場合は戻り値の順番どおり,つまり最初の戻り値は最初にスタックに積まれていきます.

また,lua_pcall関数は関数の呼び出しに失敗するとエラーコードをスタックに積みます.

Luaにグローバル変数を登録する

C言語側からLuaのグローバル変数を登録してみましょう. ここではTESTという変数(値は10とする)を登録してみます.

まずは次のようなコード,sample.luaを作ります.

--sample.lua

function show()
    if TEST == nil then
        print("TESTは登録されていません")
    else
        print("TEST の値は" .. TEST .. "です")
    end
end

そしてC言語側のコードは次のようになります.

#include <stdio.h>

#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"

void showTest(lua_State* L)
{
    //show関数をスタックに積む
    lua_getglobal(L, "show");
    //show関数を呼び出す 引数0個, 戻り値0個
    if(lua_pcall(L, 0, 0, 0) != 0) {
        printf("関数呼び出し失敗\n");
        printf("error : %s\n", lua_tostring(L, -1) );
                lua_pop(L, 1);
    }
}

int main (void)
{
    int x = 10, y = 5;

    //Luaを開く
    lua_State* L = luaL_newstate();
    //Luaの標準関数を使用できる状態にする
    luaL_openlibs(L);
    //Luaファイルsample.luaを読み込む
    if( luaL_loadfile(L, "sample.lua") || lua_pcall(L, 0, 0, 0) ) {
        printf("sample.luaを開けませんでした\n");
        printf("error : %s\n", lua_tostring(L, -1) );
        return 1;
    }

    //Luaのshow関数を呼び出す
    showTest(L);
    //変数TEST(値は10)をLuaに登録
    lua_pushnumber(L, 10);
    lua_setglobal(L, "TEST");
    //TEST表示
    showTest(L);

    lua_close(L);
    return 0;
}

実行結果

TESTは登録されていません

TEST の値は10です

Luaのshow関数を呼び出すためのC言語関数showTestを作成しました. 1回目にshow関数が呼び出された時は,変数TESTはまだ存在しないのでnilとなっています. 2回目に呼び出された時は,C言語側からTEST=10として登録されているので,値が表示されます.

さて,グローバル変数を登録するにはlua_setglobal関数を使用します.

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

この関数はスタックトップの値をnameとしてLuaに登録します. 実はlua_setglobal関数はlua_setfield関数のマクロとして定義されています.

#define lua_setglobal(L,s)   lua_setfield(L, LUA_GLOBALSINDEX, s)

LuaからCを呼び出す

この節ではLua側からCの関数を呼び出す方法について説明していきます.

C関数を呼び出す

Lua側からC言語の関数addを呼び出してみます.

Lua側では次のようなソースを書いたとします. Luaファイル名はsample.luaです.

--sample.lua

x,y = 5, 10
result = add(x, y)
print("x + y は " .. result .. " です")

ではC言語側の関数をお見せします.

#include <stdio.h>

#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"

int l_add(lua_State* L)
{
    //第1引数 intとして取得
    int x = luaL_checkint(L, -2);
    //第2引数
    int y = luaL_checkint(L, -1);
    int result = x + y;

    printf("%d + %d を計算します\n", x, y);

    //戻り値をスタックに積む
    lua_pushnumber(L, result);
    return 1; //戻り値の数を指定
}

int main (void)
{
    int x = 10, y = 5;

    //Luaを開く
    lua_State* L = luaL_newstate();
    //Luaの標準関数を使用できる状態にする
    luaL_openlibs(L);

    //l_add関数をadd関数としてLuaに登録
    lua_register(L, "add", l_add);

    //Luaファイルsample.luaを読み込む
    if( luaL_loadfile(L, "sample.lua") || lua_pcall(L, 0, 0, 0) ) {
        printf("sample.luaを開けませんでした\n");
        printf("error : %s\n", lua_tostring(L, -1) );
        return 1;
    }

    lua_close(L);
    return 0;
}

実行結果

5 + 10 を計算します

x + y  15 です

Lua側でC言語関数を呼び出すと,l\_add関数のLuaスタックに引数が積まれます. C言語側の引数に直接値が渡されるわけではないので注意してください. 第1引数は始めにプッシュされ,第2引数はその次にプッシュされます. よってC言語側からこのスタックの値を取得すればよいわけです.

スタックの値を取得する時にluaL_check***関数を使用しました. ***にはLua変数のデータ型が入ります. この関数は指定したスタックの値を***型であるかを判定してから取得します. もし***型で無かった場合はエラーが発生します.

試しにadd関数の第1引数に文字列を渡してみました. 結果は以下のようになりました.

実行結果

sample.luaを開けませんでした
error : sample.lua:4: bad argument #-2 to
'add' (number expected, got string)

l_add関数から戻り値を返す場合もLuaスタックに値を積みます. l_add関数の戻り値には戻り値の数を指定します.

さて,Lua側からC関数を呼び出すためには,Luaに関数を登録しなければなりません. その際使用するのがlua_register関数です.

void lua_register (lua_State *L, const char *name, lua_CFunction f);

nameにはLuaから呼び出す関数名を登録します. fにはC言語関数を渡します. 実はこの関数は以下のようにマクロとなっています

#define lua_register(L,n,f)  (lua_pushcfunction(L, f), lua_setglobal(L, n))

lua_pushcfunction関数はC言語関数fをスタックにプッシュするものです.

関数毎のLuaスタック

前節のl_add関数のスタック上の値について考えて見ましょう. l_add関数を呼び出した後のLuaスタックには引数x,yや戻り値resultが残ったままになっているでしょうか. もし残ったままならポップしなければいけないのかという疑問が残っています.

ここで次のようなコードを作ってみます. 前節でも登場したdumpStack関数を使用しています.

#include <stdio.h>

#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"

void dumpStack(lua_State* L)
{
    int i;
    //スタックに積まれている数を取得する
    int stackSize = lua_gettop(L);
    for( i = stackSize; i >= 1; i-- ) {
        int type = lua_type(L, i);
        printf("Stack[%2d-%10s] : ", i, lua_typename(L,type) );

        switch( type ) {
        case LUA_TNUMBER:
            //number型
            printf("%f", lua_tonumber(L, i) );
            break;
        case LUA_TBOOLEAN:
            //boolean型
            if( lua_toboolean(L, i) ) {
                printf("true");
            }else{
                printf("false");
            }
            break;
        case LUA_TSTRING:
            //string型
            printf("%s", lua_tostring(L, i) );
            break;
        case LUA_TNIL:
            //nil
            break;
        default:
            //その他の型
            printf("%s", lua_typename(L, type));
            break;
        }
        printf("\n");
    }
    printf("\n");
}

int l_add(lua_State* L)
{
    //第1引数 intとして取得
    int x = luaL_checkint(L, -2);
    //第2引数
    int y = luaL_checkint(L, -1);
    int result = x + y;

    printf("%d + %d を計算します\n", x, y);

    //戻り値をスタックに積む
    lua_pushnumber(L, result);

    //ダンプ2箇所目
    printf("2番目\n");
    dumpStack(L);

    return 1; //戻り値の数を指定
}

int main (void)
{
    int x = 10, y = 5;

    //Luaを開く
    lua_State* L = luaL_newstate();
    //Luaの標準関数を使用できる状態にする
    luaL_openlibs(L);

    //l_add関数をadd関数としてLuaに登録
    lua_register(L, "add", l_add);

    //適当な値をプッシュ
    lua_pushnumber(L, 444);
    lua_pushnumber(L, 555);

    //ダンプ1箇所目
    printf("1番目\n");
    dumpStack(L);

    //Luaファイルsample.luaを読み込む
    if( luaL_loadfile(L, "sample.lua") || lua_pcall(L, 0, 0, 0) ) {
        printf("sample.luaを開けませんでした\n");
        printf("error : %s\n", lua_tostring(L, -1) );
        return 1;
    }

    //ダンプ1箇所目
    printf("3番目\n");
    dumpStack(L);

    lua_close(L);
    return 0;
}

3箇所からdumpStack関数を呼び出しています. さて,実行結果は以下の通りとなりました.

実行結果

1番目
Stack[ 2-    number] : 555.000000
Stack[ 1-    number] : 444.000000

5 + 10 を計算します
2番目
Stack[ 3-    number] : 15.000000
Stack[ 2-    number] : 10.000000
Stack[ 1-    number] : 5.000000

x + y  15 です
3番目
Stack[ 2-    number] : 555.000000
Stack[ 1-    number] : 444.000000

実はLuaスタックは関数ごとに用意されています. つまり,l_add関数からはmain関数側のスタックは見えないわけです. また,l_add関数のスタックはLuaが勝手に削除してくれます. よって,l_add関数上のスタックはわざわざポップする必要はありません.

テーブルにC言語関数を登録する

前節では,関数をグローバル関数として定義しました. しかし,関数が増えてくると名前の衝突などが起こってしまう場合があります. そういった事態を避けるため,テーブル内に関数を定義するといった方法が取られます.

次のようなサンプルコードを書いてみます. まずはLua側のコードです.

--sample.lua

x, y = 5, 10
print( x .. " + " .. y .. " = " .. myMath.add(x, y) )
print( x .. " * " .. y .. " = " .. myMath.mul(x, y) )

myMathテーブルにあるadd関数とmul関数を呼び出しています. では,テーブルに関数を登録するCのサンプルコードをお見せします.

#include <stdio.h>

#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"

int l_add(lua_State* L)
{
    //第1引数 intとして取得
    int x = luaL_checkint(L, -2);
    //第2引数
    int y = luaL_checkint(L, -1);
    int result = x + y;

    printf("%d + %d を計算します\n", x, y);

    //戻り値をスタックに積む
    lua_pushnumber(L, result);

    return 1; //戻り値の数を指定
}

int l_mul(lua_State* L)
{
    //第1引数 intとして取得
    int x = luaL_checkint(L, -2);
    //第2引数
    int y = luaL_checkint(L, -1);
    int result = x * y;

    printf("%d * %d を計算します\n", x, y);

    //戻り値をスタックに積む
    lua_pushnumber(L, result);

    return 1; //戻り値の数を指定
}

//登録する関数
static const struct luaL_Reg myMathLib [] = {
    {"add", l_add},
    {"mul", l_mul},
    {NULL, NULL} //最後は必ずNULLのペア
};

int main (void)
{
    int x = 10, y = 5;

    //Luaを開く
    lua_State* L = luaL_newstate();
    //Luaの標準関数を使用できる状態にする
    luaL_openlibs(L);

    //add, mulをmyMathテーブルに登録
    luaL_register(L, "myMath", myMathLib);

    //Luaファイルsample.luaを読み込む
    if( luaL_loadfile(L, "sample.lua") || lua_pcall(L, 0, 0, 0) ) {
        printf("sample.luaを開けませんでした\n");
        printf("error : %s\n", lua_tostring(L, -1) );
        return 1;
    }

    lua_close(L);
    return 0;
}

実行結果

5 + 10 を計算します
5 + 10 = 15
5 * 10 を計算します
5 * 10 = 50

luaL_reg構造体は以下のように定義されています.

typedef struct luaL_Reg {
        const char *name;
        lua_CFunction func;
} luaL_Reg;

name側にはLua側に登録したい関数名を,funcにはC言語関数を渡します.

myMathテーブルを新たに作成し,luaL_reg構造体で定義した関数を登録するためにはluaL_register関数を使用します.