“#include”C程序中的文本文件为char []

时间:2009-01-04 13:37:57

标签: c include c-preprocessor

有没有办法在编译时将整个文本文件作为字符串包含在C程序中?

类似的东西:

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

获取在stdout上打印的小程序“这是 一点 文本文件“

目前我使用的是一个hackish python脚本,但是它很丑陋并且只限于一个变量名,你能告诉我另一种方法吗?

19 个答案:

答案 0 :(得分:122)

我建议使用(unix util)xxd。 你可以像这样使用它

$ echo hello world > a
$ xxd -i a

输出:

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

答案 1 :(得分:88)

问题是关于C但是如果有人试图用C ++ 11做,那么只需对包含的文本文件进行少量更改就可以完成,这要归功于新的raw string literals

在C ++中执行此操作:

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

在文本文件中执行以下操作:

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

因此,文件顶部必须只有一个前缀,并且文件末尾只有一个后缀。在它之间你可以做你想要的,只要你不需要字符序列)"就不需要特殊的转义。但是,如果您指定自己的自定义分隔符,即使这样也可以。

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

答案 2 :(得分:14)

您有两种可能性:

  1. 利用编译器/链接器扩展将文件转换为二进制文件,并使用适当的符号指向二进制数据的开头和结尾。请参阅此答案:Include binary file with GNU ld linker script
  2. 将文件转换为可以初始化数组的字符常量序列。请注意,您不能只执行“”并跨越多行。你需要一个行继续符(\),转义"个字符和其他字符才能使它工作。更容易编写一个小程序将字节转换为像'\xFF', '\xAB', ...., '\0'这样的序列(或使用另一个答案所描述的unix工具xxd,如果你有它!):
  3. 代码:

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

    (未经测试)。然后做:

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

    其中data.h由

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

答案 3 :(得分:8)

好的,受到Daemin's帖子的启发,我测试了以下简单示例:

a.data:

"this is test\n file\n"

test.c:

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

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

所以它正在工作但需要用引号括起来的数据。

答案 4 :(得分:4)

如果您执行以下操作可能会起作用:

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

当然,你必须要小心文件中的实际内容,确保没有双引号,所有适当的字符都被转义,等等。

因此,如果您只是在运行时从文件加载文本,或者将文本直接嵌入到代码中,则可能会更容易。

如果你仍然希望另一个文件中的文本可以在那里,但它必须在那里表示为一个字符串。您可以使用上面的代码,但不使用双引号。例如:

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

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

答案 5 :(得分:4)

我喜欢kayahr的回答。 但是,如果您不想触摸输入文件,并且如果您使用的是 CMake ,则可以在文件中添加分隔符字符序列。例如,以下CMake代码复制输入文件并相应地包装其内容:

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)

然后在c ++中包含这样的内容:

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

答案 6 :(得分:2)

您需要我的xtr实用程序,但您可以使用bash script。这是我称之为bin2inc的脚本。第一个参数是结果char[] variable的名称。第二个参数是file的名称。输出为C include file,文件内容已编码(小写hex)作为给定的变量名称。 char arrayzero terminated,数据长度存储在$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")';'

YOU CAN GET XTR HERE xtr(字符eXTRapolator)是GPLV3

答案 7 :(得分:1)

我在python3中重新实现了xxd,修复了所有xxd的烦恼:

  • Const正确性
  • 字符串长度数据类型:int→size_t
  • 空终止(如果您可能需要)
  • C字符串兼容:在阵列上删除place.types
  • 更小,可读的输出,正如您所写的那样:可打印的ascii按原样输出;其他字节是十六进制编码的。

这是脚本,由它自己过滤,所以你可以看到它的作用:

pyxxd.c

unsigned

用法(这会提取脚本):

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

答案 8 :(得分:1)

您可以使用objcopy

执行此操作
objcopy --input binary --output elf64-x86-64 myfile.txt myfile.o

现在您有一个目标文件,您可以链接到您的可执行文件,其中包含myfile.txt内容的开头,结尾和大小的符号。

答案 9 :(得分:0)

这是我用于Visual C ++的一种技巧。我添加以下预构建事件(其中file.txt是输入,而file_txt.h是输出):

@(
  echo const char text[] = R"***(
  type file.txt
  echo ^^^)***";
) > file_txt.h

然后在需要的地方添加file_txt.h。

这不是完美的,因为它在开始处添加了\ n,在末尾添加了\ n ^,但这并不是要解决的问题,我喜欢此解决方案的简单性。如果任何人都可以完善就是摆脱多余的字符,那就太好了。

答案 10 :(得分:0)

如果您使用的是CMake,则可能对编写CMake预处理脚本感兴趣,如下所示:

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()

您的CMakeLists.txt

include(cmake/ConvertLayout.cmake)
convert_layout_directory(layout ${CMAKE_BINARY_DIR}/include)
include_directories(${CMAKE_BINARY_DIR}/include)

c ++中的某个地方

#include "layout/menu.h"
Glib::ustring ui_info = LAYOUT_MENU;

答案 11 :(得分:0)

这个问题让我很烦,并且xxd在我的用例中不起作用,因为当我尝试编写脚本时,它使变量名为__home_myname_build_prog_cmakelists_src_autogen之类的东西,因此我制作了一个实用程序来解决这个确切的问题:

https://github.com/Exaeta/brcc

它生成源文件和头文件,并允许您显式设置每个变量的名称,以便随后可以通过std :: begin(arrayname)和std :: end(arrayname)使用它们。

我将其合并到我的cmake项目中,如下所示:

add_custom_command(
  OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/binary_resources.hpp ${CMAKE_CURRENT_BINARY_DIR}/binary_resources.cpp
  COMMAND brcc ${CMAKE_CURRENT_BINARY_DIR}/binary_resources RGAME_BINARY_RESOURCES_HH txt_vertex_shader ${CMAKE_CURRENT_BINARY_DIR}/src/vertex_shader1.glsl
  DEPENDS src/vertex_shader1.glsl)

通过小的调整,我想它也可以用于C。

答案 12 :(得分:0)

如果您愿意采取一些肮脏的技巧,则可以使用原始字符串文字和#include来创建某些类型的文件来发挥创意。

例如,假设我想在项目中包括一些用于SQLite的SQL脚本,并且希望突出显示语法,但是不需要任何特殊的构建基础结构。我可以拥有此文件test.sql,该文件对SQLite是有效的SQL,其中--可以开始注释:

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

然后在我的C ++代码中,我可以拥有:

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

    cout << mysql << endl;
}

输出为:

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

或者在文件test.py中包含一些Python代码,该文件是有效的Python脚本(因为#在Python中开始注释,而pass是无操作):

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

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

然后在C ++代码中:

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

    cout << mypython << endl;
}

哪个会输出:

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

myfunc()
#undef pass
#define pass

应该为您可能希望包含为字符串的各种其他类型的代码发挥类似的技巧。我不确定这是否是个好主意。这是一个很好的技巧,但可能不是您在实际生产代码中想要的东西。周末黑客项目可能还可以。

答案 13 :(得分:0)

我有类似的问题,对于小文件,前面提到的Johannes Schaub解决方案对我来说就像是一种魅力。

但是,对于稍微大一点的文件,它遇到了编译器的字符数组限制问题。因此,我编写了一个小编码器应用程序,它将文件内容转换为具有相同大小的块(可能还有填充零)的2D字符数组。它生成带有2D数组数据的输出文本文件,如下所示:

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

其中4实际上是编码器中的变量MAX_CHARS_PER_ARRAY。然后可以很容易地将带有生成的C代码的文件(例如“main_js_file_data.h”)内联到C ++应用程序中,例如:

#include "main_js_file_data.h"

以下是编码器的源代码:

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

答案 14 :(得分:0)

为什么不将文本链接到程序中并将其用作全局变量! Here is an example.我考虑使用它来在可执行文件中包含Open GL着色器文件,因为GL着色器需要在运行时为GPU编译。

答案 15 :(得分:0)

我认为单独使用编译器和预处理器是不可能的。 gcc允许这样:

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

但不幸的是不是这样:

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

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

错误是:

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

答案 16 :(得分:0)

Hasturkun使用xxd -i选项的答案非常好。如果你想将转换过程(text - &gt; hex include file)直接合并到你的构建中,hexdump.c工具/库最近增加了一个类似于xxd的-i选项的功能(它没有给你完整的标题 - 你需要提供char数组定义 - 但这样做的好处是可以让你选择char数组的名称):

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

它的许可证比xxd更“标准”并且非常自由 - 使用它在程序中嵌入init文件的示例可以在CMakeLists.txt和scheme.c文件中看到:

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

在源树和捆绑实用程序中包含生成的文件有利有弊 - 如何处理它将取决于项目的具体目标和需求。 hexdump.c打开了此应用程序的捆绑选项。

答案 17 :(得分:0)

x.h中的

"this is a "
"buncha text"

在main.c中

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

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

    return 0
}

应该做这个工作。

答案 18 :(得分:0)

即使它可以在编译时完成(我不认为它通常可以),文本可能是预处理的标题而不是逐字的文件内容。我希望你必须在运行时加载文件中的文本,或者做一个令人讨厌的剪切粘贴工作。