How does Luabind work?

I am wondering how the Luabind shell allows you to pass a function without lua_State *L and not use the Lua stack.

Like Luabind:

  • count function parameters?
  • to bind function parameters to the Lua stack?
  • bind these classes

I am not trying to create another binding, such as Luabind, in other libraries. I'm just wondering how they did it. Just a curious person.

+6
source share
4 answers
Good question. I had some vague idea of ​​how luabind does what it does, but I did not know enough to answer fully and accurately. Armed with an IDE and a debugger, I began to cut through the following very simple part:
 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) ]; 

First of all, it should be noted that L is passed to luabind a lot, the open call creates several global characters in the Lua state: __luabind_classes type userdata and two functions class and property . Luabind does not seem to use global variables - everything it needs is stored in the lua environment.

Now we move on to module(L)[...] . The source code is the best explanation, first here is module :

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

Simple enough, here is 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; }; 

So our little program does this call statement [] in the module_ class with some definitions (the scope parameter), but the module_ class knows in what mode Lua works. The scope class is also interesting for viewing (some parts are omitted and some are slightly simplified):

 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 creates a linked list of nodes detail::registration , this list is based on the use of operator, Therefore, when one module(L) [class_<...>..., class_<...>...] , class_ , which inherits from scope , initializes its base with an instance of detail::registration , then the scope comma operator builds a linked list of all registrations, it is passed to module_::operator[] which calls scope::register_ , which, in turn, enumerates the chain and calls register_ for all these detail::registration objects. lua_State always passed to register_ .

Phew Now let's see what happens if you do class_<C>("C").def("f", &C::f) . This creates an instance of class_<C> with a specific name, which is included in the detail::registration member in class_ . The class_::def method call is written to a regressive structure and something else, but here a very interesting line is deeper in the def call chain:

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

Oooh, deduce_signature , I really wanted to see this. Now I want to abandon it, but the way it works is this: through dark preprocessing witchcraft using boost ( BOOST_PP_ITERATE and some other utilities) for each N between the one and LUABIND_MAX_ARITY the following is created:

 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>() } 

Again, such a function is generated for all N between 1 and LUABIND_MAX_ARITY, which by default is 10. There are a couple of overloads for processing const methods, virtual wrappers and free functions, etc., which means that there are about 50 deduce_signature functions that get to your sources right after the preprocessor and before starting compilation. From there, the task of the compiler is to choose the correct deduce_signature overload for the functions you pass to def , and this will return the correct boost::mpl::vectorX type. From there, make_function can do something - it has a list of parameter types [compilation time], and after some template magic they are counted, converted to and from Lua values, and so on.

I will stay here. The study is based on Luabind 0.8.1. Feel free to browse / debug the Luabind code for additional answers - it takes some time, but it is not so difficult after you get used to the style :) Good luck.

TL DR: Magic ... black magic

+13
source

luabind has shell template functions for the familiar prototype int luafunction(lua_State* L) , which accepts the C API. Basically, lua_CFunction is made for you. The actual C or C ++ function for the call can be saved as an upvalue for the wrapper. In the case of a C ++ member function, this pointer can be taken from the first argument.

Sample code terminating a C function using 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); } 

(The luaToC templated function will be specialized for each type of C and C ++ that the library will support. The push function will be overloaded similarly.)

You will notice that the above pair of functions will only work for one specific type of function C; functions with an unimaginable return value and one parameter. Void returns can be easily handled by factoring the return value operations into a third void-specific template, but you need a bunch of overloads to support other quantities of parameters. luabind does this: it has one overload for each number of supported parameters, including one for 0 parameters (the maximum number is an arbitrary number that they selected).

(note that in C ++ 0x you can use variable templates to support any number of parameters with the same template)

+3
source

I am not familiar with luabind , but the whole idea of ​​a “wrapper” is that it is built on top of some lower level abstraction, encapsulating it. luabind almost certainly uses lua_State internally.

0
source

From the manual :

The first thing you need to do is call luabind::open(lua_State*) , which will register the functions for creating classes from Lua and initialize some of the global state structures used by luabind.

So my (somewhat educated) guess is that you are doing the luabind::open thing, and it caches the state for use in other functions. (The key is to “initialize some state-global structures.”)

0
source

Source: https://habr.com/ru/post/888943/


All Articles