• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

Lua的C++封装

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

前段时间由于公司项目需要,做了LUA的C++封装,为此看了LuaPlus(感觉过于庞大,挺混乱的..)跟LuaTinker(一个韩国人写的,只有两个代码文件,实现了大多数需要的功能)的代码,在实现LUA与C++的交互中最重要的功能莫过于实现在LUA中注册任意类型的C++函数和类,现将自己所得到的一些方法简单说下,如有不对的地方还请各位多多指正
  

注册C++函数

当Lua 调用C 函数的时候, 使用和C 调用Lua 相同类型的栈来交互。C 函数从栈中获取她的参数, 调用结束后将返回结果放到栈中。为了区分返回结果和栈中的其他的值, 每个C函数还会返回结果的个数 。这儿有一个重要的概念:用来交互的栈不是全局变量, 每一个函数都有他自己的私有栈。当Lua 调用C 函数的时候,第一个参数总是在这个私有栈的index=1 的位置

LUA中可注册的C函数类型

任何在Lua 中注册的函数必须有同样的原型,这个原型声明定义就是lua.h 中的

lua_CFunction :typedef int (*lua_CFunction) (lua_State *L);

 例子

lua_pushcfunction(l, l_sin);

lua_setglobal(l, "mysin");

第一行将类型为function 的值入栈, 第二行将

function 赋值给全局变量mysin

 
注册任意类型的C函数:

如果要向lua注册一个非lua_CFunction类型的函数,需要:
1. 为该函数实现一个封装调用。
2. 在封装调用函数中从lua栈中取得提供的参数。
3. 使用参数调用该函数。
4. 向lua传递其结果。

 
首先必须有一个LUA规定类型的C函数,例如:

template<typename Func>
int TempCallFun(lua_State* L)

注意这里有个typename Func,是函数的类型,稍后会讲这个的作用

 
然后必须在这个函数中调用真正的C函数,这个函数通过栈来传递,LUA中提供了传递用户数据的接口

用户数据

Lua提供了一个函数可以存储用户数据:

LUA_API  void * lua_newuserdata (lua_State *L, size_t size)

 

在适当的时刻,我们可以通过这个函数再取出这个数据:

LUA_API  void *     lua_touserdata (lua_State *L, int idx)

这样我们可以在注册C++函数时,把这个函数指针当作用户数据压栈,然后在调用TempCallFun时把这个函数取出

这里有个关键就是在调用时必须得到正确的参数类型和个数,以正确调用函数并向LUA传递结果,在网上流传的LUA的C++封装中,实现这一功能都是用模板,在TempCallFun中,可以这样调用从栈中取出的函数指针:

buffer = (unsigned char*)lua_touserdata(L,lua_upvalueindex1));//取出用户数据

return Call((*(Func*)buffer),L,1);//调用

注意这个Func就是我们要调用的C++的函数类型,也就是上面说的要把函数指针类型传进来的目的

接下来是Call的其中两个定义

template <typename RT>
int Call(RT (*func)(), lua_State*  L, int index)//匹配没有参数的C++函数
{
     return ReturnType<RT>::Call(func, L, index);
}

template <typename RT, typename P1>
int Call(RT (*func)(P1), lua_State*  L, int index)//匹配有一个参数的C++函数
{
     return ReturnType<RT>::Call(func, L, index);
}


假如有一个 int Test(int a)的C++函数,那么在调用时,就会转到int Call(RT (*func)(P1), lua_State*  L, int index)里面,这样我们就可以在这个函数具体处理有一个参数的C++函数的情况,因为参数类型也已经通过模板传进来了,所以可以继续通过模板来取得把栈中的参数转为正确的类型以供C++函数调用,这里有个技巧是封装栈操作:

template<class T> struct TypeWrapper {};

inline char             Get(TypeWrapper<char>, lua_State*  L, int idx)

inline short            Get(TypeWrapper<short>, lua_State*  L, int idx)


这里的TypeWrapper<typename T>只是为了传递栈中的参数类型

定义所有类型可能的类型的Get函数,就能方便的取得栈中的元素了,在上面的ReturnType<RT>::Call(func, L, index)里面,可以这样调用真正的C++函数,

RT  ReturnVal = (*func)(Get(TypeWrapper<P1>(), L, index + 0))


最后把返回值压栈传给LUA,这样就实现了任意C++函数类型的注册。 注册C++类的成员函数方法一样,只是要把这个类的某个实例也当作用户数据压栈

 
注册C++类

实现这个要比较复杂,因为LUA并不支持面向对象的特性,要实现这个必须通过一些技巧扩展,LUA中的表就是实现这个功能的媒介,也就是用表模拟C++中类的行为,具体实现方法就不详细说了,大家可以去看LuaTinker的代码,这里只说一下要点

表其实就是一种数据元素的集合,每个元素都有一个索引,用户可通过索引来访问表里的元素

 要注册类,关键要做到两点

1、  LUA中的表跟C++中的类的关联,也就是在LUA中构造一个表相应在C++中也必须构造一个类

2、  表中元素跟类中的元素的映射,以得到LUA中的表跟C++中的类的行为的一致性


因为类是自己定义的类型,要实现一个通用的注册类的功能的话,还必须对传递给LUA中的类做一个封装,在LuaTinker中,这个类是:

struct user

与LUA中的表关联的只是这个val2user,构造一个表就构造一个val2user,在val2user中再构造具体的类

下面是几个在LUA中预定义的事件

The __call Metamethod

这是在创建一个表的时候会触发的事件,可以通过在此事件的元方法中调用类的构造函数,以达到在LUA中创建元表的同时在C++中创建类

 
LUA中的表有几个比较重要的预定义的错误行为的事件

The __index Metamethod

当我们访问一个表的不存在的域, 返回结果为nil , 这是正确的, 但并不一定正确。实际上, 这种访问触发lua 解释器去查找__index metamethod : 如果不存在, 返回结果为nil ,如果存在则由__index metamethod 返回结果。


The  __newindex metamethod

用来对表更新, __index 则用来对表访问。当你给表的一个缺少的域赋值,解释器就会查找__newindex metamethod : 如果存在则调用这个函数而不进行赋值操作。像__index 一样, 如果metamethod 是一个表,解释器对指定的那个表, 而不是原始的表进行赋值操作。

可以通过定义这两个特性的元方法来实现对类中变量的访问和设置,因为userdata是没有元素的,所以访问时一定会触发__index,_newindex元方法,通过设置此元方法既可实现对类以及其基类中变量的访问

The  __gc Metamethod

这个元方法只对userdata 类型的值有效。当一个userdatum 将被收集的时候, 并且usedatum 有一个__gc 域, Lua 会调用这个域的值( 应该是一个函数):以userdatum作为这个函数的参数调用。这个函数负责释放与userdatum 相关的所有资源。

 
可以设置此事件的元方法来析构类


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
11.C++调用Lua全局变量(表)发布时间:2022-07-22
下一篇:
如何做dragonbones的lua绑定(xcode)发布时间:2022-07-22
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap