好几个项目都用了 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, 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;
}
其它
没什么了,请期待下一篇介绍实现细节。