Можете ли вы изменить структуру C с lua? - PullRequest
2 голосов
/ 30 мая 2020

Я хочу иметь этот Lua код:

function myfunc(s)
    print(s.value)
    s.value = 7
end

И он должен работать с этим C (++) кодом:

struct MyStruct {
    float value;
};

void func() {
    MyStruct var;
    var.value = 5;

    lua_getglobal(L, "myfunc");
    // push `var` to lua, somehow

    lua_call(L, 1, 0); // prints "5"

    // now, var.value is 7
    assert(var.value == 7);
}

Как Могу ли я поместить sh var в стек, чтобы код lua мог изменять свои переменные?

1 Ответ

2 голосов
/ 30 мая 2020

Стандартный Lua не имеет встроенного способа раскрытия содержимого struct через таблицы, поэтому это немного ручной процесс. Вот пример полной программы, которая делает то, что вы хотите:

#include <assert.h>
#include <string.h>

#include <lua5.3/lua.h>
#include <lua5.3/lualib.h>
#include <lua5.3/lauxlib.h>

struct MyStruct {
    float value;
};

int MyStruct_index(lua_State *L) {
    struct MyStruct *t = (struct MyStruct *)luaL_checkudata(L, 1, "MyStruct");
    const char *k = luaL_checkstring(L, 2);
    if(!strcmp(k, "value")) {
        lua_pushnumber(L, t->value);
    } else {
        lua_pushnil(L);
    }
    return 1;
}

int MyStruct_newindex(lua_State *L) {
    struct MyStruct *t = (struct MyStruct *)luaL_checkudata(L, 1, "MyStruct");
    const char *k = luaL_checkstring(L, 2);
    if(!strcmp(k, "value")) {
        t->value = luaL_checknumber(L, 3);
        return 0;
    } else {
        return luaL_argerror(L, 2,
                             lua_pushfstring(L, "invalid option '%s'", k));
    }
}

struct MyStruct *MyStruct_new(lua_State *L) {
    struct MyStruct *var = (struct MyStruct *)lua_newuserdata(L, sizeof *var);
    luaL_setmetatable(L, "MyStruct");
    return var;
}

void func(lua_State *L) {
    // Since this is allocated inside of Lua, it will be garbage collected,
    // so we don't need to worry about freeing it
    struct MyStruct *var = MyStruct_new(L);
    var->value = 5;

    lua_getglobal(L, "myfunc");
    lua_pushvalue(L, -2); // push `var` to lua, somehow

    lua_call(L, 1, 0); // prints "5"

    // now, var->value is 7
    assert(var->value == 7);
}

int main(void) {
    lua_State *L = luaL_newstate();
    luaL_openlibs(L);

    // One-time setup that needs to happen before you first call MyStruct_new
    luaL_newmetatable(L, "MyStruct");
    lua_pushcfunction(L, MyStruct_index);
    lua_setfield(L, -2, "__index");
    lua_pushcfunction(L, MyStruct_newindex);
    lua_setfield(L, -2, "__newindex");
    lua_pop(L, 1);

    luaL_dostring(L, "function myfunc(s) print(s.value) s.value = 7 end");
    func(L);

    lua_close(L);
    return 0;
}

Чтобы поддерживать больше полей, вам нужно добавить их в MyStruct_index и MyStruct_newindex. Если вы хотите, чтобы pairs работал над ним, вам также нужно добавить метаметод __pairs. В целом, тем не менее, использование геттеров и сеттеров обычно предпочтительнее, чем такой подход. косвенного обращения везде, где он используется, например:

#include <assert.h>
#include <string.h>

#include <lua5.3/lua.h>
#include <lua5.3/lualib.h>
#include <lua5.3/lauxlib.h>

struct MyStruct {
    float value;
};

int MyStruct_index(lua_State *L) {
    struct MyStruct **t = (struct MyStruct **)luaL_checkudata(L, 1, "MyStruct");
    const char *k = luaL_checkstring(L, 2);
    if(!strcmp(k, "value")) {
        lua_pushnumber(L, (*t)->value);
    } else {
        lua_pushnil(L);
    }
    return 1;
}

int MyStruct_newindex(lua_State *L) {
    struct MyStruct **t = (struct MyStruct **)luaL_checkudata(L, 1, "MyStruct");
    const char *k = luaL_checkstring(L, 2);
    if(!strcmp(k, "value")) {
        (*t)->value = luaL_checknumber(L, 3);
        return 0;
    } else {
        return luaL_argerror(L, 2,
                             lua_pushfstring(L, "invalid option '%s'", k));
    }
}

void MyStruct_push(lua_State *L, struct MyStruct *var) {
    struct MyStruct **t = (struct MyStruct **)lua_newuserdata(L, sizeof *t);
    *t = var;
    luaL_setmetatable(L, "MyStruct");
}

void func(lua_State *L) {
    struct MyStruct var;
    var.value = 5;

    lua_getglobal(L, "myfunc");
    MyStruct_push(L, &var); // push `var` to lua, somehow

    lua_call(L, 1, 0); // prints "5"

    // now, var.value is 7
    assert(var.value == 7);

    // WARNING! `var` is about to go out of scope! If Lua uses the userdata
    // that references it beyond this point, it's Undefined Behavior! Make
    // sure that it didn't keep a copy of it!
}

int main(void) {
    lua_State *L = luaL_newstate();
    luaL_openlibs(L);

    // One-time setup that needs to happen before you first call MyStruct_new
    luaL_newmetatable(L, "MyStruct");
    lua_pushcfunction(L, MyStruct_index);
    lua_setfield(L, -2, "__index");
    lua_pushcfunction(L, MyStruct_newindex);
    lua_setfield(L, -2, "__newindex");
    lua_pop(L, 1);

    luaL_dostring(L, "function myfunc(s) print(s.value) s.value = 7 end");
    func(L);

    lua_close(L);
    return 0;
}

Но это опасно! Если пользовательские данные Lua переживают объект, на который они указывают, результатом будет Undefined Behavior!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...