我已使用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[]
,但我得到了未解决的符号链接错误。
答案 0 :(得分:5)
objcopy
的诀窍并不是一种全功能的嵌入资源的方式,根本就没有便携性。
Microsoft拥有自己的资源机制,因此,如果您专门针对Windows,则可以使用Windows资源文件和RCDATA resource。
如果您想要完全可移植的东西,您唯一的选择是将文件格式化为C源代码,例如
const uint8_t my_binary[] = { 0x00, 0x01, ... }
直接为此编写自己的转化工具。
答案 1 :(得分:5)
这可能是一种完全不同的方法,但它提供了一种相当简单但便携的解决方案:
我们使用一个小工具加载二进制文件并将其输出为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...]
$ ./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 的我假设您有一个名为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 的您可以使用以下命令生成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位代码,您可以通过使用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
如果要生成带有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 Binutils
或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
为了处理大量文件,批处理文件可以派上用场。
然后我简单地将生成的目标文件与我的程序源链接起来,并通过符号取消引用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);