带有 GCC 的 C/C++:将资源文件静态添加到可执行文件/库

Posted

技术标签:

【中文标题】带有 GCC 的 C/C++:将资源文件静态添加到可执行文件/库【英文标题】:C/C++ with GCC: Statically add resource files to executable/library 【发布时间】:2011-06-19 09:20:24 【问题描述】:

有人知道如何使用 GCC 将任何资源文件静态编译成可执行文件或共享库文件吗?

例如,我想添加永远不会改变的图像文件(如果他们这样做了,我无论如何都必须替换文件)并且不希望它们在文件系统中存在。

如果这是可能的(我认为这是因为 Visual C++ for Windows 也可以做到这一点),我如何加载存储在自己的二进制文件中的文件?可执行文件是否会自行解析、查找文件并从中提取数据?

也许 GCC 有一个我还没有看到的选项。使用搜索引擎并没有真正吐出正确的东西。

我需要它来处理共享库和普通的 ELF 可执行文件。

【问题讨论】:

***.com/questions/1997172/…的可能重复 blueberryfields 指出的问题中的 objcopy 链接也是一个很好的通用解决方案 @blueberryfields:抱歉重复。你说得对。通常我会投票赞成关闭作为重复。但是因为他们都发布了很好的答案,所以我只接受一个。 我可以补充一点,John Ripley 的方法可能是这里最好的方法,原因有一个 - 对齐。如果您执行标准 objcopy 或“ld -r -b binary -o foo.o foo.txt”,然后使用 objdump -x 查看生成的对象,看起来块的对齐方式设置为 0。如果你想要对齐对于 char 以外的二进制数据是正确的,我无法想象这是一件好事。 Embedding resources in .exe using GCC的可能重复 【参考方案1】:

如果您想控制确切的符号名称和资源位置,您可以使用(或编写脚本)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 使用不同大小的整数类型,或为@ 使用不同的元素类型987654325@数组。

【讨论】:

感谢分享!绝对看起来很有趣,但这次不是我要找的=)问候 正是我想要的。也许您可以验证对于大小不能被 4 消除的文件也可以。看起来 thing_size 将包括额外的填充字节。 如果我想让事物成为本地符号怎么办?我可能可以将编译器输出与我自己的程序集一起使用,但有更好的方法吗? 备案:我的编辑解决了@Pavel 指出的额外填充字节的问题。【参考方案2】:

更新我越来越喜欢 John Ripley's assembly .incbin based solution 提供的控件,现在使用它的变体。

我使用 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 参数更多。

【讨论】:

好主意!就我而言,它不是很有用。但这是我真正要放入我的 sn-p-collection 的东西。感谢分享! 使用ld 会更容易一些,因为那里隐含了输出格式,请参阅***.com/a/4158997/201725。【参考方案3】:

阅读这里和互联网上的所有帖子,我得出的结论是没有资源工具,即:

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

【讨论】:

我认为您的答案需要更好的解释才能对更多人有用。【参考方案4】:

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

我最近需要在可执行文件中嵌入一个文件。由于我使用 gcc 等在命令行上工作,而不是使用可以让这一切神奇地发生的花哨的 RAD 工具,因此对我来说如何实现这一点并不是很明显。在网上进行了一些搜索,发现了一个 hack,基本上可以将它放到可执行文件的末尾,然后根据一堆我不想知道的信息来破译它的位置。似乎应该有更好的方法......

还有,它是 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

链接后,这些符号指定嵌入文件的开始和结束。符号名称是通过在文件名前面加上 binary 和 _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,如果数据是任何 foo 柱数组,则声明为 struct foo_bar_t。如果嵌入的数据不统一,那么 char 可能是最方便的:在遍历数据时获取它的地址并将指针转换为正确的类型。

【讨论】:

【参考方案5】:

您可以将所有资源放入一个 ZIP 文件并将其附加到可执行文件的末尾

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

这是可行的,因为 a) 大多数可执行图像格式不关心图像后面是否有额外数据,并且 b) zip 将文件签名存储在 zip 文件的末尾。这意味着,您的可执行文件在此之后是一个常规的 zip 文件(除了您的前期可执行文件,它 zip 可以处理),可以使用 libzip 打开和读取。

【讨论】:

如果我想将 foo0 和 resources.zip 加入到 foo 中,那么我需要 > 如果我在 cat 的命令行上同时给出这两个输入。 (因为我不想附加到 foo 中已有的内容) 啊,是的,我的错。在我第一次阅读时,我没有正确发现名称中的 0 +1 很棒,尤其是与miniz配对时 这将产生一个无效的二进制文件(至少在 Mac 和 Linux 上),它不能被 install_name_tool 之类的工具处理。除此之外,该二进制文件仍可作为可执行文件运行。【参考方案6】:

您可以使用ld 链接器将二进制文件嵌入到可执行文件中。 例如,如果您有文件foo.bar,那么您可以将其嵌入到可执行文件中,将以下命令添加到ld

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

如果您正在调用 ldgcc,那么您需要添加 -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 语言?) @xtofl,如果data_end 是一个指针,那么编译器会认为在文件内容之后存储了一个指针。同样,如果您将data 的类型更改为指针,那么您将获得由文件的第一个字节组成的指针,而不是指向其开头的指针。我想是的。 +1:您的回答允许我将 java 类加载器和 Jar 嵌入到 exe 中以构建自定义 java 启动器 @xtofl - 如果您打算将其设为指针,请将其设为 const pointer。编译器允许您更改非常量指针的值,如果它是数组,则不允许您更改值。所以使用数组语法可能会减少打字。【参考方案7】:

与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 生成iostreamstd::stringstream 对此并不好,当然你可以在任何可以使用迭代器的地方使用指针。

如果您将它与 automake 一起使用,请不要忘记适当地set BUILT_SOURCES。

这样做的好处是:

    您可以将文本输出,因此可以在版本控制和补丁中进行明智的处理 它是可移植的,并且在每个平台上都有很好的定义

【讨论】:

嘘!这也是我想到的解决方案。为什么有人会想要这样做,这超出了我的理解。在定义明确的命名空间中存储数据片段是文件系统的用途。 有时,您有一个在没有文件系统甚至没有操作系统的地方运行的可执行文件。或者您的算法需要一些预先计算的表进行查找。而且我敢肯定,在程序中存储数据时会有更多的情况很多有意义。 这个convert的用法和xxd -i infile.bin outfile.h一模一样 这种方法的一个缺点是,如果您的图像特别大,一些编译器无法处理如此庞大的静态数组;解决方法是,正如ndim 建议的那样,使用objcopy 将二进制数据直接转换为目标文件;然而,这很少是一个问题。 请记住,像这样在标题中定义它意味着包含它的每个文件都将获得自己的副本。最好在标头中将其声明为 extern,然后在 cpp 中定义它。 Example here

以上是关于带有 GCC 的 C/C++:将资源文件静态添加到可执行文件/库的主要内容,如果未能解决你的问题,请参考以下文章

使用 GCC 在可执行文件中嵌入资源

如何编译C/Fortran动态/静态链接库

如何将带有空格的路径作为参数添加到 CreateProcess 批处理文件?

TDM-GCC是从mingw-w64项目patch而来,全部使用静态链接,对线程不需要额外的DLL,默认使用SJLJ异常(真是好东西)

如何打包静态库文件

如何使用 gcc 和 Visual Studio 的“静态链接”和“动态链接”构建 C/C++ 程序?