Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overriding c++ method in lua and call it back in c++

I once have thought that I can override a class method in lua so that when I call that function in C++, it will do what has overriden in lua. I mean, like this :

C++ class


class Person {
public:
  Person(); // ctr
  virtual void shout(); // Meant to be overriden
};

Assume that I have that class binded to lua so that in lua, I can use the object :


--Lua code
p = Person:new()
p:shout()

What I'm trying to achieve is something like this :

Lua file


--luafile.lua
p = Person:new() --instantiate

--override shout()
p.shout = function(self) print("OVERRIDEN!") end

C++ code


int main() {
  lua_State* l = lua_open();
  luaL_loadlibs(l);
  bind_person_class(l);

  luaL_dofile("luafile.lua");
  Person* p = (Person*) get_userdata_in_global(l, "p"); // get the created person in lua
  p->shout(); // expecting "OVERRIDEN" to be printed on screen

  lua_close(l);
  return 0;
}

In the code above, you can see that I'm trying to override the Person's method in lua and expect the overriden method to be called from c++. However, when i try it, the overriden method is not executed. What I'm trying to achieve is the overriden method is executed in C++. How do you achieve this?

===================

I've thought a way to achieve this, but I'm not sure if this is good. My idea is the exported class should have a string representing the global variable name in lua that is used to hold the instance of this class. Like this:


class Person {
public:
  Person();
  string luaVarName; // lua's global variable to hold this class
  virtual void shout() { 
    luaL_dostring(luaVarName + ":shoutScript()"); // now shout will call shoutScript() in lua
  }
};

So, in lua, the object is responsible to implement shoutScript() and assign global var to object :


--LUA
p = Person:new()
p.shoutScript = function(self) print("OVERRIDEN") end
p.luaVarName = "p"

With codes above, I can achieve what I want (haven't tested it, though). But, is there any other, proper way to achieve what I want?

like image 427
Radi Avatar asked Jan 21 '11 01:01

Radi


1 Answers

What we did in lqt, the automated binding of Qt to Lua, is that for each class that we bind, which has virtual methods, we create a proxy "shell" class, which registers itself into the Lua state.

So for your (simplified) class:

class Person {
public:
  virtual void shout(); // Meant to be overriden
};

We generate the following class:

class lqt_shell_Person : public Person {
  lua_State *L;
public:
  lqt_shell_Person(lua_State *L); // registers itself into the Lua state
  virtual void shout();
};

We represent these objects in Lua using userdata. Each has it's own environment table, to which we point the __newindex and __index metamethods (__index function looks up in the environment and then in the class table). Using this, the user can store custom fields on the objects. He can also implement virtual functions, like this:

p = Person.new()
function p:shout() print("Hello world!") end

In our lqt_shell_Person::shout method, we first fetch the arguments, and then check whether there is a function shout in the environment table of the userdata. If there is, we call it with the arguments. If there isn't any, we call the original function. In case of abstract methods, we throw a Lua error.

Hope you find this useful.

like image 91
Michal Kottman Avatar answered Nov 13 '22 17:11

Michal Kottman