【问题标题】:Lua 5.2 - C++ object within an object (using lua_lightuserdata)Lua 5.2 - 对象内的 C++ 对象(使用 lua_lightuserdata)
【发布时间】:2015-02-07 07:15:25
【问题描述】:

编辑:[答案 2 中的解决方案]

我是 LUA 的新手,在尝试做我想做的事情时遇到了麻烦。我有一个看起来像这样的 C++ 对象:

C++ 对象定义

struct TLimit
{
    bool   enabled;
    double value;

    TLimit() : enabled(false), value(0.0) {}
    ~TLimit() {}
};

class TMeaurement
{
public:
    TMeasurement() : meas(0.0) {}
    ~TMeasurement() {}

    TLimit min;
    TLimit max;
    double meas;
};

我希望能够在 LUA 中以下列形式访问 TMeasurement 类型的对象:

LUA 所需用途

-- objmeas is an instance of TMeasurement
objmeas.min.enabled = true
print(objmeas.min.value);

...等

另外一件事,我不希望 LUA 为 TMeasurement 类型的对象的实例分配内存。这将在我的 C++ 代码中完成。我尝试了很多不同的方法,都没有成功。我现在将发布我的最后一次尝试。

在我的 C++ 代码中,我定义了以下内容:

TLimit - 获取将映射到 __index 的函数

#define LUA_MEAS_LIMIT    "itse.measurement.limit"

extern int llim_get(lua_State* L)
{
    TLimit*     lim = (TLimit*)lua_chekuserdata(L, 1, LUA_MEAS_LIMIT);
    std::string key = std::string(luaL_checkstring(L, 2));

    //-- this is only to check what is going on
    std::cout << "lim.get: " << key << std::endl;

    if(key.find("enabled") == 0)
        lua_pushboolean(L, lim->enabled);
    else if(key.find("value") == 0)
        lua_pushnumber(L, lim->value);
    else
        return 0;   //-- should return some sort of error, but let me get this working first

    return 1;
}

TLimit - 设置将映射到 __newindex 的函数

extern int llim_set(lua_State* L)
{
    TLimit*     lim = (TLimit*)lua_chekuserdata(L, 1, LUA_MEAS_LIMIT);
    std::string key = std::string(luaL_checkstring(L, 2));

    //-- this is only to check what is going on
    std::cout << "limit.set: " << key << " <-" << std::endl;

    if(key.find("enabled") == 0)
        lim->enabled = lua_toboolean(L, 3);
    else if(key.find("value") == 0)
        lim->value = lua_tonumber(L, 3);

    return 0;
}

现在,TMeasurement 类又多了一个函数。 (在这个例子中我不会提供成员“meas”的set函数)。

TMeasurement - 获取 __index 的函数

#define LUA_MEASUREMENT    "itse.measurement"

extern int lmeas_get(lua_State* L)
{
    TMeasurement* test = (TMeasurement*)lua_checkuserdata(L, 1, LUA_MEASUREMENT);
    std::string   key  = std::string(luaL_checkstring(L, 2));

    //-- this is only to check what is going on
    std::cout << "meas." << key << " ->" << std::endl;

    if(key.find("meas") == 0)
        lua_pushinteger(L, test->meas);
    else if(key.find("min") == 0)
    {
        lua_pushlightuserdata(L, &test->min);
        luaL_getmetatable(L, LUA_MEAS_LIMIT);
        lua_setmetatable(L, -2);
    }
    else if(key.find("max") == 0)
    {
        lua_pushlightuserdata(L, &test->max);
        luaL_getmetatable(L, LUA_MEAS_LIMIT);
        lua_setmetatable(L, -2);
    }
    else
        return 0;  //-- should notify of some error... when I make it work

    return 1;
}

现在,代码中为这两个对象创建元表的部分:

C++ - 发布元表

(别管 nsLUA::safeFunction<...> 位,它只是一个模板函数,它将以“安全模式”在 中执行该函数......遇到错误)

static const luaL_Reg lmeas_limit_f[] = { { NULL, NULL} };
static const luaL_Reg lmeas_limit[] =
{
        { "__index",    nsLUA::safeFunction<llim_get> },
        { "__newindex", nsLUA::safeFunction<lllim_set> },
        { NULL,      NULL }
};
//-----------------------------------------------------------------------------

static const luaL_Reg lmeas_f[] =  { { NULL, NULL} };
static const luaL_Reg lmeas[] =
{
        { "__index", nsLUA::safeFunction<lmeas_get> },
        { NULL,   NULL }
};
//-----------------------------------------------------------------------------

int luaopen_meas(lua_State* L)
{
    //-- Create Measurement Limit Table
    luaL_newmetatable(L, LUA_MEAS_LIMIT);
    luaL_setfuncs(L, lmeas_limit, 0);
    luaL_newlib(L, lmeas_limit_f);

    //-- Create Measurement Table
    luaL_newmetatable(L, LUA_MEASUREMENT);
    luaL_setfuncs(L, lmeas, 0);
    luaL_newlib(L, lmeas_f);

    return 1;
}

最后,我在 C++ 中的主要功能,初始化 LUA,创建对象 TMeasurement 的实例,将其作为全局对象传递给 LUA 并执行 lua 脚本。大部分功能都包含在另一个名为 LEngine 的类中:

C++ - 主函数

int main(int argc, char* argv[])
{
    if(argc < 2)
        return show_help();

    nsLUA::LEngine eng;

    eng.runScript(std::string(argv[1]));

    return 0;
}
//-----------------------------------------------------------------------------

int LEngine::runScript(std::string scrName)
{
    //-- This initialices LUA engine, openlibs, etc if not already done. It also
    //   registers whatever library I tell it so by calling appropriate "luaL_requiref"
    luaInit();

    if(m_lua)    //-- m_lua is the lua_State*, member of LEngine, and initialized in luaInit()
    {
        LMeasurement measurement;

        measurement.value = 4.5;   //-- for testing purposes

        lua_pushlightuserdata(m_lua, &tst);
        luaL_getmetatable(m_lua, LUA_MEASUREMENT);
        lua_setmetatable(m_lua, -2);
        lua_setglobal(m_lua, "step");

        if(luaL_loadfile(m_lua, scrName.c_str()) || lua_pcall(m_lua, 0, 0, 0))
            processLuaError();   //-- Pops-up a messagebox with the error
    }

    return 0;
}

现在,问题终于来了。当我执行任何 lua 脚本时,我可以访问 step 没问题,但我只能在第一次访问“min”或“max”内的 memebr ......任何后续访问都会出错。

LUA - 示例一

print(step.meas);        -- Ok
print(step.min.enabled); -- Ok
print(step.min.enabled); -- Error: attempt to index field 'min' (a nil value)

这个脚本生成的输出是:

                              first script line: print(step.meas);
meas.meas ->                     this comes from lmeas_get function
4.5                              this is the actual print from lua sentence
                              second script line: print(step.min.enabled)
meas.min ->                      accessing step.min, call to function lmeas_get
limit.get: enabled ->            accessing min.enabled, call to function llim_get
false                            actual print from script sentence
                              third script line: print(step.min.enabled)
limit.get: min ->                accessing min from limit object, call to llim_get ???????

所以。在我第一次访问字段“min”(或“max”)之后,任何后续访问它的尝试都将返回“尝试访问索引...”错误。我是先访问 __index (local e = step.min.enabled) 函数还是 __newindex 函数 (step.min.enabled = true) 都没有关系。

似乎我第一次访问对象步骤的 min metatble 时弄乱了 LUA 堆栈。它以某种方式“替换”了从 LUA_MEASUREMENT 元表到 LUA_MEAS_LIMIT 的“指向步骤的指针”......我根本不知道为什么。

请帮忙...是什么让我搞砸了这么多?

谢谢你,很抱歉发了这么长的帖子……我只是不知道如何让它更短。

【问题讨论】:

  • 为什么第三个print没有对应的meas.min -&gt;。看起来meas 被跳过了,而min 键被传递到limit.get
  • 我怀疑它与pushlightuserdata 有关。所有 lightudata 共享一个共同的元数据。您确定您绑定为 lightudata 的对象不会破坏彼此的元表吗?
  • greatwolf,这正是我不明白的。它应该有一个对应的meas.min -&gt;,但它没有。它试图从错误的元表中找到min 字段......不知道为什么。我不知道他们是否正在破坏彼此的元表......也不知道我该如何测试/找出。
  • @greatwolf 在您的第二条评论中,我不明白“所有 lightudata 共享一个共同的元数据”的说法,直到下面的 siffiejoe 再次解释了它。为你们俩干杯。
  • LUA --&gt; Lua + 使用专用绑定库

标签: c++ object lua metatable lua-api


【解决方案1】:

首先,感谢@siffiejoe 和@greatwolf 的帖子。是他们向我解释了我做错了什么。

现在,我的解决方案。我很确定这个解决方案并不是最好的,但到目前为止它满足了我的需求。如果有人有任何建议,查看/发现潜在的错误,或者只是想发表评论,请这样做。

解决方案 - 想法

由于在 LUA 中,所有 lightuserdata 共享同一个元表,因此我决定让我想要将 lightuserdata 指针传递给 LUA 的所有结构和类共享从我称为 LMetaPointer 的公共类的相同继承。此类将发布一个元表并将__index__newindex 映射到给定的静态方法LMetaPointer::__indexLMetaPointer::__newindex。该类还包含一个static std::map(列表)指向所有已创建的LMetaPointer 实例的指针。该类的构造函数确保将新创建的实例添加到此映射中。

每当在lua中,元方法__index__newindex被调用,相应的LMetaPointer::__indexLMetaPointer::__newindex被执行。该方法在映射表中搜索负责方法调用的对应指针,并调用自己的getset方法,这些方法在LMetaPointer类中定义为纯虚方法。

我知道这可能有点令人困惑,所以我现在将发布类的定义LMetaPointer

解决方案 - 框架:LMetaPointer 类

//-----------------------------------------------------------------------------
#define LUA_METAPOINTER     "itse.metapointer"    //-- Name given to the metatable for all lightuserdata (instances of LMetaPointer in C++)
//-----------------------------------------------------------------------------

class LMetaPointer
{
private:
    static lua_State*                           m_lua;           //-- All LMetaPointers will share a common lua State
    static const luaL_Reg                       m_lmembers[];    //-- Member functions (for later expansion)
    static const luaL_Reg                       m_lfunctions[];  //-- Metamethods
    static std::map<LMetaPointer*, std::string> m_pointers;      //-- List of all LMetaPointer instances

    std::string m_name;                  //-- Name of LUA global variable pointing to me.

    static int __index(lua_State* L);    //-- Shall be mapped to __index metamethod of the metatable for all lightuserdata pointers
    static int __newindex(lua_State* L); //-- Shall be mapped to __newindex metamethod of the metatable for all lightuserdata pointers

    void initialize(lua_State* L);       //-- Creates the metatable (only once) and publishes it

protected:
public:
    LMetaPointer(lua_State* L);
    virtual ~LMetaPointer();

    inline lua_State*  lua()    { return m_lua;             }
    inline std::string global() { return m_name;            }
    inline size_t      size()   { return m_pointers.size(); }

    void setGlobal(std::string n);      //-- Shall make this pointer globally accessible to LUA

    virtual int get(lua_State* L) = 0;  //-- To be implemented by inherited classes
    virtual int set(lua_State* L) = 0;  //-- To be implemented by inherited classes

    LMetaPointer* operator [](std::string n);
};

下面是类的实现

//-----------------------------------------------------------------------------
#define lua_checkmpointer(L)    (LMetaPointer*)luaL_checkudata(L, 1, LUA_METAPOINTER)
//-----------------------------------------------------------------------------
lua_State* LMetaPointer::m_lua = NULL;
std::map<LMetaPointer*, std::string> LMetaPointer::m_pointers;
const luaL_Reg LMetaPointer::m_lmembers[]   = { { NULL, NULL } };
const luaL_Reg LMetaPointer::m_lfunctions[] =
{
        { "__index",    LMetaPointer::__index    },
        { "__newindex", LMetaPointer::__newindex },
        { NULL, NULL }
};
//-----------------------------------------------------------------------------

LMetaPointer::LMetaPointer(lua_State* L) : m_name("")
{
    //-- Make sure we have created the metatable
    initialize(L);

    //-- Add this pointer as of kind LUA_METAPOINTER metatable. This bit of code
    //   might not be necessary here. (To be removed)
    lua_pushlightuserdata(m_lua, this);
    luaL_getmetatable(m_lua, LUA_METAPOINTER);
    lua_setmetatable(m_lua, -2);

    //-- Add myself to the map of all metapointers
    m_pointers[this] = m_name;
}
//-----------------------------------------------------------------------------

LMetaPointer::~LMetaPointer()
{
    //-- Remove myself from the map of metapointers
    std::map<LMetaPointer*, std::string>::iterator found = m_pointers.find(this);

    if(found != m_pointers.end())
        m_pointers.erase(found);
}
//-----------------------------------------------------------------------------

int LMetaPointer::__index(lua_State* L)
{
    //-- Obtain the object that called us and call its get method.
    //   Since get and set are pure virtual, all inherited classes of LMetaPointer
    //   must implement it, and, upon the call from here, the correct 'get' method
    //   will be called.
    LMetaPointer* p = lua_checkmpointer(L);
    return p->get(L);
}
//-----------------------------------------------------------------------------

int LMetaPointer::__newindex(lua_State* L)
{
    //-- Obtain the object that called us and call its set method
    //   Since get and set are pure virtual, all inherited classes of LMetaPointer
    //   must implement it, and, upon the call from here, the correct 'get' method
    //   will be called.
    LMetaPointer* p = lua_checkmpointer(L);
    return p->set(L);
}
//-----------------------------------------------------------------------------

void LMetaPointer::initialize(lua_State* L)
{
    //-- Only create the metatable the first time and instance of LMetaPointer is created
    if(!m_lua)
    {
        m_lua = L;

        luaL_newmetatable(m_lua, LUA_METAPOINTER);
        luaL_setfuncs(L, m_lfunctions, 0);
        luaL_newlib(L, m_lmembers);
    }
}
//-----------------------------------------------------------------------------

void LMetaPointer::setGlobal(std::string n)
{
    //-- Make myself (this) a global variable in LUA with name given by 'n'
    std::map<LMetaPointer*, std::string>::iterator found = m_pointers.find(this);

    if(found != m_pointers.end())
    {
        m_name = n;
        found->second = m_name;

        lua_pushlightuserdata(m_lua, this);
        luaL_getmetatable(m_lua, LUA_METAPOINTER);
        lua_setmetatable(m_lua, -2);
        lua_setglobal(m_lua, m_name.c_str());
    }
}
//-----------------------------------------------------------------------------

LMetaPointer* LMetaPointer::operator [](std::string n)
{
    //-- Simply for completeness, allow all metapointer access all other by their
    //   name. (Notice though that since names are only assigned to instances made
    //   global, this operator will only work properly when searching for a pointer
    //   made global. ALl othe rpointers have an empty name.
    std::map<LMetaPointer*, std::string>::iterator iter = m_pointers.begin();

    while(iter != m_pointers.end())
    {
        if(iter->second == n)
            return iter->first;
        ++iter;
    }

    return NULL;
}

现在,这个类将允许我定义任何其他结构或类,并将 LUA 指针(lightuserdata)传递给它,而无需混合方法或名称。对于我原始问题中的示例,这意味着定义以下内容:

注意:我已经扩展了我的示例,现在称为 LMeasLimit 是以前的 TLimitLMeasurement 完全是一个新类,LTest 是以前的 TMeaasurement

解决方案 - 实施

//-------------------------------------------------------------------------

struct LMeasLimit : public LMetaPointer
{
    bool   enabled;     //-- Is the limit enabled?
    double value;       //-- Limit value;

    LMeasLimit(lua_State* L) : LMetaPointer(L), enabled(false), value(0.0) {}
    ~LMeasLimit() {}

    int get(lua_State* L);   //-- Implements LMetaPointer::get
    int set(lua_State* L);   //-- Implements LMetaPointer::set
};
//-------------------------------------------------------------------------

struct LMeasurement : public LMetaPointer
{
    double      value;      //-- Measurement
    LStepResult result;     //-- Result of test
    std::string message;    //-- Message to display

    LMeasurement(lua_State* L) : LMetaPointer(L), value(0.0), result(srNothing), message("") {}
    ~LMeasurement() {}

    int get(lua_State* L);   //-- Implements LMetaPointer::get
    int set(lua_State* L);   //-- Implements LMetaPointer::set
};
//-------------------------------------------------------------------------

struct LTest : public LMetaPointer
{
    int          id;    //-- ID of test
    std::string  name;  //-- Name of test
    LMeasLimit   max;   //-- Max limit for measure
    LMeasLimit   min;   //-- Min limit for measure
    LMeasurement meas;  //-- Measurement

    LTest(lua_State* L) : LMetaPointer(L), id(0), name(""), min(L), max(L), meas(L) {}
    ~LTest() {}

    int get(lua_State* L);   //-- Implements LMetaPointer::get
    int set(lua_State* L);   //-- Implements LMetaPointer::set
};

//-----------------------------------------------------------------------------

以及为不同的类定义不同的方法

int LMeasLimit::get(lua_State* L)
{
    std::string key = std::string(luaL_checkstring(L, 2));

    if(key.find("enabled") == 0)
        lua_pushboolean(L, enabled);
    else if(key.find("value") == 0)
        lua_pushnumber(L, value);
    else
        return 0;

    return 1;
}
//-----------------------------------------------------------------------------

int LMeasLimit::set(lua_State* L)
{
    std::string key = std::string(luaL_checkstring(L, 2));

    if(key.find("enabled") == 0)
        enabled = lua_toboolean(L, 3);
    else if(key.find("value") == 0)
        value = lua_tonumber(L, 3);

    return 0;
}
//-----------------------------------------------------------------------------




int LMeasurement::get(lua_State* L)
{
    std::string key = std::string(luaL_checkstring(L, 2));

    if(key.find("value") == 0)
        lua_pushnumber(L, value);
    else if(key.find("result") == 0)
        lua_pushunsigned(L, result);
    else if(key.find("message") == 0)
        lua_pushstring(L, message.c_str());
    else
        return 0;

    return 1;
}
//-----------------------------------------------------------------------------

int LMeasurement::set(lua_State* L)
{
    std::string key = std::string(luaL_checkstring(L, 2));

    if(key.find("value") == 0)
        value = lua_tonumber(L, 3);
    else if(key.find("result") == 0)
        result = LStepResult(lua_tounsigned(L, 3));
    else if(key.find("message") == 0)
        message = std::string(lua_tostring(L, 3));

    return 0;
}
//-----------------------------------------------------------------------------



int LTest::get(lua_State* L)
{
    std::string key = std::string(luaL_checkstring(L, 2));

    if(key.find("id") == 0)
        lua_pushinteger(L, id);
    else if(key.find("name") == 0)
        lua_pushstring(L, name.c_str());
    else if(key.find("min") == 0)
    {
        lua_pushlightuserdata(L, &min);
        luaL_getmetatable(L, LUA_METAPOINTER);
        lua_setmetatable(L, -2);
    }
    else if(key.find("max") == 0)
    {
        lua_pushlightuserdata(L, &max);
        luaL_getmetatable(L, LUA_METAPOINTER);
        lua_setmetatable(L, -2);
    }
    else if(key.find("meas") == 0)
    {
        lua_pushlightuserdata(L, &meas);
        luaL_getmetatable(L, LUA_METAPOINTER);
        lua_setmetatable(L, -2);
    }
    else
        return 0;

    return 1;
}
//-----------------------------------------------------------------------------

int LTest::set(lua_State* L)
{
    std::string key = std::string(luaL_checkstring(L, 2));

    if(key.find("id") == 0)
        id = lua_tointeger(L, 3);
    else if(key.find("name") == 0)
        name = std::string(lua_tostring(L, 3));

    return 0;
}

解决方案 - 整合 最后的修改在我们原始问题的LEngine::runScript 中。

int LEngine::runScript(std::string scrName)
{
    luaInit();

    if(m_lua)
    {
        LTest tst(m_lua);

        tst.name = std::string("mierda_esta");
        tst.setGlobal("step");

        if(luaL_loadfile(m_lua, scrName.c_str()) || lua_pcall(m_lua, 0, 0, 0))
            processLuaError();
    }

    return 0;
}

最后,我将展示一个我用于测试的 LUA 脚本及其输出。

测试 - LUA 脚本

print("step.id          = " .. step.id)
print("step.name        = " .. step.name)
print(step.min.enabled)
print("step.min.value   = " .. step.min.value)


step.id = 1
step.name = "nombre del test";
step.min.enabled = true;
step.min.value   = 5.6;

print("step.id          = " .. step.id)
print("step.name        = " .. step.name)
print(step.min.enabled)
print("step.min.value   = " .. step.min.value)

测试 - 输出

step.id          = 0
step.name        = mierda_esta
false
step.min.value   = 0
step.id          = 1
step.name        = nombre del test
true
step.min.value   = 5.6

所以,现在一切似乎都如我所愿。我仍然需要修改这个LMetaPointer,以便现在能够以与我们在 C++ 中类似的方式调用任何继承类的成员函数。但这将是另一个故事。

再次感谢 @siffiejoe 和 @greatwolf 的宝贵时间和回复。

【讨论】:

    【解决方案2】:

    正如 cmets 中已经提到的,所有 lightuserdata 共享一个元表(请参阅here),因此所有 lightuserdata 值在任何时候都被完全相同地对待。如果您更改一个 lightuserdata 的元表,那么它会更改所有这些元表。这就是您的代码中发生的情况:

    1. LEngine::runScript 中,您可以使所有 lightuserdata 的行为类似于TMeasurement 对象。这对于全局变量 step 中的值是可以的。
    2. 当您第一次访问 step.min 时,您会使所有 lightuserdata 的行为类似于 TLimit 对象(在 lmeas_get 中)。这对于step.min 推送的值是可以的,但是现在step 中的值也表现得像TLimit,所以
    3. 当您第二次尝试访问step.min 时,step 充当TLimit 对象,因此它没有min 字段并返回nil

    Lightuserdata 根本不适合这项工作。参见例如this discussion 适用于可以使用 lightuserdata 的情况。对于其他一切,您需要完整的用户数据。与 lightuserdata 相比,这将分配一些额外的内存(抱歉,无能为力),但您可以进行一些缓存以避免生成过多的临时数据。

    因此,对于您的 step 值,您使用完整的用户数据来保存指向您的 TMeasurement 对象的指针。您还将一个新表设置为用户值(请参阅lua_setuservalue),它将充当子用户数据的缓存。当您使用 "min"/"max" 参数调用您的 lmeas_get 时,您使用相同的键查看 uservalue 表。如果您没有找到该字段的预先存在的用户数据,则创建一个新的完整用户数据,其中包含指向TLimit 子对象的指针(使用适当的元表),将其放入缓存中,然后返回它。如果您的对象生命周期在未来变得更加复杂,您应该添加从 TLimit 子用户数据到父 TMeasurement 用户数据的反向引用,以确保后者不会被垃圾收集,直到对前者的所有引用都被也消失了。您也可以为此使用用户值表。

    【讨论】:

    • 我现在明白了。非常感谢您的回复,并为您如此愚蠢而感到抱歉。我将重新编码所有内容以使其正常工作,如果有兴趣,我将发布一个工作设置。再次非常感谢您。
    猜你喜欢
    • 2013-01-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-08
    • 1970-01-01
    • 2019-02-18
    • 2014-09-27
    • 2020-05-28
    相关资源
    最近更新 更多