Domanda

C'è un modo per includere un intero file di testo come stringa in un programma C in fase di compilazione?

qualcosa del tipo:

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

ottenere un piccolo programma che stampa su stdout " Questo è un po file di testo "

Al momento ho usato uno script python hacker, ma è brutto e limitato a un solo nome di variabile, puoi dirmi un altro modo per farlo?

È stato utile?

Soluzione

Suggerirei di utilizzare (unix util) xxd per questo. puoi usarlo in questo modo

$ echo hello world > a
$ xxd -i a

uscite:

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

Altri suggerimenti

La domanda riguardava C ma nel caso qualcuno provasse a farlo con C ++ 11, allora si può fare solo con piccole modifiche al file di testo incluso grazie al nuovo valori letterali stringa :

In C ++ fai questo:

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

Nel file di testo, procedere come segue:

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

Quindi ci deve essere solo un prefisso nella parte superiore del file e un suffisso alla fine di esso. Nel frattempo puoi fare quello che vuoi, non è necessario scappare se non hai bisogno della sequenza di caratteri ) " . Ma anche questo può funzionare se specifichi il tuo delimitatore personalizzato:

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

Hai due possibilità:

  1. Utilizza le estensioni del compilatore / linker per convertire un file in un file binario, con simboli appropriati che indicano l'inizio e la fine dei dati binari. Vedi questa risposta: Includi file binario con GNU ld linker script .
  2. Converti il ??tuo file in una sequenza di costanti di caratteri che possono inizializzare un array. Nota che non puoi semplicemente fare " " e si estendono su più righe. Avresti bisogno di un carattere di continuazione della linea ( \ ), scappa dai caratteri " e altri per farlo funzionare. È più semplice scrivere un piccolo programma per convertire i byte in una sequenza come '\ xFF', '\ xAB', ...., '\ 0' (o usare lo strumento unix xxd descritto da un'altra risposta, se disponibile!):

Codice:

#include <stdio.h>

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

(non testato). Quindi fai:

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

Dove data.h è generato da

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

ok, ispirato a Daemin's post ho testato il seguente semplice esempio:

a.data:

"this is test\n file\n"

test.c:

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

gcc -E output 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;
}

Quindi funziona ma richiede dati racchiusi tra virgolette.

Mi piace la risposta di kayahr. Se non si desidera toccare i file di input , e se si utilizza CMake , è possibile aggiungere le sequenze di caratteri delimitatori sul file. Il seguente codice CMake, ad esempio, copia i file di input e li avvolge di conseguenza:

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)

Quindi includi in c ++ in questo modo:

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

Ciò che potrebbe funzionare è se fai qualcosa del tipo:

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

Ovviamente dovrai stare attento a ciò che è effettivamente nel file, assicurandoti che non ci siano doppie virgolette, che tutti i caratteri appropriati siano sfuggiti, ecc.

Pertanto, potrebbe essere più semplice caricare il testo da un file in fase di esecuzione o incorporarlo direttamente nel codice.

Se volessi ancora il testo in un altro file, potresti averlo lì dentro, ma dovrebbe essere rappresentato lì come una stringa. Dovresti usare il codice come sopra ma senza le doppie virgolette. Ad esempio:

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

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

Hai bisogno della mia utility xtr ma puoi farlo con uno bash script . Questo è uno script che chiamo bin2inc . Il primo parametro è il nome della char [] variabile risultante . Il secondo parametro è il nome del file . L'output è C include file con il contenuto del file codificato (in minuscolo hex ) come nome della variabile indicato. char array è zero terminato e la lunghezza dei dati è memorizzata in $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")';'

PUOI ARRIVARE QUI xtr (character eXTRapolator) è GPLV3

Puoi farlo usando objcopy :

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

Ora hai un file oggetto che puoi collegare al tuo eseguibile che contiene simboli per l'inizio, la fine e la dimensione del contenuto da myfile.txt .

Ho reimplementato xxd in python3, risolvendo tutti i fastidi di xxd:

  • Corretta correttezza
  • tipo di dati lunghezza stringa: int ? & nbsp; size_t
  • Terminazione nulla (nel caso in cui si desideri)
  • C string compatibile: rilascia unsigned sull'array.
  • Output più piccolo e leggibile, come avresti scritto: ASCII stampabile viene emesso così com'è; altri byte sono codificati in esadecimale.

Ecco lo script, filtrato da solo, quindi puoi vedere cosa fa:

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;

Utilizzo (questo estrae lo script):

#include <stdio.h>

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

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

Anche se può essere fatto in fase di compilazione (non credo che possa in generale), il testo sarebbe probabilmente l'intestazione preelaborata anziché il contenuto dei file alla lettera. Mi aspetto che dovrai caricare il testo dal file in fase di esecuzione o fare un brutto lavoro di taglia e incolla.

in x.h

"this is a "
"buncha text"

in main.c

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

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

    return 0
}

dovrebbe fare il lavoro.

La risposta di Hasturkun usando l'opzione xxd -i è eccellente. Se vuoi incorporare il processo di conversione (text - > hex include file) direttamente nella tua build, lo strumento / libreria hexdump.c ha recentemente aggiunto una funzionalità simile all'opzione -i di xxd (non ti dà l'intestazione completa - tu è necessario fornire la definizione di array di caratteri, ma ciò ha il vantaggio di consentire all'utente di scegliere il nome dell'array di caratteri):

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

La sua licenza è molto più " standard " rispetto a xxd ed è molto liberale - un esempio di come usarlo per incorporare un file init in un programma può essere visto nei file CMakeLists.txt e schema.c qui:

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

Esistono vantaggi e svantaggi sia nell'includere i file generati negli alberi dei sorgenti che nelle utilità di raggruppamento: il modo in cui gestirli dipenderà dagli obiettivi e dalle esigenze specifici del progetto. hexdump.c apre l'opzione di raggruppamento per questa applicazione.

Penso che non sia possibile solo con il compilatore e il preprocessore. gcc lo consente:

#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" );

Ma sfortunatamente non questo:

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

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

L'errore è:

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

Perché non collegare il testo al programma e usarlo come variabile globale! Ecco un esempio. I ' sto pensando di usarlo per includere file shader Open GL all'interno di un eseguibile poiché gli shader GL devono essere compilati per la GPU in fase di esecuzione.

Ho avuto problemi simili e per i file di piccole dimensioni la soluzione di Johannes Schaub sopra menzionata ha funzionato come un incantesimo per me.

Tuttavia, per file leggermente più grandi, si sono verificati problemi con il limite dell'array di caratteri del compilatore. Pertanto, ho scritto una piccola applicazione codificatore che converte il contenuto del file in una matrice di caratteri 2D di blocchi di dimensioni uguali (e possibilmente riempiendo zeri). Produce file di testo di output con dati di array 2D come questo:

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

dove 4 è in realtà una variabile MAX_CHARS_PER_ARRAY nell'encoder. Il file con il codice C risultante, chiamato ad esempio " main_js_file_data.h " può quindi essere facilmente integrato nell'applicazione C ++, ad esempio in questo modo:

#include "main_js_file_data.h"

Ecco il codice sorgente dell'encoder:

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

Se sei disposto a ricorrere ad alcuni trucchi sporchi puoi diventare creativo con valori letterali di stringa grezzi e #include per alcuni tipi di file.

Ad esempio, supponiamo che io voglia includere alcuni script SQL per SQLite nel mio progetto e voglio ottenere l'evidenziazione della sintassi ma non voglio alcuna infrastruttura di build speciale. Posso avere questo file test.sql che è SQL valido per SQLite dove - inizia un commento:

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

E poi nel mio codice C ++ posso avere:

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

    cout << mysql << endl;
}

L'output è:

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

O per includere un po 'di codice Python da un file test.py che è uno script Python valido (perché # inizia un commento in Python e passa è un no-op):

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

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

E poi nel codice C ++:

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

    cout << mypython << endl;
}

Che produrrà:

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

myfunc()
#undef pass
#define pass

Dovrebbe essere possibile giocare trucchi simili per vari altri tipi di codice che potresti voler includere come stringa. Non è sicuro che sia una buona idea. È una specie di hack pulito ma probabilmente non è qualcosa che vorresti nel vero codice di produzione. Potrebbe andare bene per un progetto di hack del fine settimana però.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top