«#include» текстовый файл в программе C как символ []

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

  •  03-07-2019
  •  | 
  •  

Вопрос

Есть ли способ включить весь текстовый файл в виде строки в программу C во время компиляции?

что-то вроде:

  • файл.txt:

    This is
    a little
    text file
    
  • основной.с:

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

Получение небольшой программы, которая печатает на stdout "Это маленький текстовый файл"

На данный момент я использовал хакерский скрипт Python, но он уродлив и ограничен только одним именем переменной. Можете ли вы рассказать мне другой способ сделать это?

Это было полезно?

Решение

Я бы предложил использовать (unix util)ххд для этого.ты можешь использовать это так

$ 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, это можно будет сделать с небольшими изменениями во включенном текстовом файле благодаря новому необработанные строковые литералы:

В С++ сделайте это:

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

В текстовом файле сделайте следующее:

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

Таким образом, должен быть только префикс вверху файла и суффикс в конце.Между ними вы можете делать все, что хотите, никакого специального экранирования не требуется, если вам не нужна последовательность символов. )".Но даже это может сработать, если вы укажете свой собственный разделитель:

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

У вас есть две возможности:

  1. Используйте расширения компилятора/компоновщика для преобразования файла в двоичный файл с соответствующими символами, указывающими на начало и конец двоичных данных.Смотрите этот ответ: Включить двоичный файл со сценарием компоновщика GNU ld..
  2. Преобразуйте ваш файл в последовательность символьных констант, которые могут инициализировать массив.Обратите внимание, что вы не можете просто сделать "" и разместить несколько строк.Вам понадобится символ продолжения строки (\), побег " персонажей и других людей, чтобы это сработало.Проще просто написать небольшую программу для преобразования байтов в последовательность, например '\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

ок, вдохновлен Дэмина пост я протестировал следующий простой пример:

данные:

"this is test\n file\n"

тест.с:

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)

Затем включите в С++ вот так:

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 script.Это сценарий, который я вызываю bin2inc.Первый параметр — это имя результирующего char[] variable.Второй параметр — это имя file.Выход: С include file с закодированным содержимым файла (в нижнем регистре) hex) в качестве заданного имени переменной.А char array является zero terminated, а длина данных хранится в $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.

Я переопределил xxd в Python3, исправив все недостатки xxd:

  • Константная корректность
  • Тип данных длины строки:целое → size_t
  • Нулевое завершение (если вам это нужно)
  • Совместимость со строкой C:Уронить unsigned на массиве.
  • Меньший, читаемый вывод, как вы бы его написали:Печатаемый формат ascii выводится «как есть»;другие байты закодированы в шестнадцатеричном формате.

Вот скрипт, отфильтрованный отдельно, чтобы вы могли видеть, что он делает:

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

Даже если это можно сделать во время компиляции (я не думаю, что это вообще возможно), текст, скорее всего, будет предварительно обработанным заголовком, а не дословным содержимым файла.Я ожидаю, что вам придется загружать текст из файла во время выполнения или выполнять неприятную работу по вырезанию и вставке.

в х.ч.

"this is a "
"buncha text"

в main.c

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

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

    return 0
}

должен выполнить работу.

Ответ Хастуркуна с использованием опции xxd -i превосходен.Если вы хотите включить процесс преобразования (текст -> шестнадцатеричный включаемый файл) непосредственно в вашу сборку, инструмент/библиотека hexdump.c недавно добавила возможность, аналогичную опции -i xxd (она не дает вам полный заголовок — вам нужно чтобы предоставить определение массива символов, но это дает вам возможность выбрать имя массива символов):

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

Ее лицензия намного более «стандартна», чем xxd, и очень либеральна — пример ее использования для встраивания файла инициализации в программу можно увидеть в файлах CMakeLists.txt и Schema.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"

Почему бы не связать текст с программой и не использовать его как глобальную переменную! Вот пример. Я подумываю использовать это для включения файлов шейдеров Open GL в исполняемый файл, поскольку шейдеры GL необходимо скомпилировать для графического процессора во время выполнения.

У меня были похожие проблемы, и для небольших файлов вышеупомянутое решение Йоханнеса Шауба сработало для меня как шарм.

Однако для файлов немного большего размера возникали проблемы с ограничением компилятора на массив символов.Поэтому я написал небольшое приложение-кодер, которое преобразует содержимое файла в двумерный массив символов, состоящий из фрагментов одинакового размера (и, возможно, дополняющих нули).Он создает выходные текстовые файлы с данными 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 в кодировщике.Файл с результирующим кодом C, называемый, например, "main_js_file_data.h", затем можно легко встроить в приложение 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 для определенных типов файлов.

Например, предположим, что я хочу включить в свой проект несколько SQL-скриптов для SQLite и хочу получить подсветку синтаксиса, но мне не нужна какая-либо специальная инфраструктура сборки.я могу получить этот файл test.sql который является действительным SQL для SQLite, где -- начинает комментарий:

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