背景:
这是之前那篇烂文章的一个扩展吧!在游戏领域,特别多的使用到lua,作为C++的补充,当然会用到lua与C++的交互。lua提供了与C++交互的API,但是这些API各种坑爹、各种坑,各种繁琐,有的API操作了lua栈,有的却没有。为了解决lua原生API的问题,就出现了一些框架、库来改善,比如lua++,luabind…,窃以为,luabind是史上最强大的lua与C++粘合层,无出其右者。但是,他依赖于boost,靠,这就是我不爽他的地方,所以,咱撸起袖子,自造了一个粘合层框架。
目标:
- 独立,无需第三方库依赖
- 小巧,仅提供大多数场景的功能需求
- 易用,接口简单明确
- 方便,提供完备的错误信息
目录结构:
state.hpp—对lua_state封装,支持对内存定制
reference.hpp—对lua的function、string、table引用,提高性能会用到
module.hpp--支持类似C++中namespace功能,以table方式实现
lua_reg.hpp--头文件包含
iterator.hpp--对不定参数的迭代
execute.hpp--执行lua文件,对lua_pcall封装,支持错误处理
error.hpp--错误处理,提供fatal_error与parameter_error,支持对堆栈内容的解析
converter.hpp--C++数据与lua数据的转换,默认支持C++原生类型、std::stringstd::pairstd::tuplestd::mapstd::vector
config.hpp--这个没什么好说的
class.hpp--对C++类的支持
call.hpp--lua_pcall封装,C++调用lua函数,支持错误处理
实现机制:
1. lua参数与C++参数转换
1: template < typename T, typename EnableT = void >2: struct convertion_t;当需要对lua与C++参数进行转换时,请考虑片特化此类,如void*对应lightuserdata
1: template < >2: struct convertion_t<void *>3: {
4: static void * from(state_t &state, int index)5: {
6: LUAREG_ERROR(lua_islightuserdata(state, index) != 0, LUA_TLIGHTUSERDATA, index);
7:
8: return ::lua_touserdata(state, index);9: }
10:
11: static std::uint32_t to(state_t &state, void *val)12: {
13: if( val != nullptr )14: ::lua_pushlightuserdata(state, val);
15: else16: ::lua_pushnil(state);
17:
18: return 1;19: }
20: };
2. C++函数与lua函数对应关系
当需要把C++函数注册到lua,如
1: int test2(int n, double d, const std::string &msg)2: {
3: return 10;4: }
1: luareg::module(state, "cpp")2: << lua::def("test2", &test2);在lua::def函数里,首先会推导test2的函数签名
1: template < typename R, typename ...Args >2: inline details::free_function_t<R, Args...> def(const char *name, R(*func)(Args...))3: {
4: return details::free_function_t<R, Args...>(name, func);5: }
根据模版参数,得到返回值类型,参数类型,构建一个free_function_t对象1: template < typename R, typename ...Args >2: struct free_function_t3: {
4: const char *name_;5:
6: typedef R(*function_t)(Args...);7: function_t function_;
8:
9: free_function_t(const char *name, function_t func)10: : name_(name)
11: , function_(func)
12: {}
13: };
free_function_t对象保存注册名及当前函数指针,通过operator<<操作符把这个free_function_t匿名对象给module的临时匿名对象1: inline module_t module(state_t &state, const char *name = nullptr)2: {
3: if( name )4: assert(std::strlen(name) != 0);
5: return module_t(state, name);6: }
在module里,提供了operator<<操作符重载1: template < typename R, typename ...Args >2: module_t &operator<<(const details::free_function_t<R, Args...> &func)3: {
4: auto lambda = [](lua_State *l)->int5: {
6: state_t state(l);
7: typedef typename details::free_function_t<R, Args...>::function_t function_t;8: auto func = static_cast<function_t>(::lua_touserdata(state, lua_upvalueindex(1)));9:
10: return details::call(state, func);11: };
12:
13: ::lua_pushlightuserdata(state_, func.function_);
14: ::lua_pushcclosure(state_, lambda, 1);
15: ::lua_setfield(state_, -2, func.name_);
16:
17: return *this;18: }
通过把free_function_t对象的function_ ame_注册到lua,这里使用了lua_pushccloure这个API,利用upvalue保存了这个注册函数的指针。这里的lambda是Lua_CFunction的原型,一旦lua调用了这个函数,就会回到这个lambda函数体中,再把函数指针取出来进行调用即可。
再来看看这个details::call
1: template < typename R, typename ...Args >2: std::int32_t call(state_t &state, R(*handler)(Args...),
3: typename std::enable_if<!std::is_same<R, void>::value>::type * = nullptr)4: {
5: return convertion_t<R>::to(state, call_impl(state, make_obj(handler), 0));6: }
1: template < typename R, typename ...Args >2: std::int32_t call(state_t &state, R(*handler)(Args...),
3: typename std::enable_if<std::is_same<R, void>::value>::type * = nullptr)4: {
5: call_impl(state, make_obj(handler), 0);
6:
7: return 0;8: }
返回值代表返回多少个数据到lua,通过convertion来完成。这里的enable_if来决断调用的C++函数返回值是否为void,如果为void则返回0个参数到lua。
解析就写到这儿吧,至于call_impl和make_obj请大家自己看源码吧,如果有什么不明白的,请加群探讨165666547
使用示例:
1. 对lua内存定制,只需要满足allocate、deallocate接口
1: std::allocator<char> std_allocator;2: luareg::state_t state(std_allocator);
2. 注册自由函数
1: luareg::module(state, "cpp")2: << lua::def("test0", &test0)3: << lua::def("test1", &test1)4: << lua::def("test2", &test2)5: << lua::def("test3", &test3)6: << lua::def("test4", &test4)7: << lua::def("test5", &test5)当然,也可以注册类的成员函数,但是并不是由lua提供的userdata作为对象指针,而是由C++保存的指针1: lua::def("test6", &t, &test_t::test6);
3. 注册类1: luareg::module(state, "cpp")2: [
3: luareg::class_t<foo_t>(state, "foo_t")4: << luareg::constructor<int>()5: << luareg::destructor()
6: << luareg::def("add", &foo_t::add)7: << luareg::def("get", &foo_t::get)8: << luareg::def("get_pointer", &foo_t::get_pointer)9: << luareg::def("get_base", &foo_t::get_base)10: ]
需要注意的是constructor与destructor都不是必须的,如果没有,则采用默认。是不是很像luabind的语法呢?
4. 执行lua文件1: lua::execute(state, "test.lua");
5. 执行lua的一个函数1: try2: {
3: lua::execute(state, "test2.lua");4: std::pair<int, std::string> n = lua::call(state, "test_call", 1, "haha", 10.2, false);5:
6: auto val = std::make_pair("test abc", 10.2);7: lua::call(state, "test_call2", 1, "haha", val);8: }
9: catch(const luareg::fatal_error_t &e)10: {
11: std::cout << e.what() << std::endl;
12: e.dump(std::cout);
13: }
执行lua的test_call函数,返回两个值,因为lua可以返回多值,所以在C++中可以采用tuple或者pair来接收。其中,错误均已throw异常来处理,当然,debug的时候会有assert及堆栈信息和参数信息。
-
对注册函数均已upvalue的方式来保存,限制了C++导出到lua函数个数(upvalue最大个数为255),不过,我认为这已足够,如果要导出很多接口道lua,那已经是不正常的了
-
与luabind比起,某些功能不支持(函数重载、导出变量等)
本框架大量使用C++11特性,使实现非常优雅的解决许多问题,比如lambda、variadic template、auto、decltype等等,所以,需要理解C++11,如果你有可能,请加入我们的C++11讨论群165666547。