C / C ++ с GCC: статически добавлять файлы ресурсов в исполняемый файл / библиотеку - PullRequest
86 голосов
/ 01 февраля 2011

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

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

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

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

Мне бы это понадобилось для работы с общими библиотеками и обычными исполняемыми файлами ELF.

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

Ответы [ 7 ]

84 голосов
/ 01 февраля 2011

Обновление Я предпочел управление Сборка Джона Рипли .incbin на основе решения предлагает и теперь использую вариант на этом.

Я использовал objcopy (GNU binutils), чтобы связать двоичные данные из файла foo-data.bin с разделом данных исполняемого файла:

objcopy -B i386 -I binary -O elf32-i386 foo-data.bin foo-data.o

Это дает вам foo-data.o объектный файл, который вы можете связать с вашим исполняемым файлом. Интерфейс C выглядит примерно так:

/** created from binary via objcopy */
extern uint8_t foo_data[]      asm("_binary_foo_data_bin_start");
extern uint8_t foo_data_size[] asm("_binary_foo_data_bin_size");
extern uint8_t foo_data_end[]  asm("_binary_foo_data_bin_end");

так что вы можете делать такие вещи, как

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 еще немного.

47 голосов
/ 24 июля 2012

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

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

Если вы вызываете ld th * gcc, вам потребуетсяadd -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, но я не проверял.

46 голосов
/ 01 февраля 2011

С 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 соответствующим образом.

Приятно, если вы сделаете это следующим образом:

  1. Вы получаете текст, поэтому он может быть в управлении версиями и корректно исправлять
  2. Это портативно и четко определено на любой платформе
38 голосов
/ 01 февраля 2011

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

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

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

35 голосов
/ 02 апреля 2011

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

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

И вот, это спасение для нас.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: взять его адрес и привести указатель к нужному типу при прохождении данных.

34 голосов
/ 06 февраля 2011

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

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

    .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

C

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

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

4 голосов
/ 04 октября 2013

Читая все посты здесь и в интернете, я сделал вывод, что не существует инструмента для ресурсов, который:

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

...