Lua封装&C++实践——Lua注册C++构造函数
Posted 湖广午王
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Lua封装&C++实践——Lua注册C++构造函数相关的知识,希望对你有一定的参考价值。
一个std::tuple<int,float,std::string>
这样的结构,如何传递给int call(int,float ,std::string)
这样的函数作为参数?如何根据函数的指针,知道这个函数的参数列表?
在后面,Lua注册C++,如果希望调用尽可能简单,可能需要这样的功能了(不需要也假装需要,然后去用一下,这么好玩的东西,研究以下总是不亏的)。
Lua注册C++类的接口
对于Lua注册C++,最理想的情况,肯定是传入一个类,我就把整个类的public方法和属性都注册好,但是我想了几天,网上查了很久,也没有发现有这样的途径(写个工具检查代码然后自动注册的想法就算了吧,不符合初衷),所以先按照能想到的办法和能找到的办法做吧。
//这个是理想中的调用方法,注册后,自己能找到所有的public方法和构造函数做注册
//对于Java来说是件小事,对于C++来说,还是先放放吧
state->register_class<Clazz>();
//麻烦点的办法,注册构造函数,注册成员方法的后面再继续
state->register_class<Clazz,type1,type2,type3>(name);
使用时,现在C++调用注册,然后执行Lua,在Lua中可以直接像C++创建对象一样,创建table。
class TestParam
public:
TestParam(const char * hello, int pos)
std::cout<<hello<<":" << pos <<std::endl;
;
TestParam(double key)
std::cout<<"create testParam : " << key <<std::endl;
;
~TestParam()
std::cout<<"TestParam Gc"<<std::endl;
void testParam(int i,int b,char * c);
;
void testRegisterCplusplusClazz()
wLua::State * state = wLua::State::create();
state->register_class<TestParam,const char *, int>("TestParam");
state->dofile("../res/test4.lua");
delete state;
Lua代码:
tp = TestParam("Hello World! I am Lua, registed by wLua.", 13)
tp2 = TestParam("Hello World! I am Lua2 registed by wLua.", 18)
print("test 4 .lua exec finished")
具体实现
在Lua封装&C++实践(一)——Lua和C/C++的基本交互中已经成功的在Lua中使用起了C++的类。这里只是做一个封装,让注册变的更简单。注册实现大致如下:
template <typename Clazz,typename ... Params>
void State::register_class(const char *name)
lua_pushcfunction(l,[](lua_State * l) -> int
//先把参数取出来,后面的操作会导致堆栈变化
std::tuple<Params...> luaRet;
TupleTraversal<std::tuple<Params...>>::traversal(luaRet, l);
auto ** pData = (Clazz**)lua_newuserdata(l, sizeof(Clazz*));
*pData = createClazzWithTuple<Clazz>(luaRet);
luaL_getmetatable(l, typeid(Clazz).name());
//此时new出来的userdata索引为-1,metatable索引为-2
//这里就是把-1的metatable设置给-2位置的userdata
lua_setmetatable(l, -2);
return 1;
);
lua_setglobal(l,name);
luaL_newmetatable(l, typeid(Clazz).name());
lua_pushstring(l,"__gc");
lua_pushcfunction(l,[](lua_State * l)-> int
std::cout << "Gc Called" << std::endl;
delete *(Clazz**)lua_topointer(l, 1);
return 0;
);
lua_settable(l, -3);
lua_pushstring(l, "__index");
lua_pushcfunction(l,[](lua_State * l)-> int
return 0;
);
lua_settable(l,-3);
关于Lua api的使用,可以直接看Lua的官方文档,主要是去理解下Lua和宿主程序的交互原理,知道它的堆栈是个什么样子,使用起来就比较简单了。
Lua中调用C++的类构造函数,实际上对于Lua来说,它也不知道你是个C++还是C,它是面向C设计的,C++的构造函数最后也会封装成C函数来调用,在上面可以看到,是用了一个无捕获的lambda来替代了一个lua_function的实现,加入到了全局栈。
在Lua调用TestParam("Hello World! I am Lua, registed by wLua.", 13)
时候,会调用到指定名为TestParam的function,两个参数也会按照从左到右的顺序加入到栈中,所以直接按照上一篇博客中的方法把栈中的参数都弹出到tuple中即可。需要注意顺序,弹出参数到tuple要在其他lua操作之前,比如lua_newuserdata
。因为其他的操作可能会改变堆栈的状态。 使参数的位置发生的变换,导致使用pop到处的参数错误。
std::tuple解包传递给函数作为参数
另外一个稍微麻烦的地方就是,我们把参数从栈里面弹出到tuple后,怎么传递给构造函数或者其他函数来作为参数使用?这部分在stl源码中有相关实现,下面是做了一点修改的实现,通过createClazzWithTuple<Clazz>(tp)
直接构造出对象。
//Num 为tuple参数个数时,Tuple的Index为0,Num推导到0的时候,Tuple的Index为0,1,...Num-1
//最后有个Num = 0的偏特化,Index就是0,1,...Num-1。后面要使用的就是这个Index
template< size_t... _Indexes >
struct IndexTuple;
template< std::size_t _Num, typename _Tuple = IndexTuple<> >
struct Indexes;
template< std::size_t _Num, size_t... _Indexes >
struct Indexes<_Num, IndexTuple< _Indexes... > >
: Indexes< _Num - 1, IndexTuple< _Indexes..., sizeof...(_Indexes) > >
;
template<size_t... _Indexes >
struct Indexes< 0, IndexTuple< _Indexes... > >
typedef IndexTuple< _Indexes... > __type;
;
//函数传入Tuple作为参数的调用
template<typename Tuple,typename Func, size_t... _Ind>
typename get_<Func>::retType __callFuncWithTupleParam(Tuple tp,IndexTuple< _Ind... >,Func func)
return func(std::get< _Ind >(tp)...);
template<typename Tuple,typename Func>
typename get_<Func>::retType callFuncWithTupleParam(Tuple tp, Func func)
return __callFuncWithTupleParam(tp,typename Indexes<std::tuple_size<Tuple>::value>::__type(),func);
template<typename Clazz, typename Tuple, size_t... _Ind>
Clazz * __createClazzWithTuple(Tuple& tp,IndexTuple< _Ind... >)
return new Clazz(std::get<_Ind>(tp)...);
template<typename Clazz,typename Tuple>
Clazz * createClazzWithTuple(Tuple& tp)
return __createClazzWithTuple<Clazz>(tp,typename Indexes<std::tuple_size<Tuple>::value>::__type());
里面重点其实就是通过模板,用传入的std::tuple推导出一个0,1,2,3,...N-1
的序列Ind,如上面代码段最上方注释那样。然后通过std::get<Ind>(tp)...
来解包为参数序列传递给函数。
根据函数指针获取参数个数
在这个过程,也看到了另外一个比较有意思的模板操作,就是通过函数指针获取参数的个数。通过编译的自动推导,来根据传入的是普通函数指针,还是成员函数指针,来做特化,获取参数个数。如下:
//获取函数、成员函数的参数个数及列表
template<typename Sig>
struct get_
;
template<typename R,typename... Args>
struct get_<R(*)(Args...)>
static size_t const value = sizeof...(Args);
using func = R(*)(Args...);
typedef R retType;
;
template<typename Clazz,typename R,typename... Args>
struct get_<R(Clazz::*)(Args...)>
static size_t const value = sizeof...(Args);
typedef R(*func)(Args...);
typedef R retType;
;
template<typename Sig>
inline size_t getParamSize(Sig)
return get_<Sig>::value;
其他
笔记相关的代码在Github上,代码会不断变动,有需要的可以直接看对应的提交。此博客仅作为个人学习笔记及有兴趣的朋友参考使用,虚心接受建议与指正,不接受吐槽和批评,引用设计思想或代码希望注明出处,欢迎Fork和Star。wLuaBind代码地址
欢迎转载,转载请保留文章出处。湖广午王的博客[http://blog.csdn.net/junzia/article/details/95928467]
以上是关于Lua封装&C++实践——Lua注册C++构造函数的主要内容,如果未能解决你的问题,请参考以下文章