2011-10-25 16 views
5

これは最適な設計上の決定ではないかもしれませんし、LuaBindのようなものを使用したくはありません... C++ 03では次のことが可能なのか不思議でしたC++ 11ではバリデーショナルテンプレートを使用することができます)。また、前に尋ねたことがあると確信していますが、私はまっすぐ答えを見つけることができませんでした!可変パラメータを持つC++呼び出しのlua関数

私はコードからLuaの関数を呼び出すヘルパーメソッドを持っていると言う:潜在的に(のva_argまたは複数の引数の任意の他の方法を使用して)引数のN数を受け入れることができる

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

どのようにすることができそれが可能であれば、各パラメータの型を調べ、それを適切なlua_push {type}()に渡します。目的のlua関数を呼び出す前に関数を呼び出す?

これをvar_argで行うことができないのかどうかは分かりません。パラメータを取得するときに型を知っている必要があるため、void *でそれを取得して特殊なテンプレートに渡そうとしましたが、それをテンプレートに渡します。

C++のはるかに優れた人には、1つまたは2つのトリックがありますように! ありがとうヒープ

+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関数を呼び出す機能をラップすることを検討します。ここにはいくつかの利点がありますが、1秒で紹介しますが、まずここでは実装の考え方があります。私はこのコードをテストしていない(またはコンパイルしようとしている)ことに注意してください。これは、同じことをやっていた私の以前の試みに基づいて頭の上から書いたものでした。

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_functionはop()をオーバーロードするため、基本的には関数オブジェクトがあります。これは、それを受け入れるアルゴリズムや関数のコールバックとして使用できるという利点があります。例えば、lua_function<int> foo("foo");があり、luaの関数fooがdoubleとstringの2つの引数を取るとします。あなたは今、これを行うことができます。

// 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"); 

あなたは今、追加のラッパーコードの任意の並べ替えを記述することなく、既存のC++コードにあなたのLuaのコードをバインドすることができ、これは、非常に強力なメカニズムです。

ご覧のとおり、これにより、lua関数からの戻り値を簡単に取得することもできます。以前の考えでは、CallFunctionを呼び出した後にスタックから値を手動で抽出する必要があります。ただし、このクラスでサポートされている戻り値は1つだけですが、それ以上の値が必要な場合は、このクラスのアイデアを簡単に拡張できます。クラスに複数の戻り値型の追加のテンプレートパラメータを持たせることもできますし、boost::anyを使用してそれらのコンテナを返すこともできます)。

+0

うわー!すばらしい答え、深く行くことに感謝します。すぐにそれを実装に行くだろう!乾杯! – GracelessROB

関連する問題