Pregunta

¿Hay alguna manera de incluir un archivo de texto completo como una cadena en un programa C en tiempo de compilación?

algo así como:

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

obteniendo un pequeño programa que imprime en stdout " Esto es un poco archivo de texto "

En este momento utilicé un script pirateado de Python, pero es feo y está limitado a un solo nombre de variable, ¿puede decirme otra forma de hacerlo?

¿Fue útil?

Solución

Sugeriría usar (unix util) xxd para esto. puedes usarlo así

$ echo hello world > a
$ xxd -i a

salidas:

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

Otros consejos

La pregunta era sobre C, pero en caso de que alguien intente hacerlo con C ++ 11, se puede hacer con solo pequeños cambios en el archivo de texto incluido gracias al nuevo raw literales de cadena :

En C ++ haga esto:

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

En el archivo de texto haga esto:

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

Por lo tanto, solo debe haber un prefijo en la parte superior del archivo y un sufijo al final del mismo. Entre ellos puede hacer lo que quiera, no es necesario un escape especial siempre que no necesite la secuencia de caracteres ) " . Pero incluso esto puede funcionar si especifica su propio delimitador personalizado:

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

Tienes dos posibilidades:

  1. Utilice las extensiones del compilador / enlazador para convertir un archivo en un archivo binario, con los símbolos adecuados apuntando al principio y al final de los datos binarios. Vea esta respuesta: Incluya un archivo binario con el script de enlace GNU ld .
  2. Convierta su archivo en una secuencia de constantes de caracteres que pueden inicializar una matriz. Tenga en cuenta que no puede simplemente hacer " " y abarcan múltiples líneas. Necesitaría un carácter de continuación de línea ( \ ), caracteres de escape " y otros para que funcione. Es más fácil escribir un pequeño programa para convertir los bytes en una secuencia como '\ xFF', '\ xAB', ...., '\ 0' (o usar la herramienta de Unix xxd descrito por otra respuesta, si lo tiene disponible):

Código:

#include <stdio.h>

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

(no probado). Luego haz:

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

Donde data.h es generado por

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

ok, inspirado en Daemin's post probé el siguiente ejemplo simple:

a.data:

"this is test\n file\n"

prueba.c:

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

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

Entonces funciona pero requiere datos rodeados de comillas.

Me gusta la respuesta de kayahr. Sin embargo, si no desea tocar los archivos de entrada y si está usando CMake , puede agregar las secuencias de caracteres delimitador en el archivo. El siguiente código CMake, por ejemplo, copia los archivos de entrada y ajusta su contenido en consecuencia:

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)

Luego incluya en c ++ de esta manera:

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

Lo que podría funcionar es si haces algo como:

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

Por supuesto, tendrá que tener cuidado con lo que realmente está en el archivo, asegurándose de que no haya comillas dobles, que se escapen todos los caracteres apropiados, etc.

Por lo tanto, podría ser más fácil si solo carga el texto de un archivo en tiempo de ejecución o incrusta el texto directamente en el código.

Si aún quisiera el texto en otro archivo, podría tenerlo allí, pero tendría que estar representado allí como una cadena. Usaría el código como arriba pero sin las comillas dobles. Por ejemplo:

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

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

Necesita mi utilidad xtr pero puede hacerlo con un bash script . Este es un script que llamo bin2inc . El primer parámetro es el nombre de la char [] variable resultante. El segundo parámetro es el nombre del archivo . El resultado es C include file con el contenido del archivo codificado (en minúsculas hexadecimal ) como el nombre de variable dado. La matriz de caracteres tiene cero terminado , y la longitud de los datos se almacena en $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")';'

PUEDE OBTENER XTR AQUÍ xtr (eXTRapolator de caracteres) es GPLV3

Puede hacer esto usando objcopy :

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

Ahora tiene un archivo de objeto que puede vincular a su ejecutable que contiene símbolos para el comienzo, el final y el tamaño del contenido de myfile.txt .

Reimplementé xxd en python3, arreglando todas las molestias de xxd:

  • Const corrección
  • tipo de datos de longitud de cadena: int ? & nbsp; size_t
  • Terminación nula (en caso de que desee)
  • Compatible con cadena C: suelte unsigned en la matriz.
  • Salida más pequeña y legible, como la habría escrito: la impresión ascii se imprime tal cual; otros bytes están codificados en hexadecimal.

Aquí está el script, filtrado por sí mismo, para que pueda ver lo que hace:

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;

Uso (esto extrae el script):

#include <stdio.h>

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

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

Incluso si se puede hacer en tiempo de compilación (no creo que pueda hacerlo en general), el texto probablemente sea el encabezado preprocesado en lugar de los contenidos de los archivos al pie de la letra. Espero que tenga que cargar el texto del archivo en tiempo de ejecución o hacer un trabajo desagradable de cortar y pegar.

en x.h

"this is a "
"buncha text"

en main.c

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

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

    return 0
}

debería hacer el trabajo.

La respuesta de Hasturkun usando la opción xxd -i es excelente. Si desea incorporar el proceso de conversión (texto - > archivo de inclusión hexadecimal) directamente en su compilación, la herramienta / biblioteca hexdump.c agregó recientemente una capacidad similar a la opción -x de xxd (no le da el encabezado completo - usted necesita proporcionar la definición de la matriz de caracteres, pero eso tiene la ventaja de permitirle elegir el nombre de la matriz de caracteres):

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

Su licencia es mucho más "estándar" que xxd y es muy liberal: un ejemplo de cómo usarlo para incrustar un archivo init en un programa se puede ver en los archivos CMakeLists.txt y scheme.c aquí:

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

Existen ventajas y desventajas tanto para incluir los archivos generados en los árboles de origen como para las utilidades de agrupación: cómo manejarlo dependerá de los objetivos y necesidades específicos de su proyecto. hexdump.c abre la opción de agrupación para esta aplicación.

Creo que no es posible solo con el compilador y el preprocesador. gcc permite esto:

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

Pero desafortunadamente no esto:

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

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

El error es:

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

¿Por qué no vincular el texto al programa y usarlo como una variable global! Aquí hay un ejemplo. I ' Estoy considerando usar esto para incluir archivos de sombreador Open GL dentro de un ejecutable ya que los sombreadores GL deben compilarse para la GPU en tiempo de ejecución.

Tuve problemas similares, y para los archivos pequeños, la solución antes mencionada de Johannes Schaub funcionó de maravilla para mí.

Sin embargo, para archivos que son un poco más grandes, se encontró con problemas con el límite de matriz de caracteres del compilador. Por lo tanto, escribí una pequeña aplicación de codificador que convierte el contenido del archivo en una matriz de caracteres 2D de fragmentos del mismo tamaño (y posiblemente ceros de relleno). Produce archivos de texto de salida con datos de matriz 2D como este:

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

donde 4 es en realidad una variable MAX_CHARS_PER_ARRAY en el codificador. El archivo con el código C resultante, llamado, por ejemplo " main_js_file_data.h " se puede insertar fácilmente en la aplicación C ++, por ejemplo, así:

#include "main_js_file_data.h"

Aquí está el código fuente del codificador:

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

Si está dispuesto a recurrir a algunos trucos sucios, puede ser creativo con literales de cadena sin formato y #include para ciertos tipos de archivos.

Por ejemplo, digamos que quiero incluir algunos scripts SQL para SQLite en mi proyecto y quiero resaltar la sintaxis pero no quiero ninguna infraestructura de compilación especial. Puedo tener este archivo test.sql que es SQL válido para SQLite donde - comienza un comentario:

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

Y luego en mi código C ++ puedo tener:

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

    cout << mysql << endl;
}

El resultado es:

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

O para incluir algún código de Python de un archivo test.py que es un script válido de Python (porque # inicia un comentario en Python y pasa es un no-op):

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

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

Y luego en el código C ++:

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

    cout << mypython << endl;
}

Que dará salida:

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

myfunc()
#undef pass
#define pass

Debería ser posible jugar trucos similares para varios otros tipos de código que quizás desee incluir como una cadena. Si es una buena idea o no, no estoy seguro. Es una especie de truco ordenado, pero probablemente no es algo que desearías en un código de producción real. Sin embargo, podría estar bien para un proyecto de pirateo de fin de semana.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top