“#include” Cプログラム内のchar []としてのテキストファイル

StackOverflow https://stackoverflow.com/questions/410980

  •  03-07-2019
  •  | 
  •  

質問

テキストファイル全体をコンパイル時にCプログラムの文字列として含める方法はありますか?

次のようなもの:

  • file.txt:

    This is
    a little
    text file
    
  • main.c:

    #include <stdio.h>
    int main(void) {
       #blackmagicinclude("file.txt", content)
       /*
       equiv: char[] content = "This is\na little\ntext file";
       */
       printf("%s", content);
    }
    

stdoutに出力する小さなプログラムの取得&quot;これは 少し テキストファイル&quot;

現時点では、ハック的なpythonスクリプトを使用しましたが、お粗末で1つだけの変数名に制限されています。別の方法を教えてください。

役に立ちましたか?

解決

(unix util) xxd を使用することをお勧めします。 次のように使用できます

$ echo hello world > a
$ xxd -i a

出力:

unsigned char a[] = {
  0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x0a
};
unsigned int a_len = 12;

他のヒント

質問はCについてでしたが、誰かがC ++ 11でそれを行おうとすると、新しい生の文字列リテラル

C ++でこれを行います:

const char *s =
#include "test.txt"
;

テキストファイルでこれを行います。

R"(Line 1
Line 2
Line 3
Line 4
Line 5
Line 6)"

そのため、ファイルの先頭には接頭辞、末尾には接尾辞のみが必要です。その間、あなたはあなたが望むことをすることができます、あなたが文字シーケンス)&quot; を必要としない限り、特別なエスケープは必要ありません。ただし、独自のカスタム区切り文字を指定すると、これでも機能します。

R"=====(Line 1
Line 2
Line 3
Now you can use "( and )" in the text file, too.
Line 5
Line 6)====="

2つの可能性があります:

  1. コンパイラ/リンカー拡張機能を使用して、ファイルをバイナリファイルに変換し、適切なシンボルがバイナリデータの開始と終了を指すようにします。この回答をご覧ください: GNU ldリンカースクリプトでバイナリファイルを含める
  2. ファイルを、配列を初期化できる一連の文字定数に変換します。 &quot;&quot;だけではできないことに注意してください。複数の行にまたがります。そのためには、行継続文字( \ )、エスケープ&quot; などが必要です。バイトを '\ xFF'、 '\ xAB'、....、 '\ 0' のようなシーケンスに変換する小さなプログラムを書く方が簡単です(またはunixツールを使用してください)別の回答で説明されているxxd (使用可能な場合)!

コード:

#include <stdio.h>

int main() {
    int c;
    while((c = fgetc(stdin)) != EOF) {
        printf("'\\x%X',", (unsigned)c);
    }
    printf("'\\0'"); // put terminating zero
}

(テストされていません)。それから:

char my_file[] = {
#include "data.h"
};

data.hが生成される場所

cat file.bin | ./bin2c > data.h

ok、 Daeminの次の簡単な例をテストした投稿:

a.data:

"this is test\n file\n"

test.c:

int main(void)
{
    char *test = 
#include "a.data"
    ;
    return 0;
}

gcc -E test.cの出力:

# 1 "test.c"
# 1 "<built-in>"
# 1 "<command line>"
# 1 "test.c"

int main(void)
{
    char *test =
# 1 "a.data" 1
"this is test\n file\n"
# 6 "test.c" 2
    ;
    return 0;
}

それで動作しますが、引用符で囲まれたデータが必要です。

私はカヤールの答えが好きです。 入力ファイルに触れたくない場合、および CMake を使用している場合は、ファイルに区切り文字シーケンスを追加できます。たとえば、次のCMakeコードは入力ファイルをコピーし、それに応じてコンテンツをラップします。

function(make_includable input_file output_file)
    file(READ ${input_file} content)
    set(delim "for_c++_include")
    set(content "R\"${delim}(\n${content})${delim}\"")
    file(WRITE ${output_file} "${content}")
endfunction(make_includable)

# Use like
make_includable(external/shaders/cool.frag generated/cool.frag)

次に、C ++に次のようにインクルードします。

constexpr char *test =
#include "generated/cool.frag"
;

機能するのは、次のような場合です:

int main()
{
    const char* text = "
#include "file.txt"
";
    printf("%s", text);
    return 0;
}

もちろん、実際にファイルに含まれるものに注意し、二重引用符がないこと、適切な文字がすべてエスケープされていることなどを確認する必要があります。

したがって、実行時にファイルからテキストをロードするか、コードに直接テキストを埋め込む方が簡単かもしれません。

別のファイルにテキストが必要な場合は、そこにテキストを含めることができますが、そのテキストを文字列として表す必要があります。上記のコードを使用しますが、二重引用符は使用しません。例:

"Something evil\n"\
"this way comes!"

int main()
{
    const char* text =
#include "file.txt"
;
    printf("%s", text);
    return 0;
}

私の xtr ユーティリティが必要ですが、 bashスクリプトで実行できます。これは、 bin2inc というスクリプトです。最初のパラメーターは、結果の char []変数の名前です。 2番目のパラメーターは、 file の名前です。出力は、指定された変数名としてエンコードされたファイルコンテンツ(小文字の hex )を持つC include file です。 char配列ゼロ終端で、データの長さは $ variableName_length

に保存されます
#!/bin/bash

fileSize ()

{

    [ -e "$1" ]  && {

        set -- `ls -l "$1"`;

        echo $5;

    }

}

echo unsigned char $1'[] = {'
./xtr -fhex -p 0x -s ', ' < "$2";
echo '0x00'
echo '};';
echo '';
echo unsigned long int ${1}_length = $(fileSize "$2")';'

ここでXTRを取得できます xtr(文字eXTRapolator)はGPLV3です

objcopy を使用してこれを行うことができます:

objcopy --input binary --output elf64-x86-64 myfile.txt myfile.o

これで、 myfile.txt のコンテンツの開始、終了、およびサイズのシンボルを含む実行可能ファイルにリンクできるオブジェクトファイルが作成されました。

python3でxxdを再実装し、xxdの煩わしさをすべて修正しました:

  • 定数の正確性
  • string lengthデータ型:int&#8594;&nbsp; size_t
  • ヌル終了(必要な場合)
  • C文字列互換:配列に unsigned をドロップします。
  • 小さくて読みやすい出力。記述したとおりです。印刷可能なasciiはそのまま出力されます。他のバイトは16進エンコードされます。

次のスクリプトは、それ自体でフィルター処理されているので、スクリプトの動作を確認できます。

pyxxd.c

#include <stddef.h>

extern const char pyxxd[];
extern const size_t pyxxd_len;

const char pyxxd[] =
"#!/usr/bin/env python3\n"
"\n"
"import sys\n"
"import re\n"
"\n"
"def is_printable_ascii(byte):\n"
"    return byte >= ord(' ') and byte <= ord('~')\n"
"\n"
"def needs_escaping(byte):\n"
"    return byte == ord('\\\"') or byte == ord('\\\\')\n"
"\n"
"def stringify_nibble(nibble):\n"
"    if nibble < 10:\n"
"        return chr(nibble + ord('0'))\n"
"    return chr(nibble - 10 + ord('a'))\n"
"\n"
"def write_byte(of, byte):\n"
"    if is_printable_ascii(byte):\n"
"        if needs_escaping(byte):\n"
"            of.write('\\\\')\n"
"        of.write(chr(byte))\n"
"    elif byte == ord('\\n'):\n"
"        of.write('\\\\n\"\\n\"')\n"
"    else:\n"
"        of.write('\\\\x')\n"
"        of.write(stringify_nibble(byte >> 4))\n"
"        of.write(stringify_nibble(byte & 0xf))\n"
"\n"
"def mk_valid_identifier(s):\n"
"    s = re.sub('^[^_a-z]', '_', s)\n"
"    s = re.sub('[^_a-z0-9]', '_', s)\n"
"    return s\n"
"\n"
"def main():\n"
"    # `xxd -i` compatibility\n"
"    if len(sys.argv) != 4 or sys.argv[1] != \"-i\":\n"
"        print(\"Usage: xxd -i infile outfile\")\n"
"        exit(2)\n"
"\n"
"    with open(sys.argv[2], \"rb\") as infile:\n"
"        with open(sys.argv[3], \"w\") as outfile:\n"
"\n"
"            identifier = mk_valid_identifier(sys.argv[2]);\n"
"            outfile.write('#include <stddef.h>\\n\\n');\n"
"            outfile.write('extern const char {}[];\\n'.format(identifier));\n"
"            outfile.write('extern const size_t {}_len;\\n\\n'.format(identifier));\n"
"            outfile.write('const char {}[] =\\n\"'.format(identifier));\n"
"\n"
"            while True:\n"
"                byte = infile.read(1)\n"
"                if byte == b\"\":\n"
"                    break\n"
"                write_byte(outfile, ord(byte))\n"
"\n"
"            outfile.write('\";\\n\\n');\n"
"            outfile.write('const size_t {}_len = sizeof({}) - 1;\\n'.format(identifier, identifier));\n"
"\n"
"if __name__ == '__main__':\n"
"    main()\n"
"";

const size_t pyxxd_len = sizeof(pyxxd) - 1;

使用法(これによりスクリプトが抽出されます):

#include <stdio.h>

extern const char pyxxd[];
extern const size_t pyxxd_len;

int main()
{
    fwrite(pyxxd, 1, pyxxd_len, stdout);
}

コンパイル時に実行できたとしても(一般的に実行できるとは思わない)、テキストはファイルの内容ではなく、前処理されたヘッダーである可能性があります。実行時にファイルからテキストをロードするか、厄介なカットアンドペーストジョブを実行する必要があると思います。

x.hで

"this is a "
"buncha text"
main.c内の

#include <stdio.h>
int main(void)
{
    char *textFileContents =
#include "x.h"
    ;

    printf("%s\n", textFileContents);

    return 0
}

仕事をする必要があります。

Hasturkunのxxd -iオプションを使用した答えは優れています。変換プロセス(テキスト-&gt; hexインクルードファイル)をビルドに直接組み込みたい場合、hexdump.cツール/ライブラリは、最近xxdの-iオプションに似た機能を追加しました(完全なヘッダーを提供しません-あなたはchar配列定義を提供する必要があります-ただし、char配列の名前を選択できるという利点があります):

http://25thandclement.com/~william/projects/hexdump.c。 html

ライセンスは、はるかに「標準」です。 xxdよりも非常にリベラルです-これを使用してプログラムにinitファイルを埋め込む例は、CMakeLists.txtおよびscheme.cファイルで確認できます:

https://github.com/starseeker/tinyscheme-cmake

生成されたファイルをソースツリーに含めることとユーティリティをバンドルすることには長所と短所があります。その処理方法は、プロジェクトの特定の目標とニーズによって異なります。 hexdump.cは、このアプリケーションのバンドルオプションを開きます。

コンパイラとプリプロセッサだけでは不可能だと思います。 gccはこれを許可します:

#define _STRGF(x) # x
#define STRGF(x) _STRGF(x)

    printk ( MODULE_NAME " built " __DATE__ " at " __TIME__ " on host "
            STRGF(
#               define hostname my_dear_hostname
                hostname
            )
            "\n" );

しかし、残念ながらこれではありません:

#define _STRGF(x) # x
#define STRGF(x) _STRGF(x)

    printk ( MODULE_NAME " built " __DATE__ " at " __TIME__ " on host "
            STRGF(
#               include "/etc/hostname"
            )
            "\n" );

エラー:

/etc/hostname: In function ‘init_module’:
/etc/hostname:1:0: error: unterminated argument list invoking macro "STRGF"

テキストをプログラムにリンクして、グローバル変数として使用するのはなぜですか! 例を次に示します。実行時にGLシェーダーをGPU用にコンパイルする必要があるため、これを使用して実行可能ファイル内にOpen GLシェーダーファイルを含めることを検討します。

同様の問題があり、小さなファイルの場合、前述のJohannes Schaubのソリューションは私にとって魅力的でした。

ただし、ファイルが少し大きい場合、コンパイラの文字配列制限の問題に遭遇しました。したがって、ファイルコンテンツを同じサイズのチャンク(および場合によってはゼロを埋め込む)の2D文字配列に変換する小さなエンコーダアプリケーションを作成しました。次のような2D配列データを含む出力テキストファイルを生成します。

const char main_js_file_data[8][4]= {
    {'\x69','\x73','\x20','\0'},
    {'\x69','\x73','\x20','\0'},
    {'\x61','\x20','\x74','\0'},
    {'\x65','\x73','\x74','\0'},
    {'\x20','\x66','\x6f','\0'},
    {'\x72','\x20','\x79','\0'},
    {'\x6f','\x75','\xd','\0'},
    {'\xa','\0','\0','\0'}};

4は、実際にはエンコーダーの変数MAX_CHARS_PER_ARRAYです。 &quot; main_js_file_data.h&quot;などと呼ばれる、結果のCコードを含むファイル次に、たとえば次のように、C ++アプリケーションに簡単にインライン化できます。

#include "main_js_file_data.h"

エンコーダのソースコードは次のとおりです。

#include <fstream>
#include <iterator>
#include <vector>
#include <algorithm>


#define MAX_CHARS_PER_ARRAY 2048


int main(int argc, char * argv[])
{
    // three parameters: input filename, output filename, variable name
    if (argc < 4)
    {
        return 1;
    }

    // buffer data, packaged into chunks
    std::vector<char> bufferedData;

    // open input file, in binary mode
    {    
        std::ifstream fStr(argv[1], std::ios::binary);
        if (!fStr.is_open())
        {
            return 1;
        }

        bufferedData.assign(std::istreambuf_iterator<char>(fStr), 
                            std::istreambuf_iterator<char>()     );
    }

    // write output text file, containing a variable declaration,
    // which will be a fixed-size two-dimensional plain array
    {
        std::ofstream fStr(argv[2]);
        if (!fStr.is_open())
        {
            return 1;
        }
        const std::size_t numChunks = std::size_t(std::ceil(double(bufferedData.size()) / (MAX_CHARS_PER_ARRAY - 1)));
        fStr << "const char " << argv[3] << "[" << numChunks           << "]"    <<
                                            "[" << MAX_CHARS_PER_ARRAY << "]= {" << std::endl;
        std::size_t count = 0;
        fStr << std::hex;
        while (count < bufferedData.size())
        {
            std::size_t n = 0;
            fStr << "{";
            for (; n < MAX_CHARS_PER_ARRAY - 1 && count < bufferedData.size(); ++n)
            {
                fStr << "'\\x" << int(unsigned char(bufferedData[count++])) << "',";
            }
            // fill missing part to reach fixed chunk size with zero entries
            for (std::size_t j = 0; j < (MAX_CHARS_PER_ARRAY - 1) - n; ++j)
            {
                fStr << "'\\0',";
            }
            fStr << "'\\0'}";
            if (count < bufferedData.size())
            {
                fStr << ",\n";
            }
        }
        fStr << "};\n";
    }

    return 0;
}

汚いトリックに頼るつもりなら、生の文字列リテラルと特定の種類のファイルの #include を使って創造性を発揮できます。

たとえば、プロジェクトにSQLiteのいくつかのSQLスクリプトを含め、構文の強調表示を取得したいが、特別なビルドインフラストラクチャは必要ないとします。このファイル test.sql を持つことができます。このファイルは、-がコメントを開始するSQLiteの有効なSQLです。

--x, R"(--
SELECT * from TestTable
WHERE field = 5
--)"

そして、C ++コードで次のことができます。

int main()
{
    auto x = 0;
    const char* mysql = (
#include "test.sql"
    );

    cout << mysql << endl;
}

出力は次のとおりです。

--
SELECT * from TestTable
WHERE field = 5
--

または、有効なPythonスクリプトである test.py ファイルからPythonコードを含めるには(はPythonでコメントを開始し、 pass はノーオペレーションです):

#define pass R"(
pass
def myfunc():
    print("Some Python code")

myfunc()
#undef pass
#define pass )"
pass

そして、C ++コードで:

int main()
{
    const char* mypython = (
#include "test.py"
    );

    cout << mypython << endl;
}

出力されるもの:

pass
def myfunc():
    print("Some Python code")

myfunc()
#undef pass
#define pass

文字列として含めることができる他のさまざまなタイプのコードに対して、同様のトリックを再生することが可能であるべきです。良いアイデアかどうかはわかりません。これは一種のきちんとしたハックですが、おそらく実際の製品コードには必要ないでしょう。ただし、週末のハックプロジェクトには問題ないかもしれません。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top