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

16

我有两个问题,第一个已经解决了。

当前问题
如果我嵌入需要库加载的文件,例如jpeg图像或mp3音乐,我将需要将该文件作为库的输入。然而,每个库都是不同的,并且使用一种方法来获取文件作为输入,输入可能是文件名或FILE*指针(来自libc的文件接口)。

我想知道如何使用名称访问嵌入式文件。如果我创建一个临时文件,那将是低效的,是否有另一种方法?我可以将文件名映射到内存吗?我的平台是Windows和Linux。

如果show_file(const char* name)是从库中提取出的函数,那么我需要一个字符串来打开该文件。
我看过以下这些问题:
如何获得内存缓冲区的文件描述符?
从文件描述符中获取文件名(C语言)

下面的代码是我的解决方案。它是一个好的解决方案吗?它是否低效?

# include <stdio.h>
# include <unistd.h>

extern char _binary_data_txt_start;
extern const void* _binary_data_txt_size;
const size_t len = (size_t)&_binary_data_txt_size;

void show_file(const char* name){
    FILE* file = fopen(name, "r");
    if (file == NULL){
        printf("Error (show_file): %s\n", name);
        return;
    }

    while (true){
        char ch = fgetc(file);
        if (feof(file) )
            break;
        putchar( ch );
    }
    printf("\n");

    fclose(file);
}

int main(){

    int fpipe[2];
    pipe(fpipe);

    if( !fork() ){
        for( int buffsize = len, done = 0; buffsize>done; ){
            done += write( fpipe[1], &_binary_data_txt_start + done, buffsize-done );
        }
        _exit(0);
    }

    close(fpipe[1]);
    char name[200];
    sprintf(name, "/proc/self/fd/%d", fpipe[0] );

    show_file(name);

    close(fpipe[0]);
}

另一个问题(已解决)
我在Linux上使用GCC尝试嵌入文件,它成功了。但是,我在Windows上使用Mingw尝试做同样的事情时,它没有编译通过。

代码如下:

# include <stdio.h>

extern char _binary_data_txt_start;
extern char _binary_data_txt_end;

int main(){
    for (char* my_file = &_binary_data_txt_start; my_file <= &_binary_data_txt_end; my_file++)
        putchar(*my_file);
    printf("\n");
}

编译命令为:

objcopy --input-target binary --output-target elf32-i386 --binary-architecture i386 data.txt data.o
g++ main.cpp data.o -o test.exe
在 Windows 上,我收到以下编译器错误:
undefined reference to `_binary_data_txt_start'
undefined reference to `_binary_data_txt_end'

我试图用 i386-pc-mingw32 替换 elf32-i386,但是我仍然得到相同的错误。


这似乎与嵌入式计算无关。已重新标记。 - uɐɪ
你能否简要评论一下你是如何解决你的另一个问题的?很有趣,想知道 :) - Kaos
我用了类似的管道技巧来处理一个第三方库,因为我不能或不想对其进行“修复”。至于效率,它对我很有效:我没有经常调用该库,这至少为我节省了一天的工作时间。 - Flexo
@Kaos,请查看Michael Burr的回答。 - Squall
@Squall:啊,好的。评论中的措辞让我以为你已经解决了它。 - Kaos
5个回答

7
我认为如果要在MinGW上使用此方法,您需要从.c文件中删除名称前面的下划线。详情请参见使用gcc mingw嵌入二进制Blob
请尝试使用以下内容:
extern char binary_data_txt_start;
extern char binary_data_txt_end;

如果您需要在Linux或MinGW构建中使用相同的源代码,您可能需要使用预处理器,在不同的环境中使用正确的名称。


7
如果您正在使用需要FILE*来读取数据的库,那么可以使用fmemopen(3)将内存块创建为伪文件。这将避免在磁盘上创建临时文件。不幸的是,它是GNU扩展,因此我不知道它是否可用于MinGW(可能不可用)。
然而,大多数编写良好的库(例如libpngIJG的JPEG库)提供了从内存中打开文件的例程,而不是从磁盘中打开。特别是libpng甚至提供了流接口,您可以在完全读入内存之前逐步解码PNG文件。如果您从网络流式传输交错的PNG并希望在加载时显示交错数据以获得更好的用户体验,则这非常有用。

1
fmemopen自2008年起成为POSIX的一部分。据我所知,它在Windows上不可用。 - R.. GitHub STOP HELPING ICE

3
在Windows平台上,您可以将自定义资源嵌入可执行文件中。您需要一个.RC文件和一个资源编译器。使用Visual Studio IDE可以轻松完成此操作。
在您的代码中,您需要使用FindResource、LoadResource和LockResource函数,在运行时将内容加载到内存中。以下是读取资源作为长字符串的示例代码:
void GetResourceAsString(int nResourceID, CStringA &strResourceString)
{
    HRSRC hResource = FindResource(NULL, MAKEINTRESOURCE(nResourceID),  L"DATA");

    HGLOBAL hResHandle = LoadResource(NULL, hResource);

    const char* lpData = static_cast<char*> ( LockResource(hResHandle) );

    strResourceString.SetString(lpData, SizeofResource(NULL, hResource));

    FreeResource(hResource);
}

其中nResourceID是自定义资源类型DATA下的资源ID。DATA只是一个名称,您可以选择其他名称。其他内置资源包括光标、对话框、字符串表等。


1
我创建了一个小型库,称为elfdataembed,它提供了一个简单的接口来提取/引用使用objcopy嵌入的部分。这允许您将偏移量/大小传递给另一个工具,或通过文件描述符直接从运行时引用它。希望这能帮助将来的某个人。
值得一提的是,这种方法比编译到符号更有效率,因为它允许外部工具引用数据而不需要被提取,并且它也不需要加载整个二进制文件到内存中才能提取/引用它。

0

使用nm data.o查看它命名的符号。可能只是文件系统差异导致基于文件名的符号不同(例如,文件名大写)。

编辑:刚看到你的第二个问题。如果你正在使用线程,可以创建一个管道并将其传递给库(首先使用fdopen()如果它需要FILE *)。如果您更具体地说明需要访问的API,我可以提供更具体的建议。


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