使用直接Lua,如何公开现有的C ++类对象以用于Lua脚本?

时间:2014-03-19 19:11:45

标签: c++ lua

我在使用C ++嵌入Lua脚本方面的经验越来越丰富, 我可以在这里用一只手。

考虑以下两个类:

// 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; }
};

......和......

// 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() { /* ... */ }
};

...其中getPerson使用pm_People检查personExists向量,以查看具有指定名称的人是否存在。

现在,考虑以下函数从Lua获取Person对象并返回其年龄。

// 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;
}

我想要做的是使用PersonPersonManager单例中获取已经实例化的现有getPerson对象,并将该对象暴露给Lua, 所以我可以这样做:

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

我尝试过类似下面的代码块,但无济于事:

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;
}

有人可以在这里伸出援助之手,或者至少指出我正确的方向吗? 我没有使用任何lua包装器库,只是直接使用Lua。

谢谢。

编辑:我用来公开我的PersonPersonManager函数的函数如下:

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

2 个答案:

答案 0 :(得分:7)

让我们从顶部开始,即在Lua中注册PersonManager。由于它是单身,我们将其注册为全球。

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");
}

现在我们处理PersonManager的{​​{1}}方法:

get

最后处理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); } 的{​​{1}}。

Person

现在,您应该在执行Lua代码之前调用getAge,最好在创建新的Lua状态并打开所需的库之后。

现在在Lua内,您应该可以这样做:

int lua_Person_getAge(lua_State *lua)
{
    Person **pptr = (Person**)lua_checkudata(lua, 1, "PersonMetaTable");

    lua_pushnumber(lua, (*pptr)->getAge());
    return 1;
}

我目前无法使用Lua或C ++来测试它,但这应该让你开始。请注意您为Lua访问的registerPersonManager指针的生命周期。

答案 1 :(得分:0)

您使用包含指向轻用户数据指针的条目的完整用户数据。 Light userdata是只能用C / C ++创建的值,它们就像Lua中的一个数字,因为它们没有方法,metatable等等。然后每当你的C ++函数获得完整的用户数据时,它们就会得到它的指针,然后可以用来访问底层C ++对象的C ++方法。

请参阅Accessing Light userdata in Lua及其中的链接,看看是否可以解决问题。您可以通过谷歌找到Lua新闻组档案中的许多帖子。

请注意,使用SWIG为您生成包装器代码,此任务将是微不足道的,您可以专注于您的应用程序而不是绑定C ++和Lua。