如何在 C++ 中将用户数据从一个 Lua 块传递到另一个块
How to pass userdata from one Lua chunk to another in C++
我正在尝试从 C++ 中的 Lua 脚本 (chunk A
) 中获取 userdata
(在我的示例中通过函数返回的变量),然后稍后将其传递userdata
从 C++ 返回 Lua 脚本 (chunk B
)(在我的示例中通过函数参数),因此 userdata
可以在 chunk B
中使用,因为它是在 chunk A
.
MyBindings.h
class Vec2
{
public:
Vec2():x(0), y(0){};
Vec2(float x, float y):x(x), y(y){};
float x, y;
};
MyBindings.i
%module my
%{
#include "MyBindings.h"
%}
%include "MyBindings.h"
main.cpp
#include <iostream>
#include <lua.hpp>
extern "C"
{
int luaopen_my(lua_State *L);
}
int main()
{
lua_State *L = luaL_newstate();
luaL_openlibs(L);
luaopen_my(L);
lua_settop(L, 0);
/* chunk A */
luaL_dostring(L, "local vec2 = my.Vec2(3, 4)\n"
"function setup()\n"
"return vec2\n"
"end\n");
/* chunk B */
luaL_dostring(L, "function test(p)\n"
"print(p.x)\n"
"end\n");
void *userDataPtr = nullptr;
/* call setup function */
int top = lua_gettop(L);
lua_getglobal(L, "setup");
if (lua_pcall(L, 0, LUA_MULTRET, 0))
{
std::cout << lua_tostring(L, -1) << '\n';
lua_pop(L, 1);
}
/* check the return value */
if (lua_gettop(L) - top)
{
/* store userdata to a pointer */
if (lua_isuserdata(L, -1))
userDataPtr = lua_touserdata(L, -1);
}
/* check if userDataPtr is valid */
if (userDataPtr != nullptr)
{
/* call test function */
lua_getglobal(L, "test");
lua_pushlightuserdata(L, userDataPtr); /* pass userdata as an argument */
if (lua_pcall(L, 1, 0, 0))
{
std::cout << lua_tostring(L, -1) << '\n';
lua_pop(L, 1);
}
}
lua_close(L);
}
我得到的结果:
[string "local vec2 = my.Vec2(3, 4)..."]:6: attempt to index a
userdata value (local 'p')
我期望的结果:
3
是否可以从 chunk A
获取 userdata
然后将其传递给 chunk B
以便它可以像在 chunk A
中一样使用?
当您获得指向 userdata 数据的原始指针并将其作为 lightuserdata 推送到参数时,您将丢失有关对象类型的所有信息。 lightuserdata 甚至没有单独的 metatables.
正确的方法是按原样传递 Lua 值。将原始返回值保留在 Lua 堆栈上,或将其复制到其他 Lua 容器中(您的 Lua table 临时容器,或 Lua 注册表),然后复制Lua 堆栈上的那个值将其作为参数传递。这样您就不必了解任何有关绑定实现的知识。您甚至不必关心这是用户数据还是任何其他 Lua 类型。
根据您的代码,这可能如下所示:
#include <iostream>
#include <lua.hpp>
extern "C"
{
int luaopen_my(lua_State *L);
}
int main()
{
lua_State *L = luaL_newstate();
luaL_openlibs(L);
/* chunk A */
luaL_dostring(L, "local vec2 = {x=3, y=4}\n"
"function setup()\n"
"return vec2\n"
"end\n");
/* chunk B */
luaL_dostring(L, "function test(p)\n"
"print(p.x)\n"
"end\n");
/* call setup function */
int top = lua_gettop(L);
lua_getglobal(L, "setup");
if (lua_pcall(L, 0, LUA_MULTRET, 0))
{
std::cout << lua_tostring(L, -1) << '\n';
lua_pop(L, 1);
exit(EXIT_FAILURE); // simpy fail for demo
}
/* check the return value */
if (lua_gettop(L) - top)
{
// the top now contains the value returned from setup()
/* call test function */
lua_getglobal(L, "test");
// copy the original value as argument
lua_pushvalue(L, -2);
if (lua_pcall(L, 1, 0, 0))
{
std::cout << lua_tostring(L, -1) << '\n';
lua_pop(L, 1);
exit(EXIT_FAILURE);
}
// drop the original value
lua_pop(L, 1);
}else
{
// nothing is returned, nothing to do
}
lua_close(L);
}
除了其他答案之外,我还想展示一个变体,您可以在其中存储对 Lua 注册表中值的引用。这种方法的优点是您不必将值保留在堆栈上并考虑偏移量是多少。另请参阅“在 Lua 中编程”中的 27.3.2 – References。
此方法使用三个函数:
int luaL_ref (lua_State *L, int t);
从堆栈弹出最上面的值,将其存储到索引 t
的 table 和 returns 值在 table 中的索引处。因此,为了在注册表中保存一个值,我们使用
userDataRef = luaL_ref(L, LUA_REGISTRYINDEX);
int lua_rawgeti (lua_State *L, int index, lua_Integer n);
将 index
处的 table 的元素 n
的值压入堆栈(Lua 中的 t[n]
)。因此,要从注册表中检索索引 userDataRef
处的值,我们使用
lua_rawgeti(L, LUA_REGISTRYINDEX, userDataRef);
void luaL_unref (lua_State *L, int t, int ref);
删除存储在 t
的 table 中的索引 ref
处的引用,以便可以对引用进行垃圾回收并重新使用索引 ref
。因此,要从注册表中删除引用 userDataRef
,我们使用
luaL_unref(L, LUA_REGISTRYINDEX, userDataRef);
#include <iostream>
#include <lua.hpp>
extern "C" {
int luaopen_my(lua_State *L);
}
int main() {
lua_State *L = luaL_newstate();
luaL_openlibs(L);
luaopen_my(L);
lua_settop(L, 0);
/* chunk A */
luaL_dostring(L, "local vec2 = my.Vec2(3, 4)\n"
"function setup()\n"
"return vec2\n"
"end\n");
/* chunk B */
luaL_dostring(L, "function test(p)\n"
"print(p.x)\n"
"end\n");
int userDataRef = LUA_NOREF;
/* call setup function */
int top = lua_gettop(L);
lua_getglobal(L, "setup");
if (lua_pcall(L, 0, LUA_MULTRET, 0)) {
std::cout << lua_tostring(L, -1) << '\n';
lua_pop(L, 1);
}
/* check the return value */
if (lua_gettop(L) - top) {
/* store userdata to a pointer */
userDataRef = luaL_ref(L, LUA_REGISTRYINDEX);
}
/* check if userDataRef is valid */
if (userDataRef != LUA_NOREF && userDataRef != LUA_REFNIL) {
/* call test function */
lua_getglobal(L, "test");
lua_rawgeti(L, LUA_REGISTRYINDEX, userDataRef);
/* free the registry slot (if you are done) */
luaL_unref(L, LUA_REGISTRYINDEX, userDataRef);
if (lua_pcall(L, 1, 0, 0)) {
std::cout << lua_tostring(L, -1) << '\n';
lua_pop(L, 1);
}
}
lua_close(L);
}
也许您想查看 Lua-C-API 的 Sol2 包装器。它可以用最少的样板文件做你想做的事。但是,它需要 C++14。
#include <iostream>
#define SOL_CHECK_ARGUMENTS 1
#include <sol.hpp>
extern "C" int luaopen_my(lua_State *L);
int main() {
sol::state L;
L.open_libraries();
luaopen_my(L);
/* chunk A */
L.script("local vec2 = my.Vec2(3, 4)\n"
"function setup()\n"
"return vec2\n"
"end\n");
/* chunk B */
L.script("function test(p)\n"
"print(p.x)\n"
"end\n");
auto userDataRef = L["setup"]();
L["test"](userDataRef);
}
我正在尝试从 C++ 中的 Lua 脚本 (chunk A
) 中获取 userdata
(在我的示例中通过函数返回的变量),然后稍后将其传递userdata
从 C++ 返回 Lua 脚本 (chunk B
)(在我的示例中通过函数参数),因此 userdata
可以在 chunk B
中使用,因为它是在 chunk A
.
MyBindings.h
class Vec2
{
public:
Vec2():x(0), y(0){};
Vec2(float x, float y):x(x), y(y){};
float x, y;
};
MyBindings.i
%module my
%{
#include "MyBindings.h"
%}
%include "MyBindings.h"
main.cpp
#include <iostream>
#include <lua.hpp>
extern "C"
{
int luaopen_my(lua_State *L);
}
int main()
{
lua_State *L = luaL_newstate();
luaL_openlibs(L);
luaopen_my(L);
lua_settop(L, 0);
/* chunk A */
luaL_dostring(L, "local vec2 = my.Vec2(3, 4)\n"
"function setup()\n"
"return vec2\n"
"end\n");
/* chunk B */
luaL_dostring(L, "function test(p)\n"
"print(p.x)\n"
"end\n");
void *userDataPtr = nullptr;
/* call setup function */
int top = lua_gettop(L);
lua_getglobal(L, "setup");
if (lua_pcall(L, 0, LUA_MULTRET, 0))
{
std::cout << lua_tostring(L, -1) << '\n';
lua_pop(L, 1);
}
/* check the return value */
if (lua_gettop(L) - top)
{
/* store userdata to a pointer */
if (lua_isuserdata(L, -1))
userDataPtr = lua_touserdata(L, -1);
}
/* check if userDataPtr is valid */
if (userDataPtr != nullptr)
{
/* call test function */
lua_getglobal(L, "test");
lua_pushlightuserdata(L, userDataPtr); /* pass userdata as an argument */
if (lua_pcall(L, 1, 0, 0))
{
std::cout << lua_tostring(L, -1) << '\n';
lua_pop(L, 1);
}
}
lua_close(L);
}
我得到的结果:
[string "local vec2 = my.Vec2(3, 4)..."]:6: attempt to index a userdata value (local 'p')
我期望的结果:
3
是否可以从 chunk A
获取 userdata
然后将其传递给 chunk B
以便它可以像在 chunk A
中一样使用?
当您获得指向 userdata 数据的原始指针并将其作为 lightuserdata 推送到参数时,您将丢失有关对象类型的所有信息。 lightuserdata 甚至没有单独的 metatables.
正确的方法是按原样传递 Lua 值。将原始返回值保留在 Lua 堆栈上,或将其复制到其他 Lua 容器中(您的 Lua table 临时容器,或 Lua 注册表),然后复制Lua 堆栈上的那个值将其作为参数传递。这样您就不必了解任何有关绑定实现的知识。您甚至不必关心这是用户数据还是任何其他 Lua 类型。
根据您的代码,这可能如下所示:
#include <iostream>
#include <lua.hpp>
extern "C"
{
int luaopen_my(lua_State *L);
}
int main()
{
lua_State *L = luaL_newstate();
luaL_openlibs(L);
/* chunk A */
luaL_dostring(L, "local vec2 = {x=3, y=4}\n"
"function setup()\n"
"return vec2\n"
"end\n");
/* chunk B */
luaL_dostring(L, "function test(p)\n"
"print(p.x)\n"
"end\n");
/* call setup function */
int top = lua_gettop(L);
lua_getglobal(L, "setup");
if (lua_pcall(L, 0, LUA_MULTRET, 0))
{
std::cout << lua_tostring(L, -1) << '\n';
lua_pop(L, 1);
exit(EXIT_FAILURE); // simpy fail for demo
}
/* check the return value */
if (lua_gettop(L) - top)
{
// the top now contains the value returned from setup()
/* call test function */
lua_getglobal(L, "test");
// copy the original value as argument
lua_pushvalue(L, -2);
if (lua_pcall(L, 1, 0, 0))
{
std::cout << lua_tostring(L, -1) << '\n';
lua_pop(L, 1);
exit(EXIT_FAILURE);
}
// drop the original value
lua_pop(L, 1);
}else
{
// nothing is returned, nothing to do
}
lua_close(L);
}
除了其他答案之外,我还想展示一个变体,您可以在其中存储对 Lua 注册表中值的引用。这种方法的优点是您不必将值保留在堆栈上并考虑偏移量是多少。另请参阅“在 Lua 中编程”中的 27.3.2 – References。
此方法使用三个函数:
int luaL_ref (lua_State *L, int t);
从堆栈弹出最上面的值,将其存储到索引
t
的 table 和 returns 值在 table 中的索引处。因此,为了在注册表中保存一个值,我们使用userDataRef = luaL_ref(L, LUA_REGISTRYINDEX);
int lua_rawgeti (lua_State *L, int index, lua_Integer n);
将
index
处的 table 的元素n
的值压入堆栈(Lua 中的t[n]
)。因此,要从注册表中检索索引userDataRef
处的值,我们使用lua_rawgeti(L, LUA_REGISTRYINDEX, userDataRef);
void luaL_unref (lua_State *L, int t, int ref);
删除存储在
t
的 table 中的索引ref
处的引用,以便可以对引用进行垃圾回收并重新使用索引ref
。因此,要从注册表中删除引用userDataRef
,我们使用luaL_unref(L, LUA_REGISTRYINDEX, userDataRef);
#include <iostream>
#include <lua.hpp>
extern "C" {
int luaopen_my(lua_State *L);
}
int main() {
lua_State *L = luaL_newstate();
luaL_openlibs(L);
luaopen_my(L);
lua_settop(L, 0);
/* chunk A */
luaL_dostring(L, "local vec2 = my.Vec2(3, 4)\n"
"function setup()\n"
"return vec2\n"
"end\n");
/* chunk B */
luaL_dostring(L, "function test(p)\n"
"print(p.x)\n"
"end\n");
int userDataRef = LUA_NOREF;
/* call setup function */
int top = lua_gettop(L);
lua_getglobal(L, "setup");
if (lua_pcall(L, 0, LUA_MULTRET, 0)) {
std::cout << lua_tostring(L, -1) << '\n';
lua_pop(L, 1);
}
/* check the return value */
if (lua_gettop(L) - top) {
/* store userdata to a pointer */
userDataRef = luaL_ref(L, LUA_REGISTRYINDEX);
}
/* check if userDataRef is valid */
if (userDataRef != LUA_NOREF && userDataRef != LUA_REFNIL) {
/* call test function */
lua_getglobal(L, "test");
lua_rawgeti(L, LUA_REGISTRYINDEX, userDataRef);
/* free the registry slot (if you are done) */
luaL_unref(L, LUA_REGISTRYINDEX, userDataRef);
if (lua_pcall(L, 1, 0, 0)) {
std::cout << lua_tostring(L, -1) << '\n';
lua_pop(L, 1);
}
}
lua_close(L);
}
也许您想查看 Lua-C-API 的 Sol2 包装器。它可以用最少的样板文件做你想做的事。但是,它需要 C++14。
#include <iostream>
#define SOL_CHECK_ARGUMENTS 1
#include <sol.hpp>
extern "C" int luaopen_my(lua_State *L);
int main() {
sol::state L;
L.open_libraries();
luaopen_my(L);
/* chunk A */
L.script("local vec2 = my.Vec2(3, 4)\n"
"function setup()\n"
"return vec2\n"
"end\n");
/* chunk B */
L.script("function test(p)\n"
"print(p.x)\n"
"end\n");
auto userDataRef = L["setup"]();
L["test"](userDataRef);
}