本文版权归 csdn whitehack 所有,转载请自觉标明原创作者及出处,以示尊重!!
作者:whitehack
出处:http://blog.csdn.net/whitehack/article/details/6402779
[-]
函数原型
作为一种替代机制 lua stack 是通过LuaStack类提供的
LuaPlus的回调函数使用了一种简单的函数机制 可以让全局函数 静态函数 非虚成员函数 与虚成员函数 成为回调函数
下面是一个示例 例子很简单就不注释了 (唯一需要注意的是 LuaStack args(state);)
使用 Register() 函数注册回调函数.
LuaObject 提供了几种重载的 Register() 函数:
虽然Register() 可以注册 c++类成员函数 但是他的第二个参数需要提供一个类对象
但是内部调用成员函数时候 this指针是一个常量 所以 Register()函数不是适合镜像c++类到lua中
this指针的问题 是通过 RegisterObjectFunctor() 函数解决的
官方文档原文
Even though Register()
can dispatch to C++ member functions, it uses a 'this' pointer as provided by the second argument passed to the function. The 'this' pointer is constant, andRegister()
is not suited for mirroring class hierarchies in Lua.
The solution to the 'this' pointer issue is through RegisterObjectFunctor()
. It is a specialized form ofRegister()
where a 'this' pointer isn't provided during the closure registration. Instead, it is retrieved from either the calling userdata or the calling table's__object
member, which must be a full or light userdata.
下面 是实现2个c++类到lua的例子
LuaPlus通过RegisterDirect() 可以直接注册c++函数
以这种方式注册的函数 函数可以是任何形式的 内部会自动进行函数参数的类型检查
如果lua调用函数 传入的参数无效 则触发 luaL_argassert 导致调用失败
例如 state->DoString("print(Add(10, 'Hello'))"); 这样就导致失败了
全局函数可以这样注册 而成员函数也同样可以这样注册
直接注册的函数在目前版本中最多只支持7个参数
官方文档原文
Even though RegisterDirect()
can dispatch directly to C++ member functions, it uses a 'this' pointer as provided by the second argument passed to the function. The 'this' pointer is constant, andRegisterDirect()
is not suited for mirroring class hierarchies in Lua.
The solution to the 'this' pointer issue is through RegisterObjectDirect()
. It is a specialized form of RegisterDirect() where a 'this' pointer isn't provided during the closure registration. Instead, it is retrieved from either the calling userdata or the calling table's __object
member, which must be a full or light userdata. The techniques presented in this section mirror closely theRegisterObjectFunctor()
description above.
使用上一个注册c++对象的那个示例
向metatable内直接注册普通的c++成员函数
测试
注销回调非常简单 只需将其设置为nil
globalsObj.SetNil("LOG");
注册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);第一行将类型为function 的值入栈, 第二行将
function 赋值给全局变量mysin
注册任意类型的C函数:
如果要向lua注册一个非lua_CFunction类型的函数,需要:
1. 为该函数实现一个封装调用。
2. 在封装调用函数中从lua栈中取得提供的参数。
3. 使用参数调用该函数。
4. 向lua传递其结果。
首先必须有一个LUA规定类型的C函数,例如:
注意这里有个typename Func,是函数的类型,稍后会讲这个的作用
然后必须在这个函数中调用真正的C函数,这个函数通过栈来传递,LUA中提供了传递用户数据的接口
用户数据
Lua提供了一个函数可以存储用户数据:
LUA_API void * lua_newuserdata (lua_State *L, size_t size)在适当的时刻,我们可以通过这个函数再取出这个数据:这样我们可以在注册C++函数时,把这个函数指针当作用户数据压栈,然后在调用TempCallFun时把这个函数取出
LUA_API void * lua_touserdata (lua_State *L, int idx)这里有个关键就是在调用时必须得到正确的参数类型和个数,以正确调用函数并向LUA传递结果,在网上流传的LUA的C++封装中,实现这一功能都是用模板,在TempCallFun中,可以这样调用从栈中取出的函数指针:
buffer = (unsigned char*)lua_touserdata(L,lua_upvalueindex1));//取出用户数据注意这个Func就是我们要调用的C++的函数类型,也就是上面说的要把函数指针类型传进来的目的
接下来是Call的其中两个定义
template <typename RT>
假如有一个 int Test(int a)的C++函数,那么在调用时,就会转到int Call(RT (*func)(P1), lua_State* L, int index)里面,这样我们就可以在这个函数具体处理有一个参数的C++函数的情况,因为参数类型也已经通过模板传进来了,所以可以继续通过模板来取得把栈中的参数转为正确的类型以供C++函数调用,这里有个技巧是封装栈操作:
这里的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中,这个类是:
与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 相关的所有资源。
可以设置此事件的元方法来析构类
注册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);第一行将类型为function 的值入栈, 第二行将
function 赋值给全局变量mysin
注册任意类型的C函数:
如果要向lua注册一个非lua_CFunction类型的函数,需要:
1. 为该函数实现一个封装调用。
2. 在封装调用函数中从lua栈中取得提供的参数。
3. 使用参数调用该函数。
4. 向lua传递其结果。
首先必须有一个LUA规定类型的C函数,例如:
注意这里有个typename Func,是函数的类型,稍后会讲这个的作用
然后必须在这个函数中调用真正的C函数,这个函数通过栈来传递,LUA中提供了传递用户数据的接口
用户数据
Lua提供了一个函数可以存储用户数据:
LUA_API void * lua_newuserdata (lua_State *L, size_t size)在适当的时刻,我们可以通过这个函数再取出这个数据:这样我们可以在注册C++函数时,把这个函数指针当作用户数据压栈,然后在调用TempCallFun时把这个函数取出
LUA_API void * lua_touserdata (lua_State *L, int idx)这里有个关键就是在调用时必须得到正确的参数类型和个数,以正确调用函数并向LUA传递结果,在网上流传的LUA的C++封装中,实现这一功能都是用模板,在TempCallFun中,可以这样调用从栈中取出的函数指针:
buffer = (unsigned char*)lua_touserdata(L,lua_upvalueindex1));//取出用户数据注意这个Func就是我们要调用的C++的函数类型,也就是上面说的要把函数指针类型传进来的目的
接下来是Call的其中两个定义
template <typename RT>
假如有一个 int Test(int a)的C++函数,那么在调用时,就会转到int Call(RT (*func)(P1), lua_State* L, int index)里面,这样我们就可以在这个函数具体处理有一个参数的C++函数的情况,因为参数类型也已经通过模板传进来了,所以可以继续通过模板来取得把栈中的参数转为正确的类型以供C++函数调用,这里有个技巧是封装栈操作:
这里的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中,这个类是:
与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 相关的所有资源。
可以设置此事件的元方法来析构类