如何在程序中包含数据对象文件(图像等)并访问符号?
How to include data object files (images, etc.) in program and access the symbols?
我已经使用 objcopy
将几个资源文件转换为 .obj 文件,并且 link 我将它们与我的程序源代码一起使用。
我可以使用以下代码很好地访问程序中目标文件中的符号,但只能使用 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
命令。
我想通过 linking 将我的程序资源(图像、着色器等)包含在我的最终可执行文件的 .data
部分中。
但是我如何访问 VC++ 中目标文件中定义的符号?
我在没有汇编命令的情况下尝试了 extern uint8_t _binary_Resources_0_png_start[]
或 extern "C" uint8_t _binary_Resources_0_png_start[]
,但我得到了未解析的符号 link 错误。
objcopy
的技巧并不意味着嵌入资源的全功能方式,而且根本不可移植,如您所见。
Microsoft 有自己的资源机制,因此如果您专门针对 windows,您可以使用 windows 资源文件和 RCDATA resource.
如果您想要完全可移植的东西,您唯一的选择是将文件格式化为 C 源代码,例如
const uint8_t my_binary[] = { 0x00, 0x01, ... }
为此编写您自己的转换工具很简单。
这可能是一种完全不同的方法,但它提供了一个相当简单但可移植的解决方案:
我们使用一个小工具加载二进制文件并将其输出为C(或C++源)。实际上,我在 XPM 和 GIMP 中看到过类似的东西,但它可以用于任何二进制数据。
在 VS 中将此类工具包含在构建链中并不困难,在 make
和 cmake
中更简单。
这样的工具可能如下所示:
#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
:
$ ./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 源代码完全复制了原件。
顺便说一句。我在 .
的回答中使用了相同的技术(以类似的形式)
你的问题最初没有说明这是针对 64 位 Cygwin G++/MSVC++ 还是 32 位的。在名称装饰方面存在细微差别。
x86(32 位 Windows PE)解决方案与 OBJCOPY
我假设您有一个名为 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[];
x86-64 (64-bit Windows PE) OBJCOPY解决方案
您可以生成 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
MSVC++ 和 Cygwin G++ 针对 64 位 Windows PE 可执行文件可以引用这些标签,与上面的 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
标签时可能会出现此链接错误:
absolute symbol '_binary_Resources_0_png_size' used as target of REL32 relocation in section 4
对于 64 位代码,您可以通过使用 start
和 end
标签之间的差异计算 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
如果要生成 Windows 带有 OBJCOPY 的 PE 对象,其中数据放置在只读 .rdata
部分而不是 .data
部分,您可以将此选项添加到上面的 OBJCOPY 命令:
--rename-section .data=.rdata,CONTENTS,ALLOC,LOAD,READONLY,DATA
我在这个 中讨论了这个选项。不同之处在于,在 Windows PE 中,只读部分通常称为 .rdata
,而对于 ELF 对象,它是 .rodata
在解决和测试不同的事情之后,我回到了我原来的方法(linking)并且它像魔术一样工作,这里是详细信息:
为了在最终可执行文件的 .data
部分中包含数据,您需要首先将该数据文件(可以是任意二进制文件(任何东西!))转换为 linkable文件格式,也称为目标文件。
工具 objcopy
包含在 GNU Binutils
中并且可以在 windows 中通过 Cygwin
或 MinGW
访问,获取一个文件并生成一个对象文件。 objcopy 在生成目标文件之前需要知道两件事,即输出文件格式和输出体系结构。
为了确定这两件事,我使用工具 objdump
:
检查了一个有效的 linkable 对象文件
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
为了处理大量文件,批处理文件可以派上用场。
然后我简单地 link 生成的目标文件连同我的程序源和取消引用 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);
我已经使用 objcopy
将几个资源文件转换为 .obj 文件,并且 link 我将它们与我的程序源代码一起使用。
我可以使用以下代码很好地访问程序中目标文件中的符号,但只能使用 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
命令。
我想通过 linking 将我的程序资源(图像、着色器等)包含在我的最终可执行文件的 .data
部分中。
但是我如何访问 VC++ 中目标文件中定义的符号?
我在没有汇编命令的情况下尝试了 extern uint8_t _binary_Resources_0_png_start[]
或 extern "C" uint8_t _binary_Resources_0_png_start[]
,但我得到了未解析的符号 link 错误。
objcopy
的技巧并不意味着嵌入资源的全功能方式,而且根本不可移植,如您所见。
Microsoft 有自己的资源机制,因此如果您专门针对 windows,您可以使用 windows 资源文件和 RCDATA resource.
如果您想要完全可移植的东西,您唯一的选择是将文件格式化为 C 源代码,例如
const uint8_t my_binary[] = { 0x00, 0x01, ... }
为此编写您自己的转换工具很简单。
这可能是一种完全不同的方法,但它提供了一个相当简单但可移植的解决方案:
我们使用一个小工具加载二进制文件并将其输出为C(或C++源)。实际上,我在 XPM 和 GIMP 中看到过类似的东西,但它可以用于任何二进制数据。
在 VS 中将此类工具包含在构建链中并不困难,在 make
和 cmake
中更简单。
这样的工具可能如下所示:
#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
$ ./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 源代码完全复制了原件。
顺便说一句。我在
你的问题最初没有说明这是针对 64 位 Cygwin G++/MSVC++ 还是 32 位的。在名称装饰方面存在细微差别。
x86(32 位 Windows PE)解决方案与 OBJCOPY
我假设您有一个名为 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[];
x86-64 (64-bit Windows PE) OBJCOPY解决方案
您可以生成 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
MSVC++ 和 Cygwin G++ 针对 64 位 Windows PE 可执行文件可以引用这些标签,与上面的 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
标签时可能会出现此链接错误:
absolute symbol '_binary_Resources_0_png_size' used as target of REL32 relocation in section 4
对于 64 位代码,您可以通过使用 start
和 end
标签之间的差异计算 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
如果要生成 Windows 带有 OBJCOPY 的 PE 对象,其中数据放置在只读 .rdata
部分而不是 .data
部分,您可以将此选项添加到上面的 OBJCOPY 命令:
--rename-section .data=.rdata,CONTENTS,ALLOC,LOAD,READONLY,DATA
我在这个 .rdata
,而对于 ELF 对象,它是 .rodata
在解决和测试不同的事情之后,我回到了我原来的方法(linking)并且它像魔术一样工作,这里是详细信息:
为了在最终可执行文件的 .data
部分中包含数据,您需要首先将该数据文件(可以是任意二进制文件(任何东西!))转换为 linkable文件格式,也称为目标文件。
工具 objcopy
包含在 GNU Binutils
中并且可以在 windows 中通过 Cygwin
或 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
为了处理大量文件,批处理文件可以派上用场。
然后我简单地 link 生成的目标文件连同我的程序源和取消引用 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);