Can you modify a C struct from lua?

亡梦爱人 提交于 2021-01-27 19:16:19

问题


I want to be able to have this Lua code:

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

And it should work with this C(++) code:

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

How would I push var onto the stack such that the lua code can modify its variables?


回答1:


Standard Lua doesn't have a built-in way to expose the contents of structs through tables, so doing so is a bit of a manual process. Here's an example of a complete program that does what you want:

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

To support more fields, you'd have to add them to MyStruct_index and MyStruct_newindex. If you want pairs to work on it, then you'd need to add a __pairs metamethod too. In general, though, exposing getters and setters is usually preferred over an approach like this one.

If you want to allocate your struct MyStruct somewhere other than inside of Lua, then you could add an extra layer of indirection everywhere it's used, like this:

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

But this is dangerous! If the Lua userdata outlives the object that it points to, Undefined Behavior will result!



来源:https://stackoverflow.com/questions/62095922/can-you-modify-a-c-struct-from-lua

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!