您的位置:首页 > 编程语言 > Lua

又一个lua与C++粘合层框架

2013-09-19 11:51 423 查看
背景:

这是之前那篇烂文章的一个扩展吧!在游戏领域,特别多的使用到lua,作为C++的补充,当然会用到lua与C++的交互。lua提供了与C++交互的API,但是这些API各种坑爹、各种坑,各种繁琐,有的API操作了lua栈,有的却没有。为了解决lua原生API的问题,就出现了一些框架、库来改善,比如lua++,luabind…,窃以为,luabind是史上最强大的lua与C++粘合层,无出其右者。但是,他依赖于boost,靠,这就是我不爽他的地方,所以,咱撸起袖子,自造了一个粘合层框架。

更新:

1.支持gcc4.7及以后版本
2.支持lua5.2及以后版本

目标:

独立,无需第三方库依赖
小巧,仅提供大多数场景的功能需求
易用,接口简单明确
方便,提供完备的错误信息



目录结构:





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::string\std::pair\std::tuple\std::map\std::vector

config.hpp--这个没什么好说的

class.hpp--对C++类的支持

call.hpp--lua_pcall封装,C++调用lua函数,支持错误处理

优势:

1.可定制内存分配器,满足allocate、deallocate即可,默认使用std::allocator

2.错误信息丰富,堆栈、参数列表、详情

3.参数转换可扩展,采用模板偏特化方式实现,默认有std::vector\std::pair\std::tuple\std::map\std::string等STL容器类型

4.支持lua中namespace(即module),采用table实现

实现机制:

1.lua参数与C++参数转换

1:template<typenameT,typenameEnableT=void>

2:structconvertion_t;


当需要对lua与C++参数进行转换时,请考虑片特化此类,如void*对应lightuserdata

1:template<>

2:structconvertion_t<void*>

3:{

4:staticvoid*from(state_t&state,intindex)

5:{

6:LUAREG_ERROR(lua_islightuserdata(state,index)!=0,LUA_TLIGHTUSERDATA,index);

7:

8:return::lua_touserdata(state,index);

9:}

10:

11:staticstd::uint32_tto(state_t&state,void*val)

12:{

13:if(val!=nullptr)

14:::lua_pushlightuserdata(state,val);

15:else

16:::lua_pushnil(state);

17:

18:return1;

19:}

20:};


2.C++函数与lua函数对应关系

当需要把C++函数注册到lua,如

1:inttest2(intn,doubled,conststd::string&msg)

2:{

3:return10;

4:}


1:luareg::module(state,"cpp")

2:<<lua::def("test2",&test2);


在lua::def函数里,首先会推导test2的函数签名

1:template<typenameR,typename...Args>


2:inlinedetails::free_function_t<R,Args...>def(constchar*name,R(*func)(Args...))


3:{


4:returndetails::free_function_t<R,Args...>(name,func);


5:}


根据模版参数,得到返回值类型,参数类型,构建一个free_function_t对象

1:template<typenameR,typename...Args>


2:structfree_function_t


3:{


4:constchar*name_;


5:


6:typedefR(*function_t)(Args...);


7:function_tfunction_;


8:


9:free_function_t(constchar*name,function_tfunc)


10::name_(name)


11:,function_(func)


12:{}


13:};


free_function_t对象保存注册名及当前函数指针,通过operator<<操作符把这个free_function_t匿名对象给module的临时匿名对象

1:inlinemodule_tmodule(state_t&state,constchar*name=nullptr)

2:{

3:if(name)

4:assert(std::strlen(name)!=0);

5:returnmodule_t(state,name);

6:}


在module里,提供了operator<<操作符重载

1:template<typenameR,typename...Args>

2:module_t&operator<<(constdetails::free_function_t<R,Args...>&func)

3:{

4:autolambda=[](lua_State*l)->int

5:{

6:state_tstate(l);

7:typedeftypenamedetails::free_function_t<R,Args...>::function_tfunction_t;

8:autofunc=static_cast<function_t>(::lua_touserdata(state,lua_upvalueindex(1)));

9:

10:returndetails::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_\name_注册到lua,这里使用了lua_pushccloure这个API,利用upvalue保存了这个注册函数的指针。这里的lambda是Lua_CFunction的原型,一旦lua调用了这个函数,就会回到这个lambda函数体中,再把函数指针取出来进行调用即可。

再来看看这个details::call

1:template<typenameR,typename...Args>

2:std::int32_tcall(state_t&state,R(*handler)(Args...),

3:typenamestd::enable_if<!std::is_same<R,void>::value>::type*=nullptr)

4:{

5:returnconvertion_t<R>::to(state,call_impl(state,make_obj(handler),0));

6:}


1:template<typenameR,typename...Args>

2:std::int32_tcall(state_t&state,R(*handler)(Args...),

3:typenamestd::enable_if<std::is_same<R,void>::value>::type*=nullptr)

4:{

5:call_impl(state,make_obj(handler),0);

6:

7:return0;

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_tstate(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:try


2:{


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:autoval=std::make_pair("testabc",10.2);


7:lua::call(state,"test_call2",1,"haha",val);


8:}


9:catch(constluareg::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、variadictemplate、auto、decltype等等,所以,需要理解C++11,如果你有可能,请加入我们的C++11讨论群165666547。

github:https://github.com/chenyu2202863/lua_reg
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: