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

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

2013-09-19 11:49 501 查看
背景:

这是之前那篇烂文章的一个扩展吧!在游戏领域,特别多的使用到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::string\std::pair\std::tuple\std::map\std::vector

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

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

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

实现机制:

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

template < typename T, typename EnableT = void >


struct convertion_t;


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

template < >


struct convertion_t<void *>


{


static void * from(state_t &state, int index)


{


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


 


return ::lua_touserdata(state, index);


}


 


static std::uint32_t to(state_t &state, void *val)


{


if( val != nullptr )


::lua_pushlightuserdata(state, val);


else


::lua_pushnil(state);


 


return 1;


}


};





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

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

int test2(int n, double d, const std::string &msg)


{


return 10;


}


luareg::module(state, "cpp")


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


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

template < typename R, typename ...Args >



 inline details::free_function_t<R, Args...> def(const char *name, R(*func)(Args...))




{



     return details::free_function_t<R, Args...>(name, func);




}



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


template < typename R, typename ...Args >



 struct free_function_t




{



     const char *name_;




 



     typedef R(*function_t)(Args...);



     function_t function_;




 



     free_function_t(const char *name, function_t func)



: name_(name)



, function_(func)




{}




};



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


inline module_t module(state_t &state, const char *name = nullptr)


{


if( name )


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


return module_t(state, name);


}


在module里,提供了operator<<操作符重载
template < typename R, typename ...Args >


module_t &operator<<(const details::free_function_t<R, Args...> &func)


{


auto lambda = [](lua_State *l)->int


{


state_t state(l);


typedef typename details::free_function_t<R, Args...>::function_t function_t;


auto func = static_cast<function_t>(::lua_touserdata(state, lua_upvalueindex(1)));




return details::call(state, func);


};


 


::lua_pushlightuserdata(state_, func.function_);


::lua_pushcclosure(state_, lambda, 1);


::lua_setfield(state_, -2, func.name_);


 


return *this;


}


通过把free_function_t对象的function_\name_注册到lua,这里使用了lua_pushccloure这个API,利用upvalue保存了这个注册函数的指针。这里的lambda是Lua_CFunction的原型,一旦lua调用了这个函数,就会回到这个lambda函数体中,再把函数指针取出来进行调用即可。

再来看看这个details::call
template < typename R, typename ...Args >


std::int32_t call(state_t &state, R(*handler)(Args...),


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


{


return convertion_t<R>::to(state, call_impl(state, make_obj(handler), 0));


}
template < typename R, typename ...Args >


std::int32_t call(state_t &state, R(*handler)(Args...),


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


{


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


 


return 0;


}


返回值代表返回多少个数据到lua,通过convertion来完成。这里的enable_if来决断调用的C++函数返回值是否为void,如果为void则返回0个参数到lua。

解析就写到这儿吧,至于call_impl和make_obj请大家自己看源码吧,如果有什么不明白的,请加群探讨165666547



使用示例:


1. 对lua内存定制,只需要满足allocate、deallocate接口

std::allocator<char> std_allocator;


luareg::state_t state(std_allocator);




2. 注册自由函数


luareg::module(state, "cpp")




    << lua::def("test0", &test0)




    << lua::def("test1", &test1)




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




    << lua::def("test3", &test3)




    << lua::def("test4", &test4)




    << lua::def("test5", &test5)



当然,也可以注册类的成员函数,但是并不是由lua提供的userdata作为对象指针,而是由C++保存的指针


 lua::def("test6", &t, &test_t::test6);



.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre{ margin: 0em;}
.csharpcode .rem{ color: #008000;}
.csharpcode .kwrd{ color: #0000ff;}
.csharpcode .str{ color: #006080;}
.csharpcode .op{ color: #0000c0;}
.csharpcode .preproc{ color: #cc6633;}
.csharpcode .asp{ background-color: #ffff00;}
.csharpcode .html{ color: #800000;}
.csharpcode .attr{ color: #ff0000;}
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum{ color: #606060;}




3. 注册类


luareg::module(state, "cpp")


    [


        luareg::class_t<foo_t>(state, "foo_t")


        << luareg::constructor<int>()


        << luareg::destructor()


        << luareg::def("add", &foo_t::add)


        << luareg::def("get", &foo_t::get)


        << luareg::def("get_pointer", &foo_t::get_pointer)


        << luareg::def("get_base", &foo_t::get_base)


    ]


需要注意的是constructor与destructor都不是必须的,如果没有,则采用默认。是不是很像luabind的语法呢?





4. 执行lua文件


 lua::execute(state, "test.lua");






5. 执行lua的一个函数


 try




{




lua::execute(state, "test2.lua");




std::pair<int, std::string> n = lua::call(state, "test_call", 1, "haha", 10.2, false);




 




auto val = std::make_pair("test abc", 10.2);




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




}



     catch(const luareg::fatal_error_t &e)




{




std::cout << e.what() << std::endl;




e.dump(std::cout);




}



执行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。



github:https://github.com/chenyu2202863/lua_reg



 





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