C/С++ с GCC: статически добавлять файлы ресурсов в исполняемый файл/библиотеку

Есть ли у кого-нибудь идея, как статически компилировать любой файл ресурсов прямо в исполняемый файл или файл общей библиотеки с помощью GCC?

Например, я бы хотел добавить файлы изображений, которые никогда не меняются (и если они это сделают, мне все равно придется заменить файл), и они не захотят, чтобы они лежали в файловой системе.

Если это возможно (и я думаю, что это потому, что Visual С++ для Windows тоже может это сделать), как я могу загрузить файлы, которые хранятся в собственном двоичном формате? Выполняет ли сам исполняемый файл, находит файл и извлекает данные из него?

Может быть, вариант GCC я еще не видел. Использование поисковых систем на самом деле не вылило нужные вещи.

Мне нужно, чтобы это работало для разделяемых библиотек и обычных ELF-исполняемых файлов.

Любая помощь приветствуется

Ответ 1

С imagemagick:

convert file.png data.h

Дает что-то вроде:

/*
  data.h (PNM).
*/
static unsigned char
  MagickImage[] =
  {
    0x50, 0x36, 0x0A, 0x23, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 
    0x77, 0x69, 0x74, 0x68, 0x20, 0x47, 0x49, 0x4D, 0x50, 0x0A, 0x32, 0x37, 
    0x37, 0x20, 0x31, 0x36, 0x32, 0x0A, 0x32, 0x35, 0x35, 0x0A, 0xFF, 0xFF, 
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 

....

Для совместимости с другим кодом вы можете использовать либо fmemopen, чтобы получить "обычный" FILE * объект, или, альтернативно, std::stringstream, чтобы сделать iostream. std::stringstream не очень подходит для этого, хотя вы можете, конечно, просто использовать указатель везде, где вы можете использовать итератор.

Если вы используете это с помощью automake, не забудьте установить BUILT_SOURCES соответствующим образом.

Самое приятное в этом:

  • Вы получаете текст, поэтому он может быть в управлении версиями и исправлениях разумно
  • Он переносим и четко определен на каждой платформе.

Ответ 2

Обновление Я вырос, чтобы предпочесть управление

чтобы вы могли делать что-то вроде

for (uint8_t *byte=foo_data; byte<foo_data_end; ++byte) {
    transmit_single_byte(*byte);
}

или

size_t foo_size = (size_t)((void *)foo_data_size);
void  *foo_copy = malloc(foo_size);
assert(foo_copy);
memcpy(foo_copy, foo_data, foo_size);

Если ваша целевая архитектура имеет особые ограничения в отношении того, где хранятся постоянные и переменные данные, или вы хотите сохранить эти данные в сегменте .text, чтобы он вписывался в тот же тип памяти, что и ваш программный код, вы можете играть с параметрами objcopy.

Ответ 3

Вы можете вставлять двоичные файлы в исполняемый файл с помощью ld linker. Например, если у вас есть файл foo.bar, вы можете вставить его в исполняемый файл, добавив следующие команды в ld

--format=binary foo.bar --format=default

Если вы вызываете ld thru gcc, тогда вам нужно будет добавить -Wl

-Wl,--format=binary -Wl,foo.bar -Wl,--format=default

Здесь --format=binary сообщает компоновщику, что следующий файл является двоичным, а --format=default переключается обратно в формат ввода по умолчанию (это полезно, если вы укажете другие входные файлы после foo.bar).

Затем вы можете получить доступ к содержимому вашего файла из кода:

extern uint8_t data[]     asm("_binary_foo_bar_start");
extern uint8_t data_end[] asm("_binary_foo_bar_end");

Существует также символ с именем "_binary_foo_bar_size". Я думаю, что это тип uintptr_t, но я не проверял его.

Ответ 4

Вы можете поместить все свои ресурсы в ZIP файл и добавить это к концу исполняемого файла:

g++ foo.c -o foo0
zip -r resources.zip resources/
cat foo0 resources.zip >foo

Это работает, потому что a) Большинство исполняемых форматов изображений не заботятся, есть ли дополнительные данные за изображением и b) zip хранит подпись файла в конце zip файла. Это означает, что ваш исполняемый файл является обычным zip файлом после этого (за исключением вашего начального исполняемого файла, который может обрабатывать zip), который можно открыть и прочитать с помощью libzip.

Ответ 5

Из http://www.linuxjournal.com/content/embedding-file-executable-aka-hello-world-version-5967:

Недавно мне потребовалось встроить файл в исполняемый файл. Поскольку я работаю в командной строке с gcc, и др., А не с причудливым RAD-инструментом, который заставляет все это происходить волшебным образом, мне не сразу стало ясно, как это сделать. Немного искал в сети, нашел взломать, по существу, поместить его в конец исполняемого файла, а затем расшифровать, где он был основан на кучке информации, о которой я не хотел знать. Казалось, что должен быть лучший способ...

И есть, это objcopy для спасения. objcopy преобразует объектные файлы или исполняемые файлы из одного формата в другой. Один из форматов, который он понимает, является "двоичным", что в основном представляет собой любой файл, который не находится в одном из других форматов, которые он понимает. Таким образом, вы, вероятно, предположили идею: конвертировать файл, который мы хотим внедрить в объектный файл, тогда его можно просто связать с остальной частью нашего кода.

Скажем, у нас есть имя файла data.txt, которое мы хотим внедрить в наш исполняемый файл:

# cat data.txt
Hello world

Чтобы преобразовать это в объектный файл, который мы можем связать с нашей программой, мы просто используем objcopy для создания файла .o:

# objcopy --input binary \
--output elf32-i386 \
--binary-architecture i386 data.txt data.o

Это говорит objcopy, что наш входной файл находится в "двоичном" формате, что наш выходной файл должен находиться в формате "elf32-i386" (объектные файлы на x86). Параметр -binary-architecture указывает objcopy, что выходной файл предназначен для "запуска" на x86. Это необходимо, так что ld примет файл для связи с другими файлами для x86. Казалось бы, что определение выходного формата как "elf32-i386" означало бы это, но это не так.

Теперь, когда у нас есть объектный файл, нам нужно включить его только при запуске компоновщика:

# gcc main.c data.o

Когда мы запускаем результат, получаем молитву за вывод:

# ./a.out
Hello world

Конечно, я еще не рассказал всю историю и не показал вам main.c. Когда objcopy выполняет вышеуказанное преобразование, он добавляет некоторые символы "компоновщика" в преобразованный файл объекта:

_binary_data_txt_start
_binary_data_txt_end

После связывания эти символы определяют начало и конец встроенного файла. Имена символов формируются путем добавления двоичного файла и добавления _start или _end к имени файла. Если имя файла содержит любые символы, которые были бы недопустимы в имени символа, они преобразуются в символы подчеркивания (например, data.txt становится data_txt). Если вы получаете неразрешенные имена при связывании с использованием этих символов, сделайте hexdump -C в объектном файле и посмотрите на конец дампа для имен, выбранных objcopy.

Код для фактического использования встроенного файла теперь должен быть достаточно очевидным:

#include <stdio.h>

extern char _binary_data_txt_start;
extern char _binary_data_txt_end;

main()
{
    char*  p = &_binary_data_txt_start;

    while ( p != &_binary_data_txt_end ) putchar(*p++);
}

Важно отметить, что символы, добавленные в объектный файл, не являются "переменными". Они не содержат никаких данных, скорее их адрес является их значением. Я объявляю их как тип char, потому что он удобен для этого примера: встроенные данные являются символьными данными. Однако вы можете объявить их как что угодно, как int, если данные представляют собой массив целых чисел, или как struct foo_bar_t, если данные были любыми массивами foo-баров. Если встроенные данные не являются однородными, то char, вероятно, наиболее удобен: возьмите его адрес и нарисуйте указатель на соответствующий тип при перемещении данных.

Ответ 6

Если вы хотите контролировать точное имя символа и место размещения ресурсов, вы можете использовать (или script) сборщик GNU (не являющийся частью gcc) для импорта целых двоичных файлов. Попробуйте следующее:

Сборка (x86/arm):

    .section .rodata
    .global thing
    .type   thing, @object
    .align  4
thing:
    .incbin "meh.bin"
thing_end:
    .global thing_size
    .type   thing_size, @object
    .align  4
thing_size:
    .int    thing_end - thing

С

#include <stdio.h>
extern char thing[];
extern unsigned thing_size;
int main() {
  printf("%p %u\n", thing, thing_size);
  return 0;
}

Независимо от того, что вы используете, лучше всего создать script для генерации всех ресурсов и иметь хорошие/унифицированные имена символов для всего.

Ответ 7

Могу ли я добавить, что метод Джон Рипли, пожалуй, самый лучший здесь по одной огромной причине - выравнивание. Если вы выполняете стандартную objcopy или "ld -r -b binary -o foo.o foo.txt", а затем смотрите на результирующий объект с objdump -x, похоже, выравнивание для блока установлено на 0. Если вы хотите выравнивание должно быть правильным для двоичных данных, кроме char, я не могу себе представить, что это хорошо.

Ответ 8

Считывая все сообщения здесь и в Интернете, я сделал вывод, что нет инструмента для ресурсов, а именно:

1) Прост в использовании в коде.

2) Автоматизированный (для легкого включения в cmake/make).

3) Кросс-платформенный.

Я решил написать инструмент самостоятельно. Код доступен здесь. https://github.com/orex/cpp_rsc

Использовать его с cmake очень просто.

Вы должны добавить в свой файл CMakeLists.txt такой код.

file(DOWNLOAD https://raw.github.com/orex/cpp_rsc/master/cmake/modules/cpp_resource.cmake ${CMAKE_BINARY_DIR}/cmake/modules/cpp_resource.cmake) 

set(CMAKE_MODULE_PATH ${CMAKE_BINARY_DIR}/cmake/modules)

include(cpp_resource)

find_resource_compiler()
add_resource(pt_rsc) #Add target pt_rsc
link_resource_file(pt_rsc FILE <file_name1> VARIABLE <variable_name1> [TEXT]) #Adds resource files
link_resource_file(pt_rsc FILE <file_name2> VARIABLE <variable_name2> [TEXT])

...

#Get file to link and "resource.h" folder
#Unfortunately it is not possible with CMake add custom target in add_executable files list.
get_property(RSC_CPP_FILE TARGET pt_rsc PROPERTY _AR_SRC_FILE)
get_property(RSC_H_DIR TARGET pt_rsc PROPERTY _AR_H_DIR)

add_executable(<your_executable> <your_source_files> ${RSC_CPP_FILE})

Настоящий пример, используя этот подход, можно скачать здесь, https://bitbucket.org/orex/periodic_table