You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Luau C API加载多文件脚本失败,如何实现跨文件require?

问题

我希望让第三方开发的Lua脚本能在我的C应用中运行,脚本可能包含多个独立文件。为保证安全,我用Luau做沙箱隔离。我的C应用按照Roblox文档编译加载第三方Luau脚本文件,但处理不了多文件的情况。

脚本文件示例

module1.luau

module = require("module2")

print('started module 1')
print(module.hello())

module2.luau

local M = {}

function M.hello()
    return 'Hello'
end

print('started module 2')

return M

C++加载代码(module.cpp)

#include <iostream>
#include <fstream>

#include <assert.h>
#include "/home/me/luau/VM/include/lua.h"
#include "/home/me/luau/VM/include/lualib.h"
#include "/home/me/luau/Compiler/include/luacode.h"

#include <gtk/gtk.h>

using namespace std;

int main() 
{  

    lua_State* L = luaL_newstate();
    luaL_openlibs(L);
    
    string content; 
    ifstream ifs;
    char* bytecode;
    size_t bytecodeSize = 0;
    
    ifs.open("module2.luau", ifstream::in);
    content.assign( (istreambuf_iterator<char>(ifs) ),
                        (istreambuf_iterator<char>()    ) );
                                     
    bytecode = luau_compile(content.c_str(), content.length(), NULL, &bytecodeSize);
    assert(luau_load(L, "foo2", bytecode, bytecodeSize, 0) == 0);
    free(bytecode); 
    
    lua_pcall(L, 0, 0, 0);
    
    ifs.open("module1.luau", ifstream::in);
    content.assign( (istreambuf_iterator<char>(ifs) ),
                        (istreambuf_iterator<char>()    ) );
                                     
    bytecode = luau_compile(content.c_str(), content.length(), NULL, &bytecodeSize);
    assert(luau_load(L, "foo1", bytecode, bytecodeSize, 0) == 0);
    free(bytecode); 
    
    lua_pcall(L, 0, 0, 0);
    
    lua_close(L);
    
    return 0;
    
}

运行结果对比

  • 直接用Luau运行脚本,结果符合预期:
$ ./luau module1.luau
started module 2
started module 1
Hello
  • 运行C++程序时结果异常:
$ g++ -Wall -o module module.cpp -L/home/me/luau/build/release -lluauvm -lluaucompiler -lluauast -lisocline -lgtk-3 -lgio-2.0 -lgobject-2.0
$ ./module
started module 2

第二个脚本的编译/加载未正常执行,不清楚问题所在。我认为应该可以加载多个脚本文件并让它们通过require协同工作,需注意Lua的部分加载C API函数在Luau中不可用。


解决方案

你的问题出在两个核心点:

  1. 文件流状态未重置:第一次读取module2.luau后,文件流ifs的EOF标志已被设置,第二次打开module1.luau时没有清除这个状态,导致读取的内容为空,后续编译加载自然失败。

  2. 模块未接入Luau的require机制:你直接执行module2.luau的字节码,只是把脚本运行了一遍,但没有将其注册为require能识别的模块。Luau的require不会从全局环境中查找模块,而是依赖内置的模块加载器。

修正后的C++代码

#include <iostream>
#include <fstream>
#include <cstring>

#include <assert.h>
#include "/home/me/luau/VM/include/lua.h"
#include "/home/me/luau/VM/include/lualib.h"
#include "/home/me/luau/Compiler/include/luacode.h"

#include <gtk/gtk.h>

using namespace std;

// 自定义模块加载器,处理require调用
int custom_require(lua_State* L)
{
    const char* module_name = luaL_checkstring(L, 1);

    // 处理module2模块
    if (strcmp(module_name, "module2") == 0)
    {
        ifstream ifs("module2.luau", ifstream::in);
        if (!ifs.is_open())
            return luaL_error(L, "无法打开module2.luau");

        string content(istreambuf_iterator<char>(ifs), istreambuf_iterator<char>());
        ifs.close();

        // 编译模块代码
        char* bytecode = luau_compile(content.c_str(), content.length(), NULL, NULL);
        if (!bytecode)
            return luaL_error(L, "编译module2.luau失败");

        // 加载字节码到VM
        int status = luau_load(L, "module2", bytecode, strlen(bytecode), 0);
        free(bytecode);

        if (status != 0)
            return luaL_error(L, "加载module2.luau失败");

        // 执行模块,将返回的模块表压入栈顶
        if (lua_pcall(L, 0, 1, 0) != 0)
            return luaL_error(L, "运行module2.luau失败: %s", lua_tostring(L, -1));

        // 缓存模块到package.loaded,避免重复加载
        lua_pushstring(L, module_name);
        lua_pushvalue(L, -2);
        lua_rawset(L, LUA_REGISTRYINDEX);

        return 1;
    }

    // 其他模块可在这里扩展,或返回错误
    return luaL_error(L, "未找到模块'%s'", module_name);
}

int main() 
{  
    lua_State* L = luaL_newstate();
    luaL_openlibs(L);

    // 替换全局require为自定义加载器
    lua_pushcfunction(L, custom_require);
    lua_setglobal(L, "require");

    string content; 
    ifstream ifs;
    char* bytecode;
    size_t bytecodeSize = 0;

    // 直接加载并运行module1,依赖由require自动处理
    ifs.open("module1.luau", ifstream::in);
    if (!ifs.is_open())
    {
        cerr << "无法打开module1.luau" << endl;
        lua_close(L);
        return 1;
    }
    content.assign(istreambuf_iterator<char>(ifs), istreambuf_iterator<char>());
    ifs.close();
                                     
    bytecode = luau_compile(content.c_str(), content.length(), NULL, &bytecodeSize);
    assert(bytecode != nullptr);
    assert(luau_load(L, "module1", bytecode, bytecodeSize, 0) == 0);
    free(bytecode); 

    // 运行module1,捕获错误
    if (lua_pcall(L, 0, 0, 0) != 0)
    {
        cerr << "运行module1.luau出错: " << lua_tostring(L, -1) << endl;
        lua_close(L);
        return 1;
    }

    lua_close(L);
    
    return 0;
}

关键修改说明

  • 修复文件流问题:每次读取文件后关闭流,确保下次打开时状态干净,也可以用ifs.clear()重置状态。
  • 自定义require加载器:这是多模块协同的核心,require调用时会触发这个函数,负责加载、编译、运行模块,并将模块缓存到Luau的注册表中,避免重复加载。
  • 移除预运行module2的逻辑:让require自动处理依赖加载,和直接用Luau运行脚本的行为保持一致,也更符合模块加载的规范。

修改后运行C++程序,就能得到和直接运行Luau脚本完全一致的输出结果。


内容的提问来源于stack exchange,提问作者Rhett

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 11:40:01