Calling a host function from lua that receives parameters from a c++ object

Question:

I have an object in c++ code like

TestObj Test();

I would like to pass not only the Lua stack to the function that will be called from the Lua script, but also a reference to this object. Those. instead of:

int lua_getOneString(lua_State *L)
{  
  lua_pushstring(L, "Hello");  
  return 1;  //Количество возвращаемых аргументов
}

I want to do something like this (this is to avoid making the TestObj object global):

int lua_getOneString(lua_State *L, TestObj & obj)
    {  
      lua_pushstring(L, obj.pushStringToLua());  
      return 1;  //Количество возвращаемых аргументов
    }

I can’t find information about this, and if you do this, the compiler swears:

note: initializing argument 2 of 'void lua_pushcclosure(lua_State*, lua_CFunction, int)' LUA_API void (lua_pushcclosure) (lua_State *L, lua_CFunction fn, int n);

Answer:

all functions called from lua must have the same signature: int (lua_State *) – so your function must be converted to this form. There are two options: make your TestObj static (which is a good option for a factory for example), or get it from the lua stack as a parameter. I suspect that you are interested in the second option (in any case, the first option is trivial).

In order to get your TestObj from the lua stack you first need to push it there – but how do you do that? lua has a special userdata type that can be used to represent objects from c code. It can be a simple pointer or some amount of memory allocated right on the lua stack. In the second case, the interpreter will free the memory it occupied, which is quite good if your object consists of basic types, like int , float , fixed-size c -arrays, etc. which do not require a destructor call. If the destructor must be called (for example, when you use new or use containers that allocate memory on the heap), then you also have to write a finalizer.

Let's show some code:

/**\brief push new TestObj to lua stack
 */
int lua_new_obj(lua_State *state) {
  void *addr = lua_newuserdata(state, sizeof(TestObj));
  TestObj *obj = new (addr) TestObj{};

  // XXX also you can use light userdata
  // TestObj *obj = new TestObj{};
  // lua_pushlightuserdata(state, obj);

  return 1;
}

/**\brief get TestObj from lua stack and do something
 */
int lua_foo(lua_State *state) {
  void *addr = lua_touserdata(state, 1);
  TestObj *obj = reinterpret_cast<TestObj *>(addr);
  obj->doSomething();
  return 0;
}

/**\brief call desctructor TestObj
 * \note needed only if you use lightuserdata, or if fou your TestObj destructor must be called
 */
int lua_finilize(lua_State *state) {
  void *addr = lua_touserdata(state, 1);
  TestObj *obj = reinterpret_cast<TestObj *>(addr);
  obj->~TestObj();
  return 0;

  // XXX if you use lightuserdata then you need free memory
  // delete obj; // destructor calls automaticly
}

This code is not perfect, as there are two obvious problems: you need to manually call the finalizer (in the case lightuserdata or if you need to call the destructor on the object) and check the input parameters (if you pass any other usedata to your function, then at best your program will immediately crash , at worst – will not fall). But these problems have a solution – metatables! Yes, for userdata , as well as for tables, you can set your own metatables, but that's another story.

Scroll to Top