Как встроить скрипт Lua в бинарный файл C? - PullRequest
8 голосов
/ 30 мая 2011

Я был избалован в мире раковин, где я могу сделать:

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

Теперь я пытаюсь включить скрипт Lua в приложение C, которое, как я ожидаю, со временем будет расти. Я начал с простого:

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 при компиляции с помощью gcc, и я хотел бы сохранить эту программу не только автономной, но и переносимой на другие компиляторы.

Каков наилучший способ включить большой скрипт Lua в программу на C, а не отправлять в виде отдельного файла конечному пользователю? В идеале я хотел бы переместить сценарий в отдельный файл * .lua, чтобы упростить тестирование и контроль изменений, и чтобы этот файл каким-то образом был скомпилирован в исполняемый файл.

Ответы [ 2 ]

11 голосов
/ 30 мая 2011

В системах, которые поддерживают binutils, вы также можете «скомпилировать» файл Lua в .o с помощью «ld -r», связать .o с общим объектом, а затем связать ваше приложение с общей библиотекой. Во время выполнения вы dlsym (RTLD_DEFAULT, ...) в тексте lua и можете затем оценить его, как вам нравится.

Чтобы создать some_stuff.o из some_stuff.lua:

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 ++, который предполагает, что у вас есть оболочка вокруг lua_State с именем SomeLuaStateWrapper:

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 будет lua_load'ed в контексте 'wrapper'.

Если, кроме того, вы хотите, чтобы разделяемая библиотека, содержащая some_stuff.lua, могла загружаться из Lua с помощью 'require', просто предоставьте ту же библиотеку, которая содержит точку входа luaopen some_stuff.oa в каком-то другом файле C / C ++ :

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 довольно легко объяснить системе сборки, что, когда она видит файл .lua в разделе источников SharedLibrary, она должна «скомпилировать» файл с помощью шагов 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, но может использоваться для встраивания практически любого ресурса в двоичный файл.

4 голосов
/ 30 мая 2011

Действительно дешевый, но не очень простой способ изменить это использовать что-то вроде bin2c для генерации заголовка из выбранного файла lua (или его скомпилированного байт-кода, который быстрее и меньше), затем вы можете передать это luaвыполнить.

Вы также можете попробовать встроить его в качестве ресурса, но я понятия не имею, как это работает вне Visual Studio / Windows.

в зависимости от того, что вы хотите сделать, вы даже можете найти exeLua использования.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...