2011-10-25 55 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

相关问题