Using straight Lua, how do I expose an existing C++ class objec for use in a Lua script?

后端 未结 2 466
盖世英雄少女心
盖世英雄少女心 2020-12-05 12:08

I\'ve been furthering my experience in embedding Lua scripting in C++, and I could use a hand, here.

Consider the following two classes:



        
相关标签:
2条回答
  • 2020-12-05 12:46

    You use a full userdata that contains an entry that is pointer to a light userdata. Light userdata are values that can only be created from C/C++, they are like a number in Lua in that they don't have methods, metatable, etc. Then whenever your C++ functions get the full userdata, they get the pointer from it, which can then be used to access C++ methods of the underlying C++ object.

    See Accessing Light userdata in Lua and the links there and see if you can work it out. There are also many posts on the Lua newsgroup archive that you can find via google.

    Note that with SWIG to generate wrapper code for you, this task would be trivial and you could focus on your app rather than on binding C++ and Lua.

    0 讨论(0)
  • 2020-12-05 12:55

    Let's start off the top, that is by registering PersonManager in Lua. Since it's a singleton, we'll register it as a global.

    void registerPersonManager(lua_State *lua)
    {
        //First, we create a userdata instance, that will hold pointer to our singleton
        PersonManager **pmPtr = (PersonManager**)lua_newuserdata(
            lua, sizeof(PersonManager*));
        *pmPtr = PersonManager::getInstance();  //Assuming that's the function that 
                                                //returns our singleton instance
    
        //Now we create metatable for that object
        luaL_newmetatable(lua, "PersonManagerMetaTable");
        //You should normally check, if the table is newly created or not, but 
        //since it's a singleton, I won't bother.
    
        //The table is now on the top of the stack.
        //Since we want Lua to look for methods of PersonManager within the metatable, 
        //we must pass reference to it as "__index" metamethod
    
        lua_pushvalue(lua, -1);
        lua_setfield(lua, -2, "__index");
        //lua_setfield pops the value off the top of the stack and assigns it to our 
        //field. Hence lua_pushvalue, which simply copies our table again on top of the stack.
        //When we invoke lua_setfield, Lua pops our first reference to the table and 
        //stores it as "__index" field in our table, which is also on the second 
        //topmost position of the stack.
        //This part is crucial, as without the "__index" field, Lua won't know where 
        //to look for methods of PersonManager
    
        luaL_Reg personManagerFunctions[] = {
             'get', lua_PersonManager_getPerson,
              nullptr, nullptr
        };
    
        luaL_register(lua, 0, personManagerFunctions);
    
        lua_setmetatable(lua, -2);
    
        lua_setglobal(lua, "PersonManager");
    }
    

    Now we handle PersonManager's get method:

    int lua_PersonManager_getPerson(lua_State *lua)
    {
        //Remember that first arbument should be userdata with your PersonManager 
        //instance, as in Lua you would call PersonManager:getPerson("Stack Overflower");
        //Normally I would first check, if first parameter is userdata with metatable 
        //called PersonManagerMetaTable, for safety reasons
    
        PersonManager **pmPtr = (PersonManager**)luaL_checkudata(
            lua, 1, "PersonManagerMetaTable");
        std::string personName = luaL_checkstring(lua, 2);
    
        Person *person = (*pmPtr)->getPerson(personName);
        if (person)
            registerPerson(lua, person);    //Function that registers person. After 
                    //the function is called, the newly created instance of Person 
                    //object is on top of the stack
        else
            lua_pushnil(lua);
    
        return 1;
    }
    
    void registerPerson(lua_State *lua, Person *person)
    {
        //We assume that the person is a valid pointer
        Person **pptr = (Person**)lua_newuserdata(lua, sizeof(Person*));
        *pptr = person; //Store the pointer in userdata. You must take care to ensure 
                        //the pointer is valid entire time Lua has access to it.
    
        if (luaL_newmetatable(lua, "PersonMetaTable")) //This is important. Since you 
            //may invoke it many times, you should check, whether the table is newly 
            //created or it already exists
        {
            //The table is newly created, so we register its functions
            lua_pushvalue(lua, -1);
            lua_setfield(lua, -2, "__index");
    
            luaL_Reg personFunctions[] = {
                "getAge", lua_Person_getAge,
                nullptr, nullptr
            };
            luaL_register(lua, 0, personFunctions);
        }
    
        lua_setmetatable(lua, -2);
    }
    

    And finally handling Person's getAge.

    int lua_Person_getAge(lua_State *lua)
    {
        Person **pptr = (Person**)lua_checkudata(lua, 1, "PersonMetaTable");
    
        lua_pushnumber(lua, (*pptr)->getAge());
        return 1;
    }
    

    You should now call registerPersonManager before executing your Lua code, best just after you create new Lua state and open needed libraries.

    Now within Lua, you should be able to do that:

    local person = PersonManager:getPerson("Stack Overflower");
    print(person:getAge());
    

    I don't have access to either Lua or C++ at the moment to test it, but that should get you started. Please be careful with lifetime of the Person pointer you give Lua access to.

    0 讨论(0)
提交回复
热议问题