如何在程序中包含数据对象文件(图像等)并访问符号?

我使用objcopy将几个资源文件转换为.obj文件,然后将它们与我的程序源代码链接起来。 我可以使用以下代码很好地访问程序中目标文件内的符号,但只能使用GCC / G ++(Cygwin):

 extern uint8_t data[] asm("_binary_Resources_0_png_start"); extern uint8_t size[] asm("_binary_Resources_0_png_size"); extern uint8_t end[] asm("_binary_Resources_0_png_end"); 

该代码在Visual Studio中不起作用,可能是因为VS拥有自己的__asm命令。 我希望通过链接它们将我的程序资源(图像,着色器等)包含在我的最终可执行文件的.data部分中。

但是如何在VC ++中访问目标文件中定义的符号? 我在没有汇编命令的情况下尝试了extern uint8_t _binary_Resources_0_png_start[]extern "C" uint8_t _binary_Resources_0_png_start[] ,但是我得到了未解决的符号链接错误。

使用objcopy的技巧并不是一种全function的嵌入资源的方式,并且根本不可移植,正如您所看到的那样。

Microsoft有自己的资源机制,因此如果您专门针对Windows,则可以使用Windows资源文件和RCDATA资源 。

如果你想要一些完全可移植的东西,你唯一的选择是将文件格式化为C源代码,例如

 const uint8_t my_binary[] = { 0x00, 0x01, ... } 

为此编写自己的转换工具是很简单的。

这可能是一种完全不同的方法,但它提供了一个相当简单但便携的解决方案:

我们使用一个小工具来加载二进制文件并将其输出为C(或C ++源代码)。 实际上,我在XPM和GIMP中看到了类似的东西,但它可以用于任何二进制数据。

在VS中包含这样的工具并不困难,在makecmake也更简单。

这样的工具可能如下所示:

 #include  #include  #include  using namespace std; int main(int argc, char **argv) { if (argc < 2) { cerr << "Usage: " << argv[0] << " FILE [FILE...]" << endl; return -1; } for (size_t i = 1; i < argc; ++i) { fstream fIn(argv[i], ios::in | ios::binary); if (!fIn.good()) { cerr << "ERROR: Cannot open '" << argv[i] << "'!" << endl; continue; } // make name string name = argv[i]; name = name.substr(0, name.find('.')); /// @todo more sophisticated name mangling? // print preface cout << "struct { const char *data; size_t size; } " << name << " = {" << endl << " \""; // print data const char hex[] = "0123456789abcdef"; unsigned char byte; enum { BytesPerLine = 16 }; size_t n = 0; for (unsigned char byte; fIn.get((char&)byte); ++n) { if (n && !(n % BytesPerLine)) cout << "\"\n \""; cout << "\\x" << hex[byte / 16] << hex[byte % 16]; } // print size cout << "\",\n" " " << n << "\n" "};" << endl; } return 0; } 

编译和测试:

 $ g++ -std=c++11 -o binToC binToC.cc $ ./binToC Usage: ./binToC FILE [FILE...] 

使用fluffy_cat.png更多测试 fluff_cat.png

 $ ./binToC fluffy_cat.png > fluffy_cat.inc $ cat >fluffy_cat_test.cc <<'EOF' > #include  > > using namespace std; > > #include "fluffy_cat.inc" > > int main() > { > ofstream fOut("fluffy_cat_test.png", ios::out | ios::binary); > fOut.write(fluffy_cat.data, fluffy_cat.size); > fOut.close(); > return 0; > } > EOF $ g++ -std=c++11 -o fluffy_cat_test fluffy_cat_test.cc $ ./fluffy_cat_test $ diff fluffy_cat.png fluffy_cat_test.png $ 

正如diff显示 - C源完全再现原始。

顺便说一句。 我在回答SO时使用了相同的技术(类似的forms) :在特定时间在qglwidget上绘制一个rect 。

您的问题最初没有说明这是用于64位Cygwin G ++ / MSVC ++还是32位。 名字装饰有一个微妙的区别。


带有OBJCOPY的x86(32位Windows PE)解决方案

我假设你有一个名为Resources_0.png的资源文件。 您可以使用以下命令生成32位Windows PE对象文件:

 objcopy --prefix-symbol=_ --input-target binary --output-target \ pe-i386 --binary-architecture i386 Resources_0.png Resources_0.obj 

--prefix-symbol=_在每个标签上附加一个下划线( _ )。 使用附加_名称装饰是Win32 / PE外部对象的标准。 生成的文件会产生一个带有这些标签的对象:

 __binary_Resources_0_png_start __binary_Resources_0_png_end __binary_Resources_0_png_size 

针对32位可执行文件的MSVC ++和Cygwin G ++可以将这些标签引用为:

 extern "C" uint8_t _binary_Resources_0_png_start[]; extern "C" uint8_t _binary_Resources_0_png_end[]; extern "C" uint8_t _binary_Resources_0_png_size[]; 

带有OBJCOPY的x86-64(64位Windows PE)解决方案

您可以使用以下命令生成64位Windows PE对象文件:

 objcopy --input-target binary --output-target pe-x86-64 --binary-architecture i386 \ Resources_0.png Resources_0.obj 

这类似于32位,但我们不再在每个标签之前添加额外的下划线( _ )。 这是因为在64位PE代码中,名称没有使用额外的下划线进行修饰。

生成的文件会产生一个带有这些标签的对象:

 _binary_Resources_0_png_start _binary_Resources_0_png_end _binary_Resources_0_png_size 

针对64位Windows PE可执行文件的MSVC ++和Cygwin G ++可以引用这些标签,与上面的32位Windows PE版本完全相同:

 extern "C" uint8_t _binary_Resources_0_png_start[]; extern "C" uint8_t _binary_Resources_0_png_end[]; extern "C" uint8_t _binary_Resources_0_png_size[]; 

特别说明 :使用MSVC ++作为64位代码进行编译时,使用size标签时可能会出现此链接错误:

绝对符号’_binary_Resources_0_png_size’在第4节中用作REL32重定位的目标

使用64位代码,您可以通过使用startend标签之间的差异计算C ++代码中的大小来避免这种情况,如下所示:

 size_t binary_Resources_0_png_size = _binary_Resources_0_png_end - \ _binary_Resources_0_png_start; 

其他观察

即使使用G ++ / GCC,这也是不好的forms:

 extern uint8_t data[] asm("_binary_Resources_0_png_start"); extern uint8_t size[] asm("_binary_Resources_0_png_size"); extern uint8_t end[] asm("_binary_Resources_0_png_end"); 

没有必要这样做,而且便携性较差。 请参阅上面的解决方案,该解决方案不对G ++代码的变量使用asm指令。


这个问题被标记为C和C ++,问题包含带有extern "C"代码。 上面的答案假设您正在使用G ++ / MSVC ++编译.cpp文件。 如果使用GCC / MSVC编译.c文件,则将extern "C"更改为extern


如果要生成带有OBJCOPY的Windows PE对象,其中数据放在只读.rdata节而不是.data节中,则可以将此选项添加到上面的OBJCOPY命令中:

 --rename-section .data=.rdata,CONTENTS,ALLOC,LOAD,READONLY,DATA 

我在这个Stackoverflow答案中讨论了这个选项。 不同之处在于,在Windows PE中,只读部分通常称为.rdata ,与ELF对象一样,它是.rodata

在解决并测试不同的东西之后,我回到了原来的方法(链接),它像魔术一样工作,这里有详细信息:

为了在最终可执行文件的.data部分中包含数据,您需要首先将这些数据文件(可以是任意二进制文件(任何!))转换为可链接文件格式,也称为目标文件。

工具objcopy包含在GNU Binutils ,可以通过CygwinMinGW在Windows中访问,它接收一个文件并生成一个目标文件。 在生成目标文件,输出文件格式和输出体系结构之前,objcopy需要知道两件事。 为了确定这两件事,我用工具objdump检查一个有效的可链接对象文件:

 objdump -f main.o 

这给了我以下信息:

 main.o: file format pe-x86-64 architecture: i386:x86-64, flags 0x00000039: HAS_RELOC, HAS_DEBUG, HAS_SYMS, HAS_LOCALS start address 0x0000000000000000 

有了这些知识,我现在可以创建目标文件:

 objcopy -I binary -O pe-x86-64 -B i386 data_file.data data_file_data.o 

为了处理大量文件,批处理文件可以派上用场。

然后我简单地将生成的目标文件与我的程序源链接起来,并通过符号取消引用objcopy生成的指针,这些符号的名称很容易被查询:

 objdump -t data_file_data.o 

结果如下:

 data_file_data.o: file format pe-x86-64 SYMBOL TABLE: [ 0](sec 1)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x0000000000000000 _binary_data_file_data_start [ 1](sec 1)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x0000000000000006 _binary_data_file_data_end [ 2](sec -1)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x0000000000000006 _binary_data_file_data_size 

实际上,以下代码适用于GCC/G++

 extern uint8_t data[] asm("_binary_data_file_data_start"); extern uint8_t end[] asm("_binary_data_file_data_end"); 

以下是MSVC++

 extern "C" uint8_t _binary_data_file_data_start[]; // Same name as symbol extern "C" uint8_t _binary_data_file_data_end[]; // Same name as symbol 

每个文件的大小计算如下:

 _binary_data_file_data_end - _binary_data_file_data_start 

例如,您可以将数据写回文件:

 FILE* file; file = fopen("data_file_reproduced.data", "wb"); fwrite(_binary_data_file_data_start, //Pointer to data 1, //Write block size _binary_data_file_data_end - _binary_data_file_data_start, //Data size file); fclose(file);