簡體   English   中英

C ++用可變參數調用lua函數

[英]C++ call lua function with variable parameters

好的,所以這可能不是最好的設計決定,我真的不想使用像LuaBind這樣的東西...我只是好奇如果在C ++ 03中可以使用以下內容(C ++ 11可以使用可變參數模板)。 此外,我確信之前已經問過,但我找不到直接的答案!

假設我有一個幫助方法從代碼調用Lua函數:

void CallFunction(char* functionName, ...);

它可能接受N個args(使用va_arg或多個args的任何其他方法)

如果可能的話,我怎樣才能計算出每個參數的類型,並將其傳遞給相應的lua_push {type}(); 在調用所需的lua函數之前的函數?

我不確定這是否可以用var_arg完成,因為你在獲取參數時必須知道類型,我試圖用void *抓住它並將其傳遞給專門的模板,但它試圖將它傳遞給模板。

希望在C ++上好得多的人會有一兩招! 謝謝堆

我會考慮包裝你在類中調用lua函數的功能。 它有幾個好處,我會在一秒鍾內告訴你,但首先這里是一個可能的實現想法。 請注意,我沒有測試過這段代碼(或者甚至嘗試編譯它),這只是我根據之前嘗試做同樣事情而從頭腦中快速寫的東西。

namespace detail
{
    // we overload push_value instead of specializing
    // because this way we can also push values that
    // are implicitly convertible to one of the types

    void push_value(lua_State *vm, lua_Integer n)
    {
        lua_pushinteger(vm, n);
    }

    void push_value(lua_State *vm, lua_Number n)
    {
        lua_pushnumber(vm, n);
    }

    void push_value(lua_State *vm, bool b)
    {
        lua_pushboolean(vm, b);
    }

    void push_value(lua_State *vm, const std::string& s)
    {
        lua_pushstring(vm, s.c_str());
    }

    // other overloads, for stuff like userdata or C functions

    // for extracting return values, we specialize a simple struct
    // as overloading on return type does not work, and we only need
    // to support a specific set of return types, as the return type
    // of a function is always specified explicitly

    template <typename T>
    struct value_extractor
    {
    };

    template <>
    struct value_extractor<lua_Integer>
    {
        static lua_Integer get(lua_State *vm)
        {
            lua_Integer val = lua_tointeger(vm, -1);
            lua_pop(vm, 1);
            return val;
        }
    };

    template <>
    struct value_extractor<lua_Number>
    {
        static lua_Number get(lua_State *vm)
        {
            lua_Number val = lua_tonumber(vm, -1);
            lua_pop(vm, 1);
            return val;
        }
    };

    template <>
    struct value_extractor<bool>
    {
        static bool get(lua_State *vm)
        {
            bool val = lua_toboolean(vm, -1);
            lua_pop(vm, 1);
            return val;
        }
    };

    template <>
    struct value_extractor<std::string>
    {
        static std::string get(lua_State *vm)
        {
            std::string val = lua_tostring(vm, -1);
            lua_pop(vm, 1);
            return val;
        }
    };

    // other specializations, for stuff like userdata or C functions
}

// the base function wrapper class
class lua_function_base
{
public:
    lua_function_base(lua_State *vm, const std::string& func)
        : m_vm(vm)
    {
        // get the function
        lua_getfield(m_vm, LUA_GLOBALSINDEX, func.c_str());
        // ensure it's a function
        if (!lua_isfunction(m_vm, -1)) {
            // throw an exception; you'd use your own exception class here
            // of course, but for sake of simplicity i use runtime_error
            lua_pop(m_vm, 1);
            throw std::runtime_error("not a valid function");
        }
        // store it in registry for later use
        m_func = luaL_ref(m_vm, LUA_REGISTRYINDEX);
    }

    lua_function_base(const lua_function_base& func)
        : m_vm(func.m_vm)
    {
        // copy the registry reference
        lua_rawgeti(m_vm, LUA_REGISTRYINDEX, func.m_func);
        m_func = luaL_ref(m_vm, LUA_REGISTRYINDEX);
    }

    ~lua_function_base()
    {
        // delete the reference from registry
        luaL_unref(m_vm, LUA_REGISTRYINDEX, m_func);
    }

    lua_function_base& operator=(const lua_function_base& func)
    {
        if (this != &func) {
            m_vm = func.m_vm;
            lua_rawgeti(m_vm, LUA_REGISTRYINDEX, func.m_func);
            m_func = luaL_ref(m_vm, LUA_REGISTRYINDEX);
        }
        return *this;
    }
private:
    // the virtual machine and the registry reference to the function
    lua_State *m_vm;
    int m_func;

    // call the function, throws an exception on error
    void call(int args, int results)
    {
        // call it with no return values
        int status = lua_pcall(m_vm, args, results, 0);
        if (status != 0) {
            // call failed; throw an exception
            std::string error = lua_tostring(m_vm, -1);
            lua_pop(m_vm, 1);
            // in reality you'd want to use your own exception class here
            throw std::runtime_error(error.c_str());
        }
    }
};

// the function wrapper class
template <typename Ret>
class lua_function : public lua_function_base
{
public:
    lua_function(lua_State *vm, const std::string& func)
        : lua_function_base(vm, func)
    {
    }

    Ret operator()()
    {
        // push the function from the registry
        lua_rawgeti(m_vm, LUA_REGISTRYINDEX, m_func);
        // call the function on top of the stack (throws exception on error)
        call(0);
        // return the value
        return detail::value_extractor<Ret>::get(m_vm);
    }

    template <typename T1>
    Ret operator()(const T1& p1)
    {
        lua_rawgeti(m_vm, LUA_REGISTRYINDEX, m_func);
        // push the argument and call with 1 arg
        detail::push_value(m_vm, p1);
        call(1);
        return detail::value_extractor<Ret>::get(m_vm);
    }

    template <typename T1, typename T2>
    Ret operator()(const T1& p1, const T2& p2)
    {
        lua_rawgeti(m_vm, LUA_REGISTRYINDEX, m_func);
        // push the arguments and call with 2 args
        detail::push_value(m_vm, p1);
        detail::push_value(m_vm, p2);
        call(2);
        return detail::value_extractor<Ret>::get(m_vm);
    }

    template <typename T1, typename T2, typename T3>
    Ret operator()(const T1& p1, const T2& p2, const T3& p3)
    {
        lua_rawgeti(m_vm, LUA_REGISTRYINDEX, m_func);
        detail::push_value(m_vm, p1);
        detail::push_value(m_vm, p2);
        detail::push_value(m_vm, p3);
        call(3);
        return detail::value_extractor<Ret>::get(m_vm);
    }

    // et cetera, provide as many overloads as you need
};

// we need to specialize the function for void return type
// as the other class would fail to compile with void as return type
template <>
class lua_function<void> : public lua_function_base
{
public:
    lua_function(lua_State *vm, const std::string& func)
        : lua_function_base(vm, func)
    {
    }

    void operator()()
    {
        lua_rawgeti(m_vm, LUA_REGISTRYINDEX, m_func);
        call(0);
    }

    template <typename T1>
    void operator()(const T1& p1)
    {
        lua_rawgeti(m_vm, LUA_REGISTRYINDEX, m_func);
        detail::push_value(m_vm, p1);
        call(1);
    }

    template <typename T1, typename T2>
    void operator()(const T1& p1, const T2& p2)
    {
        lua_rawgeti(m_vm, LUA_REGISTRYINDEX, m_func);
        detail::push_value(m_vm, p1);
        detail::push_value(m_vm, p2);
        call(2);
    }

    template <typename T1, typename T2, typename T3>
    void operator()(const T1& p1, const T2& p2, const T3& p3)
    {
        lua_rawgeti(m_vm, LUA_REGISTRYINDEX, m_func);
        detail::push_value(m_vm, p1);
        detail::push_value(m_vm, p2);
        detail::push_value(m_vm, p3);
        call(3);
    }

    // et cetera, provide as many overloads as you need
};

這里的想法是,在構造時,函數類將找到具有名稱的函數並將其存儲在注冊表中。 之所以我這樣做,而不是僅存儲函數名稱並在每次調用時從全局索引中獲取它,是因為這樣,如果稍后的某個其他腳本將用另一個值替換全局名稱(可能是函數對象仍然會引用正確的函數。

無論如何你可能想知道為什么要經歷這一切的麻煩。 這種方法有很多好處:

您現在有一個自包含類型來處理lua函數對象。 您可以輕松地在代碼中傳遞它們,而不必擔心lua堆棧或lua內部。 以這種方式編寫代碼也更簡潔,更不容易出錯。

因為lua_function重載op(),所以你基本上有一個函數對象。 這樣做的好處是能夠將其用作接受它們的任何算法或函數的回調。 例如,假設你有一個lua_function<int> foo("foo"); ,讓我們說lua中的函數foo有兩個參數,一個double和一個字符串。 你現在可以這樣做:

// or std::function if C++11
boost::function<int (double, std::string)> callback = foo;
// when you call the callback, it calls the lua function foo()
int result = callback(1.0, "hello world");

這是非常強大的機制,因為您現在可以將您的lua代碼綁定到現有的C ++代碼,而無需編寫任何類型的其他包裝器代碼。

正如您所看到的,這也可以讓您輕松地從lua函數中獲取返回值。 根據您之前的想法,您必須在調用CallFunction后從堆棧中手動提取值。 這里明顯的缺點是,這個類只支持一個返回值,但如果你需要更多,你可以很容易地擴展這個類的想法(即你可以讓類采取額外的模板參數進行多次返回類型,或者你可以使用boost::any並返回它們的容器)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM