Skip to content
Hisai Toru edited this page May 18, 2013 · 15 revisions

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

C で実装した関数を Lua から呼び出すには、あらかじめ lua_register などを使って、Lua 側からアクセスできるようにしておく必要がある。

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

ここで、name は Lua からみた時の関数名で、グローバルな名前空間上に登録される。関数本体は lua_CFunction という型で、Lua から呼ばれる C の関数は、必ずこの型でなくてはいけない。

lua_CFunction

lua_CFunction の型は次のように宣言されている。

typedef int (*lua_CFunction) (lua_State *L);

まず、戻り値は整数で、これは C から Lua へ返す値の数を表す。Lua の関数は多値を返すことができるので、2 個以上の値を返しても良い。また、関数の実行結果によって、返す値の数が変わっても良い。(「多値と引数」の項を参照)

関数本体のとる引数は、lua_State のポインタのみである。この L を通じて、Lua から C に渡された引数を取得できる。

C から Lua の関数を呼び出す時と同様に、C の関数へ渡された引数はスタックを使って表される。たとえば、Lua 側から次のような関数呼び出しをしたとする。

a, b, c = c_func(123, "abc")

すると、スタックには数値の 123 と文字列の "abc" が、この順番にプッシュされている。

    +-------------
  2 | "abc"
    +-------------
  1 | 123
    +-------------

ではここで、まず第 1 引数を数値として取り出すには、lua_tonumber という関数を使う。

lua_Number lua_tonumber (lua_State *L, int index);

第 1 引数を取り出すには index に 1 を渡せば良いので、次のようになる。

double first_arg = lua_tonumber(L, 1);

同様に、第 2 引数を文字列として取り出すには、lua_tostring という関数を使う。

const char *lua_tostring (lua_State *L, int index);

今度は第 2 引数なので index には 2 を渡す。すると、コードは次のようになる。

const char *second_arg = lua_tostring(L, 2);

なお、lua_tostring は、C の文字列(ナル文字で終端されている文字列)しか扱えないので、そうでない場合は後述の lua_tolstring を使う。

さて、ここまで来ると Lua から渡された引数の値が分かったので、次に何かしらの処理をして Lua へ再び値を戻す。Lua へ値を返すには、Lua から引数を受け取ったスタックに、今度は返したい値をプッシュすれば良い。既に述べたように、Lua の関数は多値を返すことができるので、好きなだけ値をプッシュしてよい。

たとえば、"1a""2b""3c" という 3 個の文字列を返すには、次のように順番に書く。

lua_pushstring(L, "1a");
lua_pushstring(L, "2b");
lua_pushstring(L, "3c");

lua_pushstring はスタックに文字列をプッシュする関数である。

const char *lua_pushstring (lua_State *L, const char *s);

文字列は Lua のインタプリタの内部でコピーされるので、ここでリテラル文字列や、ローカルのポインタ変数を渡してもメモリアクセスの問題は起こらない。ただし、もし本当にリテラル文字列をプッシュしたい場合は、それに特化した lua_pushliteral という関数もあるので、これらを使っても良い。

    +-------------
  5 | "3c"
    +-------------
  4 | "2b"
    +-------------
  3 | "1a"
    +-------------
  2 | "abc"
    +-------------
  1 | 123
    +-------------

同じように、数値をプッシュする lua_pushnumber や真理値をプッシュする lua_pushboolean などがある。

最後に、3 個の値を返したことを Lua インタプリタに伝えるために、整数の 3 を返す。

return 3;

これで C 側の実装は終わり。スタックは C の関数が呼び出されるたびに新しく作り直されるので、不要な要素を取り除くなどの後片付けは必要ない。

lua_tolstring を使って任意のバイト列を受け取る

既に述べたように、lua_tostring は文字列の最後が必ずナル文字 '\0' で終わっていて、かつ文字列中にナル文字が含まれていない場合にのみ正しく動作する。しかし Lua では、文字列中にナル文字が含まれていてはいけないという決まりはないので、任意のバイト列を文字列として扱うことができる。

ナル文字を含む可能性のある任意のバイト列からなる文字列を Lua から受け取りたい場合は、lua_tolstring を代わりに使う。

const char *lua_tolstring (lua_State *L, int index, size_t *len);

これは、文字列のポインタを返す点では lua_tostring と変わらないが、引数の最後に、文字列の長さを返すための出力用のポインタをとる。たとえば、次のように呼び出す。

size_t len;
const char *second_arg = lua_tolstring(L, -2, &len);

このようにすると、len に文字列の長さが代入される。

可変引数

Lua の関数を呼び出すときは、任意の数の引数を与えることができる。逆に呼び出される関数から見ると、あらかじめ幾つの引数が渡されるかは、呼び出されるまでは正確には分からない。Lua で書かれた関数なら ... という記号を使って可変個数の引数にアクセスできることを述べた。

C で実装された関数では、与えられた引数の数はスタックのサイズをはかることで知ることができる。関数が呼び出されるとき、スタックには Lua から渡された引数がすべて積まれ、かつそれ以外のものは積まれていないから、これは常に正しいといえる。

lua_gettop という関数を呼び出すと、スタックのサイズを得られる。

int lua_gettop (lua_State *L);

lua_gettop は、本来は、スタックのトップのインデックスを返す関数であるが、インデックスは 1 から始まるので、上に書いた理由も含めるとスタックのサイズと等しい。スタックが空の時は 0 を返す。

    +-------------
  3 | 第 3 引数 (トップ)
    +-------------
  2 | 第 2 引数
    +-------------
  1 | 第 1 引数
    +-------------


int numargs = lua_gettop(L);

このように呼び出せば、Lua からいくつ引数を渡されようと正しく扱うことができる。

引数の型

さてここで、いくつでも引数を受け取れるようになったのはいいが、あらかじめ引数の型が分かっていないと、C 側で正しく取得できない。そこで、lua_type という関数を使って引数の型を調べる。

int lua_type (lua_State *L, int index);

ここでもマイナスのインデックスが使える。たとえば、後ろか 3 番目の引数を調べたい場合は、次のように index に -3 を指定すればよい。

int argtype = lua_type(L, -3);

lua_type は、引数の型によって次の定数のうちのどれかを返す。すなわち LUA_TNILLUA_TNUMBERLUA_TBOOLEANLUA_TSTRINGLUA_TTABLELUA_TFUNCTIONLUA_TUSERDATALUA_TTHREADLUA_TLIGHTUSERDATA。あるいは、要素の存在しないインデックスを指定した場合は LUA_TNONE を返す。

SWIG

以上のようなことをすれば C や C++ で書かれた関数を Lua から呼ぶことができるが、既存のライブラリなどを Lua から使えるようにするのは少し面倒である。そこで、C や C++ で書かれた関数やクラス、構造体へのアクセスのために lua_CFunction 型のラッパー関数を生成するツールがいくつかある。

   +-------------------------+   call    +-----+
   | lua_CFunction (wrapper) |<----------| Lua |
   | +---------------------+ |           +-----+
   | | library function    | |
   | +---------------------+ |
   +-------------------------+

ここでは SWIG というツールを紹介する。

SWIG は、Lua だけでなく Perl、PHP、Python、Tcl それに Ruby など多くの言語のためのラッパーを生成することができる。また SWIG が生成するラッパー関数のコードは Lua 同様にポータブルなので、様々なプラットフォームのためにコンパイルできる。

ちなみに、SWIG は公式には Simplified Wrapper and Interface Generator の略だが、英和辞書を引くと「がぶがぶ飲む」という意味もあることが分かる。C のライブラリをがぶがぶとスクリプティング環境に飲み込むような意味合いがあるのかもしれない。

インターフェイスファイル

SWIG を使うためには、まず SWIG の入力となるインターフェイスファイルを用意する必要がある。このインターフェイスファイルは、C のソースコードの断片を書く部分と、SWIG が解釈するための部分の 2 つの部分を含む。

%module module_name

%{
#include <ヘッダファイル>
... C のコード ...
%}

- 関数プロトタイプ宣言 ...
- 構造体やクラスの定義 ...
- SWIG の命令 ...

まず最初に %module という命令(directive)を使ってモジュールの名前を与える。

次に、C のヘッダファイルのインクルードやその他の C のコードを %{%} で囲んだ中に記述する。ここに書いた内容は、SWIG によって解釈されることはなく、そのままラッパーとなる C のコードとして出力される。

そしてそれ以降に、SWIG が解釈すべき命令などを書く。次節以降で詳しくみるが、C や C++ の関数プロトタイプ宣言は原則としてそのままの形で SWIG も解釈できる。従って、ヘッダファイルの内容をそのままコピーしても良い。

関数

簡単な関数呼び出しのラッパーをつくってみよう。まず次のような C で書かれた関数があるとする。

~~code_any("hello.c", function (spin)
#include <stdio.h>
void hello(const char *name)
{
    printf("hello, %s!\n", name);
}
~~end)

これを使って hello という名のモジュールを作るとすると、インターフェイスファイルは次のようになる。

~~code_any("hello.i", function (spin)
%module hello
%{
// ラッピングする関数のプロトタイプ宣言
void hello(const char *name);
%}
void hello(const char *name);
~~end)

このファイル名を hello.i とすると、次のようにして swig コマンドを実行する。

swig -lua hello.i

すると、hello_wrap.c というファイルが生成される。いったんこのラッパーコードを出力してしまうと、SWIG の出番は終わりである。言い換えると、SWIG の機能のすべてがここに含まれるということである。このファイルを覗いてみると、冒頭では様々なユーティリティ関数が定義されているのが見て取れる。

上で作った hello のラッパー関数は _wrap_hello として書かれている。

static int _wrap_hello(lua_State* L) {
  int SWIG_arg = 0;
  char *arg1 = (char *) 0 ;
  
  SWIG_check_num_args("hello",1,1)
  if(!SWIG_lua_isnilstring(L,1)) SWIG_fail_arg("hello",1,"char const *");
  arg1 = (char *)lua_tostring(L, 1);
  hello((char const *)arg1);
  
  return SWIG_arg;
  
  if(0) SWIG_fail;
  
fail:
  lua_error(L);
  return SWIG_arg;
}

C++ のクラス

SWIG は C++ のクラスをほぼ自然な形で Lua のオブジェクトとしてラッピングできる。ここで、少し実用的な例として、Box2D の 2 次元ベクトルのクラスを例にとる。このクラスの定義は Google Code 上で公開されている Box2D のソースコードの中で参照できる。

SWIG のインターフェイスファイルには次にように書く。

~~code_any("b2Vec2.i", function (spin)
%module b2

%{
#include <Box2D/Common/b2Math.h>
%}

/// A 2D column vector.
struct b2Vec2
{
    b2Vec2();
    b2Vec2(float32 x, float32 y);
    void SetZero();
    void Set(float32 x_, float32 y_);
    b2Vec2 operator -() const;
    float32 operator () (int32 i) const;
    void operator += (const b2Vec2& v);
    void operator -= (const b2Vec2& v);
    void operator *= (float32 a);
    float32 Length() const;
    float32 LengthSquared() const;
    float32 Normalize();
    bool IsValid() const;
    b2Vec2 Skew() const;

    float32 x, y;
};
~~end)

モジュール名は b2 としたが、実際は何でも良い。次に、コンパイルに必要なヘッダファイルのインクルードを %{%} の間に書く。ここでは、一見して分かるように、クラスの宣言をそのまま書けば良い。ただし、オリジナルの Box2D のヘッダファイルでは、各メソッドの実装もヘッダファイルに書いてあるが、SWIG のインターフェイスファイル中ではヘッダファイルの中のメソッドのインライン定義は単に無視されるので省いた。また float32 という型は Box2D のヘッダファイルの中で定義されているが、ここでは単なる浮動小数点数と思ってよい。

ここで、実際に SWIG を起動してみよう。以下のようなコマンドを入力する。

swig -lua -c++ b2Vec2.i

これで、b2Vec2_wrap.cxx というファイルが生成される。ソースが C++ で記述されているので、-c++ というオプションの指定が必要である。このようにして出来た C++ のファイルは、組み込み先のホストプログラムの他のソースコードと同じようにコンパイルする事が出来る。例えば、g++ を使う場合は次のようにする。

g++ -I ~/local/lua/include -I ~/src/Box2D_v2.2.1 -c -o b2Vec2_wrap.o b2Vec2_wrap.cxx

ただし、インクルードパス等のコンパイラオプションは、環境に合わせて指定する。

苦手なこと

上記のようにして、既存のライブラリを簡単に SWIG でラッピングする事が出来るが、Lua の性質上、SWIG を使っても簡単にラッパーが作れない場合がある。たとえば、上で作った Box2D の b2Vec2 クラスでは、左辺値を返すメソッドをこっそり除いてある。除いたのは次のメソッドである。

/// Write to an indexed element.
float32& operator () (int32 i)
{
        return (&x)[i];
}

このメソッドは、ベクトルの任意の要素を書き換えるためのものであり、次のメソッドの左辺値版である。

    float32 operator () (int32 i) const;

残念ながら Lua のユーザデータには、C++ の左辺値に対応するような操作は出来ないので、このメソッドと同等のメソッドを Lua 上で再現する事は出来ない。ただ、このクラスの場合は、メンバ変数である xy に Lua からアクセスできるので、多くの場合は問題にならないだろう。

このように、SWIG によって言語間の差異がすべて吸収される訳ではない事に注意すること。

さらに SWIG

つぎにもう少し実用的な例として、LibXML の Lua バインディングを作ってみる。

SWIG のインターフェイスファイルで、モジュール名、ヘッダファイルのインクルードの指定と、ラッパーを作りたい関数のプロトタイプ宣言を書く。基本的には、%include ディレクティブを使ってヘッダファイルをそのままインクルードしてやれば動く。

単純なラッパ

インターフェイスファイルの基本的な記述方法は C のヘッダファイルと同じなので、%include ディレクティブを使ってヘッダファイルをインクルードしてしまえば、一応インターフェイスファイルができたことになる。たとえば、LibXML2 の tree.h に対するインターフェイスファイルのもっとも簡単な形は次のようになる。

~~code_any("libxml2.i", function (spin)
%module libxml2

%{
  #include <libxml2/libxml/tree.h>
  #include <stdio.h>
%}

%include </usr/include/libxml2/libxml/xmlexports.h>
%include </usr/include/libxml2/libxml/xmlversion.h>
%include </usr/include/libxml2/libxml/tree.h>
~~end)

ここでは、libxml2.xmlDocDumpFormatMemory() のような形で呼び出したいので libxml2 というモジュール名にした。

次に %{ から %} までに書かれている部分は、生成されるラッパーのソースコードにそのまま書き出されるものなので、ライブラリ関数の呼び出しに必要なヘッダファイルをインクルードするように指定する。

そして次の行以降に並んでいる %include ディレクティブが、このインターフェイスファイルの本体部分である。%include は、単に指定されたファイルをこの位置に展開するだけだ。ヘッダファイルの内容は SWIG のインターフェイスファイルと互換性があるのでこのような手抜きができる。

SWIG 起動

さて、ここまでできたら実際に SWIG を起動してラッパーを生成してみよう。シェルのコマンドラインから次のように入力する。

swig -lua libxml2.i

そうすると、libxml2_wrap.c というファイルが生成される。これを通常の LibXML2 のアプリケーションと同じようにコンパイル、リンクすると Lua のモジュールができる。ただし、これだけでは役に立たないので、具体的なビルド方法については後に詳しく述べる。

typemap

組み込み型や std::string のような基本的な型については、SWIG が自動的に Lua の対応する型に変換する。しかし、ライブラリ固有の型については、どのように Lua のデータに変換するかを SWIG に教えてあげる必要がある。このデータ型の変換のために %typemap というディレクティブを使って変換の方法を指示する。

パターンマッチ

%typemap では、関数の入出力の型と仮引数名をつかって、Lua の値へのマッピングのやり方を記述する。通常、C の関数プロトタイプ宣言では仮引数の名前は意味を持たないが、SWIG はこれを型を補うための重要な情報として活用する。これらの情報をパターンとして記述して、SWIG はこのパターンとそれぞれの関数プロトタイプ宣言を比較し、どのようなラッパーを適用するかを決めるのである。

%typemap(in, numinputs=0)
    (xmlChar ** mem, int * size)
    (xmlChar *temp, int templen) {
  $1 = &temp;
  $2 = &templen;
}

%typemap(argout) (xmlChar ** mem, int * size) {
  // Append output value $1 to $result
  lua_pushlstring (L, *$1, *$2);
  SWIG_arg ++;
}

この例では、まず xmlChar ** mem, int * size の部分がパターンで、このパターンにマッチする引数を持つ関数に対して、その下に書いた処理を実行する。このパターンはたとえば、次のような関数プロトタイプ宣言にマッチする。

XMLPUBFUN void XMLCALL
    xmlDocDumpFormatMemory(xmlDocPtr cur,
                           xmlChar **mem,
                           int *size,
                           int format);

ここで、xmlChar **memint *size がこの順番で出てきていることに注意すること。同じ型でも、別の仮引数名を使った次のような関数にはマッチしない。

XMLPUBFUN void XMLCALL
    xmlDocDumpFormatMemoryEnc(xmlDocPtr out_doc,
                             xmlChar **doc_txt_ptr,
                             int * doc_txt_len,
                             const char *txt_encoding,
                             int format);

xmlChar** 型の仮引数と int * 型の仮引数の名前がそれぞれ memsize ではないことに注意しよう。

さて、%typemap ディレクティブの中身をもう少し見てみる。最初の %typemap では、まず in と書いて、与えられた引数を入力として使うことを宣言している。次の numinputs=0 は、「この引数は Lua からの引数を消費しませんよ」という意味だ。

次に続く (xmlChar ** mem, int * size) はすでに説明したように、ラッパーを適用したい関数のパターンである。

そして、その次の (xmlChar *temp, int templen) は、ラッパー関数内で使うローカル変数の宣言である。

説明するよりも、実際に生成されたラッパー関数の中身を見るほうが分かりやすいので、ちょっとのぞいてみよう。たとえば、すでに出てきた xmlDocDumpFormatMemoryEnc のラッパー関数は次のようになった。

static int _wrap_xmlDocDumpFormatMemory(lua_State* L) {
  int SWIG_arg = 0;
  xmlDocPtr arg1 = (xmlDocPtr) 0 ;
  xmlChar **arg2 = (xmlChar **) 0 ;
  int *arg3 = (int *) 0 ;
  int arg4 ;
  xmlChar *temp2 ;
  int templen2 ;

  {
    arg2 = &temp2;
    arg3 = &templen2;
  }
  SWIG_check_num_args("xmlDocDumpFormatMemory",2,2)
  if(!SWIG_isptrtype(L,1)) SWIG_fail_arg("xmlDocDumpFormatMemory",1,"xmlDocPtr");
  if(!lua_isnumber(L,2)) SWIG_fail_arg("xmlDocDumpFormatMemory",2,"int");

  if (!SWIG_IsOK(SWIG_ConvertPtr(L,1,(void**)&arg1,SWIGTYPE_p__xmlDoc,0))){
    SWIG_fail_ptr("xmlDocDumpFormatMemory",1,SWIGTYPE_p__xmlDoc);
  }

  arg4 = (int)lua_tonumber(L, 2);
  xmlDocDumpFormatMemory(arg1,arg2,arg3,arg4);

  {
    // Append output value arg2 to result
    lua_pushlstring (L, *arg2, *arg3);
    SWIG_arg ++;
  }
  return SWIG_arg;

  if(0) SWIG_fail;

fail:
  lua_error(L);
  return SWIG_arg;
}

順を追ってみてみる。

static int _wrap_xmlDocDumpFormatMemory(lua_State* L) {

Lua が処理できる関数の型は、必ず lua_State* をとって int を返す。

  int SWIG_arg = 0;

SWIG_arg は引数の個数を数えるカウンタである。

  xmlDocPtr arg1 = (xmlDocPtr) 0 ;
  xmlChar **arg2 = (xmlChar **) 0 ;
  int *arg3 = (int *) 0 ;
  int arg4 ;

これらは、Lua から渡された引数を C の値に変換するときの受け皿となる。

  xmlChar *temp2 ;
  int templen2 ;

ここで、1 つめの %typemap に書いた変数宣言を思い出そう。あれはここにそのまま書かれる。

  {
    arg2 = &temp2;
    arg3 = &templen2;
  }

そしてさらに、1 つ目の %typemap の中身がそのままここにコピーされる。ただし、$1arg2 に、$2arg3 に変換されていることに注意すること。

ここからしばらくは、SWIG が自動生成するラッパーコードで、主に型のマッピングである。

  SWIG_check_num_args("xmlDocDumpFormatMemory",2,2)
  if(!SWIG_isptrtype(L,1)) SWIG_fail_arg("xmlDocDumpFormatMemory",1,"xmlDocPtr");
  if(!lua_isnumber(L,2)) SWIG_fail_arg("xmlDocDumpFormatMemory",2,"int");

  if (!SWIG_IsOK(SWIG_ConvertPtr(L,1,(void**)&arg1,SWIGTYPE_p__xmlDoc,0))){
    SWIG_fail_ptr("xmlDocDumpFormatMemory",1,SWIGTYPE_p__xmlDoc);
  }

  arg4 = (int)lua_tonumber(L, 2);
  xmlDocDumpFormatMemory(arg1,arg2,arg3,arg4);

最後の行で、実際に xmlDocDumpFormatMemory 関数を呼び出している。しかし、この関数は arg2arg3 を出力変数として使うので、これらの値を今度は Lua の値に変換して Lua に返してやる必要がある。

  {
    // Append output value arg2 to result
    lua_pushlstring (L, *arg2, *arg3);
    SWIG_arg ++;
  }

ここで、2 つ目の %typemap が登場する。ここで SWIG_arg という変数をインクリメントしているのは、Lua に返す値の数を表すためである。

  return SWIG_arg;

  if(0) SWIG_fail;

fail:
  lua_error(L);
  return SWIG_arg;
}

残りは SWIG が生成するコードでだ。特に SWIG_arg(返り値の数)を返している事に注意する。