Кто-нибудь знает, как статически скомпилировать любой файл ресурсов прямо в исполняемый файл или файл общей библиотеки с помощью GCC?
Например, я хотел бы добавить файлы изображений, которые никогда не меняются (а если они это сделают, мне все равно придется заменить файл), и я бы не хотел, чтобы они валялись в файловой системе.
Если это возможно (и я думаю, это потому, что Visual C ++ для Windows тоже может это сделать), как я могу загрузить файлы, которые хранятся в собственном двоичном файле? Разбирает ли исполняемый файл сам, находит ли файл и извлекает из него данные?
Может быть, есть вариант для GCC, которого я еще не видел. Использование поисковых систем на самом деле не выдавало того, что нужно.
Мне нужно, чтобы это работало для общих библиотек и обычных исполняемых файлов ELF.
Любая помощь приветствуется
Ответы:
С помощью 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 .
Что хорошо в том, чтобы сделать это таким образом:
источник
xxd -i infile.bin outfile.h
objcopy
для преобразования двоичных данных непосредственно в объектный файл; однако это редко вызывает беспокойство.Обновление. Я вырос, чтобы предпочесть элемент управления ,
.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
немного поиграть с параметрами.источник
ld
поскольку там подразумевается выходной формат, см. Stackoverflow.com/a/4158997/201725 .Вы можете вставлять двоичные файлы в исполняемый файл с помощью
ld
компоновщика. Например, если у вас есть файл,foo.bar
вы можете встроить его в исполняемый файл, добавив следующие команды вld
--format=binary foo.bar --format=default
Если вы вызываете
ld
через,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
но не проверял.источник
data_end
массив, а не указатель? (Или это идиоматический C?)data_end
будет указатель, то компилятор подумает, что после содержимого файла хранится указатель. Аналогично, если вы измените типdata
на указатель, то вместо указателя на его начало вы получите указатель, состоящий из первых байтов файла. Я так думаю.const pointer
. Компилятор позволяет вам изменять значение неконстантных указателей, он не позволяет вам изменять значение, если это массив. Таким образом, использование синтаксиса массива, возможно, требует меньше усилий.Вы можете поместить все свои ресурсы в ZIP-файл и добавить его в конец исполняемого файла :
g++ foo.c -o foo0 zip -r resources.zip resources/ cat foo0 resources.zip >foo
Это работает, потому что а) большинство исполняемых форматов изображений не заботятся о том, есть ли за изображением дополнительные данные, и б) zip сохраняет подпись файла в конце zip-файла . Это означает, что после этого ваш исполняемый файл представляет собой обычный zip-файл (за исключением исходного исполняемого файла, который может обрабатывать zip), который можно открыть и прочитать с помощью libzip.
источник
install_name_tool
. Кроме того, двоичный файл по-прежнему работает как исполняемый файл.С http://www.linuxjournal.com/content/embedding-file-executable-aka-hello-world-version-5967 :
Недавно мне понадобилось встроить файл в исполняемый файл. Поскольку я работаю в командной строке с помощью gcc и др., А не с причудливым инструментом RAD, который заставляет все это происходить волшебным образом, мне не сразу было очевидно, как это сделать. Немного поискав в сети, был обнаружен хак, который по существу помещал его в конец исполняемого файла, а затем расшифровывал, где он был основан на кучке информации, о которой я не хотел знать. Казалось, что должен быть способ получше ...
И вот, objcopy спешит на помощь. objcopy преобразует объектные файлы или исполняемые файлы из одного формата в другой. Один из понимаемых им форматов - это «двоичный», то есть любой файл, не принадлежащий ни одному из других форматов, которые он понимает. Итак, вы, вероятно, представили идею: преобразовать файл, который мы хотим встроить, в объектный файл, а затем его можно просто связать с остальной частью нашего кода.
Допустим, у нас есть файл с именем data.txt, который мы хотим встроить в наш исполняемый файл:
Чтобы преобразовать это в объектный файл, который мы можем связать с нашей программой, мы просто используем objcopy для создания файла ".o":
Это сообщает objcopy, что наш входной файл находится в «двоичном» формате, а наш выходной файл должен быть в формате «elf32-i386» (объектные файлы на x86). Параметр --binary-architecture сообщает objcopy, что выходной файл предназначен для «запуска» на x86. Это необходимо для того, чтобы ld принял файл для связывания с другими файлами для x86. Можно было бы подумать, что указание формата вывода как "elf32-i386" будет подразумевать это, но это не так.
Теперь, когда у нас есть объектный файл, нам нужно только включить его при запуске компоновщика:
Когда мы запускаем результат, мы получаем молитву о выходе:
Конечно, я еще не рассказал всю историю и не показал вам main.c. Когда objcopy выполняет указанное выше преобразование, он добавляет некоторые символы "компоновщика" в преобразованный объектный файл:
После связывания эти символы определяют начало и конец встроенного файла. Имена символов формируются путем добавления двоичного кода. файла и добавления _start или _end к имени файла. Если имя файла содержит какие-либо символы, которые могут быть недопустимыми в имени символа, они преобразуются в символы подчеркивания (например, data.txt становится data_txt). Если вы получаете неразрешенные имена при связывании с использованием этих символов, выполните шестнадцатеричный дамп -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: возьмите его адрес и приведите указатель к нужному типу при перемещении по данным.
источник
Если вы хотите контролировать точное имя символа и размещение ресурсов, вы можете использовать (или создать скрипт) ассемблер GNU (на самом деле не являющийся частью gcc) для импорта целых двоичных файлов. Попробуй это:
Сборка (x86 / arm):
.section .rodata .global thing .type thing, @object .balign 4 thing: .incbin "meh.bin" thing_end: .global thing_size .type thing_size, @object .balign 4 thing_size: .int thing_end - thing
C:
#include <stdio.h> extern const char thing[]; extern const unsigned thing_size; int main() { printf("%p %u\n", thing, thing_size); return 0; }
Что бы вы ни использовали, вероятно, лучше всего создать сценарий для генерации всех ресурсов и иметь красивые / единообразные имена символов для всего.
В зависимости от ваших данных и особенностей системы вам может потребоваться использовать разные значения выравнивания (желательно с
.balign
для переносимости) или целочисленные типы другого размера для массиваthing_size
или другой тип элемента дляthing[]
массива.источник
Прочитав все сообщения здесь и в Интернете, я пришел к выводу, что нет инструмента для ресурсов, а именно:
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
источник