Lua 5.2 — объект C ++ внутри объекта (с использованием lua_lightuserdata)

редактировать: [РЕШЕНИЕ В ОТВЕТЕ 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. (Я не буду предоставлять в этом примере функцию set для члена «measure»).

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, я могу получить доступ к шагу без проблем, но я могу получить доступ к memebr только в течение «min» или «max» в первый раз … любой последующий доступ дает ошибку.

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 при первом обращении к минимальному методу объекта step. Он каким-то образом «заменяет» «указатель на шаг» от LUA_MEASUREMENT, метатируемого на LUA_MEAS_LIMIT … и я просто не знаю почему.

Пожалуйста, помогите … что я так запутался?

Спасибо и извините за длинный пост … Я просто не знаю, как сделать его короче.

4

Решение

Как уже упоминалось в комментариях, все lightuserdata разделяют одну метатаблицу (см. Вот), поэтому все значения 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 просто не правильный инструмент для работы. Смотрите, например это обсуждение для случаев, когда могут использоваться lightuserdata. Для всего остального вам нужны полные пользовательские данные. Это выделит некоторую дополнительную память по сравнению с lightuserdata (извините, ничего не поделаешь), но вы можете выполнить некоторое кэширование, чтобы избежать создания слишком большого количества временных файлов.

Так что для вашего step значение, которое вы используете полные пользовательские данные, содержащие указатель на ваш TMeasurement объект. Вы также устанавливаете новую таблицу как пользовательское значение (см. lua_setuservalue), который будет действовать как кэш для данных субпользователя. Когда ваш lmeas_get называется с "min"/"max" В качестве аргумента вы смотрите в таблицу значений пользователя, используя тот же ключ. Если вы не нашли ранее существующие пользовательские данные для этого поля, вы создаете новые полные пользовательские данные, содержащие указатель на TLimit подобъект (используя соответствующий метатабельный объект), поместите его в кеш и верните. Если время жизни вашего объекта в будущем станет более сложным, вы должны добавить обратную ссылку из TLimit субпользовательские данные для родителя TMeasurement userdata, чтобы гарантировать, что последний не будет собирать мусор, пока не исчезнут все ссылки на первый. Вы также можете использовать таблицы пользовательских значений для этого.

1

Другие решения

Прежде всего, спасибо @siffiejoe и @greatwolf за их посты. Именно они объяснили мне, что я делаю неправильно.

Теперь мое решение. Я почти уверен, что это решение далеко не самое лучшее, но пока оно покрывает мои потребности. Если у кого-то есть предложения, посмотрите / найдите потенциальные ошибки или просто прокомментируйте, пожалуйста, сделайте это.

Решение — идея

Поскольку в LUA все lightuserdata имеют одинаковую метатабельность, я решил сделать все структуры и классы, которым я хочу передать указатель lightuserdata в LUA, одинаковое наследование от общего класса, который я назвал LMetaPointer, Этот класс опубликует метатаблицу и отображает __index а также __newindex на данные статические методы LMetaPointer::__index а также LMetaPointer::__newindex, Класс также содержит static std::map (список) указателей на все экземпляры LMetaPointer которые когда-либо были созданы. Конструктор класса гарантирует, что вновь созданный экземпляр будет добавлен на эту карту.

Всякий раз, когда в Луа, метаметод __index или же __newindex называется, соответствующий LMetaPointer::__index или же LMetaPointer::__newindex выполнен. Эти методы ищут на карте соответствующий указатель, который отвечает за вызов метода и вызывает свой собственный get или же set методы, которые определены как чисто виртуальные в 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 предыдущий TLimit, LMeasurement это новый класс в целом и 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

По вопросам рекламы [email protected]