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


    背景:

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

     

    目标:

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

    目录结构:

    image

    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:         else
      16:             ::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_t
       3: {
       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)->int
       5:     {
       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: 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:         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及堆栈信息和参数信息。
    局限性:
      1. 对注册函数均已upvalue的方式来保存,限制了C++导出到lua函数个数(upvalue最大个数为255),不过,我认为这已足够,如果要导出很多接口道lua,那已经是不正常的了
      2. 与luabind比起,某些功能不支持(函数重载、导出变量等)
    后记:
    本框架大量使用C++11特性,使实现非常优雅的解决许多问题,比如lambda、variadic template、auto、decltype等等,所以,需要理解C++11,如果你有可能,请加入我们的C++11讨论群165666547
     

  • 相关阅读:
    Java中类的继承
    信号量、PV原语及其应用
    Python-subprocess执行命令并将输出劫持实现实时记录到日志
    Python-logging模块定制格式描述符实现定长日志等级
    Python-logging模块实现同时向控制台和文件打印日志
    SpringBoot学习笔记(二)
    Kubernetes学习日记(四)
    Kubernetes学习日记(三)
    SpringBoot学习笔记(一)
    Kubernetes学习日记(二)
  • 原文地址:https://www.cnblogs.com/yu-yu/p/3329611.html
Copyright © 2020-2023  润新知