web-dev-qa-db-ja.com

CMakeを使用して、リソース(シェーダーコード、画像など)を実行可能ファイル/ライブラリに埋め込む

私のプロジェクトのさまざまなリソースに依存するアプリケーションをC++で作成しています。現在、生成された実行可能ファイルから各リソースへの相対パスがソースにハードコーディングされているため、プログラムでファイルを開いて各リソースのデータを読み取ることができます。これは問題なく動作しますが、実行可能ファイルをリソースに相対する特定のパスから起動する必要があります。したがって、他の場所から実行可能ファイルを起動しようとすると、ファイルを開くことができず、続行できません。

CMakeが実行可能ファイル(またはライブラリ)にリソースを埋め込むポータブル方法はありますか?パスがもろいファイル? 関連する質問 を見つけました。リソースの埋め込みは、いくつかのldマジックで十分に実行できるようです。だから私の質問は、CMakeを使用してこれをポータブルなクロスプラットフォームの方法で行う方法ですか?実際には、x86とARMの両方でアプリケーションを実行する必要があります。私はLinux(Embedded)のみをサポートすることに問題はありませんが、Windows(Embedded)でこれを行う方法を誰かが提案できる場合はボーナスポイントも同様です。

編集:私はソリューションの望ましいプロパティを言及するのを忘れていました。 ARMターゲットでネイティブにコンパイルする必要があるのではなく、ARMターゲットでビルドするのではなく、CMakeを使用してアプリケーションをクロスコンパイルできるようにしたい。

31
Nicu Stiurca

これを行う最も簡単な方法の1つは、リソースを読み取り、定数データリテラルの配列としてリソースデータと実際のリソースデータの長さを含むCファイルを生成する、ビルドに小さなポータブルCプログラムを含めることです。これは完全にプラットフォームに依存しませんが、かなり小さいリソースにのみ使用する必要があります。より大きなリソースの場合、ファイルをプログラムに埋め込む必要はおそらくありません。

リソース「foo」の場合、生成されたCファイル「foo.c」には次のものが含まれます。

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

C++からリソースにアクセスするには、ヘッダーまたはcppファイルで次の2つのシンボルを宣言して、それらを使用します。

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;
}
28
sfstewman

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()
33
Youka

C++にリソースを埋め込む最もエレガントな方法は、さまざまなプラットフォーム間で移植可能で、CMakeと互換性のあるQtリソースシステムを使用することであり、圧縮を提供するだけでなく、上記の答えで行われたすべてを本質的にラップします。テストされ、誰にでもできる、その他すべて。

Qtリソースファイルを作成します-埋め込むファイルをリストしたXML:

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

Qtres.qrcファイルを呼び出します。上記のリソースファイルには、2つのpngファイル(qtres.qrcと同じディレクトリにあります)が最終的な実行可能ファイルに埋め込まれています。 QtCreator(Qt IDE)を使用して、モニターリソースをqrcファイルに簡単に追加/削除できます。

CMakeLists.txtファイルに次を追加します。

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

Main.cppで、リソースにアクセスする必要がある前に、次の行を追加します。

Q_INIT_RESOURCE(qtres);

これで、QPixmap、QImageなど、Qt Resource Systemと互換性のあるQtクラスを使用して上記のリソースにアクセスできます。さらに、最も重要なのは、一般的なケースとして、埋め込まれたQtリソースをラップし、それを介したアクセスを可能にするQResourceラッパークラスです。フレンドリーなインターフェース。例として、上記のリソースの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を使用して、res.data()、res.size()...を使用してデータに直接アクセスできます。ファイルの画像コンテンツを解析するには、pmを使用します。 pm.size()、pm.width()を使用します...

そして、あなたは行ってもいいです。お役に立てば幸いです。

5
polonium

別の代替案を提案したいと思います。 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" );
2
Itay Grudev