您可以将指向 C++ 值的指针作为元表的上值推送到包含这些全局变量的表中,而不是在 Lua 模块中使用全局值。然后将具有相同元表的globals 表推送到两个虚拟机。当您现在访问globals.num 时,将触发getglobal 和setglobal 元方法(取决于您是读还是写)。这些将更新 C++ 端的值,以便在两个 VM 之间共享。
注: 从冗长的样板文件中可以看出,这不是一个好的解决方案。您应该避免同时拥有多个虚拟机。如果您需要多个 VM 用于并发目的,请考虑使用像 Lua Lanes 这样的成熟库,而不是自己滚动(要做到这一点需要数千行代码)。
#include <string>
#include <lua.hpp>
int setglobal(lua_State *L) {
void *p = luaL_checkudata(L, 1, "globals_meta");
luaL_argcheck(L, p != nullptr, 1, "invalid userdata");
std::string key = lua_tostring(L, 2);
luaL_argcheck(L, key == "num", 2, "unknown global");
int value = luaL_checkinteger(L, 3);
luaL_argcheck(L, lua_isnumber(L, 3), 3, "not a number");
int *num = static_cast<int *>(lua_touserdata(L, lua_upvalueindex(1)));
*num = value;
lua_pop(L, 1);
return 0;
}
int getglobal(lua_State *L) {
void *p = luaL_checkudata(L, 1, "globals_meta");
luaL_argcheck(L, p != nullptr, 1, "invalid userdata");
std::string key = lua_tostring(L, 2);
luaL_argcheck(L, key == "num", 2, "unknown global");
int num = *static_cast<int *>(lua_touserdata(L, lua_upvalueindex(1)));
lua_pop(L, 1);
lua_pushinteger(L, num);
return 1;
}
static const struct luaL_Reg globals_meta[] = {
{"__newindex", setglobal},
{"__index", getglobal},
{nullptr, nullptr} // sentinel
};
int main() {
int num = 2;
// script A
lua_State *L1 = luaL_newstate();
luaL_openlibs(L1);
luaL_newmetatable(L1, "globals_meta");
lua_pushlightuserdata(L1, &num);
luaL_setfuncs(L1, globals_meta, 1);
lua_newuserdata(L1, 0);
luaL_getmetatable(L1, "globals_meta");
lua_setmetatable(L1, -2);
lua_setglobal(L1, "globals");
luaL_dostring(L1, "print('Script A: ' .. globals.num) globals.num = 5");
// script B
lua_State *L2 = luaL_newstate();
luaL_openlibs(L2);
luaL_newmetatable(L2, "globals_meta");
lua_pushlightuserdata(L2, &num);
luaL_setfuncs(L2, globals_meta, 1);
lua_newuserdata(L2, 0);
luaL_getmetatable(L2, "globals_meta");
lua_setmetatable(L2, -2);
lua_setglobal(L2, "globals");
luaL_dostring(L2, "print('Script B: ' .. globals.num)");
lua_close(L1);
lua_close(L2);
}
作为对自己的挑战,我实现了一个完整的全局表,它可以在两个 Lua 状态之间传递 nil、bool、int、double 和 string 类型的值。它们可以用任何具有字符串表示的东西来命名。
-- To be on the safe side, just use numbers and strings as keys
globals[1] = "x"
globals.num = 5
-- Be careful when using table or function literals as keys
-- Two empty tables don't have the same representation
globals[{}] = 2 -- "table: 0x10d55a0" = 2
globals[{}] = 1 -- "table: 0x10ce2c0" = 1
各种异常情况我都没有仔细检查过,所以不予退款!
#include <iostream>
#include <string>
#include <unordered_map>
#include <boost/variant.hpp>
#include <lua.hpp>
enum class nil {};
using Variant = boost::variant<nil, bool, int, double, std::string>;
int setglobal(lua_State *L) {
void *p = luaL_checkudata(L, 1, "globals_meta");
luaL_argcheck(L, p != nullptr, 1, "invalid userdata");
std::string key = luaL_tolstring(L, 2, nullptr);
auto &globals = *static_cast<std::unordered_map<std::string, Variant> *>(
lua_touserdata(L, lua_upvalueindex(1)));
Variant &v = globals[key];
switch (lua_type(L, 3)) {
case LUA_TNIL:
v = nil{};
break;
case LUA_TBOOLEAN:
v = static_cast<bool>(lua_toboolean(L, 3));
lua_pop(L, 1);
break;
case LUA_TNUMBER:
if (lua_isinteger(L, 3)) {
v = static_cast<int>(luaL_checkinteger(L, 3));
} else {
v = static_cast<double>(luaL_checknumber(L, 3));
}
lua_pop(L, 1);
break;
case LUA_TSTRING:
v = std::string(lua_tostring(L, 3));
lua_pop(L, 1);
break;
default:
std::string error = "Unsupported global type: ";
error.append(lua_typename(L, lua_type(L, 3)));
lua_pushstring(L, error.c_str());
lua_error(L);
break;
}
return 0;
}
int getglobal(lua_State *L) {
void *p = luaL_checkudata(L, 1, "globals_meta");
luaL_argcheck(L, p != nullptr, 1, "invalid userdata");
std::string key = luaL_tolstring(L, 2, nullptr);
auto globals = *static_cast<std::unordered_map<std::string, Variant> *>(
lua_touserdata(L, lua_upvalueindex(1)));
lua_pop(L, 1);
auto search = globals.find(key);
if (search == globals.end()) {
lua_pushstring(L, ("unknown global: " + key).c_str());
lua_error(L);
return 0;
}
Variant const &v = search->second;
switch (v.which()) {
case 0:
lua_pushnil(L);
break;
case 1:
lua_pushboolean(L, boost::get<bool>(v));
break;
case 2:
lua_pushinteger(L, boost::get<int>(v));
break;
case 3:
lua_pushnumber(L, boost::get<double>(v));
break;
case 4:
lua_pushstring(L, boost::get<std::string>(v).c_str());
break;
default: // Can't happen
std::abort();
break;
}
return 1;
}
static const struct luaL_Reg globals_meta[] = {
{"__newindex", setglobal},
{"__index", getglobal},
{nullptr, nullptr} // sentinel
};
int main() {
std::unordered_map<std::string, Variant> globals;
globals["num"] = 2;
// script A
lua_State *L1 = luaL_newstate();
luaL_openlibs(L1);
luaL_newmetatable(L1, "globals_meta");
lua_pushlightuserdata(L1, &globals);
luaL_setfuncs(L1, globals_meta, 1);
lua_newuserdata(L1, 0);
luaL_getmetatable(L1, "globals_meta");
lua_setmetatable(L1, -2);
lua_setglobal(L1, "globals");
if (luaL_dostring(L1, "print('Script A: ' .. globals.num)\n"
"globals.num = 5") != 0) {
std::cerr << "L1:" << lua_tostring(L1, -1) << '\n';
lua_pop(L1, 1);
}
// script B
lua_State *L2 = luaL_newstate();
luaL_openlibs(L2);
luaL_newmetatable(L2, "globals_meta");
lua_pushlightuserdata(L2, &globals);
luaL_setfuncs(L2, globals_meta, 1);
lua_newuserdata(L2, 0);
luaL_getmetatable(L2, "globals_meta");
lua_setmetatable(L2, -2);
lua_setglobal(L2, "globals");
if (luaL_dostring(L2, "print('Script B: ' .. globals.num)") != 0) {
std::cerr << "L1:" << lua_tostring(L2, -1) << '\n';
lua_pop(L2, 1);
}
lua_close(L1);
lua_close(L2);
}