Skip to content

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、7result=17无错误,0
add.lua、1000000、1空callback / counter out of range,1
close.lua、10、7result=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
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 ​

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 ​

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 ​

lua
return function(counter, delta)
    return counter:add(delta)
end

scripts/close.lua ​

lua
return function(counter, delta)
    counter:close()
    counter:close()
    return 0
end

scripts/closed.lua ​

lua
return function(counter, delta)
    counter:close()
    return counter:add(delta)
end

scripts/string-delta.lua ​

lua
return function(counter, delta)
    return counter:add("1")
end

scripts/float-delta.lua ​

lua
return function(counter, delta)
    return counter:add(1.0)
end

scripts/huge-delta.lua ​

lua
return function(counter, delta)
    return counter:add(1000001)
end

scripts/wrong-result.lua ​

lua
return function(counter, delta)
    return "7"
end

scripts/not-function.lua ​

lua
return 42

scripts/syntax-error.lua ​

lua
-- Intentionally invalid source: negative load fixture, not a valid plugin.
return function(

scripts/object-error.lua ​

lua
return function(counter, delta)
    -- No libraries opened: invoke a missing value to produce a runtime error.
    return missing_capability()
end

README.md ​

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 示例:

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 示例:

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 $?

路径是示意,需替换成本机已有安装;不同生成器决定二进制是否在 Release 子目录。Windows 动态链接还要求能搜索到匹配 DLL;Linux/macOS 静态库可能需要平台链接项,由 FindLua 的库列表承担。没有 Windows、Linux 或 macOS 的实测证据,不能把这些文字命令当成跨平台验证。

继续阅读