资讯动态

sol2 的 sol::environment 完全指南:用 Lua 表实现脚本沙箱与运行环境隔离(Piccolo 引擎)

发布时间:2026/10/16 6:29:16 来源:尧图企业网站定制
游戏开发图形学【免费下载链接】PiccoloPiccolo (formerly Pilot) – mini game engine for games104项目地址https://gitcode.com/gh_mirrors/pi/Piccolo点击查看免费下载本文以 sol2-3.3.0 官方 API 文档 environment.rst 为核心结合 Piccolo 引擎第三方库目录下的 sol2 源码、示例与测试系统讲解sol::environment的构造、set_on/set_environment/get_environment的底层实现、在sol::state脚本执行中的沙箱用法以及 Lua 5.1 与 5.2 两代运行时的环境机制差异。读完本文你将掌握如何为同一 Lua 状态创建互不污染、可回退到全局表的隔离运行环境并能在 C 侧安全地检索与注入环境。一、概述sol::environment是什么sol::environment是 sol2 提供的脚本沙箱封装类型官方文档将其定位为 encapsulation table for script sandboxing。它的核心类型关系如下class environment : public table;即environment直接继承自sol::table而sol::table又继承自sol::reference参考 environment.hpp 中basic_environment : basic_tablebase_type的定义。因此environment本质上就是一张普通 Lua 表sol::table支持的所有表操作读写字段、遍历、调用元方法等见 table 文档对它全部适用由于继承链的引用语义把一个environment拷贝给另一个environment不会深拷贝表内容只是创建了一个指向同一 Lua 对象的新引用。沙箱的核心机制该类型会被传递给sol::state(_view)的script/do_x系列函数详见 state 文档用于提供脚本运行时的局部变量读写环境。借助表的元表机制你可以做出非常灵活的隔离策略例如用 sol::metatable_key 为环境表设置元表让元表的__index指向全局表_G从而实现只读地查询全局变量、但不能直接写入全局变量——脚本写入var x时只会落在当前环境表中而读取时若环境表中不存在则回退到全局表查找。适用对象sol::environment可用于脚本调用script calls作为lua.script(code, env)的第二个参数函数set on functions通过sol::set_environment(env, fn)或env.set_on(fn)挂接到 Lua 函数上线程threads同样可应用于 sol::thread协程。二、构造环境sol::create与四种构造函数sol::environment的构造函数来自文档 members 一节environment(lua_State* L, sol::new_table nt); environment(lua_State* L, sol::new_table nt, const sol::reference fallback); environment(sol::env_key_t, const sol::reference object_that_has_environment); environment(sol::env_key_t, const sol::stack_reference object_that_has_environment);前两个沿用sol::table的构造方式特别常用的是sol::environment my_env(lua, sol::create);这会创建一张全新的、未命名的环境表。其中sol::create是sol::new_table类型的便捷常量其内部带有sequence_hint/map_hint两个容量提示参数帮助 Lua 预分配合适大小的表结构99% 的场景直接用sol::create即可详见 new_table 文档。带 fallback 的构造回退表sol::environment my_env(lua, sol::create, my_fallback_table);第三个参数fallback作为环境表的查找回退当环境表中找不到某个键时转而去 fallback 中查询。其实现位于 environment.hpptemplate bool b basic_environment(lua_State* L, new_table t, const basic_referenceb fallback) : basic_environment(L, std::move(t)) { stack_table mt(L, new_table(0, 1)); mt.set(meta_function::index, fallback); this-set(metatable_key, mt); mt.pop(); }也就是说sol2 只是简单地新建一张元表、把__index指向 fallback、再把这张元表挂到环境表上。理解了这一点你就可以通过 sol::metatable_key 自定义元表实现更复杂的效果比如组合多个回退、接入代理逻辑等。sol::env_key构造从已有对象提取环境后两个构造函数接收一个特殊空类型sol::env_key_t其唯一的用途就是触发该构造函数配合便捷常量sol::env_key使用sol::environment my_env(sol::env_key, some_object);它会从第二个参数可能是sol::object、sol::function、sol::table、sol::userdata等任何引用类型中提取其所属的环境。若目标对象没有环境构造函数仍会完成但该对象会引用 Lua 的nil即env.valid() false。从源码看env_key_t构造和get_environment最终都依赖stack::push_environment_of见 stack_push.hpp在 Lua 5.1 下直接调用lua_getfenv在 5.2 下则遍历 upvalue 搜索名为_ENV的变量C 函数则取第一个空名字 upvalue找不到时压入lua_nil。这正是没有环境则 valid() 为 false的来源。三、应用环境set_on与sol::set_environment设置环境有两种等价写法sol::set_environment(my_env, some_reference); // 等价于 my_env.set_on(some_reference);文档给出的签名template typename T void set_on(const T target);set_on的底层实现environment.hpp充分体现了 Lua 两个版本的差异Lua 5.1 及更早调用lua_setfenv(L, target_index)直接为目标设置函数环境返回非 0 即成功Lua 5.2不再有setfenv全局函数环境是函数的一个 upvalue。sol2 的实现分为两种情况若目标是C 函数环境总是放在第一个 upvaluesol2 约定所有 upvalue 名称为空字符串通过lua_setupvalue(L, target_index, 1)写入若目标是Lua 函数则从第 1 个 upvalue 开始逐个调用lua_getupvalue搜索名为_ENV的 upvalue找到后替换之找不到则返回false。版本兼容性注意事项文档明确提醒Lua 5.1 只允许将环境应用到 userdata、thread 和 function 上5.2 的规则则更为宽松几乎所有可挂 upvalue 的对象都可行。编写跨版本代码时应留意这一差异。四、读取环境sol::get_environmenttemplate typename E reference, typename T basic_environmentE get_environment(const T target);自由函数get_environment从目标对象中取回环境返回basic_environmentE默认为E reference即sol::environment。其实现environment.hpp就是一次stack::push_environment_of加一次引用包装template typename E reference, typename T basic_environmentE get_environment(const T target) { lua_State* L target.lua_state(); auto pp stack::pop_n(L, stack::push_environment_of(target)); return basic_environmentE(L, -1); }文档指出每个普通 Lua 函数、可执行脚本等都有环境在某些版本的 Lua 运行时中 userdata 也有环境。如果目标没有有效环境创建出的 environment 的valid()会返回false因此使用前建议用env.valid()做检查。五、实战示例从官方 example 到仓库测试1. 基础沙箱preparing_environments.cpp文档正文引用的完整示例位于 preparing_environments.cpp#define SOL_ALL_SAFETIES_ON 1 #include sol/sol.hpp int main(int, char*[]) { sol::state lua; lua.open_libraries(); sol::environment my_env(lua, sol::create); // set value, and we need to explicitly allow for // access to print, since a new environment hides // everything thats not defined inside of it // NOTE: hiding also hides library functions (!!) // BE WARNED my_env[var] 50; my_env[print] lua[print]; sol::environment my_other_env( lua, sol::create, lua.globals()); // do not need to explicitly allow access to print, // since we used the Set a fallback version // of the sol::environment constructor my_other_env[var] 443; // output: 50 lua.script(print(var), my_env); // output: 443 lua.script(print(var), my_other_env); return 0; }运行输出50 443这个例子揭示了沙箱的两个关键行为全新环境是隔离但不继承的my_env中没定义的东西包括print等所有库函数都不可见所以必须手动my_env[print] lua[print]放行带 fallback 的环境自动继承全局表my_other_env把lua.globals()作为 fallbackprint等全局资源天然可用无需逐个授权。2. 全局回退的验证environments.cpp示例 environments.cpp 演示了能读全局、不能写全局的效果先在全局设置lua[b] 2142随后sol::environment plain_env(lua, sol::create); lua.script(a 24, plain_env); // 只写入 plain_env sol::environment env_with_fallback(lua, sol::create, lua.globals()); lua.script(a 56, env_with_fallback); // 只写入 env_with_fallback对两个环境分别检查变量a与b后可以看到脚本在环境内赋值的a只存在于各自环境表中而全局变量b对带 fallback 的环境可读、对无 fallback 的环境不可见。3. 函数级环境environments_on_functions.cpp示例 environments_on_functions.cpp 展示了把环境挂到函数上的完整流程lua.script(f function() return test end); sol::function f lua[f]; sol::environment env_f(lua, sol::create); env_f[test] 31; bool ok sol::set_environment(env_f, f); // f() 现在返回环境表中的值 31 lua.script(g function() test 5 end); sol::function g lua[g]; sol::environment env_g(lua, sol::create); env_g.set_on(g); // 等价写法 g(); // 写入的是 env_g[test] // 全局表未被污染lua[test] 是无效对象它同时演示了从 C 侧反查环境// 方式一env_key 构造 sol::environment target_env(sol::env_key, target); // 方式二自由函数 sol::environment target_env sol::get_environment(target);两种方式都能取回与env_f/env_g相同的环境示例中用sol_c_assert(env_f target_env)断言验证。4. 仓库测试佐证this_environment.cpp在 tests/environment/source/this_environment.cpp 中environments/this_environment测试用例验证了环境隔离与回退的数值语义lua[x] 5、e[x] 20时以环境e执行safe_script(code, e)f(10)返回10 20 30环境优先从 C 侧e.set_on(f)后再调用同样返回30不设环境直接调用f(10)则回退到全局表返回10 5 15。六、在 state 中执行脚本时使用环境sol::state/sol::state_view为带环境的脚本执行提供了专门的重载见 state_view.hppprotected_function_result safe_script(const string_view code, const basic_environmentE env, const std::string chunkname detail::default_chunk_name(), load_mode mode load_mode::any);而script系列script/script_file、safe_script/unsafe_script均以环境为可选参数转发到上述实现文档state.rst给出的签名摘要protected_function_result script(const string_view code, const environment env, const std::string chunk_name [string], load_mode mode load_mode::any); protected_function_result script_file(const std::string filename, const environment env, load_mode mode load_mode::any);更底层的do_string(code, env)/do_file(filename, env)则只负责加载并执行、不做回调包装。这种设计非常适合在同一个 Lua 状态内运行多个相互隔离的视图或视角不同模块各自持有独立的sol::environment通过 fallback 共享只读的全局资源同时保证写入互不干扰。若需要更强的隔离连全局都不共享文档建议考虑创建独立 state 或其他沙箱方案。在 C 函数中感知调用环境sol::this_environment与sol::this_state类似sol::this_environment是一个透明参数C 绑定函数可以把它声明在参数列表中调用方无需在 Lua 侧传参详见 this_environment 文档void env_check(sol::this_state ts, int x, sol::this_environment te) { if (!te) { // 函数没有环境 return; } sol::environment env te; // 取出调用方的环境 // ... }其实现environment.hpp通过 Lua Debug API 定位当前调用帧、再用env_key构造取出环境取不到时this_environment的optionalenvironment为空可通过if (te)判断。这为根据脚本来源环境决定 C 行为提供了便捷入口。七、版本差异、安全边界与使用建议关注点Lua 5.1Lua 5.2底层 APIlua_setfenv/lua_getfenv搜索/替换_ENVupvalueC 函数为第一个空名 upvalue可挂环境的对象仅 userdata、thread、function规则更宽松环境存储位置独立的 fenv 槽位函数的_ENVupvalue综合文档与源码使用sol::environment时建议注意隔离即全隐藏无 fallback 的新建环境会隐藏包括库函数在内的一切全局内容需要手动放行如my_env[print] lua[print]否则脚本会在运行时报访问错误读取前先校验用sol::env_key或sol::get_environment提取环境后先用valid()判断目标是否真的有环境回退只读语义把lua.globals()作为 fallback 得到的可读全局、不可写全局隔离适合多模块共享只读配置如 Piccolo 这类引擎加载 Lua 逻辑时按模块隔离全局命名空间的场景跨版本移植若项目需要同时支持 Lua 5.1 与 5.2应避免依赖各版本特有的 fenv 行为统一使用sol::set_environment/env.set_on等高层 API让 sol2 帮你处理版本差异如上表所示的分支逻辑位于 environment.hpp 与 stack_push.hpp。小结sol::environment的本质是一张可挂元表、可被引用复制的普通 Lua 表sol2 通过它把 Lua 的运行环境抽象成了 C 侧的一等公民既能作为脚本执行的沙箱边界也能挂到函数、userdata 与线程上还能用sol::env_key/sol::get_environment反查回来。配合 fallback 构造与sol::this_environment透明参数你可以在同一 Lua 状态下轻松搭建隔离写入、受控读取、按模块分环境的脚本架构——这正是 sol2 文档中 encapsulation table for script sandboxing 一句的完整含义。赞分享游戏开发图形学【免费下载链接】PiccoloPiccolo (formerly Pilot) – mini game engine for games104项目地址https://gitcode.com/gh_mirrors/pi/Piccolo点击查看免费下载相关推荐sol2 环境封装用 sol::environment 实现脚本沙箱与环境管理sol2 环境封装用 sol::environment 实现脚本沙箱与环境管理 本文基于 sol2 官方 API 文档 environment.rst htt开发工具ESP-IDF 以太网驱动开发指南从 IEEE 802.3 帧格式到 EMAC/PHY 配置、TCP/IP 对接与高级功能ESP IDF 以太网驱动开发指南从 IEEE 802.3 帧格式到 EMAC/PHY 配置、TCP/IP 对接与高级功能 以太网是 ESP IDF 中最可靠游戏开发图形学Piccolo 引擎 Lua 脚本实战sol2 函数注册、重载调用与多返回值完全指南Piccolo 引擎 Lua 脚本实战sol2 函数注册、重载调用与多返回值完全指南 本文以 sol2 3.3.0 官方教程 functions.rst ht游戏开发图形学上一篇5步打造高效Windows工作流Komorebi窗口管理器实战指南下一篇最硬核的前端工程化工具Rust驱动的Volta架构深度剖析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑