2012-08-05 113 views
20

我正在编写一个依赖于我项目中的各种资源的C++应用程序。现在,我拥有从源代码生成的可执行文件到每个资源的相对路径,并允许我的程序打开文件并读入每个资源中的数据。这工作正常,但它需要我从相对于资源的特定路径启动可执行文件。因此,如果我尝试从其他地方启动我的可执行文件,它将无法打开文件并且无法继续。使用CMake将资源(例如,着色器代码;图像)嵌入可执行文件/库

有有CMake的嵌入我的资源到可执行文件(或库),这样我可以简单地在运行时访问它们在内存而不是打开文件,文件的路径是脆的便携式方式?我发现一个related question,它看起来像嵌入资源可以做得很好,有一些神奇的ld。所以我的问题是如何在使用CMake的便携式,跨平台方式中做到这一点?实际上我需要在x86和ARM上运行我的应用程序。如果任何人都可以建议如何为Windows(嵌入式)执行此操作,那么我支持Linux(嵌入式)也是可以的。

编辑: 我忘了提到解决方案的理想属性。我想能够使用CMake的交叉编译应用程序时,我建设ARM,而不是要本地编译它在我的手臂的目标。

回答

22

一个做到这一点的最简单的方法就是在您建立一个小型的,可移植的C程序,读取资源和生成包含资源数据和实际资源数据的长度,数组的C文件不变的字符文字。这将完全独立于平台,但应仅用于相当小的资源。对于较大的资源,您可能不希望将这些文件嵌入到您的程序中。

对于资源“foo”的,所生成的C文件“foo.c的”将包含:

const char foo[] = { /* bytes of resource foo */ }; 
const size_t foo_len = sizeof(foo); 

要由C访问资源++,则在任一页眉或cpp文件声明如下两个符号他们用在:

extern "C" const char foo[]; 
extern "C" const size_t foo_len; 

要在构建产生foo.c,你需要一个目标C程序(称之为embedfile.c),你需要使用ADD_CUSTOM_COMMAND命令来调用这个程序:

add_executable(embedfile embedfile.c) 

add_custom_command(
    OUTPUT foo.c 
    COMMAND embedfile foo foo.rsrc 
    DEPENDS foo.rsrc) 

然后,在需要“foo”资源的目标的源列表上包含foo.c。您现在可以访问“foo”的字节。

程序embedfile.c是:

#include <stdlib.h> 
#include <stdio.h> 

FILE* open_or_exit(const char* fname, const char* mode) 
{ 
    FILE* f = fopen(fname, mode); 
    if (f == NULL) { 
    perror(fname); 
    exit(EXIT_FAILURE); 
    } 
    return f; 
} 

int main(int argc, char** argv) 
{ 
    if (argc < 3) { 
    fprintf(stderr, "USAGE: %s {sym} {rsrc}\n\n" 
     " Creates {sym}.c from the contents of {rsrc}\n", 
     argv[0]); 
    return EXIT_FAILURE; 
    } 

    const char* sym = argv[1]; 
    FILE* in = open_or_exit(argv[2], "r"); 

    char symfile[256]; 
    snprintf(symfile, sizeof(symfile), "%s.c", sym); 

    FILE* out = open_or_exit(symfile,"w"); 
    fprintf(out, "#include <stdlib.h>\n"); 
    fprintf(out, "const char %s[] = {\n", sym); 

    unsigned char buf[256]; 
    size_t nread = 0; 
    size_t linecount = 0; 
    do { 
    nread = fread(buf, 1, sizeof(buf), in); 
    size_t i; 
    for (i=0; i < nread; i++) { 
     fprintf(out, "0x%02x, ", buf[i]); 
     if (++linecount == 10) { fprintf(out, "\n"); linecount = 0; } 
    } 
    } while (nread > 0); 
    if (linecount > 0) fprintf(out, "\n"); 
    fprintf(out, "};\n"); 
    fprintf(out, "const size_t %s_len = sizeof(%s);\n\n",sym,sym); 

    fclose(in); 
    fclose(out); 

    return EXIT_SUCCESS; 
} 
+0

这看起来就像一个非常优雅的平台独立解决方案+1。我会试试看。然而,关于我最初忘记提及的东西有一个小小的倒台(参见我的编辑)。如果我将这个解决方案整合到项目中,并尝试对其进行交叉编译,那么在尝试执行embedfile时会遇到麻烦,因为该可执行文件将针对ARM进行编译,并且我将尝试在x86上执行它。我想我可以为我的主机平台分别编译'embedfile'并将其安装到我的路径中。稍微不方便,但我只需要做一次。 – 2012-08-05 19:47:20

+1

@SchighSchagh这可能是CMake的标志,但我目前没有很多交叉编译的经验。如果您知道解释器在感兴趣的主机平台上可用,则可以使用Python或Perl等语言轻松重写embedfile。 – sfstewman 2012-08-05 19:59:26

+0

@SchighSchagh:你可能想看看这个:http://www.cmake.org/Wiki/CMake_Cross_Compiling#Using_executables_in_the_build_created_during_the_build – sfstewman 2012-08-05 20:02:40

4

我想说的最优雅的方式在C具有嵌入式资源++是简单地使用Qt的资源系统,这是在不同的平台之间移植,以兼容的CMake ,而且基本上包了一切在上面的回答做了,除了提供压缩,进行全面测试和防呆,一切。

创建一个Qt资源文件 - 一个XML列出文件嵌入:

<RCC> 
    <qresource prefix="/"> 
     <file>uptriangle.png</file> 
     <file>downtriangle.png</file> 
    </qresource> 
</RCC> 

调用该文件qtres.qrc。上面的资源文件将有两个PNG文件(位于同一目录qtres.qrc)嵌入到最终的可执行文件。您可以使用QtCreator(Qt IDE)轻松地将监视资源添加/删除到qrc文件。

set(CMAKE_AUTOMOC ON) 
find_package(Qt5Core) 
qt5_add_resources(QT_RESOURCE qtres.qrc) 

在你的main.cpp中,您需要访问资源之前,添加以下行:

Q_INIT_RESOURCE(qtres); 

现在您可以访问

在你的CMakeLists.txt文件中添加

现在以上使用与Qt资源系统兼容的Qt类的任何资源,例如QPixmap,QImage ...以及最重要的可能是一般情况下的QResource包装类,它封装了一个嵌入式Qt资源并允许通过友好的界面访问它。作为一个例子,到downtriangle.png内访问在上述资源的数据,下面的线就可以了:

#include <QtCore> 
#include <QtGui> 

// ... 

int main(int argc, char **argv) 
{ 

    // ... 

    Q_INIT_RESOURCE(qtres); 

    // ... 
    QResource res("://downtriangle.png"); // Here's your data, anyway you like 
    // OR 
    QPixmap pm("://downtriangle.png"); // Use it with Qt classes already 

    // ... 

} 

在这里,资源可用于直接访问使用res.data(),RES的数据.size()... 解析文件的图像内容使用pm。使用pm.size(),pm.width()...

而你很好走。 我希望它有帮助。

+6

Qt是一个相当沉重的依赖我的口味,但无论如何,一个好的答案+1。 – 2014-07-24 13:54:41

+0

我现在正在为ARM/DSP平台开发一个专门使用STL + Boost的嵌入式应用程序,并且已经设法使用QtResources,它基本上封装了第一个答案中完成的内容。除了QResource(如果您使用该文件作为原始二进制资源)和Qt5Core cmake模块(随框架提供并且也可以单独使用默认发行版配置),不会有任何依赖关系(至少不是重度关联)。查看由'qt5_add_resources(QT_RESOURCE qtres.qrc)'命令生成的资源文件。 – polonium 2014-07-31 17:39:31

+0

Qt资源系统非常棒,只有当你想要和被允许使用Qt时,这个答案才适用。例如,一个精简的SDK库文件将不可能使用这种方法。 – 2015-01-27 00:13:28

19

作为一种替代的sfstewman答案,这里是一个小的CMake(2.8)函数将所有文件转换成一个特定的文件夹C数据,并把它们写在希望的输出文件:

# Creates C resources file from files in given directory 
function(create_resources dir output) 
    # Create empty output file 
    file(WRITE ${output} "") 
    # Collect input files 
    file(GLOB bins ${dir}/*) 
    # Iterate through input files 
    foreach(bin ${bins}) 
     # Get short filename 
     string(REGEX MATCH "([^/]+)$" filename ${bin}) 
     # Replace filename spaces & extension separator for C compatibility 
     string(REGEX REPLACE "\\.| |-" "_" filename ${filename}) 
     # Read hex data from file 
     file(READ ${bin} filedata HEX) 
     # Convert hex data for C compatibility 
     string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," filedata ${filedata}) 
     # Append data to output file 
     file(APPEND ${output} "const unsigned char ${filename}[] = {${filedata}};\nconst unsigned ${filename}_size = sizeof(${filename});\n") 
    endforeach() 
endfunction() 
+4

'字符串(MAKE_C_IDENTIFIER $ {文件名}文件名)'可以替换'字符串(REGEX REPLACE ...)' – nishantjr 2015-07-09 08:14:59

+0

我发现MAKE_C_IDENTIFIER在文件名中不能使用句号和连字符。原来的正则表达式也没有工作。我修改了一下,以便为这些情况创建的变量也使用下划线:'string(REGEX REPLACE“\\。| | - ”“_”filename $ {filename})' – Gary 2016-01-14 14:14:44

+0

另外,如果您将数据输出更改为'file(APPEND $ {output}“const char $ {filename} [] = {$ {filedata} 0x00}; \ nconst unsigned $ {filename} _size = sizeof($ {filename}) ; \ n“) ',你可以将文件的内容直接输出到像cout这样的流,而不需要转换或转换。 0x00也会自动终止它,所以当你以这种方式使用时,你不会开始打印随机存储器的值。 – Gary 2016-01-14 14:30:08