簡體   English   中英

如何在 C 二進制文件中嵌入 Lua 腳本?

[英]How to embed a Lua script within a C binary?

我在 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);

但這有幾個缺點。 首先,我必須避開換行符和引號。 但是現在我發現string length '1234' is greater than the length '509' ISO C90 compilers are required to support警告,我想讓這個程序不僅是獨立的,而且可以移植到其他程序編譯器。

在 C 程序中包含大型 Lua 腳本並且不作為單獨文件提供給最終用戶的最佳方法是什么? 理想情況下,我想將腳本移動到單獨的 *.lua 文件中以簡化測試和更改控制,並以某種方式將該文件編譯到可執行文件中。

在支持 binutils 的系統上,您還可以使用“ld -r”將 Lua 文件“編譯”為 ao,將.o 鏈接到共享的 object,然后將您的應用程序鏈接到共享庫。 在運行時,您 dlsym(RTLD_DEFAULT,...) 在 lua 文本中,然后可以根據需要對其進行評估。

從 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

這將為您提供一個 object 文件,其中包含分隔 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 鏈接到共享的 object 中,就像任何其他 object 文件一樣。 然后,在您的應用程序中,編寫一個名為“some_stuff_lua”的 function,並執行適當的 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());
}

因此,現在在您的應用程序中,假設您已鏈接或 dlopen'ed 包含 some_stuff.o 的庫,您可以說:

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

並且 some_stuff.lua 的原始內容將在 'wrapper' 的上下文中被 lua_load'ed。

此外,如果您希望包含 some_stuff.lua 的共享庫能夠使用“require”從 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 的源部分中看到一個 .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,而是可以用來在二進制文件中嵌入幾乎任何資源。

一種非常便宜但不容易改變的方法是使用 bin2c 之類的東西從選定的 lua 文件(或其編譯的字節碼,更快更小)中生成 header,然后您可以將其傳遞給 ZF8DBBBDB23B80B4F19708 執行。

您也可以嘗試將其作為資源嵌入,但我不知道它在 Visual Studio/Windows 之外是如何工作的。

取決於你想做什么,你甚至可能會發現exeLua有用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM