如何将文件嵌入可执行文件中?

34
我有一个用C++编写的小型演示可执行文件,它只依赖于加载一个5kb的PNG图像,这个图像用于我制作的像素文本。由于这个文件,我需要提供一个ZIP压缩文件而不是一个可执行文件,这在下载和“播放”之间产生了足够的摩擦,我相信这会阻止一些人尝试它。
我的问题是,是否有办法将PNG文件(以及任何其他文件)嵌入到可执行文件或源代码中,使其成为一个单独的文件,并且可执行文件可以使用它?
我有能力将PNG解析为字节流,因此不需要将其转换为像素数据。
提前感谢!(存在与此类似标题的其他问题,但它们及其答案似乎涉及更具体的问题,没有太大帮助)
编辑:编译器是Visual C++ 2010,运行在Windows上(尽管我想避免使用特定于Windows的工具来实现这一点)

edit2: Alf的答案似乎是最便携的方法,所以我迅速编写了一个函数,将PNG文件解析为可以读取为unsigned char数组的TXT或头文件。在这种形式下,它与PNG文件本身相同,但我的png加载程序不接受该数组。当从内存加载时,PNG解析器采用(void * buffer, size_t length)(如果有影响的话)。

如果您想查看代码,这是代码,但如果您认为还有其他更好的方法,我仍然会接受其他答案:

void compileImagePNGtoBinary(char * filename, char * output){

    FILE * file = fopen(filename, "rb");
    FILE * out = fopen(output, "w");

    unsigned char buffer[32];
    size_t count;
    fprintf(out, "#pragma once \n\n static unsigned char TEXT_PNG_BYTES[] = { ");
    while(!feof(file)){
            count = fread(buffer, 1, 32, file);

            for(int n = 0; n < count; ++n){
                    fprintf(out, "0x%02X, ", buffer[n]);
            };
    };
    fprintf(out, "};");
    fclose(file);
    fclose(out);

};

最终编辑: ImageMagick,正如Alf所提到的,它完全满足了我的需求,谢谢!


1
好的,简单(但不一定易于维护)的方法是将其包含为一个数组。http://thedailywtf.com/Articles/The-cbitmap.aspx - Nick ODell
1
@Seth,awoodland - 在关闭投票之前,啊哈,这些问题是通过使用GCC的链接器选项解决的,而我不使用。 - Anne Quinn
3
VC++不支持“资源”吗?(虽然这是特定于VS或兼容软件...) - user166390
我没有投票关闭,但在VC2010编辑之前,它们是密切相关的。这个通用问题不可能以可移植的方式解决(除了说“使用QT”等),因为可执行文件本质上是不可移植的。虽然ImageMagick可以实现可移植性,但不能将其作为PNG文件处理。 - Flexo
@Roddy - 谢谢,我已经修复了,不确定那个问题是从哪里来的。我可能最终会使用一个辅助程序来完成这个任务,哈哈。在将文件转换为字节数组时,似乎我缺少一些细微差别。 - Anne Quinn
显示剩余8条评论
9个回答

20

一种便于移植的方法是定义一个函数,如下所示:

typedef unsigned char Byte;

Byte const* pngFileData()
{
    static Byte const data =
    {
        // Byte data generated by a helper program.
    };
    return data;
}

接下来你只需要编写一个小助手程序,以二进制方式读取PNG文件并生成C++花括号初始化程序文本。编辑: @awoodland在问题的评论中指出,ImageMagick 已经提供了这样一个小助手程序…

当然,对于特定于Windows的程序,应使用普通的Windows资源方案。

祝好运!


我快速编写了一个解析函数来完成这个任务。它仍然有一些错误,但似乎输出了正确的数组。这是你所想要的吗?(我将代码粘贴到问题正文中) - Anne Quinn
哦,已经有程序可以做到这个了?我没注意到那个编辑,哈哈,最好直接使用它! - Anne Quinn
如果文件太大(对于我的带有VS2017的PC来说,它超过了3MB),将数据代码放入您的代码后,IDE/编辑器会变得很慢。 - zwcloud
@zwcloud 是啊...我也不感到意外。最好将其作为单独的文件并进行“包含”。 - spacer GIF
1
实际上是 static Byte const data[] - PinkTurtle

15

看一下 XD:

http://www.fourmilab.ch/xd/

最后,xd可以读取二进制文件并生成包含文件数据的C语言数据声明。当您希望在C程序中嵌入二进制数据时,这非常方便。

个人而言,我会使用Windows资源,但如果您需要一种真正便携且不涉及可执行文件格式知识的方法,那么这就是选择的方式。PNG,JPG等...


6
默认情况下,几乎任何shell都可以使用xxd -i命令,通常由vim软件包安装。 - kert
1
xd 也是某种目录更改程序的名称。 xxd -i 的效果非常好 - 从 data.txt 中,结果包含以空字符结尾的 unsigned char data_txt[]unsigned int data_txt_len;您可以将其原样包含,将其包含在命名空间/结构/函数中,静态编译和链接等。 - John P

8

Base64 将文件编码并将其放入代码中的某个字符串中;)


6

您可以将任何任意文件嵌入到程序资源中:(MSDN)用户定义资源

A user-defined resource-definition statement defines a resource that contains application-specific data. The data can have any format and can be defined either as the content of a given file (if the filename parameter is given) or as a series of numbers and strings (if the raw-data block is specified).

nameID typeID filename

The filename specifies the name of a file containing the binary data of the resource. The contents of the file are included as the resource. RC does not interpret the binary data in any way. It is the programmer's responsibility to ensure that the data is properly aligned for the target computer architecture.

一旦你完成了这个步骤,你就可以使用LoadResource函数来访问文件中包含的字节。

4
这是与可执行文件格式有关的,这意味着与操作系统/编译器相关。如此问题所述,Windows提供了资源系统来处理这个问题。

MSDN的使用资源(Windows):https://msdn.microsoft.com/zh-cn/library/ms648008(v=VS.85).aspx - Andrew
不一定,如果你将文件内容转储到一个字节数组常量中。 - SwiftMango

4

为了完整起见,我将提及#embed,并希望这很快能成为(标准)现实。

C23(不是C++编程语言)已经将此功能作为一个语言特性纳入其中,允许您编写:


int main (int, char*[]) {
    static const char sound_signature[] = {
#embed <sdk/jump.wav>
// ^^^^^^^^^^^^^^^^^^
    };
    static_assert((sizeof(sound_signature) / sizeof(*sound_signature)) >= 4,
        "There should be at least 4 elements in this array.");

    return 0;
}

对于一个C++项目来说,专门为这个用例添加一个C子组件可能有些牵强,除非你的应用程序在某种程度上需要嵌入数据。我的直觉是,在它成为标准之前,实现将会“悄悄地”将其提供给C++使用。你可以在特性作者的博客上阅读更多相关内容(以上示例摘自该博客)。
请记住,即使在C++之前,也曾经追求过完全相同的特性,但最终先进入了C标准。一份文件(可能是为C++26准备的)可以在这里找到,其中建议使用以下语法:
#include <embed>


constexpr std::span<const std::byte> 
  fxaa_binary = std::embed("fxaa.spirv");

避免使用预处理器,即完全不使用预处理器。

3
如果我想将静态数据嵌入可执行文件中,我会将其打包成.lib/.a文件或头文件,并作为无符号字符数组。如果您正在寻找一种可移植的方法,那么就是这样做的。 实际上,我创建了一个命令行工具在这里,它可以同时完成这两个任务。您只需要列出文件,然后选择选项-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}

2

在 Linux 上,我使用这个。它基于我在尝试做一些 4k 演示时找到的一些示例,经过修改后。我相信它也可以在 Windows 上工作,但不使用默认的 VS 内联汇编。我的解决方法是 #define 宏,要么使用此代码,要么使用 @MarkRansom 建议的 Windows 资源系统(非常痛苦才能正常工作)。

//USAGE: call BINDATA(name, file.txt) and access the char array &name.

#ifndef EMBED_DATA_H
#define EMBED_DATA_H

#ifdef _WIN32
//#error The VS ASM compiler won't work with this, but you can get external ones to do the trick
#define BINDATA #error BINDATA requires nasm
#else

__asm__(
".altmacro\n" \
".macro binfile p q\n" \
"   .global \\p\n" \
"\\p:\n" \
"   .incbin \\q\n" \
"\\p&_end:\n" \
"   .byte 0\n" \
"   .global \\p&_len\n" \
"\\p&_len:\n" \
"   .int(\\p&_end - \\p)\n" \
".endm\n\t"
);

#ifdef __cplusplus
    extern "C" {
#endif

#define BINDATA(n, s) \
__asm__("\n\n.data\n\tbinfile " #n " \"" #s "\"\n"); \
extern char n; \
extern int n##_len;

#ifdef __cplusplus
    }
#endif

#endif

#endif

0

我来这里寻找一个bash脚本,以便以大多数跨平台兼容的方式生成C字节数组(无论如何,我都依赖于mingw bash进行我的Windows构建),而不必编译帮助工具或依赖于任何不随普通bash shell一起提供的工具。以下是我的想法:

#!/bin/sh
set -e

SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
OUT_FILE="$SCRIPT_DIR/src/alloverse_binary_schema.h"
BINARY_FILE="$SCRIPT_DIR/include/allonet/schema/alloverse.bfbs"
VAR_NAME="alloverse_schema"

echo "static const unsigned char ${VAR_NAME}_bytes[] = {" > "$OUT_FILE"
hexdump -ve '1/1 "0x%02x, "' "$BINARY_FILE" >> "$OUT_FILE"
echo "0x00}; static const int ${VAR_NAME}_size = sizeof(${VAR_NAME}_bytes); " >> "$OUT_FILE"

然后我可以从使用它的C文件中直接 #include 它,并根据需要使用 foo_bytesfoo_size

#include "alloverse_binary_schema.h"
bool allo_initialize(void)
{
    g_alloschema = reflection_Schema_as_root(alloverse_schema_bytes);
}

通过调整OUT_FILEBINARY_FILEVAR_NAME(也许将它们作为脚本的参数)来适应您的需求。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接