9

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

Consider the following two classes:

// Person.hpp
#pragma once
#include <string>

class Person {
    private:
        std::string p_Name;
        int p_Age;

    public:
        Person(const std::string & strName, const int & intAge)
            : p_Name(strName), p_Age(intAge) { }

        Person() : p_Name(""), p_Age(0) { }

        std::string getName() const { return p_Name; }
        int getAge() const { return p_Age; }

        void setName(const std::string & strName) { p_Name = strName; }
        void setAge(const int & intAge) { p_Age = intAge; }
};

... and ...

// PersonManager.hpp
#pragma once
#include "Person.hpp"
#include <vector>

class PersonManager {
    // Assume that this class is a singleton, and therefore
    // has no public constructor, but a static function that returns the
    // singleton instance.
    private:
        std::vector<Person *> pm_People;

    public:
        bool personExists(const std::string & strName) { /* ... */ }
        bool addPerson(const std::string & strName, const int & intAge) { /* ... */ }
        Person * getPerson(const std::string & strName) { /* ... */ }
        void removePerson(const std::string & strName) { /* ... */ }
        void removeAllPeople() { /* ... */ }
};

... where getPerson checks the pm_People vector to see if the person with the specified name exists, using personExists.

Now, consider the following function that gets a Person object from Lua and returns its age.

// Lua_Person.cpp
#include "Lua_Person.hpp"       // "Lua_Person.hpp" declares the function called to expose the "Person" functions to Lua.
#include "PersonManager.hpp"
#include "Person.hpp"

int lua_GetPersonAge(lua_State * LS) {
    // Validate the userdata.
    luaL_checktype(LS, 1, LUA_TUSERDATA);

    // Get the "Person" userdata.
    Person * luaPerson = reinterpret_cast<Person *>(lua_touserdata(LS, 1));

    // Check to see if the Person pointer is not null.
    if(luaPerson == nullptr)
        luaL_error(LS, "lua_GetPersonAge: You gave me a null pointer!");

    // Push the person's age onto the Lua stack.
    lua_pushnumber(LS, luaPerson->getAge());

    // Return that age integer.
    return 1;
}

What I want to do is to get an already-instantiated and existing Person object from the PersonManager singleton, using getPerson, and expose that object to Lua, so I can do something like this:

local testPerson = People.get("Stack Overflower")
print(testPerson:getAge())

I tried something like the code block below, to no avail:

int lua_GetPerson(lua_State * LS) {
    // Validate the argument passed in.
    luaL_checktype(LS, 1, LUA_TSTRING);

    // Get the string.
    std::string personName = lua_tostring(LS, 1);

    // Verify that the person exists.
    if(PersonManager::getInstance().personExists(personName) == false)
        luaL_error(LS, "lua_GetPerson: No one exists with this ID: %s", personName.c_str());

    // Put a new userdata into a double pointer, and assign it to the already existing "Person" object requested.
    Person ** p = static_cast<Person **>(lua_newuserdata(LS, sizeof(Person *)));    // <Userdata>
    *p = PersonManager::getInstance().getPerson(personName);

    // Put that person object into the "Meta_Person" metatable.
    // Assume that metatable is created during the registration of the Person/Person Manager functions with Lua.
    luaL_getmetatable(LS, "Meta_Person");   // <Metatable>, <Userdata>
    lua_setmetatable(LS, -2);               // <Metatable>

    // Return that metatable.
    return 1;
}

Can anybody lend a helping hand here, or at least point me in the right direction? I am not using any lua wrapper libraries, just straight Lua.

Thank you.

EDIT: The functions that I use to expose my Person and PersonManager functions are as follows:

void exposePerson(lua_State * LS) {
    static const luaL_reg person_functions[] = {
        { "getAge", lua_getPersonAge },
        { nullptr, nullptr }
    };

    luaL_newmetatable(LS, "Meta_Person");
    lua_pushstring(LS, "__index");
    lua_pushvalue(LS, -2);
    lua_settable(LS, -3);

    luaL_openlib(LS, nullptr, person_functions, 0);
}

void exposePersonManager(lua_State * LS) {
    static const luaL_reg pman_functions[] = {
        { "get", lua_getPerson },
        { nullptr, nullptr }
    };

    luaL_openlib(LS, "People", pman_functions, 0);

    lua_pop(LS, 1);
}
Dennis
  • 231
  • 2
  • 8
  • I think you'd be better off using `std::unordered_map` instead of `std::vector` for your `PersonManager` class. Using vector entails scanning each value one by one to see if a name exists. Now if you use a map with person's name as a key, that would be much more efficient in terms of lookups. – W.B. Mar 21 '14 at 12:19
  • Why not use...? First, find one that's still being maintained. Second, spend hours figuring out if it works for your need. Is it massive amounts of templates? What are the odds you can figure out what happened when things go wrong? "Separating the binding concern" means "sign up for a bunch of other concerns that may matter vastly more". :-) Speaking of maintenance pain: did you (@DmitryLedentsov) make a link to useful info that has decayed into vacation link spam page? – Ron Burk Aug 20 '23 at 06:57
  • @RonBurk, these were viable options in 2014. Deleting, as not helpful in 2023, indeed – Dmitry Ledentsov Aug 24 '23 at 10:21

2 Answers2

10

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.

Oliver
  • 27,510
  • 9
  • 72
  • 103
W.B.
  • 5,445
  • 19
  • 29
  • Best walkthrough I've seen. I rewrapped the comments so don't need to scroll horizontally so much, I hope you don't mind. – Oliver Mar 21 '14 at 14:31
  • It works. `person:getAge()` is now printing the proper age integers, and the destructors are working as proper. Thank you for the help. – Dennis Mar 21 '14 at 18:23
0

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.

Community
  • 1
  • 1
Oliver
  • 27,510
  • 9
  • 72
  • 103
  • 1
    That's kinda convoluted. You would normally just create userdata and store pointer to Person. Then assign metatable to it and create appropriate methods. Lightuserdata is not needed to accomplish that. And both userdata and lightuswrdata can only be created from C. I'm typing on a mobile so can't post a solution at the moment. – W.B. Mar 20 '14 at 14:20
  • @W.B. +1 for comment That would be great. – Oliver Mar 20 '14 at 15:28
  • I think it's a good approach to start by integrating Lua using plain C API. That way you can really learn how it really works (after all that's what SWIG is using). – W.B. Mar 21 '14 at 12:24