Lua、元表和全局变量

4
我正在改进我们处理Bitfighter机器人玩家的Lua脚本的方式。目前,每个机器人都有自己的L实例,我们试图通过交换环境表使它们共享一个L实例。请注意,机器人可能是完全不同的脚本。
我意识到这种方法在Lua 5.2中已被弃用,但我们目前使用的是lua-vec,它仍然使用Lua 5.1。游戏是用C++编写的。
因此...
首先,我们创建一个环境,并称其为:
// Create a table with room for 0 array and 1 non-array elements
lua_createtable(L, 0, 1);                 // -- tab

// Set the globals table to handle any requests that the 
// script's environment can't
lua_pushstring(L, "__index");             // -- tab, "__index"
lua_pushvalue(L, LUA_GLOBALSINDEX);       // -- tab, "__index", _G

// Set table["__index"] = _G, pops top two items from stack
lua_settable(L, -3);                      // -- tab

// Store the new table in the retistry for future use
lua_setfield(L, LUA_REGISTRYINDEX, name); // -- <<empty stack>>

稍后,我们将加载一些Lua代码,并调用环境表:
luaL_loadfile(L, "luascripts.lua");

lua_getfield(L, LUA_REGISTRYINDEX, name); // -- function, table 
lua_setfenv(L, -2);                       // -- function

然后运行加载的代码:

lua_pcall(L, 0, 0, 0);

当加载的 Lua 尝试使用基本函数,比如 print 时,会出现以下错误:
attempt to call global 'print' (a nil value) 

然而,脚本可以执行以下操作:

__index["print"](12)

那么...为什么我们无法直接访问打印功能呢?我们缺少了什么吗?或者有没有一种根本上更好的方法在同一个Lua实例中运行多个脚本?

1个回答

3
你的代码接近正确,但存在几个问题-你试图做一些行不通的事情,并且你的尝试以错误的方式做了错误的事情。
你正在将函数环境设置为一个表格,该表格看起来像这样:
{__index = _G}

当你尝试访问print时,它在这个表中找不到。

从你的评论中可以推断出,实际上你想要设置环境表的元表的__index字段。也就是说,你想让环境表像下面例子中的t一样:

t = {}
setmetatable(t, {__index = _G})

这个的C++翻译相当简单。

不要这样做。它可以解决你的直接问题,但是它不能提供足够的沙箱保护。例如,考虑以下脚本:

table.sort = 10

在metatable事件处理程序中,"table"可以在_G中找到。sort只是table表中的一个元素,因此可以毫不顾虑地替换它。现在,其他脚本将无法使用table.sort来对表进行排序。
执行这种分离的一种方法是通过一些类型的(可能是递归的)userdata以手写处理程序来调解访问所有全局值的方式。(这种方式可能具有最大的性能和控制潜力,但可能难以实现)
另一种方式是为每个脚本创建一个环境表,并将安全/沙盒化的元素从全局表复制到该环境表中(这样每个脚本都有全局表可变元素的完全独立版本)。 很抱歉我没有时间对您的问题提出的建议解决方案进行适当的解释。希望我给您提供了一些开始的地方。随时可以回来编辑此内容,包括您最终使用的解决方案。

感谢您的回复!“另一种方法是为每个脚本创建一个环境表...”这正是我想做的,尽管我没有想到为每个脚本提供全局表的副本。从您的示例中,我看到这将改善脚本安全性和隔离性,但我不确定如何实现这一点,也没有找到任何好的实现示例,尽管进行了相当多的研究。 - Watusimoto
@Watusimoto:其实很简单。你只需要将全局表中的每个白名单元素复制到环境表中(当处理可变元素时,确保正确地复制元素而不仅是引用)。我几天后会回来并提供一些适当的示例。(请提醒我,免得我忘记!) - Mankarse
谢谢。这应该相对简单,因为我的全局表已经限制在我希望脚本可以访问的函数和值上,所以这将是一个批量复制操作。 - Watusimoto

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接