Lua C++ bindings (1)

归档: lua | 标签:

好几个项目都用了 Lua 作为配置文件语言,但是一直都用官方提供的 C API,写起来十分繁琐。本来大部分的配置文件都是 key-value 式的配置,简单地封装一下就足够了,但是本着完美主义的倾向一直都觉得要么就别封装,要么就写一个功能完善的,而函数调用和自定义类导出这部分一直没想明白怎么弄,因此迟迟没动手。至于为什么不直接使用 LuaBind 这样强大的东东,一方面是不喜欢 LuaBind 依赖 Boost 太臃肿,另一方面也是趁着这个机会学习一下。

从去年 10 月左右就开始磕磕碰碰地写,三两下就把变量部分搞定了,不过之后就停滞了。直到去年年底的时候看了下 C++11 提供的变长参数模板有了些头绪,于是写成了现在这样,总的来说已经基本可用,心里的成就感满满的,于是写点东西记录一下,也算是打个广告。关于这个库准备写两篇博客,第一篇从使用者的角度介绍一下使用方法,第二篇从开发者的角度写写演变过程及设计原则。这里介绍的是第一个提交的版本,后续有变化的话不再同步更新博客了,只会更新相关的文档。

哦,忘了打广告,代码放在 github 上,猛点 这里。需要 C++11 的支持,使用 g++ 编译时加上选项 -std=c++11。

变量设置

先来看一个“Hello, world!”式的程序:

#include <iostream>
using namespace std;

#include "luacpp.hpp"
using namespace luacpp;

int main(void)
{
    LuaState l;

    l.set("msg", "Hello, luacpp from ouonline!");
    auto lobj = l.get("msg");
    if (lobj.type() == LUA_TSTRING)
        cout << "get msg -> " << lobj.tostring() << endl;
    else
        cerr << "unknown object type -> " << lobj.typestr() << endl;

    return 0;
}

首先生成一个 LuaState 对象,其实就是 lua_State 的封装。set() 是一系列的重载函数,可以设置各种类型的变量:

void LuaState::set(const char* name, const char* str);
void LuaState::set(const char* name, const char* str, size_t len);
void LuaState::set(const char* name, lua_Number);
bool LuaState::set(const char* name, const LuaObject& lobj);
bool LuaState::set(const char* name, const LuaTable& ltable);
bool LuaState::set(const char* name, const LuaFunction& lfunc);
bool LuaState::set(const char* name, const LuaUserdata& lud);

既然有 set() 就有 get():

LuaObject LuaState::get(const char* name) const;

返回值是一个 LuaObject 对象,可以转换成各种类型:

std::string LuaObject::tostring() const;
lua_Number LuaObject::tonumber() const;
LuaTable LuaObject::totable() const;
LuaFunction LuaObject::tofunction() const;
LuaUserdata LuaObject::touserdata() const;

这些转换并没有进行错误检查,因此如果不确定变量的类型,最好在转换前调用

int LuaRefObject::type() const;

判断一下类型。type() 函数的返回值就是 lua_type() 的返回值: LUA_TNIL,LUA_TNUMBER,LUA_TBOOLEAN,LUA_TSTRING,LUA_TTABLE,LUA_TFUNCTION,LUA_TUSERDATA,LUA_TTHREAD,LUA_TLIGHTUSERDATA。另外还有

const char* LuaRefObject::typestr() const;

用于获取返回值的名称,其实就是 lua_typename() 的返回值。

table 相关

#include <iostream>
using namespace std;

#include "luacpp.hpp"
using namespace luacpp;

int main(void)
{
    LuaState l;

    auto iterfunc = [] (const LuaObject& key, const LuaObject& value) -> bool {
        cout << "    "; // indention
        if (key.type() == LUA_TNUMBER)
            cout << key.tonumber();
        else if (key.type() == LUA_TSTRING)
            cout << key.tostring();
        else {
            cout << "unsupported key type -> " << key.typestr() << endl;
            return false;
        }

        if (value.type() == LUA_TNUMBER)
            cout << " -> " << value.tonumber() << endl;
        else if (value.type() == LUA_TSTRING)
            cout << " -> " << value.tostring() << endl;
        else
            cout << " -> unsupported iter value type: " << value.typestr() << endl;

        return true;
    };

    cout << "table1:" << endl;
    l.dostring("var = {'mykey', value = 'myvalue', others = 'myothers'}");
    l.get("var").totable().foreach(iterfunc);

    cout << "table2:" << endl;
    auto ltable = l.newtable();
    ltable.set("x", 5);
    ltable.set("o", "ouonline");
    ltable.set("t", ltable);
    ltable.foreach(iterfunc);

    return 0;
}

程序首先调用

bool LuaState::eval(const char* chunk, std::string* errstr = nullptr);

创建了一个有 3 个元素的 table。接着调用

bool foreach(std::function<bool (const LuaObject& key,
                                 const LuaObject& value)> func) const;

遍历 table。foreach() 的参数是一个回调函数,接收两个参数,分别是 key 和对应的 value。

除了使用 LuaState::eval() 来创建 table 外,也可以使用

LuaTable LuaState::newtable(const char* name = nullptr);

来创建一个新的空 table,然后调用一系列的 set() 重载函数来设置 table 中的变量值:

void set(int idx, const char* str);
void set(int idx, const char* str, size_t len);
void set(int idx, lua_Number);
bool set(int idx, const LuaObject& lobj);
bool set(int idx, const LuaTable& ltable);
bool set(int idx, const LuaFunction& lfunc);
bool set(int idx, const LuaUserdata& lud);

void set(const char* name, const char* str);
void set(const char* name, const char* str, size_t len);
void set(const char* name, lua_Number);
bool set(const char* name, const LuaObject& lobj);
bool set(const char* name, const LuaTable& ltable);
bool set(const char* name, const LuaFunction& lfunc);
bool set(const char* name, const LuaUserdata& lud);

既可以用下标作为 key,也可以用字符串作为 key。

函数调用

#include <iostream>
using namespace std;

#include "luacpp.hpp"
using namespace luacpp;

int main(void)
{
    LuaState l;

    vector<LuaObject> res;
    typedef int (*func_t)(const char*);

    func_t echo = [] (const char* msg) -> int {
        cout << msg;
        return 5;
    };
    auto lfunc = l.newfunction(echo, "echo");

    l.set("msg",  "calling cpp function with return value from cpp");
    lfunc.exec(1, &res, nullptr, l.get("msg"));
    cout << ", return value -> " << res[0].tonumber() << endl;

    l.eval("res = echo('calling cpp function with return value from lua');"
           "io.write(', return value -> ', res, '\\n')");

    res.clear();
    l.eval("function return2(a, b) return a, b end");
    l.get("return2").tofunction().exec(2, &res, nullptr, 2, 3);
    cout << "calling lua funciont from cpp:" << endl;
    for (unsigned int i = 0; i < res.size(); ++i)
        cout << "    res[" << i << "] -> " << res[i].tonumber() << endl;

    return 0;
}

首先调用

template<typename FuncRetType, typename... FuncArgType>
LuaFunction LuaState::newfunction(FuncRetType (*)(FuncArgType...),
                                  const char* name = nullptr);

将函数 echo() 导出给 Lua。newfunction() 同时返回生成的对象。然后使用

template<typename... Argv>
bool LuaFunction::exec(int nresults, std::vector<LuaObject>* res,
                       std::string* errstr, const Argv&... argv);

来执行对应的函数。exec() 的第一个参数 nresults 表示这个函数要返回多少个结果(因为 Lua 中的函数可返回多个结果);第二个参数是一个 LuaObject 数组,用来存放返回结果,如果为 nullptr 则表示不保存返回结果;第三个参数是出错信息;最后是一系列的不定参数,这些参数最后都会被传递给要调用的函数(这里是之前一直想不通的地方,下一篇博客会介绍一下实现方法)。

导出自定义类

#include <iostream>
using namespace std;

#include "luacpp.hpp"
using namespace luacpp;

class TestClass {

    public:

        TestClass()
        {
            cout << "TestClass::TestClass() is called without value." << endl;
        }

        TestClass(const char* msg, int x)
            : m_msg(msg)
        {
            cout << "TestClass::TestClass() is called with string -> '"
                << msg << "' and value -> " << x << "." << endl;
        }

        virtual ~TestClass()
        {
            cout << "TestClass::~TestClass() is called." << endl;
        }

        void set(const char* msg) { m_msg = msg; }

        void print() const
        {
            cout << "TestClass::print(): " << m_msg << endl;
        }

        void echo(const char* msg) const
        {
            cout << "TestClass::echo(string): " << msg << endl;
        }

        void echo(int v) const
        {
            cout << "TestClass::echo(int): " << v << endl;
        }

        static void s_echo(const char* msg)
        {
            cout << "TestClass::s_echo(string): " << msg << endl;
        }

    private:

        string m_msg;
};

int main(void)
{
    LuaState l;

    // NOTE: export only once
    auto lclass = l.newclass<TestClass>("TestClass"); // with default constructor

    cout << "--------------------------------------------" << endl;
    l.eval("tc = TestClass()");

    cout << "--------------------------------------------" << endl;
    lclass.setconstructor<const char*, int>();
    l.eval("tc = TestClass('ouonline', 5)");

    cout << "--------------------------------------------" << endl;
    lclass.set("print", &TestClass::print)
        .set<void, const char*>("echo_str", &TestClass::echo) // overloaded function
        .set<void, int>("echo_int", &TestClass::echo);
    l.eval("tc = TestClass(5); tc:print();"
           "tc:echo_str('calling class member function from lua')");

    lclass.setconstructor(); // reset constructor

    cout << "--------------------------------------------" << endl;
    lclass.set("s_echo", &TestClass::s_echo);
    l.eval("TestClass:s_echo('static member function is called without being instantiated');"
           "tc = TestClass(); tc:s_echo('static member function is called by an instance')");

    cout << "--------------------------------------------" << endl;

    return 0;
}

程序首先调用

template<typename T>
LuaClass<T> LuaState::newclass(const char* name);

来导出一个类。然后可以使用

template<typename... FuncArgType>
LuaClass<T>& LuaClass::setconstructor();

设置构造函数。如果没有设置构造函数则会生成一个不带任何参数的默认构造函数。

就是调用一系列的 set() 函数来设置成员函数了(暂不支持导出变量):

// common member function
template<typename FuncRetType, typename... FuncArgType>
LuaClass<T>& LuaClass::set(const char* funcname,
                           FuncRetType (T::*func)(FuncArgType...));

// member function with const qualifier
template<typename FuncRetType, typename... FuncArgType>
LuaClass<T>& LuaClass::set(const char* funcname,
                           FuncRetType (T::*func)(FuncArgType...) const);

// static member function
template<typename FuncRetType, typename... FuncArgType>
LuaClass<T>& LuaClass::set(const char* funcname,
                           FuncRetType (*func)(FuncArgType...));

另外如果要在 Lua 中调用 C++ 函数,而这个 C++ 函数使用了自定义类型,则自定义类型的参数要以指针传递。

在 C++ 和 Lua 中使用自定义类型

目前对自定义类型的支持有限,如果要使用自定义类型作为函数参数或者返回值,只能传指针。

#include <iostream>
using namespace std;

#include "luacpp.hpp"
using namespace luacpp;

int main(void)
{
    LuaState l;

    l.newclass<TestClass>("TestClass").set("print", &TestClass::print);
    l.newuserdata<TestClass>("tc").object<TestClass>()->set("in lua: print test data from cpp");
    l.dostring("tc:print()");

    return 0;
}

程序首先注册了一个类 TestClass,然后生成了一个实例并导出为“tc”,调用 set() 设置了字符串的内容,然后在 Lua 中打印这些内容,说明在 Lua 中能够看到 C++ 中的修改内容。

下面这个程序和上面的类似,区别在于在 Lua 中设置了字符串的内容但是在 C++ 中打印。

#include <iostream>
using namespace std;

#include "luacpp.hpp"
using namespace luacpp;

int main(void)
{
    LuaState l;

    l.newclass<TestClass>("TestClass").set("set", &TestClass::set);
    l.dostring("tc = TestClass(); tc:set('in cpp: print test data from lua')");
    l.get("tc").touserdata().object<TestClass>()->print();

    return 0;
}

其它

没什么了,请期待下一篇介绍实现细节。

发表于 2015年1月17日
本文目前尚无任何评论.

发表评论

XHTML: 您可以使用这些标签: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>