Pergunta

Existe uma maneira de incluir um arquivo de texto inteiro como uma string em um programa C em tempo de compilação?

algo como:

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

obtenção de um pequeno programa que imprime em stdout "Este é um pouco arquivo de texto "

No momento eu usei um script python hackish, mas é butt-feio e limitada a apenas um nome de variável, você pode me dizer outra maneira de fazê-lo?

Foi útil?

Solução

Eu sugiro usando (unix util) xxd para isso. você pode usá-lo como assim

$ echo hello world > a
$ xxd -i a

saídas:

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

Outras dicas

A pergunta era sobre C mas no caso de alguém tentar fazê-lo com C ++ 11, então ele pode ser feito com apenas pequenas alterações nas graças de arquivo de texto incluído para o novo raw :

Em C ++ fazer isso:

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

No arquivo de texto fazer isso:

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

Portanto, não deve ser apenas um prefixo no topo do arquivo e um sufixo no final do mesmo. Entre ele você pode fazer o que quiser, não escapando especial é necessária, desde que você não precisa do )" sequência de caracteres. Mas mesmo isso pode funcionar se você especificar seu próprio delimitador personalizado:

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

Você tem duas possibilidades:

  1. Fazer uso de extensões de compilador / vinculador para converter um arquivo em um arquivo binário, com símbolos adequados que apontam para o início e fim dos dados binários. Veja esta resposta: Incluir arquivo binário com GNU ld vinculador script de .
  2. converter o arquivo em uma seqüência de constantes de caracteres que pode inicializar uma matriz. Note que você não pode simplesmente fazer "" e abranger várias linhas. Você precisaria de um caractere de continuação (\), escapar caracteres " e outros para fazer esse trabalho. Mais fácil simplesmente escrever um pequeno programa para converter os bytes em uma seqüência como '\xFF', '\xAB', ...., '\0' (ou use o xxd ferramenta unix descrito por outra resposta, se você tê-lo disponível!):

Código:

#include <stdio.h>

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

(não testado). Em seguida, faça:

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

Onde data.h é gerado pelo

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

ok, inspirado por Daemin é post Eu testei o seguinte exemplo simples:

a.data:

"this is test\n file\n"

test.c:

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

gcc -E test.c saída:

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

Assim, ele está trabalhando, mas exigem dados entre aspas.

Eu gosto de resposta de kayahr. Se você não quer tocar os arquivos de entrada no entanto, e se você estiver usando CMake , você pode adicionar as sequências de caracteres delimitador no arquivo. O seguinte código CMake, por exemplo, copia os arquivos de entrada e envolve o seu conteúdo de acordo:

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)

Em seguida, incluir no c ++ como esta:

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

O trabalho de força é se você fizer algo como:

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

É claro que você tem que ter cuidado com o que está realmente no arquivo, certificando-se não há aspas duplas, que todos os caracteres apropriados são escapou, etc.

Por isso, pode ser mais fácil se você apenas carregar o texto de um arquivo em tempo de execução, ou incorporar o texto diretamente no código.

Se você ainda queria que o texto em outro arquivo que você poderia tê-lo lá, mas ele teria que estar representado no local como uma string. Você usaria o código como acima, mas sem as aspas duplas na mesma. Por exemplo:

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

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

Você precisa da minha utilidade xtr mas você pode fazê-lo com um bash script. Esta é uma bin2inc script que eu chamada. O primeiro parâmetro é o nome do char[] variable resultante. O segundo parâmetro é o nome do file. A saída é C include file com o conteúdo do ficheiro codificado (em hex minúsculas) como o nome da variável dada. O char array é zero terminated, eo comprimento dos dados é armazenado em $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")';'

VOCÊ PODE OBTER XTR AQUI XTR (extrapolador personagem) é GPLV3

Você pode fazer isso usando objcopy:

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

Agora você tem um arquivo de objeto que você pode ligar à sua executável que contém símbolos para o início, fim e tamanho do conteúdo de myfile.txt.

Eu reimplemented xxd em python3, fixando todos os aborrecimentos do xxd:

  • Const correção
  • comprimento da corda tipo de dados: int ? size_t
  • terminação nulo (no caso de você querer isso)
  • string C compatível:. unsigned Gota no array
  • Menor, saída legível, como você teria escrito: Printable ascii é emitido tal como está; outros bytes são codificados em hex.

Aqui está o script, filtrada por si só, para que possa ver o que ele faz:

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 (Isso extrai o script):

#include <stdio.h>

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

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

Mesmo que isso pode ser feito em tempo de compilação (eu não acho que ele pode em geral), o texto seria provavelmente o cabeçalho pré-processado, em vez dos arquivos de conteúdo textual. Eu espero que você vai ter que carregar o texto do arquivo em tempo de execução ou fazer uma cut-n-paste trabalho desagradável.

em x.h

"this is a "
"buncha text"

em main.c

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

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

    return 0
}

deve fazer o trabalho.

A resposta de Hasturkun usando a opção -i xxd é excelente. Se você deseja incorporar o processo de conversão (texto -> hex arquivo de inclusão) diretamente em sua construção a ferramenta hexdump.c / biblioteca recentemente adicionou uma capacidade semelhante à opção -i do xxd (ele não lhe dá o cabeçalho completo - você precisa para fornecer a definição matriz de char - mas que tem a vantagem de permitir que você escolher o nome da matriz de char):

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

É de licença é muito mais "padrão" do que xxd e é muito liberal - um exemplo de usá-lo para incorporar um arquivo de inicialização de um programa pode ser visto no CMakeLists.txt e arquivos scheme.c aqui:

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

Há prós e contras tanto para incluindo arquivos gerados em árvores de origem e agregação de serviços públicos - como lidar com isso vai depender dos objetivos e necessidades de seu projeto específico. hexdump.c abre a opção de agrupamento para esta aplicação.

Eu acho que não é possível com o compilador e pré-processador sozinho. gcc permite isso:

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

Mas, infelizmente, não o seguinte:

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

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

O erro é:

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

Por que não unir o texto no programa e usá-lo como uma variável global! Aqui está um exemplo. I' m pensando em usar isso para incluir arquivos de sombreamento Open GL dentro de um executável desde shaders GL precisam ser compilados para a GPU em tempo de execução.

Eu tive problemas semelhantes, e para arquivos pequenos a solução acima mencionado de Johannes Schaub funcionou como um encanto para mim.

No entanto, para os arquivos que estão um pouco maior, ele correu para problemas com o limite de matriz de caracteres do compilador. Portanto, eu escrevi um pequeno aplicativo codificador que o conteúdo do arquivo converte em uma matriz de caracteres 2D de pedaços de tamanho igual (e possivelmente preenchimento zeros). Ela produz textfiles saída com dados 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'}};

onde 4 é na verdade um MAX_CHARS_PER_ARRAY variável no codificador. O arquivo com o código C resultante, chamado, por exemplo "main_js_file_data.h" pode então ser facilmente embutido no C ++ aplicativo, por exemplo como este:

#include "main_js_file_data.h"

Aqui está o código-fonte do 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 você está disposto a recorrer a alguns truques sujos Você pode obter criativo com strings literais-primas e #include para certos tipos de arquivos.

Por exemplo, digamos que eu quero incluir alguns scripts SQL para SQLite no meu projeto e eu quero começar destaque de sintaxe, mas não querem qualquer infra-estrutura de construção especial. Eu posso ter esta test.sql arquivo que é SQL válido para SQLite onde -- começa um comentário:

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

E então no meu código C ++ eu posso ter:

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

    cout << mysql << endl;
}

A saída é:

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

Ou para incluir algum código Python a partir de um test.py arquivo que é um script Python válido (porque # começa um comentário em Python e pass é um não-op):

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

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

E então, no código C ++:

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

    cout << mypython << endl;
}

Qual saída será:

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

myfunc()
#undef pass
#define pass

Deve ser possível jogar truques semelhantes para vários outros tipos de código que você pode querer incluir como uma string. Se é ou não é uma boa idéia não tenho certeza. É uma espécie de um corte limpo, mas provavelmente não algo que você gostaria no código de produção real. Pode ser ok para um projeto de corte fim de semana embora.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top