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

新手上路,请多包涵

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

是否有一种 可移植 的方式让 CMake 将我的资源嵌入到可执行文件(或库)中,以便我可以在运行时简单地在内存中访问它们,而不是打开路径脆弱的文件?我发现了一个 相关的问题,看起来嵌入资源可以通过一些 ld 魔法来完成。所以我的问题是如何使用 CMake 以 可移植的跨平台 方式执行此操作?我实际上需要我的应用程序在 x86 和 ARM 上运行。我可以只支持 Linux(嵌入式),但如果有人可以建议如何为 Windows(嵌入式)执行此操作,则可以加分。

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

原文由 Nicu Stiurca 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 979
2 个回答

最简单的方法之一是在您的构建中包含一个小型、可移植的 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;
}

原文由 sfstewman 发布,翻译遵循 CC BY-SA 4.0 许可协议

作为 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()

原文由 Youka 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题