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

时间:2011-05-30 03:02:36

标签: c lua

我已经在shell世界中被宠坏了,我可以这样做:

./lua <<EOF
> x="hello world"
> print (x)
> EOF
hello world

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

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

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

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

2 个答案:

答案 0 :(得分:11)

在支持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

这将为您提供一个带有符号的目标文件,这些符号用于界定您的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

现在将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());
}

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

SomeLuaStateWrapper wrapper;
wrapper.loadEmbedded("some_stuff_lua");

并且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"

您的嵌入式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))

我确信可以使用像CMake这样的其他现代构建系统来做这样的事情。

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

答案 1 :(得分:4)

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

你也可以尝试将它作为资源嵌入,但我不知道它是如何在visual studio / windows之外工作的。

根据您的要求,您甚至可以找到exeLua使用。