«#include» текстовый файл в программе C как символ []
-
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)====="
У вас есть две возможности:
- Используйте расширения компилятора/компоновщика для преобразования файла в двоичный файл с соответствующими символами, указывающими на начало и конец двоичных данных.Смотрите этот ответ: Включить двоичный файл со сценарием компоновщика GNU ld..
- Преобразуйте ваш файл в последовательность символьных констант, которые могут инициализировать массив.Обратите внимание, что вы не можете просто сделать "" и разместить несколько строк.Вам понадобится символ продолжения строки (
\
), побег"
персонажей и других людей, чтобы это сработало.Проще просто написать небольшую программу для преобразования байтов в последовательность, например'\xFF', '\xAB', ...., '\0'
(или используйте инструмент unixxxd
описано в другом ответе, если он у вас есть!):
Код:
#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
Должно быть возможно проделывать подобные трюки и с другими типами кода, которые вы, возможно, захотите включить в виде строки.Я не уверен, хорошая это идея или нет.Это своего рода изящный хак, но, вероятно, это не то, что вам нужно в реальном производственном коде.Хотя, возможно, это подойдет для хакерского проекта на выходные.