C++ 中使用 lua 的程序、table

我们开始这部分之前,首先引入一层抽象简化调用 lua 的 API,这层我们称为 wrapper,作用就是将 lua 提供的 C API 直接封装成为 C++ 的一个 utility 类,这样使用 lua 的 API 的同志可以直接 private 继承这个 wrapper,因为几乎所有的 API 以 lua_State 为入口,我们可以让这个 wrapper 假定“宿主”提供了一个 state() 方法返回这个指针,这个 wrapper 就作为一个 CRTP 的基类

namespace lua {
  // Derived has state() that returns lua_State*
  template <typename Derived>
  struct wrapper {
    inline lua_State* ls() {
      return static_cast<Derived*>(this)->state();
    }

    // getters/setters
    inline void get_global(const char* name) {
      lua_getglobal(ls(), name);
    }
    // ...

    // pushes
    inline void push() {
      lua_pushnil(ls());
    }
    // ...

    // conversion
    template <class T>
    inline bool to(int idx = -1) {
      return detail::to<T>::value(ls(), idx); // detail::to calls lua_is*
    }
    // ...
};

这样无论是后面如何复杂的一个部件,都可以直接如下的修改,下面一个例子就是将前面的 vm 类稍加修改的结果:

namespace lua {
  class vm : private wrapper<vm> {
    std::unique_ptr<lua_State, void (*)(lua_State*)> state_;
    friend class wrapper<vm>;

    inline lua_State* state() {
      return state_.get();
    }

  public:
    vm() : state_(luaL_newstate(), lua_close) {
      at_panic(detail::atpanic);
    }

    inline vm& libs () {
      open_libs ();
      return *this;
    }

    // ...
  };
}

访问一个 lua_State 里面的东西主要依赖于 new/set/get 方法,我们先看看 table 相关的基本操作,table 是 lua 最常见的类型,通过 newtable 在 stack 里面产生一个 table 的 handler,之后我们可以为其添加成员,比如 push 一个 key 入栈,然后 setfield 设置其 value,另一种是 push key/value 入栈后直接用 settable 方法,一个初步的实现如下

namespace lua {
  class table : public stateful, private wrapper<table> {
    friend class wrapper<table>;

  public:
    table(lua_State* s) : stateful(s) {
      new_table();
    }

    template <class T> inline table&
    add(const char* key, T v) {
      push(v);
      set_field(-2, key);
      return *this;
    }

    global<table> to_global(const char* name) {
      return global<table>(state(), name);
    }
  };
}

这样我们可以写个简单的生成 table 的例子

int
main (int, char**) {
  lua::vm vm;
  auto table = vm
    .libs()
    .new_table()
    .add("message", "hello world!")
    .to_global("blob");
  vm.run("hello.lua");
  return 0;
}

这里的 hello.lua 只有一行 print(blob.message),输出为 hello world!。global 类现在其实什么都没有,我们后面看看怎么把这个部分弄得更合理一些。类似可以写一个调用函数的例子

int
main(int argc, char* argv[]) {
  lua::vm vm;
  int res = vm.run("add.lua")
    .call ("add")
    .arg (1)
    .arg (1)
    .done<int> ();
  std::cout << "1 + 1 = " << res << '\n';
  return 0;
}

这里 add.lua 定义了一个很无趣的 add 函数,我们通过 call 方法返回一个 caller 对象,传递参数后进行调用。大致的 caller 实现如下:

namespace lua {
  class caller : public stateful, private wrapper<caller> {
    int argc_;
    friend class wrapper<caller>;

  public:
    caller (lua_State* s, const char* fn)
      : stateful(s), argc_(0) {
      get_global(fn);
    }

    inline caller& arg() {
      ++ argc_; push();
      return *this;
    }

    template <class T>
    inline caller& arg(T val) {
      ++ argc_; push(val);
      return *this;
    }

    template <class T>
    inline T done();
  };

  // done implemented with wrapper::to
}

这里设计上面还是有点小问题,比如 set/get_global 究竟应该是谁来设置。另外这里 argc_ 计数理论上应该可以编译时完成,需要类似 expression template,这里简化了其实现。

前面一个例子我们通过 table 对象向 lua 传递了数据让 lua 可以利用这些数据,后面的例子我们在 C++ 里面调用了 lua 提供的函数。这是一些基本的类型的操作,后面我们继续看一些复杂的例子,比如 table 相关的高级用法以及 OOP 相关的知识。同时我们也会解决以上遗留的一些问题。其实写一个好的 library 真的很难… 裹了这么长时间了还没裹出一个像样的东西。

——————
And Esau ran to meet him, and embraced him, and fell on his neck, and kissed him:and they wept

Advertisements
C++ 中使用 lua 的程序、table

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s