2011-05-24 19 views
6

Me interesa saber cómo el envoltorio Luabind permite pasar una función sin el lua_State *L y no utiliza la pila Lua.¿Cómo funciona Luabind?

¿Cómo Luabind:

  1. recuento de los parámetros de la función?
  2. ¿vincular los parámetros de la función a la pila Lua?
  3. enlazar esas clases

No estoy tratando de crear otro vinculante como Luabind a otras bibliotecas. Me pregunto cómo lo hicieron. Solo un hombre curioso.

Respuesta

0

No estoy familiarizado con luabind, pero la idea de un "envoltorio" es que está construida sobre una abstracción de nivel inferior, encapsulándola. luabind casi con certeza hace utiliza lua_Stateinternamente.

0

De the manual:

La primera cosa que hay que hacer es llamar luabind::open(lua_State*) que registrará las funciones para crear clases de Lua, e inicializar algunas estructuras globales de estado usados ​​por luabind.

Así que mi (algo educada) conjetura es que usted hace el luabind::open cosa y almacena en caché el estado para su uso en las funciones restantes. (La pista se encuentra en "inicializar algunas estructuras estatales globales".)

3

luabind tiene plantillas de funciones de envoltura para el familiar prototipo int luafunction(lua_State* L) que acepta la API C. En esencia, la función lua_C se ha creado para ti. La función C o C++ real para llamar se puede almacenar como un valor ascendente a la envoltura. En el caso de una función de miembro de C++, el puntero this se puede tomar del primer argumento.

código

Ejemplo envolver una función C usando upvalues:

template<typename R, typename T1> 
int arg1wrapper(lua_State* L) 
{ 
    typedef R (*F)(T1); 
    F func = (F)lua_touserdata(L, lua_upvalueindex(1)); 
    R retValue = func(luaToC<T1>(L, 1)); 
    push(L, retValue); 
    return 1; 
} 

// example use 
template<typename R, typename T1> 
void push(R (*func)(T1)) 
{ 
    lua_pushlightuserdata(L, func); 
    lua_pushcclosure(L, &arg1wrapper<R, T1>, 1); 
} 

(.. La función de plantilla luaToC sería especializados para cada C y C++ escriba la biblioteca tiene la intención de apoyar la función push sería sobrecargado similarily)

Notarás que las dos funciones anteriores funcionarán solo para un tipo particular de función C; funciona con un valor de retorno no nulo y un solo parámetro. Los retornos inválidos se pueden manejar fácilmente al factorizar las operaciones de valor de retorno en una tercera plantilla especializada para nulo, pero para soportar otras cantidades de parámetros, necesita un montón de sobrecargas. luabind hace esto: tiene una sobrecarga para cada cantidad de parámetros que admite, incluido uno para 0 parámetros (la cantidad máxima es un número arbitrario que eligieron).

(nótese que en C++ 0x puede utilizar plantillas variadic para soportar cualquier cantidad de parámetros con la misma plantilla)

13

Buena pregunta. Tenía una vaga idea de cómo luabind hace lo que hace, pero no sabía lo suficiente como para responder de manera completa y precisa.Armado con un IDE y depurador empecé a diseccionar la siguiente pieza muy simple:

struct C 
{ 
    int i; 
    int f(int x, const char* s) 
}; 

    lua_State* L = luaL_newstate(); 

open(L); 
module(L) 
[ 
    class_<C>("C") 
     .def_readwrite("index", &C::i) 
     .def("f", &C::f) 
]; 

primero que hay que notar es que L se pasa a luabind mucho, la llamada a open crea un par de variables globales en el estado de Lua: la __luabind_classes de tipo userdata y dos funciones class y property. Luabind no parece usar variables globales; todo lo que necesita se guarda en el entorno lua.

Ahora llegamos a module(L)[...]. El código original es la mejor explicación, primero aquí está module: lo suficientemente

inline module_ module(lua_State* L, char const* name = 0) 
{ 
    return module_(L, name); 
} 

simple, este es module_:

class LUABIND_API module_ 
{ 
public: 
    module_(lua_State* L_, char const* name); 
    void operator[](scope s); 

private: 
    lua_State* m_state; 
    char const* m_name; 
}; 

Así que lo que hace nuestro pequeño programa es llamada operador [] en la clase module_ con algunas definiciones (ese es el parámetro scope), pero la clase module_ sabe en qué estado de Lua operar. La clase scope También es interesante observar (algunas partes se omiten y algunos ligeramente simplificada):

struct LUABIND_API scope 
{ 
    //... 
    explicit scope(detail::registration* reg); 
    scope& operator,(scope s); 
    void register_(lua_State* L) const; 
private: 
    detail::registration* m_chain; 
}; 

scope es la construcción de una lista enlazada de nodos detail::registration, esa lista proviene del uso de operator,. Así que cuando uno hace module(L) [class_<...>..., class_<...>...], class_ que hereda de scope inicializa su base de un ejemplo detail::registration, entonces el operador coma de scope construye una lista enlazada de todos los registros, esta se pasa a module_::operator[] que exige scope::register_ que a su vez enumera la cadena y llama register_ en todos esos objetos detail::registration. El lua_State siempre se pasa al register_.

Phew. Ahora veamos qué sucede cuando uno hace class_<C>("C").def("f", &C::f). Esto construye una instancia de class_<C> con un nombre determinado que va en el miembro detail::registration en class_. Al llamar al método class_::def escribe en la estructura de registro y todo eso, pero aquí hay una línea muy interesante profundizar en la cadena de llamada de def:

  object fn = make_function(
       L, f, deduce_signature(f, (Class*)0), policies); 

Oooh, deduce_signature, realmente quería ver eso. Ahora quiero unsee, pero la forma en que funciona es la siguiente: por medio de la magia oscura preprocesador ayudado por impulso (BOOST_PP_ITERATE y algunas otras utilidades) se genera para cada N entre uno y LUABIND_MAX_ARITY lo siguiente:

template <class R, class T, class A1, classA2, ..., classAN> 
boost::mpl::vectorN_PLUS_2<R, T, A1, A2, ..., AN> // type of return value 
    deduce_signature(R(T::*)(A1, A2, ..., AN)) 
    { 
      return boost::mpl::vectorN_PLUS_2<R, T, A1, A2, ..., AN>() 
    } 

Una vez más, una función como esta se genera para todo N entre 1 y LUABIND_MAX_ARITY que es 10 por defecto. Hay un par de sobrecargas para manejar los métodos const, los wrappers virtuales y las funciones gratuitas, lo que significa que hay alrededor de 50 deduce_signature funciones que terminan en sus fuentes justo después del preprocesador y antes de que la compilación haya comenzado. A partir de ahí, es tarea del compilador elegir la sobrecarga deduce_signature correcta para las funciones que pasa al def y que devolverá el tipo correcto boost::mpl::vectorX. A partir de ahí, make_function puede hacer cualquier cosa: tiene una lista [de tiempo de compilación] de tipos de parámetros y, a través de más magia de plantilla, se cuentan, se convierten a valores de Lua y viceversa, y así sucesivamente.

Aquí es donde voy a parar.La investigación se basa en Luabind 0.8.1. Siéntase libre de navegar/depurar el código de Luabind para obtener más respuestas; lleva un tiempo, pero no es tan difícil una vez que se acostumbre al estilo :) Buena suerte.

TL; DR: Magia ... magia negra