当前位置 : 主页 > 网络编程 > lua >

Lua 5.2 – 对象中的C对象(使用lua_lightuserdata)

来源:互联网 收集:自由互联 发布时间:2021-06-23
编辑:[解答2中的解决方案] 我是LUA的新手,我很难尝试做我想做的事.我有一个看起来像这样的C对象: C对象定义 struct TLimit{ bool enabled; double value; TLimit() : enabled(false), value(0.0) {} ~TLimit(
编辑:[解答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< ...>位,它只是一个模板函数,它将在“安全模式”下执行<>中的函数…它将弹出一个MessaegBox时遇到错误)

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脚本时,我可以访问步骤没有问题,但我只能在第一次访问“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(本地e = step.min.enabled)函数还是__newindex函数(step.min.enabled = true)都没关系.

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

请帮忙……我搞砸的是什么?

谢谢你,对不起这篇长篇文章…我只是不知道如何缩短它.

正如评论中已经提到的,所有lightuserdata共享一个metatable(参见 here),因此所有lightuserdata值始终处理完全相同.如果更改了一个lightuserdata的metatable,那么它将针对所有这些更改.这就是你的代码中发生的事情:

>在LEngine :: runScript中,您使所有lightuserdata都像TMeasurement对象一样.这适用于全局变量步骤中的值.
>第一次访问step.min时,所有lightuserdata都表现得像TLimit对象(在lmeas_get中).这对于s​​tep.min推送的值是可以的,但是现在步骤中的值也表现得像TLimit,所以
>当您尝试第二次访问step.min时,step充当TLimit对象,因此它没有字段min并返回nil.

Lightuserdata根本不适合这项工作.参见例如this discussion适用于可以使用lightuserdata的情况.对于其他一切,您需要完整的用户数据.与lightuserdata相比,这将分配一些额外的内存(对不起,无法帮助),但你可以做一些缓存,以避免产生太多的临时数据.

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

网友评论