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]


©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页