又一个lua与C++粘合层框架
2013-09-19 11:49
501 查看
背景:
目标:
独立,无需第三方库依赖
小巧,仅提供大多数场景的功能需求
易用,接口简单明确
方便,提供完备的错误信息
目录结构:
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函数,支持错误处理
实现机制:
2. C++函数与lua函数对应关系
当需要把C++函数注册到lua,如
在lua::def函数里,首先会推导test2的函数签名
在module里,提供了operator<<操作符重载
通过把free_function_t对象的function_\name_注册到lua,这里使用了lua_pushccloure这个API,利用upvalue保存了这个注册函数的指针。这里的lambda是Lua_CFunction的原型,一旦lua调用了这个函数,就会回到这个lambda函数体中,再把函数指针取出来进行调用即可。
再来看看这个details::call
返回值代表返回多少个数据到lua,通过convertion来完成。这里的enable_if来决断调用的C++函数返回值是否为void,如果为void则返回0个参数到lua。
解析就写到这儿吧,至于call_impl和make_obj请大家自己看源码吧,如果有什么不明白的,请加群探讨165666547
使用示例:
1. 对lua内存定制,只需要满足allocate、deallocate接口
2. 注册自由函数
.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;}
这是之前那篇烂文章的一个扩展吧!在游戏领域,特别多的使用到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++参数转换
当需要对lua与C++参数进行转换时,请考虑片特化此类,如void*对应lightuserdata
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
相关文章推荐
- 又一个lua与C++粘合层框架
- lua与C++粘合层框架
- 发现一个nginx LUA开发Web App的框架
- 一个C++调用lua文件操作的工具软件
- 如何创建一个简单的C++同步锁框架(译)
- 介绍一个轻量级的C++ Unit test 框架: TUT
- c++作为底层框架和lua进行打通
- C++ 自己实现的一个testSuite 框架
- lua笔记 --- 一个C++调用lua函数的类的实现
- 一个无聊男人的疯狂《数据结构与算法分析-C++描述》学习笔记 用C++/lua/python/bash的四重实现(3) 最大子序列和问题
- RCF:一个相当不错的C++分布式RPC框架
- 初学lua --lua嵌入c++的一个问题(初始化lua出错,版本问题)
- C++多线程框架(一)--------- new一下就启动一个线程
- 推荐:一个写的相当好的介绍C++单元测试框架Google Test (gtest) 教程
- 一个无聊男人的疯狂《数据结构与算法分析-C++描述》学习笔记 用C++/lua/python/bash的四重实现(7)习题2.8 随机数组的三种生成算法
- 一个使用c++在lua中创建自定义数据类型的简易方法
- 函数编译lua笔记 --- 一个C++调用lua函数的类的实现
- [原][译][lua][luabridge]一个简单的luabridge与c++例子结合例子
- Lua与C/C++之我最初对于luaL_loadfile的一个误解
- c++ lua服务器框架