Создание потока FILE *, который приводит к строке

Я ищу способ передать FILE * какой-либо функции, чтобы функция могла писать в нее с помощью fprintf. Это легко, если я хочу, чтобы вывод отображался, скажем, в реальном файле на диске. Но вместо этого я хотел бы получить весь вывод в виде строки (char *). Тип API, который мне нужен:

/** Create a FILE object that will direct writes into an in-memory buffer. */
FILE *open_string_buffer(void);

/** Get the combined string contents of a FILE created with open_string_buffer
    (result will be allocated using malloc). */
char *get_string_buffer(FILE *buf);

/* Sample usage. */
FILE *buf;
buf = open_string_buffer();
do_some_stuff(buf);   /* do_some_stuff will use fprintf to write to buf */
char *str = get_string_buffer(buf);
fclose(buf);
free(str);

Заголовки glibc, по-видимому, указывают на то, что ФАЙЛ может быть настроен с помощью функций-ловушек для выполнения фактического чтения и записи. В моем случае я думаю, что хочу, чтобы хук записи добавлял копию строки в связанный список, и чтобы была функция get_string_buffer, которая вычисляет общую длину списка, выделяет для него память, а затем копирует каждый элемент в него в правильном месте.

Я стремлюсь к чему-то, что можно было бы передать функции, такой как do_some_stuff, без необходимости этой функции знать что-либо, кроме того, что у нее есть FILE *, в которую она может писать.

Существует ли существующая реализация чего-то подобного? Кажется, это полезная и дружественная к C вещь, если предположить, что я прав насчет FILE расширяемости.


person Edmund    schedule 16.11.2009    source источник


Ответы (3)


Если переносимость для вас не важна, вы можете взглянуть на fmemopen и open_memstream. Это расширения GNU, поэтому они доступны только в системах glibc. Хотя похоже, что они являются частью POSIX.1-2008 (fmemopen и open_memstream).

person quinmars    schedule 16.11.2009
comment
open_memstream — это именно то, что я хочу. Я не уверен, что он использует подход со связанными списками, но я не буду писать для него огромные суммы, так что это не имеет значения. - person Edmund; 16.11.2009

Я не уверен, возможно ли непереносимое расширение объектов FILE, но если вы ищете что-то более дружественное к POSIX, вы можете использовать pipe и fdopen.

Это не совсем то же самое, что иметь FILE*, который возвращает байты из буфера, но это определенно FILE* с программно определенным содержимым.

int fd[2];
FILE *in_pipe;

if (pipe(fd))
{
   /* TODO: handle error */
}

in_pipe = fdopen(fd[0], "r");
if (!in_pipe)
{
   /* TODO: handle error */
}

Оттуда вы захотите записать свой буфер в fd[1], используя write(). Будьте осторожны с этим шагом, потому что write() может заблокироваться, если буфер канала заполнен (т. е. кому-то нужно прочитать другой конец), и вы можете получить EINTR, если ваш процесс получит сигнал во время записи. Также следите за SIGPIPE, который происходит, когда другой конец закрывает трубу. Возможно, для вашего использования вы захотите сделать write буфера в отдельном потоке, чтобы избежать блокировки и убедиться, что вы обрабатываете SIGPIPE.

Конечно, это не создаст доступный для поиска FILE*...

person asveikau    schedule 16.11.2009

Я не уверен, что понимаю, почему вы хотите напортачить с FILE *. Не могли бы вы просто записать в файл, а затем загрузить его в строку?

 char *get_file_in_buf(char *filename) {
   char *buffer;
   ... get file size with fseek or fstat ...
   ... allocate buffer ...
   ... read buffer from file ...
   return buffer;
 }

Если вы хотите только «записать» форматированный текст в строку, другим вариантом может быть обработка расширяемого буфера с использованием snprintf() (см. ответы на этот вопрос SO для предложения о том, как это сделать: Возобновление [vf]?nprintf после достижения предела).

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

person Remo.D    schedule 16.11.2009
comment
Есть веские причины хотеть такой FILE*: есть много библиотек, которые предлагают только ввод-вывод на основе FILE* и настаивают на чтении файла, когда данные уже могут быть доступны в памяти. Поэтому желательно иметь возможность передавать буфер памяти, а не записывать в файл только для этих библиотек. К сожалению, эти библиотеки редко, если вообще когда-либо, предлагают альтернативу своему FILE*-зависимому API. - person greyfade; 06.10.2011