问题
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 struct
s 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