2011-10-25 54 views
5

好的,所以這可能不是最好的設計決策,我真的不想使用類似LuaBind的東西......我只是好奇,如果以下是可能的C++ 03( C++ 11使可變模板成爲可能)。另外,我確定之前已經詢問過這個問題,但我找不到直接的答案!C++調用具有可變參數的lua函數

說我有一個輔助方法調用Lua函數從代碼:

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

其可以潛在地接受args來N個(使用的va_arg或多個參數的個數的任何其它方法)

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

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

希望有人在C++更好將有一個或兩個把戲! 謝謝你堆

+0

這是不可能的。 'va_arg'基本上是'void *'的榮耀鏈表。沒有附加的'void *'附加類型信息。 – GManNickG

+0

你真的在使用C或C++嗎?你在談論C99,但你已經把它標記爲C++(也許你正在使用C++ 03)?即使沒有可變模板,您也可以提供一組函數模板重載來處理這個問題。通常這是非常乏味的,因爲轉發參數必須爲const和非const引用(在C++ 03中)重載每個參數,但在你的情況下,lua_push *函數永遠不需要非const參考版本,所以你只需要N個過載(其中N是你想要支持的參數的最大數量)。 –

+0

啊對不起這是C++ 03,我的錯。謝謝,你的建議似乎是最好的方法(我的一個同事這樣做,但我很好奇,如果有更通用的方式)。 – GracelessROB

回答

9

我會考慮包裝你的功能在一個類中調用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並返回它們的容器)。

+0

哇!驚人的答案,非常感謝你的深入。將盡快實施它!乾杯! – GracelessROB

相關問題