Подтвердить что ты не робот

Встраивать ресурсы (например, шейдерный код, изображения) в исполняемый файл/библиотеку с помощью CMake

Я пишу приложение на С++, которое опирается на различные ресурсы в моем проекте. Прямо сейчас у меня есть относительный путь от созданного исполняемого файла к каждому ресурсу, жестко закодированному в моих источниках, и это позволяет моей программе открывать файлы и читать данные в каждом ресурсе. Это работает нормально, но для этого требуется, чтобы я запускал исполняемый файл из определенного пути относительно ресурсов. Поэтому, если я пытаюсь запустить свой исполняемый файл из другого места, он не может открыть файлы и не может продолжить.

Есть ли переносимый способ включения CMake моих ресурсов в исполняемые файлы (или библиотеки), чтобы я мог просто получить к ним доступ в памяти во время выполнения, вместо того, чтобы открывать файлы, чьи пути хрупки? Я нашел вопрос , и похоже, что вложение ресурсов может быть сделано достаточно хорошо с помощью магии ld. Поэтому мой вопрос заключается в том, как мне это сделать с помощью переносного кросс-платформенного метода с использованием CMake? Мне действительно нужно, чтобы мое приложение запускалось как на x86, так и на ARM. Я в порядке с поддержкой только Linux (Embedded), но бонусных очков, если кто-нибудь может предложить, как это сделать для Windows (Embedded).

EDIT: Я забыл упомянуть о желаемом свойстве решения. Я хотел бы иметь возможность использовать CMake для кросс-компиляции приложения, когда я создаю ARM, вместо того, чтобы компилировать его изначально на мою цель ARM.

4b9b3361

Ответ 1

Один из самых простых способов сделать это - включить небольшую переносимую C-программу в вашей сборке, которая считывает ресурс и генерирует файл C, который содержит длину данных ресурса и фактических данных ресурсов как массив констант символьные литералы. Это будет полностью независимым от платформы, но его следует использовать только для ресурсов, которые достаточно малы. Для больших ресурсов вы, вероятно, не хотите вставлять файлы в свою программу.

Для ресурса "foo" сгенерированный C файл "foo.c" будет содержать:

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

Чтобы получить доступ к ресурсу из С++, вы объявляете следующие два символа в заголовке или в файле 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.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;
}

Ответ 2

В качестве альтернативы ответу 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()

Ответ 3

Я бы сказал, что самый элегантный способ иметь встроенные ресурсы на С++ - это просто использовать Qt Resource System, которая переносима на разных платформах, совместима с CMake, и по существу завершает все, что было сделано в ответе выше, помимо обеспечения сжатия, будучи полностью протестированным и безупречным, все остальное.

Создайте файл ресурсов Qt - XML, в котором перечислены файлы для встраивания:

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

Вызвать файл qtres.qrc. Файл ресурсов, указанный выше, будет содержать два файла png (расположенные в том же каталоге, что и qtres.qrc), встроенные в окончательный исполняемый файл. Вы можете легко добавить/удалить ресурсы монитора в файл qrc с помощью QtCreator (Qt IDE).

Теперь в файле CMakeLists.txt добавьте:

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

В вашем main.cpp, прежде чем вам понадобится доступ к ресурсу, добавьте следующую строку:

Q_INIT_RESOURCE(qtres);

Теперь вы можете получить доступ к любому из вышеперечисленных ресурсов, используя классы Qt, совместимые с Qt Resource System, такие как 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 your data, anyway you like
    // OR
    QPixmap pm("://downtriangle.png");  // Use it with Qt classes already

    // ...

}

Здесь res могут использоваться для прямого доступа к данным с помощью res.data(), res.size()... Для анализа содержимого изображения в файле используйте pm. Используйте pm.size(), pm.width()...

И тебе хорошо идти. Надеюсь, это помогло.

Ответ 4

Я хотел бы предложить другую альтернативу. Он использует компоновщик 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} )

Тогда в ваших файлах C/C++ все, что вам нужно, это:

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" );