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

时间:2017-11-21 13:42:49

标签: c++ c assembly linker undefined-reference

我已使用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[],但我得到了未解决的符号链接错误。

4 个答案:

答案 0 :(得分:5)

objcopy的诀窍并不是一种全功能的嵌入资源的方式,根本就没有便携性。

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

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

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

直接为此编写自己的转化工具。

答案 1 :(得分:5)

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

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

要在构建链中包含此类工具在VS中并不困难,在makecmake中也更为简单。

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

#include <fstream>
#include <iostream>
#include <string>

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 <fstream>
> 
> 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: Paint a rect on qglwidget at specifit times时使用了相同的技术(类似的形式)。

答案 2 :(得分:3)

您的问题最初并未说明这是针对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标签时可能会出现此链接错误:

  

绝对符号&#39; _binary_Resources_0_png_size&#39;在第4节中用作REL32重定位的目标

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

size_t binary_Resources_0_png_size = _binary_Resources_0_png_end - \
                                     _binary_Resources_0_png_start;

其他观察

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

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命令中:< / p>

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

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

答案 3 :(得分:0)

在解决并测试不同的东西后,我回到原来的方法(链接),它就像魔术一样,这里是细节:

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

objcopy中包含且可通过GNU BinutilsCygwin在窗口中访问的工具MinGW获取文件并生成目标文件。在生成目标文件,输出文件格式和输出体系结构之前,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);