Lua C 宿主完整实践:受信任计数器插件 · 输入与输出契约至构建前提与命令
前置为整数与栈协议和userdata 所有权。目标是让 C 宿主加载本地可信 Lua 插件,导出带生命周期的计数器,再验证一个整数返回值。示例未执行;以下结果均为预期,不代表构建、运行或测试通过。
Lua C 宿主完整实践:受信任计数器插件
前置为整数与栈协议和userdata 所有权。目标是让 C 宿主加载本地可信 Lua 插件,导出带生命周期的计数器,再验证一个整数返回值。示例未执行;以下结果均为预期,不代表构建、运行或测试通过。
输入与输出契约
Lua计数器宿主接受什么输入?命令行提供脚本路径、initial 和 delta,后两者为正负一百万以内的十进制整数。词法允许可选负号及至少一位数字,拒绝加号、前后空白、小数、指数和超范围值。没有输入文件解析器或 JSON 库,tests.json 只是给未来执行者的验收数据。
脚本必须是文本 chunk,且返回一个函数。宿主以 (counter, delta) 调用该函数,counter 是 full userdata,只有 add、close 两种方法;函数必须返回范围内整数。宿主不验证结果与内部计数器相等,因此可以返回合法常量;若业务要求真实状态快照,应另设计查询或提交协议,不把“返回整数”误说成“计算正确”。
成功只向 stdout 输出一行 result=N 并退出 0;命令行错误退出 2,状态创建、加载、脚本和协议错误退出 1,错误写 stderr。失败不得输出成功结果。文件不存在是加载错误,不是零初值;关闭后调用 add 是生命周期错误,不是自动重新打开。
| 输入场景 | 预期 stdout | 预期错误/退出 |
|---|---|---|
| add.lua、10、7 | result=17 | 无错误,0 |
| add.lua、1000000、1 | 空 | callback / counter out of range,1 |
| close.lua、10、7 | result=0 | 无错误,0 |
| closed.lua、10、7 | 空 | callback / counter closed,1 |
| wrong-result.lua、10、7 | 空 | result / callback must return integer,1 |
| add.lua、1.0、7 | 空 | usage,2 |
完整源码与目录
Lua C宿主完整源码在哪里?工程根是 examples/lua/p2-c-host,这里以同源包含展示全部项目文件,避免文中代码与实际文件分叉。host.c 是唯一 C 翻译单元,scripts 提供十个插件;其中 syntax-error.lua 故意语法错误,用于加载失败规格。没有省略测试入口:本包明确交付 tests.json 验收规格,不含伪称已跑的自动执行脚本。
main 校验参数并创建状态;run_request 注册类型、加载 chunk、保存引用、创建计数器、执行插件并验证结果;counter_add 等 C 回调处理方法调用。Lua 插件负责组合导出的能力,不能直接访问 Counter 内存。状态不打开标准库,所以脚本使用函数、数值和方法调用等语言能力,不能依赖 print、require 或 error 全局函数。
host.c
完整文件:host.c 完整源码。保留原始内容,使用前请核对本文的版本与环境说明。
CMakeLists.txt
cmake_minimum_required(VERSION 3.24)
project(lua_host_lesson LANGUAGES C)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
find_package(Lua 5.4.9 EXACT REQUIRED)
add_executable(lua_host host.c)
target_include_directories(lua_host PRIVATE ${LUA_INCLUDE_DIR})
target_link_libraries(lua_host PRIVATE ${LUA_LIBRARIES})dependency.json
{
"lua": "5.4.9",
"source": "https://www.lua.org/ftp/lua-5.4.9.tar.gz",
"sourceSha256": "2335b6c582a52654f94612bf10d2f4672805d05329aa6568b1d8cd9e5c6fb8e6",
"language": "C99",
"cmakeMinimum": "3.24",
"luaBuild": "matching headers and library, default integer/number configuration",
"librariesOpened": [],
"checkedOn": "2026-10-17",
"installed": false,
"compiled": false,
"executed": false
}tests.json
{
"status": "示例未执行;以下均为预期,不是运行记录",
"workingDirectory": "examples/lua/p2-c-host",
"comparison": "Decode captured stdout/stderr as UTF-8 and normalize CRLF to LF before comparing stdout exactly or matching stderrContains; expectations are logical text, not cross-platform raw byte equality. stderrContains ignores platform paths and Lua source line prefixes.",
"cases": [
{"name":"add","args":["scripts/add.lua","10","7"],"exit":0,"stdout":"result=17\n","stderr":""},
{"name":"negative","args":["scripts/add.lua","-10","-7"],"exit":0,"stdout":"result=-17\n","stderr":""},
{"name":"upper-bound","args":["scripts/add.lua","999999","1"],"exit":0,"stdout":"result=1000000\n","stderr":""},
{"name":"lower-bound","args":["scripts/add.lua","-999999","-1"],"exit":0,"stdout":"result=-1000000\n","stderr":""},
{"name":"repeat-close","args":["scripts/close.lua","10","7"],"exit":0,"stdout":"result=0\n","stderr":""},
{"name":"overflow-domain","args":["scripts/add.lua","1000000","1"],"exit":1,"stdout":"","stderrContains":["error[callback]","counter out of range"]},
{"name":"underflow-domain","args":["scripts/add.lua","-1000000","-1"],"exit":1,"stdout":"","stderrContains":["error[callback]","counter out of range"]},
{"name":"closed","args":["scripts/closed.lua","10","7"],"exit":1,"stdout":"","stderrContains":["error[callback]","counter closed"]},
{"name":"string-delta","args":["scripts/string-delta.lua","10","7"],"exit":1,"stdout":"","stderrContains":["error[callback]","delta must be integer"]},
{"name":"float-delta","args":["scripts/float-delta.lua","10","7"],"exit":1,"stdout":"","stderrContains":["error[callback]","delta must be integer"]},
{"name":"huge-delta","args":["scripts/huge-delta.lua","10","7"],"exit":1,"stdout":"","stderrContains":["error[callback]","delta out of range"]},
{"name":"wrong-result","args":["scripts/wrong-result.lua","10","7"],"exit":1,"stdout":"","stderrContains":["error[result]","callback must return integer"]},
{"name":"not-function","args":["scripts/not-function.lua","10","7"],"exit":1,"stdout":"","stderrContains":["error[contract]","chunk must return function"]},
{"name":"syntax-error","args":["scripts/syntax-error.lua","10","7"],"exit":1,"stdout":"","stderrContains":["error[load]"]},
{"name":"missing-file","args":["scripts/absent.lua","10","7"],"exit":1,"stdout":"","stderrContains":["error[load]"]},
{"name":"runtime-error","args":["scripts/object-error.lua","10","7"],"exit":1,"stdout":"","stderrContains":["error[callback]","missing_capability"]},
{"name":"bad-arg","args":["scripts/add.lua","1.0","7"],"exit":2,"stdout":"","stderrContains":["usage:"]},
{"name":"out-of-range-arg","args":["scripts/add.lua","1000001","7"],"exit":2,"stdout":"","stderrContains":["usage:"]},
{"name":"missing-arg","args":["scripts/add.lua","10"],"exit":2,"stdout":"","stderrContains":["usage:"]}
]
}scripts/add.lua
return function(counter, delta)
return counter:add(delta)
endscripts/close.lua
return function(counter, delta)
counter:close()
counter:close()
return 0
endscripts/closed.lua
return function(counter, delta)
counter:close()
return counter:add(delta)
endscripts/string-delta.lua
return function(counter, delta)
return counter:add("1")
endscripts/float-delta.lua
return function(counter, delta)
return counter:add(1.0)
endscripts/huge-delta.lua
return function(counter, delta)
return counter:add(1000001)
endscripts/wrong-result.lua
return function(counter, delta)
return "7"
endscripts/not-function.lua
return 42scripts/syntax-error.lua
-- Intentionally invalid source: negative load fixture, not a valid plugin.
return function(scripts/object-error.lua
return function(counter, delta)
-- No libraries opened: invoke a missing value to produce a runtime error.
return missing_capability()
endREADME.md
# C 宿主计数器插件
示例未执行。这里是完整源文件与确定性验收规格;未安装 Lua、未调用 CMake、未编译 C、未执行 Lua 或测试。
依赖固定 Lua 5.4.9(C99、默认数值配置),CMake 最低 3.24。官方包及 SHA256 在 dependency.json;校验值只从官方页面记录,没有下载或验证源包。Lua 5.4 已结束后续发布,本课讲解现有 5.4 宿主接口,不代表它是最新系列。头文件、链接库、运行时 DLL/共享库必须同版同架构;CMake 的 EXACT 不能替代运行时动态库路径核对。
文件:host.c 为宿主;CMakeLists.txt 为构建描述;dependency.json 固定依赖;scripts 下十个 Lua 插件(syntax-error.lua 故意无效);tests.json 为十九条正反例规格;本 README 给出命令和边界。无第三方 Lua 模块,无测试执行器,无网络业务。
以下命令仅供后续有环境者使用,本轮不执行。先自行准备匹配的 Lua 头文件和库;构建文件不会下载依赖。Windows PowerShell(示意路径需改为实际路径):
```powershell
cmake -S . -B build -DLUA_INCLUDE_DIR=C:/deps/lua-5.4.9/include -DLUA_LIBRARY=C:/deps/lua-5.4.9/lib/lua54.lib
cmake --build build --config Release
& ./build/Release/lua_host.exe scripts/add.lua 10 7
$LASTEXITCODE
```
Linux Bash / macOS Zsh(需已准备相应工具链与 Lua 开发库):
```sh
cmake -S . -B build -DLUA_INCLUDE_DIR=/opt/lua-5.4.9/include -DLUA_LIBRARY=/opt/lua-5.4.9/lib/liblua.a
cmake --build build
./build/lua_host scripts/add.lua 10 7
echo $?
```
目标工作目录为本项目根,预期 stdout `result=17` 后换行、stderr 空、退出 0。多配置生成器使用 Release 子目录;单配置生成器通常没有该目录。Windows 若链接动态库,必须让对应 DLL 在可信可搜索位置;静态 Lua 库在 Unix 可能还要求数学和动态加载库,FindLua 的 LUA_LIBRARIES 负责声明平台链接项。三平台均未构建验证。
命令格式 `lua_host SCRIPT INITIAL DELTA`;十进制允许负号,不接受加号、空白、小数、指数或非数字,两个值必须在 -1000000..1000000。SCRIPT 为本地受信任文本插件路径;宿主通过 luaL_loadfilex 的 `t` 模式加载,chunk 必须返回一个函数,之后传入 full userdata 计数器及整数 delta,插件必须返回域内整数。该结果是插件返回值,不验证它一定等于计数器当前值;例如 close.lua 返回 0 是合法协议。
计数器仅有 add 和 close,内联储存 value/closed,无 malloc、FILE 或系统句柄。add 在运算前验证差值,close 可重复,关闭后 add 失败;__gc 重用关闭逻辑。成功路径释放两个 registry 引用,失败路径由一次性状态 lua_close 整体回收。不能直接把此策略搬到长期复用的状态,应补齐每次失败时的引用释放与宿主事务回滚。
tests.json 是输入、退出码和预期流的唯一规格:五条成功、十四条失败。按 args 顺序调用二进制,捕获 stdout/stderr 后以 UTF-8 解码,将 CRLF 规范化为 LF,再比对 stdout 全文、exit 和 stderrContains。这里比较逻辑文本,不要求跨平台原始字节完全相同;Windows C 文本模式可能把换行输出为 CRLF。错误文字中的路径、行号及平台文件错误不同,因此只匹配稳定片段。不执行故意缺失的 absent.lua 以外的未知路径,不把 syntax-error.lua 当有效脚本做语法验收。
main 在 luaL_newstate 后设置外层 lua_pcall;注册元表、加载插件、创建 userdata 和引用均在保护内。Lua 错误可能 longjmp,C 回调不持有需要手动释放的外部资源。错误对象只在字符串类型时用 lua_tolstring 取字节和长度,再 fwrite;不对任意对象调用可能执行元方法的 tostring。无论成功失败都清栈并 lua_close。
没有打开标准库只是在演示最小能力面,并非安全沙箱。可信插件仍能无限循环、构造大量 table、递归耗尽资源;数值业务范围不是内存预算。生产不可信代码需要进程隔离、明确 allocator 预算和 CPU/权限策略,本示例没有实现这些机制。
资料:[Lua 5.4 手册](https://www.lua.org/manual/5.4/manual.html)、[版本历史](https://www.lua.org/versions.html)、[官方源码校验值](https://www.lua.org/ftp/),核对 2026-10-17。构建前提与命令
Lua宿主怎样用CMake构建?先准备匹配 Lua 5.4.9 头文件和库,再在工程目录进行配置和编译;构建描述不会下载依赖。CMake 的 EXACT 查找限制开发库版本,源码宏额外拒绝错误头文件;架构、默认数值配置和实际装载库仍需人工核对。本轮仅审阅命令,未调用构建工具。
Windows PowerShell 示例:
cmake -S . -B build -DLUA_INCLUDE_DIR=C:/deps/lua-5.4.9/include -DLUA_LIBRARY=C:/deps/lua-5.4.9/lib/lua54.lib
cmake --build build --config Release
& ./build/Release/lua_host.exe scripts/add.lua 10 7
$LASTEXITCODELinux Bash / macOS Zsh 示例:
cmake -S . -B build -DLUA_INCLUDE_DIR=/opt/lua-5.4.9/include -DLUA_LIBRARY=/opt/lua-5.4.9/lib/liblua.a
cmake --build build
./build/lua_host scripts/add.lua 10 7
echo $?路径是示意,需替换成本机已有安装;不同生成器决定二进制是否在 Release 子目录。Windows 动态链接还要求能搜索到匹配 DLL;Linux/macOS 静态库可能需要平台链接项,由 FindLua 的库列表承担。没有 Windows、Linux 或 macOS 的实测证据,不能把这些文字命令当成跨平台验证。
继续阅读
- 知识专题
- 知识专题
- 排错