如何在C二进制文件中嵌入Lua脚本?

BMi*_*tch 8 c lua

我一直在贝壳世界被宠坏了,我可以做:

./lua <<EOF
> x="hello world"
> print (x)
> EOF
hello world
Run Code Online (Sandbox Code Playgroud)

现在我试图在C应用程序中包含一个Lua脚本,我希望它会随着时间的推移而增长.我从一个简单的开始:

const char *lua_script="x=\"hello world\"\n"
  "print(x)\n";
luaL_loadstring(L, lua_script);
lua_pcall(L, 0, 0, 0);
Run Code Online (Sandbox Code Playgroud)

但这有几个缺点.首先,我必须逃避换行和引用.但是现在我在string length ‘1234’ is greater than the length ‘509’ ISO C90 compilers are required to support使用gcc进行编译时遇到了警告,我想保持这个程序不仅是自包含的,而且可以移植到其他编译器中.

在C程序中包含大型Lua脚本的最佳方法是什么,而不是作为单独的文件提供给最终用户?理想情况下,我想将脚本移动到单独的*.lua文件中以简化测试和更改控制,并将该文件以某种方式编译到可执行文件中.

acm*_*acm 12

在支持binutils的系统上,您还可以使用"ld -r"将Lua文件"编译"为.o,将.o链接到共享对象,然后将应用程序链接到共享库.在运行时,您可以在lua文本中使用dlym(RTLD_DEFAULT,...),然后可以根据需要对其进行评估.

要从some_stuff.lua创建some_stuff.o:

ld -s -r -o some_stuff.o -b binary some_stuff.lua
objcopy --rename-section .data=.rodata,alloc,load,readonly,data,contents some_stuff.o some_stuff.o
Run Code Online (Sandbox Code Playgroud)

这将为您提供一个目标文件,其中包含用于分隔lua数据的开头,结尾和大小的符号.据我所知,这些符号由文件名中的ld确定.您无法控制名称,但它们始终是派生的.你会得到类似的东西:

$ nm some_stuff.o 
000000000000891d R _binary_some_stuff_lua_end
000000000000891d A _binary_some_stuff_lua_size
0000000000000000 R _binary_some_stuff_lua_start
Run Code Online (Sandbox Code Playgroud)

现在将some_stuff.o链接到一个共享对象,就像任何其他目标文件一样.然后,在你的应用程序中,编写一个名为"some_stuff_lua"的函数,并执行相应的dlsym魔术.像下面的C++,它假设你有一个名为SomeLuaStateWrapper的lua_State包装器:

void SomeLuaStateWrapper::loadEmbedded(const std::string& embeddingName)
{
    const std::string prefix = "_binary_";
    const std::string data_start = prefix + embeddingName + "_start";
    const std::string data_end = prefix + embeddingName + "_end";

    const char* const data_start_addr = reinterpret_cast<const char*>(
        dlsym(RTLD_DEFAULT, data_start.c_str()));

    const char* const data_end_addr = reinterpret_cast<const char*>(
        dlsym(RTLD_DEFAULT, data_end.c_str()));

    THROW_ASSERT(
        data_start_addr && data_end_addr,
        "Couldn't obtain addresses for start/end symbols " <<
        data_start << " and " << data_end << " for embedding " << embeddingName);

    const ptrdiff_t delta = data_end_addr - data_start_addr;

    THROW_ASSERT(
        delta > 0,
        "Non-positive offset between lua start/end symbols " <<
        data_start << " and " << data_end << " for embedding " << embeddingName);

    // NOTE: You should also load the size and verify it matches.

    static const ssize_t kMaxLuaEmbeddingSize = 16 * 1024 * 1024;
    THROW_ASSERT(
        delta <= kMaxLuaEmbeddingSize,
        "Embedded lua chunk exceeds upper bound of " << kMaxLuaEmbeddingSize << " bytes");

    namespace io = boost::iostreams;
    io::stream_buffer<io::array_source> buf(data_start_addr, data_end_addr);
    std::istream stream(&buf);

    // Call the code that knows how to feed a
    // std::istream to lua_load with the current lua_State.
    // If you need details on how to do that, leave a comment
    // and I'll post additional details.
    load(stream, embeddingName.c_str());
}
Run Code Online (Sandbox Code Playgroud)

所以,现在在你的应用程序中,假设你已经链接或dlopen'ed包含some_stuff.o的库,你可以说:

SomeLuaStateWrapper wrapper;
wrapper.loadEmbedded("some_stuff_lua");
Run Code Online (Sandbox Code Playgroud)

some_stuff.lua的原始内容将在'wrapper'的上下文中被lua_load'.

此外,如果您希望能够使用'require'从Lua加载包含some_stuff.lua的共享库,只需在其他一些C/C++文件中提供包含some_stuff.oa luaopen入口点的相同库:

extern "C" {

int luaopen_some_stuff(lua_State* L)
{
    SomeLuaStateWrapper wrapper(L);
    wrapper.loadEmbedded("some_stuff_lua");
    return 1;
}

} // extern "C"
Run Code Online (Sandbox Code Playgroud)

您的嵌入式Lua现在也可以通过require获得.这与luabind特别有效.

使用SCons,教育构建系统相当容易,当它在SharedLibrary的sources部分中看到一个.lua文件时,应该使用上面的ld/objcopy步骤"编译"该文件:

# NOTE: The 'cd'ing is annoying, but unavoidable, since
# ld in '-b binary' mode uses the name of the input file to
# set the symbol names, and if there is path info on the
# filename that ends up as part of the symbol name, which is
# no good. So we have to cd into the source directory so we
# can use the unqualified name of the source file. We need to
# abspath $TARGET since it might be a relative path, which
# would be invalid after the cd.

env['SHDATAOBJCOM'] = 'cd $$(dirname $SOURCE) && ld -s -r -o $TARGET.abspath -b binary $$(basename 
$SOURCE)'
env['SHDATAOBJROCOM'] = 'objcopy --rename-section .data=.rodata,alloc,load,readonly,data,contents $
TARGET $TARGET'

env['BUILDERS']['SharedLibrary'].add_src_builder(
    SCons.Script.Builder(
        action = [
            SCons.Action.Action(
                "$SHDATAOBJCOM",
                "$SHDATAOBJCOMSTR"
                ),
                SCons.Action.Action(
                "$SHDATAOBJROCOM",
                "$SHDATAOBJROCOMSTR"
                ),
            ],
            suffix = '$SHOBJSUFFIX',
            src_suffix='.lua',
            emitter = SCons.Defaults.SharedObjectEmitter))
Run Code Online (Sandbox Code Playgroud)

我确信可以用其他现代构建系统(如CMake)做类似的事情.

这种技术当然不仅限于Lua,而是可以用于嵌入二进制文件中的任何资源.

  • +1是一个非常有用的解决方案.如果我需要的东西不是特定于Lua并且可移植性不那么令人担忧的话,我会把它留在我的后袋里. (2认同)

Nec*_*lis 4

一种非常便宜但不太容易改变的方法是使用像 bin2c 这样的东西从选定的 lua 文件(或其编译的字节码,更快更小)生成标头,然后你可以将其传递给 lua 执行。

您也可以尝试将其作为资源嵌入,但我不知道它在 Visual Studio/Windows 之外如何工作。

根据您想要做什么,您甚至可能会发现exeLua有用。