使用CMake将资源(例如,着色器代码;图像)嵌入到可执行文件/库中

Nic*_*rca 29 c++ cmake cross-compiling embedded-resource

我正在用C++编写一个应用程序,它依赖于我项目中的各种资源.现在,我有从生成的可执行文件到我的源中硬编码的每个资源的相对路径,这允许我的程序打开文件并读入每个资源中的数据.这工作正常,但它要求我从相对于资源的特定路径启动可执行文件.因此,如果我尝试从其他任何地方启动我的可执行文件,它将无法打开文件而无法继续.

有没有一种可移植的方法让CMake将我的资源嵌入到可执行文件(或库)中,以便我可以在运行时只在内存中访问它们而不是打开路径脆弱的文件?我找到了一个相关的问题,看起来嵌入资源可以用一些ld魔法做得很好.所以我的问题是如何使用CMake 以便携,跨平台的方式做到这一点?我实际上需要在x86和ARM上运行我的应用程序.我只支持Linux(嵌入式),但如果有人可以建议如何为Windows(嵌入式)做这个,也可以获得奖励积分.

编辑:我忘了提到解决方案的理想属性.我希望能够在为ARM构建时使用CMake交叉编译应用程序,而不是必须在我的ARM目标上本地编译它.

You*_*uka 33

作为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()
Run Code Online (Sandbox Code Playgroud)

  • `string(MAKE_C_IDENTIFIER $ {filename} filename)`可以替换`string(REGEX REPLACE ...)` (4认同)

sfs*_*man 28

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

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

const char foo[] = { /* bytes of resource foo */ };
const size_t foo_len = sizeof(foo);
Run Code Online (Sandbox Code Playgroud)

要从C++访问资源,请在头文件或使用它们的cpp文件中声明以下两个符号:

extern "C" const char foo[];
extern "C" const size_t foo_len;
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

然后,包含foo.c在需要"foo"资源的目标的源列表中.您现在可以访问"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;
}
Run Code Online (Sandbox Code Playgroud)

  • 看起来这个方法已经被整合到一个很好的 CMake 兼容包中:https://github.com/cyrilcode/embed-resource (2认同)

Ita*_*dev 7

我想提出另一种选择。它使用 GCC 链接器将二进制文件直接嵌入到可执行文件中,没有中间源文件。在我看来,哪个更简单,更有效。

set( RC_DEPENDS "" )

function( add_resource input )
    string( MAKE_C_IDENTIFIER ${input} input_identifier )
    set( output "${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/${input_identifier}.o" )
    target_link_libraries( ${PROJECT_NAME} ${output} )

    add_custom_command(
        OUTPUT ${output}
        COMMAND ${CMAKE_LINKER} --relocatable --format binary --output ${output} ${input}
        DEPENDS ${input}
    )

    set( RC_DEPENDS ${RC_DEPENDS} ${output} PARENT_SCOPE )
endfunction()

# Resource file list
add_resource( "src/html/index.html" )

add_custom_target( rc ALL DEPENDS ${RC_DEPENDS} )
Run Code Online (Sandbox Code Playgroud)

然后在您的 C/C++ 文件中,您只需要:

set( RC_DEPENDS "" )

function( add_resource input )
    string( MAKE_C_IDENTIFIER ${input} input_identifier )
    set( output "${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/${input_identifier}.o" )
    target_link_libraries( ${PROJECT_NAME} ${output} )

    add_custom_command(
        OUTPUT ${output}
        COMMAND ${CMAKE_LINKER} --relocatable --format binary --output ${output} ${input}
        DEPENDS ${input}
    )

    set( RC_DEPENDS ${RC_DEPENDS} ${output} PARENT_SCOPE )
endfunction()

# Resource file list
add_resource( "src/html/index.html" )

add_custom_target( rc ALL DEPENDS ${RC_DEPENDS} )
Run Code Online (Sandbox Code Playgroud)


Ami*_*yan 7

纯 CMake 函数将任何文件转换为 C/C++ 源代码,仅使用 CMake 命令实现:

####################################################################################################
# This function converts any file into C/C++ source code.
# Example:
# - input file: data.dat
# - output file: data.h
# - variable name declared in output file: DATA
# - data length: sizeof(DATA)
# embed_resource("data.dat" "data.h" "DATA")
####################################################################################################

function(embed_resource resource_file_name source_file_name variable_name)

    file(READ ${resource_file_name} hex_content HEX)

    string(REPEAT "[0-9a-f]" 32 column_pattern)
    string(REGEX REPLACE "(${column_pattern})" "\\1\n" content "${hex_content}")

    string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1, " content "${content}")

    string(REGEX REPLACE ", $" "" content "${content}")

    set(array_definition "static const unsigned char ${variable_name}[] =\n{\n${content}\n};")

    set(source "// Auto generated file.\n${array_definition}\n")

    file(WRITE "${source_file_name}" "${source}")

endfunction()
Run Code Online (Sandbox Code Playgroud)

https://gist.github.com/amir-saniyan/de99cee82fa9d8d615bb69f3f53b6004


小智 6

我想说,在C ++中嵌入资源的最优雅的方法就是简单地使用Qt资源系统,该系统可跨不同平台移植,与CMake兼容,并且除了提供压缩功能外,还囊括了以上答案中完成的所有工作经过测试且非常简单的其他功能。

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

<RCC>
    <qresource prefix="/">
        <file>uptriangle.png</file>
        <file>downtriangle.png</file>
    </qresource>
</RCC>
Run Code Online (Sandbox Code Playgroud)

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

现在在您的CMakeLists.txt文件中添加:

set(CMAKE_AUTOMOC ON)
find_package(Qt5Core)
qt5_add_resources(QT_RESOURCE qtres.qrc)
Run Code Online (Sandbox Code Playgroud)

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

Q_INIT_RESOURCE(qtres);
Run Code Online (Sandbox Code Playgroud)

现在,您可以使用与Qt Resource System兼容的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

    // ...

}
Run Code Online (Sandbox Code Playgroud)

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

而且你很好。希望对您有所帮助。

  • Qt对我的口味有很大的依赖性,但是无论如何+1都是一个很好的答案。 (9认同)