直接使用Lua,如何公开现有的c++类对象,以便在Lua脚本中使用?

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

本文关键字:Lua 对象 脚本 何公开 c++      更新时间:2023-10-16

我一直在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使用personExists检查pm_People向量,查看具有指定名称的人是否存在。

现在,考虑下面的函数,它从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;
}

我想做的是从PersonManager单例中获得一个已经实例化和存在的Person对象,使用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);
}

让我们从顶部开始,即在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");
}

现在处理PersonManagerget方法:

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

最后处理PersongetAge

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

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

现在在Lua中,你应该可以这样做:

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

我目前没有Lua或c++来测试它,但这应该让你开始。请注意你给Lua访问的Person指针的生命周期。

您使用一个完整的userdata,其中包含一个指向轻userdata的指针条目。轻用户数据是只能从C/c++中创建的值,它们就像Lua中的数字,因为它们没有方法、元表等。然后,无论何时c++函数获得完整的用户数据,它们都会从中获得指针,然后可以使用该指针访问底层c++对象的c++方法。

参见在Lua中访问Light userdata和那里的链接,看看你是否可以解决它。在Lua新闻组存档中也有许多帖子,您可以通过google找到。

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