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]


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章