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

问题描述 投票:0回答:7

我正在寻找一种方法来轻松地将任何外部二进制数据嵌入到由 GCC 编译的 C/C++ 应用程序中。

我想做的一个很好的例子是处理着色器代码 - 我可以将其保留在像

const char* shader = "source here";
这样的源文件中,但这非常不切实际。

我希望编译器为我做这件事:在编译(链接阶段)时,读取文件“foo.bar”并将其内容链接到我的程序,以便我能够以二进制数据形式访问内容代码。

对于我想作为单个 .exe 文件分发的小型应用程序可能很有用。

GCC 支持这样的东西吗?

c++ c gcc resources embedded-resource
7个回答
95
投票

有几种可能性:

  • 使用 ld 的功能将任何文件转换为对象(使用 gcc mingw 嵌入二进制 blob):

    ld -r -b binary -o binary.o foo.bar # then link in binary.o
    
    
  • 使用

    bin2c

    /
    bin2h
    实用程序将任何文件转换为字节数组(
    在代码中嵌入图像,而不使用资源部分或外部图像


更新:这是一个更完整的示例,说明如何使用

ld -r -b binary

 使用绑定到可执行文件中的数据:

#include <stdio.h> // a file named foo.bar with some example text is 'imported' into // an object file using the following command: // // ld -r -b binary -o foo.bar.o foo.bar // // That creates an bject file named "foo.bar.o" with the following // symbols: // // _binary_foo_bar_start // _binary_foo_bar_end // _binary_foo_bar_size // // Note that the symbols are addresses (so for example, to get the // size value, you have to get the address of the _binary_foo_bar_size // symbol). // // In my example, foo.bar is a simple text file, and this program will // dump the contents of that file which has been linked in by specifying // foo.bar.o as an object file input to the linker when the progrma is built extern char _binary_foo_bar_start[]; extern char _binary_foo_bar_end[]; int main(void) { printf( "address of start: %p\n", &_binary_foo_bar_start); printf( "address of end: %p\n", &_binary_foo_bar_end); for (char* p = _binary_foo_bar_start; p != _binary_foo_bar_end; ++p) { putchar( *p); } return 0; }


更新 2 - 获取资源大小:我无法正确读取 _binary_foo_bar_size 。在运行时,gdb 使用

display (unsigned int)&_binary_foo_bar_size

 显示文本资源的正确大小。但是将其分配给变量总是给出错误的值。我可以通过以下方式解决这个问题:

unsigned int iSize = (unsigned int)(&_binary_foo_bar_end - &_binary_foo_bar_start)

这是一种解决方法,但效果很好,而且不太难看。


47
投票
除了已经提到的建议之外,在linux下您可以使用十六进制转储工具xxd,它具有生成C头文件的功能:

xxd -i mybinary > myheader.h
    

25
投票

.incbin

 GAS 指令
可用于此任务。这是一个完全免费的许可库:

https://github.com/graphitemaster/incbin

回顾一下。 incbin方法是这样的。 您有一个使用 gcc -c thing.s 编译的 thing.s 汇编文件

.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 或 cpp 代码中,您可以通过以下方式引用它:

extern const char thing[]; extern const char* thing_end; extern int thing_size;
然后将生成的 .o 与其余编译单元链接。
感谢 @John Ripley 的回答:

C/C++ with GCC:静态地将资源文件添加到可执行文件/库

但是上面的方法没有incbin给你的那么方便。 要使用 incbin 完成上述操作,您不需要编写任何汇编程序。 只需执行以下操作即可:

#include "incbin.h" INCBIN(thing, "meh.bin"); int main(int argc, char* argv[]) { // Now use thing printf("thing=%p\n", gThingData); printf("thing len=%d\n", gThingSize); }
    

21
投票
对于 C23,现在存在预处理器指令

#embed

,它无需使用外部工具即可准确实现您正在寻找的目标。请参阅 C23 标准的 6.10.3.1(此处是最新
工作草案的链接)。这是关于这一新功能背后的一位委员会成员的#embed历史的
好博文

以下是标准草案中的一个片段,展示了其用途:

#include <stddef.h>
void have_you_any_wool(const unsigned char*, size_t);

int main (int, char*[]) {
    static const unsigned char baa_baa[] = {
#embed "black_sheep.ico"
    };
    
    have_you_any_wool(baa_baa, sizeof(baa_baa));
    return 0;
}

目前不存在 C++ 的等效指令。


2
投票

如果我想将静态数据嵌入到可执行文件中,我会将其作为无符号字符数组打包到 .lib/.a 文件或头文件中。如果您正在寻找一种便携式方法。 我创建了一个命令行工具,实际上可以在here完成这两个任务。您所要做的就是列出文件,然后选择选项 -l64 来输出 64 位库文件以及包含指向每个数据的所有指针的标头。

您还可以探索更多选项。例如,这个选项:

>BinPack image.png -j -hx

将把 image.png 的数据输出到头文件中,因为十六进制和行将根据 -j 选项进行调整。

const unsigned char BP_icon[] = { 
0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a,0x00,0x00,0x00,0x0d,0x49,0x48,0x44,0x52,
0x00,0x00,0x01,0xed,0x00,0x00,0x01,0xed,0x08,0x06,0x00,0x00,0x00,0x34,0xb4,0x26,
0xfb,0x00,0x00,0x02,0xf1,0x7a,0x54,0x58,0x74,0x52,0x61,0x77,0x20,0x70,0x72,0x6f,
0x66,0x69,0x6c,0x65,0x20,0x74,0x79,0x70,0x65,0x20,0x65,0x78,0x69,0x66,0x00,0x00,
0x78,0xda,0xed,0x96,0x5d,0x92,0xe3,0x2a,0x0c,0x85,0xdf,0x59,0xc5,0x2c,0x01,0x49,
0x08,0x89,0xe5,0x60,0x7e,0xaa,0xee,0x0e,0xee,0xf2,0xef,0x01,0x3b,0x9e,0x4e,0xba,
0xbb,0x6a,0xa6,0x66,0x5e,0x6e,0x55,0x4c,0x8c,0x88,0x0c,0x07,0xd0,0x27,0x93,0x84,
0xf1,0xef,0x3f,0x33,0xfc,0xc0,0x45,0xc5,0x52,0x48,0x6a,0x9e,0x4b,0xce,0x11,0x57,
0x2a,0xa9,0x70,0x45,0xc3,0xe3,0x79,0xd5,0x5d,0x53,0x4c,0xbb,0xde,0xd7,0xe8,0x57,
0x8b,0x9e,0xfd,0xe1,0x7e,0xc0,0xb0,0x02,0x2b,0xe7,0x03,0xcf,0xa7,0xa5,0x87,0xff,
0x1a,0xf0,0xb0,0x54,0xd1,0xd2,0x0f,0x42,0xde,0xae,0x07,0xc7,0xf3,0x83,0x92,0x4e,
0xcb,0xfe,0x22,0xc4,0xa7,0x91,0xb5,0xa2,0xd5,0xee,0x97,0x50,0xb9,0x84,0x84,0xcf,
0x07,0x74,0x09,0xd4,0x73,0x5b,0x31,0x17,0xb7,0x8f,0x5b,0x38,0xc6,0x69,0xaf}

0
投票

我想分享一个完整的 C++ 程序示例,希望它对某人有所帮助。

生成文件

data_base := $(wildcard *.dat)
all: main
main: main.o $(data_base:.dat=.o)
    $(CXX) -z noexecstack $(LDFLAGS) -o $@ $^ $(LDLIBS)
%.o: %.dat
    ld -r -b binary -o $@ $<
clean:
    rm -f *.o

main.cpp

#include <iostream>
#include <sstream>
extern char _binary_table_dat_start[];
extern char _binary_table_dat_end[];

int main (int argc, char* argv[]) {
    std::cout << "Address of start" << &_binary_table_dat_start << std::endl;
    std::cout << "Address of end" << &_binary_table_dat_end << std::endl;

    std::istringstream ss;
    ss.rdbuf()->pubsetbuf(_binary_table_dat_start, _binary_table_dat_end - _binary_table_dat_start - 1);  // Exclude null terminator
    std::size_t i = 0;
    while (!ss.eof()) {
        std::string s;
        std::getline(ss, s);
        std::cout << "Line" << ++i << ": " << s << std::endl;
    }
    std::cout << "Total number of lines " << i << std::endl;
    return 0;

}

表.dat

random text....

-4
投票

您可以在头文件中执行此操作:

#ifndef SHADER_SRC_HPP
#define SHADER_SRC_HPP
const char* shader= "

//source

";
#endif

然后将其包括在内。

另一种方法是读取着色器文件。

© www.soinside.com 2019 - 2024. All rights reserved.