Embed resources (eg, shader code; images) into executable/library with CMake

后端 未结 5 522
醉梦人生
醉梦人生 2020-12-04 12:20

I am writing an application in C++ which relies on various resources in my project. Right now, I have the relative path from the produced executable to each resource hard-co

相关标签:
5条回答
  • 2020-12-04 12:56

    Pure CMake function to convert any file into C/C++ source code, implemented with only CMake commands:

    ####################################################################################################
    # 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()
    

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

    0 讨论(0)
  • 2020-12-04 13:00

    I'd say the most elegant way to have embedded resources in C++ is simply to use the Qt Resource System which is portable across different platforms, compatible with CMake, and essentially wraps up everything done in the answer above, besides providing compression, being fully tested and fool-proof, everything else.

    Create a Qt resource file - an XML listing the files to be embedded:

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

    Call the file qtres.qrc. The resource file above will have the two png files (located in the same directory as qtres.qrc) embedded in the final executable. You can easily add/remove monitor resources to a qrc file using QtCreator (the Qt IDE).

    Now in your CMakeLists.txt file add:

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

    In your main.cpp, before you need to access the resource, add the following line:

    Q_INIT_RESOURCE(qtres);
    

    Now you can access any of the resources above using Qt classes compatible with Qt Resource System, such as QPixmap, QImage ... and mosty importantly maybe in general cases the QResource wrapper class which wraps an embedded Qt resource and enables access to it through a friendly interface. As an example, to access data within downtriangle.png in the above resources, the following lines will do the trick:

    #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
    
        // ...
    
    }
    

    Here, res can be used to directly access the data using res.data(), res.size() ... To parse the image content of the file use pm. Use pm.size(), pm.width() ...

    And you're good to go. I hope it helped.

    0 讨论(0)
  • 2020-12-04 13:00

    I would like to propose another alternative. It uses the GCC linker to directly embed a binary file into the executable, with no intermediary source file. Which in my opinion is simpler and more efficient.

    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} )
    

    Then in your C/C++ files all you need is:

    extern char index_html_start[] asm( "_binary_src_html_index_html_start" );
    extern char index_html_end[]   asm( "_binary_src_html_index_html_end" );
    extern size_t index_html_size  asm( "_binary_src_html_index_html_size" );
    
    0 讨论(0)
  • 2020-12-04 13:07

    One of the easiest ways to do this is to include a small, portable C program in your build that reads the resource and generates a C file that contains the length of the resource data and the actual resource data as an array of constant character literals. This will be entirely platform independent, but should only be used for resources that are reasonably small. For larger resources, you probably don't want to embed the files in your program.

    For resource "foo", the generated C file "foo.c" would contain:

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

    To access the resource from C++, you declare the following two symbols in either a header or the cpp file where they're used:

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

    To generate foo.c in the build, you need a target for the C program (call it embedfile.c), and you need to use the ADD_CUSTOM_COMMAND command to call this program:

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

    Then, include foo.c on the source list of a target that requires the "foo" resource. You now have access to the bytes of "foo".

    The program embedfile.c is:

    #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 讨论(0)
  • 2020-12-04 13:14

    As an alternative to the answer of sfstewman, here's a small cmake (2.8) function to convert all files in a specific folder to C data and write them in wished output file:

    # 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()
    
    0 讨论(0)
提交回复
热议问题