Luaパッケージ&C++実践(二)——C++がLua関数を呼び出すパッケージ

33845 ワード

前編ブログでは、LuaとC/C++の基本的なインタラクションを記録していましたが、そのように使えば、本当に面倒なので、パッケージ化を始めました.このブログでは主にC++呼び出しLua関数のパッケージを記録しています.
パッケージングターゲット
C++呼び出しLuaは,複雑な点は主にLuaのスタックを理解する必要があり,関数,パラメータともにスタックに順次追加する必要があり,結果としてスタックから取る必要があり,Luaは複数の値を返すことをサポートし,値を取るにはスタック内の順序で複数回取る必要がある.実際には、lua関数を呼び出し、関数の実行結果を返し、C++11でサポートされているコードで呼び出し形式を表す必要があります.
//      ,ret       
auto ret = lua::call<ret1,ret2,...>("funcName",param1,param2,param3,...);

最終的な実際の呼び出し例は次のとおりです.
function sayHello(name, age, ismale, after)
    if ismale then
        sex = 'Mr'
    else
        sex = 'Ms'
    end
    print("call func sayHello")
    return string.format("Hello, %s %s, after %d years , your are %d years old!", sex, name, after, after+age), after+age, name, ismale
end

function sayHelloToWorld()

    return "Hello Wolrd!"
end

function sayHelloInLua()
    print("Hello World, I'm Lua!")
end

C++呼び出し例
void test()
    wLua::State * state = wLua::State::create();
    state->dofile("../res/test3.lua");
    //    ,     
    auto ret = state->call<char *,int,char*,bool>("sayHello","LiMing", 21, true, 10);
    //   ,     
    auto ret2 = state->call<char *>("sayHelloToWorld");
    //   ,    
    auto ret3 = state->call("sayHelloInLua");
    //       
    TupleTraversal<decltype(ret)>::traversal(ret);
    TupleTraversal<decltype(ret2)>::traversal(ret2);
    TupleTraversal<decltype(ret3)>::traversal(ret3);
    
    delete state;
}

実装ポリシー
上記のパッケージングターゲットに従って、まず、戻り値には戻り結果が含まれる必要がありますが、戻り結果は1つでも2つ以上でも、戻り値がない場合もありますが、C++はサポートされていない関数によって多く返されます.pythonerなら簡単にtupleを思い浮かべることができます.C++11もtupleをサポートしています.これにより、vector、mapなどの他の方法ではなく、tupleを直接使用して結果をパッケージすることができます.
また、上記の汎用呼び出し式から見ると、関数が返すデータ型(同じ関数、異なるタイプ、異なる戻り個数などを返す)を指定するためにテンプレートを使用する必要があることは明らかです.
また,我々が呼び出したLuaメソッドでは,入力パラメータと戻り値のタイプと個数は知られておらず,呼び出し者のみが指定する,すなわち入力と出力はいずれも変長テンプレートに用いられる.
具体的な実装
呼び出す時、実はステータスマシンに注目したくなくて、私はただ1つの方法を使って、1つの結果を得たいだけです.だからパッケージの时にステータスマシンも遮断して、必要な时にまた开放します.
最後に明らかになったAPIは、
namespace wLua{
	class State{
	public:
		static State * create(LuaLib type = eLL_all);

        ~State();

        int dostring(std::string lua);

        int dofile(std::string path);

        template <typename ... Args,typename ... Params>
        std::tuple<int,Args...> call(std::string name,Params ... p);
	}
}

その中で、私たちが最も重要なのはcall方法を実現することです.コードは以下の通りです.callメソッドでは、次のようにします.
  • 呼び出し関数を取得し、スタックの上部に配置します.
  • パラメータを順次スタックに押し込む.
  • luaに関数
  • を実行させる
  • スタックから取得する戻り値は、C++に返される.

  • キーは主に3つあります.
  • 変長パラメータテンプレート減算処理
  • 不定長不定タイプのtupleのタイプ取得と付与
  • テンプレート特化
  • template <typename ... Args,typename ... Params>
    std::tuple<int,Args...> State::call(std::string name,Params ... p){
        int ret = lua_getglobal(l, name.c_str());
        push(p...);
        int retSize = sizeof...(Args);
        lua_call(l, sizeof...(Params), retSize);
        std::tuple<int,Args...> luaRet;
        TupleTraversal<std::tuple<int,Args...>>::traversal(luaRet, this, ret);
        return luaRet;
    }
    

    最終インプリメンテーションは、プロジェクトコードを直接見ることができます.ここでは主にこの過程で出会った問題を記録する.主にやはりC++初心者なので、C++を使って間もなく、関連知識の掌握に対して熟練していないで、熟練した手と達人は直接省略することができます.
    テンプレート内のタイプ判定とタイプ変換
    以下のコードはpushの実装であり,文字列の特化に関するコードの一部が貼られていない.最初の考えは,メソッドでtypeidでタイプを判断し,luaのapiを異なるタイプに基づいてスタックした.しかしこの時問題が発生し,もともとstatic_castreinterpret_castの方式で変換されており,コンパイルができない.typeidの判断は実行時判断であり,タイプ変換とテンプレート導出はコンパイル期間中に確定しなければならないことを考えると当然過ぎない(呼び出しと実装は別々にコンパイルできるだろうか?)コンパイルすると、すべてのTがターゲットタイプに変換できるかどうかをチェックし、文字列は上の方法でintを回転できないので、エラーを報告します.typeidはそれが実行期間のことだと判断し、コンパイラはif elseのために入らないことはありません.だから、後で別の方法を考えてこの問題を処理しました.主な2つの方法は、実は似ています.
  • アドレスを介してvoid*を先に回し、ターゲットタイプのポインタを回し、使用時にポインタでデータを取り出す.
  • ターゲットタイプとtypeidが判断したタイプのデータ長に基づいて、データコピーを行う.

  • 1つ目の方法は、メモリの消費量が小さく、メモリの消費量が回転すると問題が発生します.例えばTはfloat、ターゲットタイプはlua_Numberつまりdoubleは、floatの後ろのメモリの内容をfloatと一緒に誤ったデータにします.だから最後に2つ目の方法を使いました.
    しかし、2つ目の方法は、文字列に遭遇すると問題があり、入力されたのはchar*であり、コピーが必要なデータの長さは確定しません.strlenなんて使えないし、コンパイルできないし、原因は前と同じです.だからこの时はテンプレートを特化する必要があります.std::string、char*を直接テンプレートを特化します.userdataは今しばらく管理していないので、後で追加します.
    template <typename T>
    void State::push(T& t){
        const std::type_info &tid = typeid(t);
        std::cout  <<tid.name() << std::endl;
        //typeid      ,           ,         ,       
        if(tid.__is_pointer_p()){
            std::cout<<"t is pointer"<<std::endl;
        }else{
            if(tid == typeid(nullptr)){
                lua_pushnil(l);
                std::cout << "push nil" << std::endl;
            }else if(tid == typeid(int)
                || tid == typeid(long)
                || tid == typeid(long long)
                || tid == typeid(unsigned int)
                || tid == typeid(unsigned long)
                || tid == typeid(unsigned long long)
                || tid == typeid(short)
                || tid == typeid(unsigned short)){
                lua_Integer ans = 0;
                memcpy(&ans, (void *)&t, sizeof(t));
                lua_pushinteger(l, ans);
                std::cout << "push integer" << t << "," << ans << std::endl;
            }
        }
    }
    
    template <typename T,typename ... Params>
    void State::push(T& t,Params ... p){
        push(t);
        push(p...);
    }
    

    テンプレートクラスの特化とTupleの遍歴
    stdライブラリはTuple内の要素の個数を取得する方法を提供しているが、テンプレートパラメータが実行時の変数を使用できないため、forサイクルでstd::get(tuple)で対応する位置の値を取得することはできない.std::getのテンプレートパラメータを入力します.コンパイル期間定数でなければなりません.この時、私たちは子の矛で子の盾を攻め、テンプレートでテンプレート問題を解決する必要があります.次のコードは、Tuple遍歴賦値の実装です.閲覧の便宜上、callメソッドの実装も同様に以下に貼り付けました.
    callの戻り値の1つ目はintであり,lua関数の取得結果を表し,取得に失敗した場合は処理されない.ここで、最初の値をintとすることは、実際にはcallの戻り値を一定に制限することである.最初はこのintがなかったのですが、luaの戻りパラメータのない関数を呼び出すとコンパイルの問題が発生します.Tupleはvoidタイプをサポートしていないので、他のタイプに直接戻るので、callメソッドは特化しても実現しにくいので、別のテンプレート関数を構築する必要があるかもしれません.トップにintを付けて、Tupleに要素があることを保証すれば、この問題を避けることができます.
    遍歴の実装では,クラスの偏特化が用いられ,その後,Stateをパラメータとして伝達して遍歴中の処理を行うが,直接Stateでテンプレート関数で行うのではなく,C++11ではテンプレート関数が偏特化をサポートせず,テンプレートクラスのみがサポートするためである.
    遍歴して、後から先へTuple要素の賦値を行い、最後の要素の時、つまり特化したもので、lua関数が取得した結果を賦値して、関数の取得に失敗して、後続の呼び出しは終了すべきで、この暫定的に処理していないので、後で処理します.
    template <typename ... Args,typename ... Params>
    std::tuple<int,Args...> State::call(std::string name,Params ... p){
        int ret = lua_getglobal(l, name.c_str());
        push(p...);
        int retSize = sizeof...(Args);
        lua_call(l, sizeof...(Params), retSize);
        std::tuple<int,Args...> luaRet;
        TupleTraversal<std::tuple<int,Args...>>::traversal(luaRet, this, ret);
        return luaRet;
    }
    
    template <typename Tuple,size_t N = std::tuple_size<Tuple>::value>
    class TupleTraversal{
    public:
        static void traversal(Tuple& tuple,State * state,int ret){
            using type = typename std::tuple_element<N - 1, Tuple>::type;
            std::get<N-1>(tuple) = state->pop<type>();
            TupleTraversal<Tuple, N - 1>::traversal(tuple, state, ret);
        }
    };
    
    template <typename Tuple>
    class TupleTraversal<Tuple,1>{
    public:
        static void traversal(Tuple& tuple, State * state,int ret){
            using type = typename std::tuple_element<0, Tuple>::type;
            std::get<0>(tuple) = ret;
        }
    };
    
    

    上記の作業後,C++がLua関数を呼び出すのは比較的簡単である.luaの変数、tableなどを取得してから改善します.
    その他
    メモ関連のコードはGithub上ではコードが変動し続け、必要なものがあれば対応する提出を直接見ることができます.このブログは個人学習ノートや興味のある友人の参考としてのみ使用され、謙虚にアドバイスや指摘を受け、ツッコミや批判を受けず、デザイン思想やコードを引用して出典を明記したいので、ForkとStarを歓迎します.wLuaBindコードアドレス
    転載を歓迎します.転載は文章の出所を残してください.湖広午王のブログ[http://blog.csdn.net/junzia/article/details/95029880]