Existe-t-il un moyen d'inclure un fichier texte entier sous forme de chaîne dans un programme C au moment de la compilation?
quelque chose comme:
fichier.txt:
This is
a little
text file
principal c:
#include <stdio.h>
int main(void) {
#blackmagicinclude("file.txt", content)
/*
equiv: char[] content = "This is\na little\ntext file";
*/
printf("%s", content);
}
obtenir un petit programme qui imprime sur stdout "Ceci est un petit fichier texte"
Pour le moment, j'ai utilisé un script hackish python, mais il est affreux et limité à un seul nom de variable, pouvez-vous me dire une autre façon de le faire?
Je suggère d'utiliser (unix util) xxd pour cela. vous pouvez l'utiliser comme ça
$ echo hello world > a
$ xxd -i a
les sorties:
unsigned char a[] = {
0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x0a
};
unsigned int a_len = 12;
La question portait sur C mais au cas où quelqu'un essaierait de le faire avec C++ 11, cela peut être fait avec seulement de petites modifications dans le fichier texte inclus grâce au nouveau littéraux de chaîne bruts :
En C++, procédez comme suit:
const char *s =
#include "test.txt"
;
Dans le fichier texte, procédez comme suit:
R"(Line 1
Line 2
Line 3
Line 4
Line 5
Line 6)"
Il ne doit donc y avoir qu'un préfixe en haut du fichier et un suffixe en fin de fichier. Entre les deux, vous pouvez faire ce que vous voulez, aucun échappement spécial n'est nécessaire tant que vous n'avez pas besoin de la séquence de caractères )"
. Mais même cela peut fonctionner si vous spécifiez votre propre délimiteur personnalisé:
R"=====(Line 1
Line 2
Line 3
Now you can use "( and )" in the text file, too.
Line 5
Line 6)====="
Vous avez deux possibilités:
\
), échapper "
caractères et autres pour que cela fonctionne. Plus facile d'écrire simplement un petit programme pour convertir les octets en une séquence comme '\xFF', '\xAB', ...., '\0'
(ou utilisez l'outil unix xxd
décrit par une autre réponse, si vous l'avez disponible!):Code:
#include <stdio.h>
int main() {
int c;
while((c = fgetc(stdin)) != EOF) {
printf("'\\x%X',", (unsigned)c);
}
printf("'\\0'"); // put terminating zero
}
(pas testé). Alors fais:
char my_file[] = {
#include "data.h"
};
Où data.h est généré par
cat file.bin | ./bin2c > data.h
ok, inspiré par Daemin's post j'ai testé l'exemple simple suivant:
a.données:
"this is test\n file\n"
test.c:
int main(void)
{
char *test =
#include "a.data"
;
return 0;
}
sortie 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;
}
Cela fonctionne donc mais nécessite des données entourées de guillemets.
J'aime la réponse de kayahr. Si vous ne voulez pas toucher les fichiers d'entrée cependant, et si vous utilisez CMake, vous pouvez ajouter les séquences de caractères de délimitation sur le fichier. Le code CMake suivant, par exemple, copie les fichiers d'entrée et encapsule leur contenu en conséquence:
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)
Incluez ensuite en c ++ comme ceci:
constexpr char *test =
#include "generated/cool.frag"
;
Vous pouvez le faire en utilisant objcopy
:
objcopy --input binary --output elf64-x86-64 myfile.txt myfile.o
Vous avez maintenant un fichier objet que vous pouvez lier à votre exécutable qui contient des symboles pour le début, la fin et la taille du contenu de myfile.txt
.
Si vous êtes prêt à recourir à des trucs sales, vous pouvez faire preuve de créativité avec des littéraux de chaîne bruts et #include
pour certains types de fichiers.
Par exemple, disons que je veux inclure des scripts SQL pour SQLite dans mon projet et que je souhaite obtenir une coloration syntaxique mais que je ne souhaite pas d'infrastructure de construction spéciale. Je peux avoir ce fichier test.sql
qui est un SQL valide pour SQLite où --
commence un commentaire:
--x, R"(--
SELECT * from TestTable
WHERE field = 5
--)"
Et puis dans mon code C++ je peux avoir:
int main()
{
auto x = 0;
const char* mysql = (
#include "test.sql"
);
cout << mysql << endl;
}
La sortie est:
--
SELECT * from TestTable
WHERE field = 5
--
Ou pour inclure du code Python à partir d'un fichier test.py
qui est un script Python valide (car #
commence un commentaire dans Python et pass
est un no-op):
#define pass R"(
pass
def myfunc():
print("Some Python code")
myfunc()
#undef pass
#define pass )"
pass
Et puis dans le code C++:
int main()
{
const char* mypython = (
#include "test.py"
);
cout << mypython << endl;
}
Qui produira:
pass
def myfunc():
print("Some Python code")
myfunc()
#undef pass
#define pass
Il devrait être possible de jouer des tours similaires pour divers autres types de code que vous voudrez peut-être inclure sous forme de chaîne. Que ce soit ou non une bonne idée, je ne suis pas sûr. C'est une sorte de hack soigné mais probablement pas quelque chose que vous voudriez dans un vrai code de production. Cela pourrait être bien pour un projet de hack de week-end.
Ce que pourrait fonctionner est si vous faites quelque chose comme:
int main()
{
const char* text = "
#include "file.txt"
";
printf("%s", text);
return 0;
}
Bien sûr, vous devrez faites attention à ce qui se trouve réellement dans le fichier, en vous assurant qu'il n'y a pas de guillemets doubles, que tous les caractères appropriés sont échappés, etc.
Par conséquent, cela pourrait être plus facile si vous chargez simplement le texte d'un fichier au moment de l'exécution, ou incorporez le texte directement dans le code.
Si vous vouliez toujours le texte dans un autre fichier, vous pourriez l'avoir dedans, mais il devrait y être représenté sous forme de chaîne. Vous utiliseriez le code comme ci-dessus mais sans les guillemets doubles. Par exemple:
file.txt
"Something evil\n"\
"this way comes!"
main.cpp
int main()
{
const char* text =
#include "file.txt"
;
printf("%s", text);
return 0;
}
Donc, en gros, avoir une chaîne de style C ou C++ dans un fichier texte que vous incluez. Cela rendrait le code plus net car il n'y a pas énormément de texte au début du fichier.
Vous avez besoin de mon utilitaire xtr
mais vous pouvez le faire avec un bash script
. Ceci est un script que j'appelle bin2inc
. Le premier paramètre est le nom du char[] variable
. Le deuxième paramètre est le nom du file
. La sortie est C include file
avec le contenu du fichier encodé (en minuscule hex
) comme nom de variable donné. Le char array
est zero terminated
, et la longueur des données est stockée dans $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")';'
VOUS POUVEZ OBTENIR XTR ICI xtr (caractère eXTRapolator) est GPLV3
J'ai réimplémenté xxd en python3, corrigeant tous les ennuis de xxd:
unsigned
sur le tableau.Voici le script, filtré par lui-même, pour que vous puissiez voir ce qu'il fait:
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 __== '__main__':\n"
" main()\n"
"";
const size_t pyxxd_len = sizeof(pyxxd) - 1;
Utilisation (ceci extrait le script):
#include <stdio.h>
extern const char pyxxd[];
extern const size_t pyxxd_len;
int main()
{
fwrite(pyxxd, 1, pyxxd_len, stdout);
}
en x.h
"this is a "
"buncha text"
dans main.c
#include <stdio.h>
int main(void)
{
char *textFileContents =
#include "x.h"
;
printf("%s\n", textFileContents);
return 0
}
devrait faire le travail.
Même si cela peut être fait au moment de la compilation (je ne pense pas que ce soit possible en général), le texte serait probablement l'en-tête prétraité plutôt que le contenu des fichiers textuellement. Je m'attends à ce que vous deviez charger le texte du fichier lors de l'exécution ou faire un travail de couper-coller méchant.
Je pense que ce n'est pas possible avec le compilateur et le préprocesseur seuls. gcc permet ceci:
#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" );
Mais malheureusement pas ceci:
#define _STRGF(x) # x
#define STRGF(x) _STRGF(x)
printk ( MODULE_NAME " built " __DATE__ " at " __TIME__ " on Host "
STRGF(
# include "/etc/hostname"
)
"\n" );
L'erreur est:
/etc/hostname: In function ‘init_module’:
/etc/hostname:1:0: error: unterminated argument list invoking macro "STRGF"
La réponse d'Hasturkun utilisant l'option xxd -i est excellente. Si vous souhaitez incorporer le processus de conversion (texte -> fichier include hex) directement dans votre build, l'outil/bibliothèque hexdump.c a récemment ajouté une fonctionnalité similaire à l'option -i de xxd (elle ne vous donne pas l'en-tête complet - vous avez besoin pour fournir la définition du tableau char - mais cela a l'avantage de vous permettre de choisir le nom du tableau char):
http://25thandclement.com/~william/projects/hexdump.c.html
Sa licence est beaucoup plus "standard" que xxd et est très libérale - un exemple de son utilisation pour incorporer un fichier init dans un programme peut être vu dans les fichiers CMakeLists.txt et schema.c ici:
https://github.com/starseeker/tinyscheme-cmake
Il y a des avantages et des inconvénients à inclure les fichiers générés dans les arborescences sources et les utilitaires de regroupement - la façon de les gérer dépendra des objectifs et des besoins spécifiques de votre projet. hexdump.c ouvre l'option de regroupement pour cette application.
J'ai eu des problèmes similaires et pour les petits fichiers, la solution susmentionnée de Johannes Schaub a fonctionné comme un charme pour moi.
Cependant, pour les fichiers un peu plus volumineux, il a rencontré des problèmes avec la limite de tableau de caractères du compilateur. Par conséquent, j'ai écrit une petite application d'encodeur qui convertit le contenu du fichier en un tableau de caractères 2D de morceaux de taille égale (et éventuellement de zéros de remplissage). Il produit des fichiers texte de sortie avec des données de tableau 2D comme ceci:
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'}};
où 4 est en fait une variable MAX_CHARS_PER_ARRAY dans l'encodeur. Le fichier avec le code C résultant, appelé, par exemple "main_js_file_data.h" peut ensuite être facilement inséré dans l'application C++, par exemple comme ceci:
#include "main_js_file_data.h"
Voici le code source de l'encodeur:
#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;
}
Pourquoi ne pas lier le texte au programme et l'utiliser comme variable globale! Voici un exemple. J'envisage d'utiliser ceci pour inclure les fichiers de shaders ouverts GL dans un exécutable depuis GL doivent être compilé pour le GPU lors de l'exécution.