Чтение файла в строку во время компиляции

Я хотел бы написать что-то в файле (назовите его foo.cpp) и включить его как строку в мою программу во время компиляции, аналогично тому, как это делает #include.

Сейчас я использую этот препроцессор C #define:

#define toString(src) #src

чтобы преобразовать связку кода в строку, используемую как в этом примере:

const char* str = toString(
  int x;
  void main(){}
);

Вы можете прочитать о макроснижении там, если хотите.

Я хотел бы переместить этот код во внешний файл, который будет "связан" во время компиляции. Я не хочу, чтобы файл должен был быть распространен вместе с программой, что было бы, если бы я прочитал его во время выполнения.

Я попытался использовать директиву #include, как показано ниже, но компилятор отказался:

const char* str = toString(
#include "foo.cpp"
);

g++ кажется полностью запутанным, но clang++ дал мне эту ошибку:

error: embedding a #include directive within macro arguments is not supported

Кто-нибудь знает, если/как это можно сделать?

Примечание. Я использую это, чтобы написать мои шейдеры GLSL, хотя я сомневаюсь, что эта информация может быть использована.

PS: Прежде чем вы скажете мне, что это дубликат этого вопроса, поместив мой код в гигантскую строку в свой собственный файл или используя внешний инструмент (например. xxd), чтобы сбрасывать его шестнадцатеричное представление, для меня не являются "решениями", поскольку они не лучше (т.е. проще/чище), чем мой текущий метод.


Обновление, через несколько лет:
Я просто понял, что мне никогда не приходилось отвечать на этот вопрос, поскольку он был закрыт как дубликат. Я нашел ответ, который я искал, когда увидел этот коммит, сам по себе комментарий к этой статье и использовали его с тех пор.

В двух словах небольшой файл сборки содержит нужные вам файлы и предоставляет их под заданным NAME, причем три переменные NAME_begin, NAME_end и NAME_len позволяют вам получить доступ к их содержимому с вашего C код.

Таким образом, у вас есть обычный файл, содержащий ничего, кроме нужного вам кода, и он автоматически считывается во время компиляции, вместо того, чтобы читать его во время выполнения или переходить через xxd обручи.

Ответы

Ответ 1

Я не совсем уверен, чего вы пытаетесь выполнить, но утилита командной строки Linux xxd может быть тем, что вы ищете:

xxd -i [filename]

создаст заголовочный файл стиля C, содержащий массив с содержимым вашего файла в полном двоичном кодировании и переменную с его длиной.

Пример:

xxd -i /proc/cpuinfo

создает файл с

unsigned char _proc_cpuinfo[] = {
  0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x6f, 0x72, 0x09, 0x3a, 0x20,
  0x30, 0x0a, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x5f, 0x69, 0x64, 0x09,
...
};
unsigned int _proc_cpuinfo_len = 654390;

Вы можете включить результирующий заголовок в свой код и получить доступ к массиву и длине файла через эти переменные.

Ответ 2

Вы говорите, что вам не нравится xxd, потому что он оставляет файл нечитаемым. Справедливо. Было бы просто написать собственную утилиту, которая кодирует данные в другом формате, так как вы специально хотите, чтобы строки вводились и выводились. Вы можете использовать конкатенацию строковых литералов, чтобы сделать это легко читаемым.

const char* str =
#include "foo.h"
    ;

foo.h:

"  int x;" "\n"
"  void main(){}" "\n"

Я кратко попытался использовать С++ 11 необработанные строковые литералы, которые позволили бы использовать файл без каких-либо переформатирования, но это не Работа. #include считается частью строки, а не включает файл по желанию.

Ответ 3

Простейшая вещь в этом случае - написать небольшую препроцессор, который читает ваш файл и выводит его на упаковку каждая строка в кавычках. Я бы, наверное, сделал это на Python, но это довольно прямо в С++. Предположим, что вы получили inputFile, outputFile и variableName из (возможно, argv, но вы можете получить последние два из входного имени файла:

void
wrapFile( std::istream& inputFile,
          std::ostream& outputFile,
          std::string const& variableName )
{
    outputFile << "extern char const " << variableName << "[] =\n";
    std::string line;
    while ( std::getline( inputFile, line ) ) {
        outputFile << "    \"" << line << "\\n\"\n";
    }
    std::outputFile << ";" << std::endl;
}

В зависимости от того, что содержится в файлах, которые вы включаете, вы можете должны калечить line, прежде чем выводить его, чтобы избежать вещей например " или \.

Если вы хотите получить фантазию, вы можете добавить некоторые тесты для вставки точка с запятой на последней завернутой линии, а не строка его, но это не обязательно.

Это приведет к завершенной строке '\0'. Учитывая Вероятная длина строк, может быть предпочтительнее добавить вторая переменная с длиной:

std::outputFile << "extern int const "
                << variableName
                << "_len = sizeof(" << variableName << ") - 1;\n";

(Не забывайте -1, так как вы не хотите считать завершение '\0', которое компилятор добавит к вашей строке literal.) Если вы включаете сгенерированный файл, где он будет вам это не обязательно понадобится, поскольку std::begin и std::end предоставит необходимую информацию (но опять же, не забудьте использовать std::end( variableName ) - 1, чтобы игнорировать '\n').

Если вы используете make, довольно легко сделать свой сгенерированный файл зависит от обертываемого файла и исполняемого файла который делает обертывание (которое, в свою очередь, зависит от источника выше и т.д.). С помощью Visual Studios вам необходимо создать отдельный проект для кода упаковки, если вы напишете его на С++ (одна из причин, по которой я буду использовать Python для этого), и вы, скорее всего, имеют некоторые проблемы с управлением зависимостями; Визуальные студии на самом деле не предназначена для профессиональной работы (где крупные блоки кода регулярно генерируются с использованием таких методов).