第 5 回: Lua を組み込み用の言語として利用する方法 (関数編)

Lua 5.1 Reference Manual

一番簡単な Lua プログラム

C 言語で Lua のスクリプトファイルを実行する一番簡単なサンプルは以下のようになる

hello.lua

1
2
3
4
5
-- hello.lua
-- print 関数は自動的に "\n" を入れることを忘れない
print("Hello Lua World")
print("你好 Lua")
print("こんにちは Lua")

lua_in_c.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>

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

int main(int argc, char *argv[]){
  const char *fn = "hello.lua";
  // Lua の言語エンジンを初期化
  lua_State *lua = luaL_newstate();

  // Lua のライブラリを使えるようにする
  luaL_openlibs(lua);

  // Lua のスクリプトを読み込む
  printf("%d\n", luaL_dofile(lua, fn));

  return 0;
}

lua_in_c.c の実行結果は:

[wtopia lua.hajime]$ gcc -Wall -O2 -o lua_in_c lua_in_c.c -llua
[wtopia lua.hajime]$ ./lua_in_c
Hello Lua World
你好 Lua
こんにちは Lua
0

C 言語から Lua のプログラムを呼び出す

func1.lua

1
2
3
4
-- func1.lua
function lua_add(a, b)
   return (a+b)
end

func1_in_c.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/*
  func1_in_c.c
*/

#include <stdio.h>

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

int main(int argc, char *argv[]){
  lua_State *L = luaL_newstate();
  luaL_openlibs(L); // Lua のライブラリを使えるようにする
  luaL_dofile(L, "func1.lua"); // Lua のスクリプトを読み込む

  // 関数を呼ぶためにスタックに値を積む
  lua_getglobal(L, "lua_add"); // 関数名を積む
  lua_pushnumber(L, 'A'); // 第 1 引数の値を積む
  lua_pushnumber(L, 5); // 第 2 引数

  // 関数を呼ぶ. 引数 (二つ), 戻り値 (一つ).
  lua_call(L, 2, 1);

  // 関数の結果を得る
  int n = lua_tointeger(L, -1);
  lua_pop(L, 1);

  printf("%d\n", n);

  // 閉じる
  lua_close(L);

  return 0;
}

func1_in_c.c の実行結果は:

[wtopia lua.hajime]$ ./func1_in_c
70

簡単に組み込めるという割には, 意外と面倒なと思われたのではないでしょうか. もう少し, 関数を呼ぶ部分を見てみる:

// 関数名を積む
lua_getglobal(L, "lua_add");

// 第 1 引数の値を積む
lua_pushnumber(L, 12);

// 第 2 引数の値を積む
lua_pushnumber(L, 6);

// 関数を呼ぶ. 引数 (二つ), 戻り値 (一つ)
lua_call(L, 2, 1);

Lua の関数を呼ぶときには, スタックを意識する必要がある.

関数の引数や戻り値はスタックを介して処理される.

関数の呼び出し時には, 始めに, 関数の名前をスタックに積み, その後, 関数の引数をスタックに積む. そして [lua_call] を呼び出すことで, 実際に Lua の関数が実行される.

実行結果を得るには, 以下のように記述する. Lua では複数の戻り値を得ることができる.

関数の結果を得るには:

lua_tointeger()
lua_tonumber()
など

関数を利用するが, これらは, スタックから値を取り出すのに利用する:

// 関数の結果を得る
int n = lua_tointeger(L, -1); // 戻り値の一つ目を得る
lua_pop(L, 1);

lua_toXXX() 系の関数は, スタックから値を取り出すが, 関数の第 1 引数には lua_State * 型の第 2 引数には, スタックの位置を指定する. 値が 0 以下の場合, スタックの後ろから n 個目の値を返すようになっている.

エラー処理を行う

上記のプログラムは, まったくエラー処理を行っていないので, 何か問題があったときにその原因がわからない状態になる.

そこで, ファイルのロードや関数の呼び出しがうまく行っているかどうかチェックするように書き換えたのが以下になる

func1.lua

1
2
3
4
-- func1.lua
function lua_add(a, b)
   return (a+b)
end

func1_err.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// func1_err.c
#include <stdio.h>

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

void show_error(lua_State* L){
  const char *err = lua_tostring(L, -1);
  printf("ERROR: %s\n", err);
}

int main(int argc, char *argv[]){
  lua_State *L = luaL_newstate();

  luaL_openlibs(L); // Lua のライブラリを使えるようにする

  // Lua のスクリプトを読み込む
  if( luaL_loadfile(L, "func1.lua") || lua_pcall(L, 0, 0, 0) ){
    printf("func1.lua が読めなかった\n");
    show_error(L);
    return -1;
  }

  // 関数を呼ぶためにスタックに値を積む
  lua_getglobal(L, "lua_add"); // 関数名を積む
  lua_pushnumber(L, 12); // 第 1 引数の値を積む
  lua_pushnumber(L, 6); // 第 2 引数の値を積む
  // 関数を呼ぶ. 引数 (二つ), 戻り値 (一つ)
  if( lua_pcall(L, 2, 1, 0) != 0 ){
    printf("lua_add() の呼び出しに失敗した.\n");
    show_error(L);
    return -1;
  }

  // 関数の結果を得る
  int n = lua_tointeger(L, -1);
  lua_pop(L, 1);

  printf("%d\n", n);

  lua_close(L); // 閉じる

  return 0;
}

func1_c.c の実行結果は:

[wtopia lua.hajime]$ gcc -Wall -O2 -o func1_err func1_err.c -llua
[wtopia lua.hajime]$ ./func1_err
func1.lua が読めなかった
ERROR: cannot open func1.ua: No such file or directory
[wtopia lua.hajime]$ gcc -Wall -O2 -o func1_err func1_err.c -llua
[wtopia lua.hajime]$ ./func1_err
18

Lua から C 言語の関数を呼び出す

Lua から C 言語の関数を呼び出す方法を紹介する.

C 言語側では [c_add(a, b)] という関数を定義し, これを Lua スクリプトから呼び出してみる.

call_c_func.lua

for_lua.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/*
  for_lua.c
*/

#include <stdio.h>

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

/* 足し算を行う関数 */
int c_add(lua_State *L){
  // 引数を得る
  int a1 = lua_tointeger(L, 1); // 第 1 引数を得る
  int a2 = lua_tointeger(L, 2); // 第 2 引数を得る
  // 計算
  int r = a1 + a2;
  printf("[%d + %d]\n", a1, a2);
  // 結果をスタックに戻す
  lua_pushinteger(L, r);
  return 1; // 戻り値の数を返す
}

/* エラーを表示するための関数 */
void show_error(lua_State *L){
  const char *err = lua_tostring(L, -1);
  printf("ERROR: %s\n", err);
}

/* メイン */
int main(int argc, char *argv[]){
  lua_State *L = luaL_newstate();
  luaL_openlibs(L); // Lua のライブラリを使えるようにする
  
  // Lua に関数を登録する
  lua_register(L, "c_add", c_add);

  // Lua のスクリプトを読み込む
  if (luaL_loadfile(L, "call_c_func.lua") || lua_pcall(L, 0, 0, 0) ){
    printf("call_c_func.lua が読めなかった\n");
    show_error(L);
    return -1;
  }

  lua_close(L); // 閉じる
  return 0;
}

for_lua.c の実行結果は:

[wtopia lua.hajime]$ gcc -Wall -O2 -o for_lua for_lua.c -llua
[wtopia lua.hajime]$ ./for_lua
[3 + 4]
7
[30 + 40]
70
[10 + 9]
19
[wtopia lua.hajime]$ ./for_lua
[3 + 4]
7
[30 + 40]
70
[10 + 9]
19
[9 + 1]
10

call_c_func.lua の中にある C 言語で定義された関数を動的に換えて, C 言語のソースコードをもう一回コンパイル必要なく使えるようになっているので, 拡張性が高いと考えられる.

具体的には, 呼び出された C 言語側の関数で, [lua_tointeger()] や [lua_tonumber()] などを使ってスタックに積まれている引数を取り出して, 計算を行い, [lua_pushinteger()] や [lua_pushdouble()] などを使って計算結果をスタックに返す.