tolua++で登録したc関数にluaのコールバック関数を渡す方法を探す

こちらの記事
http://blogs.wankuma.com/izmktr/archive/2009/04/06/170817.aspx
を参考にやってみた。
謎のエラーに襲われるなど紆余曲折を経てとりあえず目的を達成することができた。美しくない方法だけど。

まず、やりたいこと。

-- test.lua

-- toluaで公開したc++クラス
func=c.FrameFunc:new()
-- luaの関数を渡す
func:setOnFrame(function()
  print('OnFrame')
end)

-- このloopはc++側にあってもいい
local frame=0
while true do
  -- 中でsetOnFrameで登録したLua関数を呼び出す
  func:onFrame()

  frame=frame+1
  if frame>10 then
    break
  end
end

c++のクラス

// framefunc.h
#ifndef FRAME_FUNC_H
#define FRAME_FUNC_H

#include <iostream>

class FrameFunc
{
  lua_State *L_;
  // レジストリに登録したLua関数へのキー
  int key_;
public:
  FrameFunc()
    : L_(0), key_(0)
    {}

  // setOnFrameから呼ばれる
  void 
    FrameFunc::setLuaFunctionKey(lua_State *L, int key)
    {
      // key は無引数、無返値の関数へのレジストリキー
      L_=L;
      key_=key;
    }

  // 保持しているLua関数を実行する
  void 
    FrameFunc::onFrame()
    {
      int base=lua_gettop(L_);
      lua_rawgeti(L_, LUA_REGISTRYINDEX, key_);
      if(lua_isfunction(L_, -1)){
        if(lua_pcall(L_, 0, 0, 0)!=0){
          std::cout << lua_tostring(L_, -1) << std::endl;
        }
      }
      lua_settop(L_, base);
    }

  static int 
    FrameFunc::setOnFrame(lua_State *L)
    {
      // luaスクリプトから呼ばれる。第1引数にFrameFuncのインスタンスが
      // 入っている。
      FrameFunc *self=(FrameFunc*)tolua_tousertype(L, 1, 0);
      int key=luaL_ref(L, LUA_REGISTRYINDEX);
      self->setLuaFunctionKey(L, key);
      return 0;
    }

};
#endif // FRAME_FUNC_H

tolua用

// framefunc.pkg
$#include "framefunc.h"

module c {
  class FrameFunc
  {
    FrameFunc();
    void onFrame();
  };
}

tolua実行

> tolua++ -n framefunc -o framefunc_tolua.cpp -H framefunc_tolua.h framefunc.pkg

toluaが出力したcppに追記する。

// framefunc_tolua.cppの最後の方

/* Open function */
TOLUA_API int tolua_framefunc_open (lua_State* tolua_S)
{
 tolua_open(tolua_S);
 tolua_reg_types(tolua_S);
 tolua_module(tolua_S,NULL,0);
 tolua_beginmodule(tolua_S,NULL);
  tolua_module(tolua_S,"c",0);
  tolua_beginmodule(tolua_S,"c");
   #ifdef __cplusplus
   tolua_cclass(tolua_S,"FrameFunc","FrameFunc","",tolua_collect_FrameFunc);
   #else
   tolua_cclass(tolua_S,"FrameFunc","FrameFunc","",NULL);
   #endif
   tolua_beginmodule(tolua_S,"FrameFunc");
    tolua_function(tolua_S,"new",tolua_framefunc_c_FrameFunc_new00);
    tolua_function(tolua_S,"new_local",tolua_framefunc_c_FrameFunc_new00_local);
    tolua_function(tolua_S,".call",tolua_framefunc_c_FrameFunc_new00_local);
    tolua_function(tolua_S,"onFrame",tolua_framefunc_c_FrameFunc_onFrame00);

    // 追記した
    // これをpkgファイルに書くことができるとよいのだが
    tolua_function(tolua_S,"setOnFrame",FrameFunc::setOnFrame);

   tolua_endmodule(tolua_S);
  tolua_endmodule(tolua_S);
 tolua_endmodule(tolua_S);
 return 1;
}

main.cpp

#include <stdlib.h>
#include <iostream>

#include <lua.hpp> // lua.hだとリンク時にエラーになる
#include <lauxlib.h>
#include <lualib.h>

#include <tolua++.h>
#include "framefunc_tolua.h"

lua_State *L=0;

void finalize()
{
  std::cout << "finalize..." << std::endl;
  lua_close(L);
}

void error(lua_State *L)
{
  fprintf(stderr, "%s\n", lua_tostring(L, -1));
}

int main(int argc, char **argv)
{
  if(argc<2){
    return 1;
  }

  // initialize
  L=luaL_newstate();
  atexit(finalize);

  // setup
  luaL_openlibs(L);
  tolua_framefunc_open(L);

  // Luaスクリプト(チャンク)
  // をコンパイルしてスタックのトップに関数として積む
  if(luaL_loadfile(L, argv[1])){
    error(L);
    return 2;
  }
  // スタックトップの関数を保護モード(エラーが発生したら
  // エラーコードを返す)で実行する。
  if(lua_pcall(L, 0, 0, 0)){
    error(L);
    return 3;
  }

  return 0;
}

以上のソースをビルドして

> main.exe test.lua

とすれば動作が確認できる。

追記。ましな方法

手動で出力コードを編集していた部分を自動化する方法。
tolua++には-Lオプションなるものがありこれにluaでコールバック関数を記述することができる。
http://www.codenix.com/~tolua/tolua++.html#customizing
今回の例だとこんな感じで目的を達成できる。

--tolua_hook.lua
-- FrameFuncに
--    tolua_function(tolua_S,"setOnFrame",FrameFunc::setOnFrame);
-- を追加する
function process(p)
  if p.name=="FrameFunc" then
    f={
      lname='setOnFrame',
      cname='FrameFunc::setOnFrame',
    }
    setmetatable(f, { __index=classFunction})
    table.insert(p, f)
    return
  end

  local i=1
  while p[i] do
    process(p[i])
    i=i+1
  end
end

function pre_register_hook(p)
  process(p)
end

tolua実行

> tolua++ -n framefunc -o framefunc_tolua.cpp -H framefunc_tolua.h -L tolua_hook.lua framefunc.pkg

toluaを改造しようと思ってソースを見ていたらできてしまった。
これで任意のint (*)(Lua_State *L)形式の関数を後付で登録できる。