Lua注册C++类及函数

本文版权归 csdn whitehack 所有,转载请自觉标明原创作者及出处,以示尊重!!

作者:whitehack

出处:http://blog.csdn.net/whitehack/article/details/6402779

[-]

  1. Registering Callbacks(注册lua c函数)
  2. Registering Object Dispatch Functors(注册一个c++类到lua)
  3. Registering Functions Directly(直接注册普通函数)
  4. Object Dispatch to Directly Called C++ Member Functions(注册普通c++成员函数)
  5. Unregistering Callbacks(撤销已经注册到lua内的对象)

 

Registering Callbacks(注册lua c函数)

函数原型

 

 

作为一种替代机制 lua stack 是通过LuaStack类提供的

LuaPlus的回调函数使用了一种简单的函数机制  可以让全局函数 静态函数 非虚成员函数 与虚成员函数 成为回调函数

 

下面是一个示例 例子很简单就不注释了 (唯一需要注意的是  LuaStack args(state);)

 

 

使用 Register() 函数注册回调函数.

LuaObject 提供了几种重载的 Register() 函数:

 

 

 

Registering Object Dispatch Functors(注册一个c++类到lua)

虽然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的例子

 

 

 

上面的obj1 和 obj2 是作为  userdata 然后设置它的metatables 的方法创建的 
另一种方法是将代表的c++对象的tuserdata作为表的成员 __object
 

 

 

Registering Functions Directly(直接注册普通函数)

LuaPlus通过RegisterDirect() 可以直接注册c++函数

 

以这种方式注册的函数  函数可以是任何形式的 内部会自动进行函数参数的类型检查

如果lua调用函数 传入的参数无效 则触发 luaL_argassert  导致调用失败

例如 state->DoString("print(Add(10, 'Hello'))"); 这样就导致失败了

 

全局函数可以这样注册  而成员函数也同样可以这样注册

 

直接注册的函数在目前版本中最多只支持7个参数

 

 

 

Object Dispatch to Directly Called C++ Member Functions(注册普通c++成员函数)

官方文档原文


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++成员函数

 

测试

 

 

 

Unregistering Callbacks(撤销已经注册到lua内的对象)

注销回调非常简单 只需将其设置为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);
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)

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

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

这里有个关键就是在调用时必须得到正确的参数类型和个数,以正确调用函数并向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++函数调用,这里有个技巧是封装栈操作:
这里的TypeWrapper<typename T>只是为了传递栈中的参数类型

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)

定义所有类型可能的类型的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
{
     user(void* p) : m_p(p) {}

  virtual ~user() {}

   void* m_p;

};

template<typename T>
struct val2user : user
{
      val2user() : user(new T) {} //构造函数没有参数的类

   template<typename T1>      //构造函数有一个参数的类
  
      val2user(T1 t1) : user(new T(t1)) {}

//以此类推。。。。。。。

   ~val2user() { delete ((T*)m_p); }

};

与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);
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)

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

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

这里有个关键就是在调用时必须得到正确的参数类型和个数,以正确调用函数并向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++函数调用,这里有个技巧是封装栈操作:
这里的TypeWrapper<typename T>只是为了传递栈中的参数类型

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)

定义所有类型可能的类型的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
{
     user(void* p) : m_p(p) {}

  virtual ~user() {}

   void* m_p;

};

template<typename T>
struct val2user : user
{
      val2user() : user(new T) {} //构造函数没有参数的类

   template<typename T1>      //构造函数有一个参数的类
  
      val2user(T1 t1) : user(new T(t1)) {}

//以此类推。。。。。。。

   ~val2user() { delete ((T*)m_p); }

};

与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 相关的所有资源。


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

 
 
 
 
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值