Czy istnieje sposób na dołączenie całego pliku tekstowego jako ciągu w programie C w czasie kompilacji?
coś jak:
plik.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); }
uzyskanie małego programu, który drukuje na stdout "To jest mały plik tekstowy"
W tej chwili użyłem hakerskiego skryptu w Pythonie, ale jest on brzydki i ogranicza się do jednej nazwy zmiennej, czy możesz mi powiedzieć inny sposób, aby to zrobić?
c
include
c-preprocessor
Brian Tompsett - 汤 莱恩
źródło
źródło
Odpowiedzi:
Sugerowałbym użycie do tego (unix util) xxd . możesz go tak używać
wyjścia:
unsigned char a[] = { 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x0a }; unsigned int a_len = 12;
źródło
xxd
, jak mówi odpowiedź. nazwą tablicy jest nazwa pliku wejściowego. jeśli przepuszczasz dane zamiast korzystać z pliku wejściowego, zamiast tego otrzymasz listę wartości szesnastkowych (bez deklaracji tablicy lub zmiennej len).xxd -i file.txt | sed 's/\([0-9a-f]\)$/\0, 0x00/' > file.h
Pytanie dotyczyło C, ale na wypadek, gdyby ktoś próbował to zrobić w C ++ 11, można to zrobić z niewielkimi zmianami w dołączonym pliku tekstowym dzięki nowym surowym literałom ciągów :
W C ++ zrób to:
const char *s = #include "test.txt" ;
W pliku tekstowym zrób to:
R"(Line 1 Line 2 Line 3 Line 4 Line 5 Line 6)"
Zatem musi znajdować się tylko przedrostek na początku pliku i przyrostek na końcu. Między nim możesz robić, co chcesz, nie jest konieczne specjalne uciekanie, o ile nie potrzebujesz sekwencji znaków
)"
. Ale nawet to może działać, jeśli określisz własny niestandardowy separator:R"=====(Line 1 Line 2 Line 3 Now you can use "( and )" in the text file, too. Line 5 Line 6)====="
źródło
1+R"...
jako początkowego separatora zamiastR"...
, a następnie wstawisz przed nim nowy wierszLine 1
. Spowoduje to przekształcenie wyrażenia z tablicy w wskaźnik, ale nie stanowi to problemu, ponieważ inicjalizujesz wskaźnik, a nie tablicę.Masz dwie możliwości:
\
),"
znaków zmiany znaczenia i innych, aby to zadziałało. Łatwiej jest po prostu napisać mały program do konwersji bajtów na sekwencję, taką jak'\xFF', '\xAB', ...., '\0'
(lub użyj narzędzia uniksowegoxxd
opisanego w innej odpowiedzi, jeśli masz je dostępne!):Kod:
#include <stdio.h> int main() { int c; while((c = fgetc(stdin)) != EOF) { printf("'\\x%X',", (unsigned)c); } printf("'\\0'"); // put terminating zero }
(nie testowany). Następnie wykonaj:
char my_file[] = { #include "data.h" };
Gdzie data.h jest generowana przez
źródło
char my_file[] = { #include my_large_file.h };
dzięki!bin2c
nie jest tym samym bin2c co z debianahxtools
, uważajbin2c -H myoutput.h myinput1.txt myinputN.txt
ok, zainspirowany postem Daemina przetestowałem następujący prosty przykład:
a. dane:
"this is test\n file\n"
test.c:
int main(void) { char *test = #include "a.data" ; return 0; }
Wyjście 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; }
Więc to działa, ale wymaga danych w cudzysłowie.
źródło
Podoba mi się odpowiedź Kayahra. Jeśli jednak nie chcesz dotykać plików wejściowych i używasz CMake , możesz dodać sekwencje znaków separatora do pliku. Na przykład następujący kod CMake kopiuje pliki wejściowe i odpowiednio opakowuje ich zawartość:
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)
Następnie dołącz w c ++ w ten sposób:
constexpr char *test = #include "generated/cool.frag" ;
źródło
Możesz to zrobić za pomocą
objcopy
:objcopy --input binary --output elf64-x86-64 myfile.txt myfile.o
Teraz masz plik obiektowy, który możesz połączyć z plikiem wykonywalnym, który zawiera symbole początku, końca i rozmiaru treści
myfile.txt
.źródło
Jeśli chcesz uciec się do brudnych sztuczek, możesz wykazać się kreatywnością dzięki surowym literałom tekstowym i
#include
niektórym typom plików.Na przykład, powiedzmy, że chcę dołączyć do projektu niektóre skrypty SQL dla SQLite i chcę uzyskać podświetlanie składni, ale nie chcę żadnej specjalnej infrastruktury kompilacji. Mogę mieć ten plik,
test.sql
który jest prawidłowym kodem SQL dla SQLite, gdzie--
zaczyna się komentarz:--x, R"(-- SELECT * from TestTable WHERE field = 5 --)"
A potem w moim kodzie C ++ mogę mieć:
int main() { auto x = 0; const char* mysql = ( #include "test.sql" ); cout << mysql << endl; }
Wynik to:
-- SELECT * from TestTable WHERE field = 5 --
Lub dołączyć kod w Pythonie z pliku,
test.py
który jest prawidłowym skryptem w Pythonie (ponieważ#
rozpoczyna komentarz w Pythonie i niepass
działa):#define pass R"( pass def myfunc(): print("Some Python code") myfunc() #undef pass #define pass )" pass
A potem w kodzie C ++:
int main() { const char* mypython = ( #include "test.py" ); cout << mypython << endl; }
Który wyświetli:
pass def myfunc(): print("Some Python code") myfunc() #undef pass #define pass
Powinno być możliwe wykonywanie podobnych sztuczek dla różnych innych typów kodu, które możesz chcieć dołączyć jako ciąg. Nie jestem pewien, czy to dobry pomysł. To trochę fajny hack, ale prawdopodobnie nie jest to coś, czego chciałbyś w prawdziwym kodzie produkcyjnym. Może być jednak ok na weekendowy projekt hakerski.
źródło
Potrzebujesz mojego
xtr
narzędzia, ale możesz to zrobić za pomocą plikubash script
. To jest skrypt, który nazywambin2inc
. Pierwszym parametrem jest nazwa wynikuchar[] variable
. Drugi parametr to nazwa plikufile
. Dane wyjściowe to Cinclude file
z zawartością pliku zakodowaną (małymi literamihex
) jako podaną nazwę zmiennej.char array
Jestzero terminated
i długość danych są przechowywane w$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")';'
TUTAJ MOŻESZ OTRZYMAĆ XTR xtr (znak eXTRapolator) to GPLV3
źródło
Ponownie zaimplementowałem xxd w pythonie3, naprawiając wszystkie niedogodności xxd:
unsigned
w tablicy.Oto skrypt, przefiltrowany przez siebie, więc możesz zobaczyć, co robi:
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;
Użycie (to wyodrębnia skrypt):
#include <stdio.h> extern const char pyxxd[]; extern const size_t pyxxd_len; int main() { fwrite(pyxxd, 1, pyxxd_len, stdout); }
źródło
Co może zadziałać, jeśli zrobisz coś takiego:
int main() { const char* text = " #include "file.txt" "; printf("%s", text); return 0; }
Oczywiście będziesz musiał uważać na to, co faktycznie znajduje się w pliku , upewniając się, że nie ma podwójnych cudzysłowów, że wszystkie odpowiednie znaki są wyłączone itp.
Dlatego może być łatwiej, jeśli po prostu załadujesz tekst z pliku w czasie wykonywania lub osadzisz tekst bezpośrednio w kodzie.
Gdybyś nadal chciał mieć tekst w innym pliku, mógłbyś go tam mieć, ale musiałby być tam przedstawiony jako ciąg. Użyłbyś kodu jak powyżej, ale bez podwójnych cudzysłowów. Na przykład:
plik.txt
"Something evil\n"\ "this way comes!"
main.cpp
int main() { const char* text = #include "file.txt" ; printf("%s", text); return 0; }
Tak więc zasadniczo posiadanie ciągu znaków w stylu C lub C ++ w dołączanym pliku tekstowym. Dzięki temu kod byłby bardziej uporządkowany, ponieważ na początku pliku nie ma tak dużej ilości tekstu.
źródło
Nawet jeśli można to zrobić w czasie kompilacji (nie sądzę, aby tak było w ogóle), tekst będzie prawdopodobnie wstępnie przetworzonym nagłówkiem, a nie dosłowną zawartością plików. Spodziewam się, że będziesz musiał załadować tekst z pliku w czasie wykonywania lub wykonać nieprzyjemną pracę typu „wytnij i wklej”.
źródło
Odpowiedź Hasturkuna przy użyciu opcji xxd -i jest doskonała. Jeśli chcesz włączyć proces konwersji (tekst -> plik dołączania szesnastkowego) bezpośrednio do swojej kompilacji, narzędzie / biblioteka hexdump.c niedawno dodało możliwość podobną do opcji xxd -i (nie daje ci pełnego nagłówka - potrzebujesz aby podać definicję tablicy char - ale ma tę zaletę, że pozwala wybrać nazwę tablicy char):
http://25thandclement.com/~william/projects/hexdump.c.html
Jego licencja jest dużo bardziej "standardowa" niż xxd i jest bardzo liberalna - przykład wykorzystania jej do osadzenia pliku init w programie można zobaczyć w plikach CMakeLists.txt i scheme.c tutaj:
https://github.com/starseeker/tinyscheme-cmake
Uwzględnianie wygenerowanych plików w drzewach źródłowych i pakietowanie narzędzi ma zalety i wady - sposób obsługi tego zależy od konkretnych celów i potrzeb projektu. hexdump.c otwiera opcję tworzenia pakietów dla tej aplikacji.
źródło
Myślę, że nie jest to możliwe z samym kompilatorem i preprocesorem. gcc pozwala na to:
#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" );
Ale niestety nie to:
#define _STRGF(x) # x #define STRGF(x) _STRGF(x) printk ( MODULE_NAME " built " __DATE__ " at " __TIME__ " on host " STRGF( # include "/etc/hostname" ) "\n" );
Błąd:
/etc/hostname: In function ‘init_module’: /etc/hostname:1:0: error: unterminated argument list invoking macro "STRGF"
źródło
/etc/hostname
sposobu osadzenia nazwy maszyny budującej w ciągu, który (nawet gdyby działał) nie byłby przenośny, ponieważ Mac OS X nie ma pliku/etc/hostname
. Zauważ, że używanie nazw makr zaczynających się od podkreślenia, po którym następuje duża litera, oznacza używanie nazwy zarezerwowanej dla implementacji, czyli A Bad Thing ™.Dlaczego nie połączyć tekstu z programem i użyć go jako zmiennej globalnej! Oto przykład. Rozważam użycie tego do włączenia plików cieniowania Open GL do pliku wykonywalnego, ponieważ shadery GL muszą być kompilowane dla GPU w czasie wykonywania.
źródło
Miałem podobne problemy, a dla małych plików wspomniane rozwiązanie Johannesa Schauba działało dla mnie jak urok.
Jednak w przypadku plików, które są nieco większe, napotkał problemy z limitem tablicy znaków kompilatora. Dlatego napisałem małą aplikację kodującą, która konwertuje zawartość pliku na tablicę znaków 2D o równej wielkości (i ewentualnie z zerami). Tworzy wyjściowe pliki tekstowe z danymi tablicowymi 2D, takimi jak:
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'}};
gdzie 4 to w rzeczywistości zmienna MAX_CHARS_PER_ARRAY w koderze. Plik z wynikowym kodem C, nazywany na przykład „main_js_file_data.h”, można następnie łatwo wstawić do aplikacji C ++, na przykład w następujący sposób:
#include "main_js_file_data.h"
Oto kod źródłowy kodera:
#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; }
źródło
Ten problem mnie irytował, a xxd nie działa w moim przypadku użycia, ponieważ spowodował, że zmienna o nazwie coś takiego jak __home_myname_build_prog_cmakelists_src_autogen, gdy próbowałem to skrypty, stworzyłem narzędzie do rozwiązania tego dokładnego problemu:
https://github.com/Exaeta/brcc
Generuje plik źródłowy i nagłówkowy oraz umożliwia jawne ustawienie nazwy każdej zmiennej, dzięki czemu można ich używać za pośrednictwem std :: begin (nazwa tablicy) i std :: end (nazwa tablicy).
Włączyłem to do mojego projektu cmake w następujący sposób:
Przypuszczam, że dzięki drobnym poprawkom może działać również dla C.
źródło
Jeśli używasz
CMake
, prawdopodobnie możesz być zainteresowany napisaniemCMake
skryptu przetwarzania wstępnego, takiego jak poniżej:cmake / ConvertLayout.cmake
function(convert_layout file include_dir) get_filename_component(name ${file} NAME_WE) get_filename_component(directory ${file} DIRECTORY) get_filename_component(directory ${directory} NAME) string(TOUPPER ${name} NAME) string(TOUPPER ${directory} DIRECTORY) set(new_file ${include_dir}/${directory}/${name}.h) if (${file} IS_NEWER_THAN ${new_file}) file(READ ${file} content) string(REGEX REPLACE "\"" "\\\\\"" content "${content}") string(REGEX REPLACE "[\r\n]" "\\\\n\"\\\\\n\"" content "${content}") set(content "\"${content}\"") set(content "#ifndef ${DIRECTORY}_${NAME}\n#define ${DIRECTORY}_${NAME} ${content} \n#endif") message(STATUS "${content}") file(WRITE ${new_file} "${content}") message(STATUS "Generated layout include file ${new_file} from ${file}") endif() endfunction() function(convert_layout_directory layout_dir include_dir) file(GLOB layouts ${layout_dir}/*) foreach(layout ${layouts}) convert_layout(${layout} ${include_dir}) endforeach() endfunction()
Twój CMakeLists.txt
gdzieś w c ++
#include "layout/menu.h" Glib::ustring ui_info = LAYOUT_MENU;
źródło
w xh
"this is a " "buncha text"
w main.c
#include <stdio.h> int main(void) { char *textFileContents = #include "x.h" ; printf("%s\n", textFileContents); return 0 }
powinien wykonać swoją pracę.
źródło