Перенаправить printf на fopencookie - PullRequest
0 голосов
/ 30 декабря 2018

В Linux (Raspbian на Raspberry Pi) я хотел бы сделать так, чтобы все, что мое приложение C печатало с использованием printf, отправлялось мне обратно в обратном вызове.

(Нет, яя не говорю о перенаправлении оболочки с > some_file.txt. Я говорю о программе на C, которая сама принимает решение отправить stdout (и, следовательно, printf) обратному вызову в той же программе.)

(Да, я действительно хочу это сделать. Я делаю полноэкранную программу с использованием OpenGL и хочу представить любой текст printf пользователю в этой программе, используя мой собственный рендеринг.код. Замена всех вызовов printf чем-то другим не представляется возможным.)

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

Я могу использовать fopencookie, чтобы получить FILE*, который в конечном итоге вызывает мой обратный вызов.Все идет нормально.Задача состоит в том, чтобы заставить stdout и printf идти туда.

I не может использовать freopen, потому что он принимает путь строки.FILE* Я хочу перенаправить не файл в файловой системе, а просто существует во время выполнения.

I не может использовать dup2, потому что FILE* from fopencookie не имеет дескриптора файла (fileno возвращает -1).

Документация glibc предполагает, что я могу просто переназначить stdout на мою новую FILE*: "stdin, stdout и stderr - это обычные переменные, которые вы можете установить, как и любые другие." .Это почти работает .Все, что напечатано с fprintf (stdout, "whatever") , действительно отправляется на мой обратный вызов, также как и любое printf, которое имеет любые спецификаторы формата.Тем не менее, любой вызов printf со строкой без спецификаторов формата все равно переходит к «исходному» стандартному выводу.

Как мне добиться того, что я пытаюсь сделать?

PS: меня не волнует мобильность.Это будет работать только в моей текущей среде.

#define _GNU_SOURCE                                                         
#include <stdio.h>                                                          
#include <unistd.h>                                                         
#include <assert.h>                                                         
#include <stdarg.h>                                                         
#include <alloca.h>                                                         
#include <string.h>                                                         


static ssize_t my_write_func (void * cookie, const char * buf, size_t size) 
{                                                                           
    fprintf (stderr, "my_write_func received %d bytes\n", size);            
    char * copy = (char*) alloca (size + 1);                                
    assert (copy);                                                          
    copy[size] = 0;                                                         
    strncpy (copy, buf, size);                                              
    fprintf (stderr, "Text is: \"%s\"\n", copy);                            
    fflush (stderr);                                                        
    return size;                                                            
}                                                                           


static FILE * create_opencookie ()                                          
{                                                                           
    cookie_io_functions_t funcs;                                            
    memset (&funcs, 0, sizeof (funcs));                                     
    funcs.write = my_write_func;                                            
    FILE * f = fopencookie (NULL, "w", funcs);                              
    assert (f);                                                             
    return f;                                                               
}                                                                           


int main (int argc, char ** argv)                                           
{                                                                           
    FILE * f = create_opencookie ();                                        
    fclose (stdout);                                                        
    stdout = f;                                                             

    // These two DO go to my callback:                                                                        
    fprintf (stdout, "This is a long string, fprintf'd to stdout\n");                           
    printf ("Hello world, this is a printf with a digit: %d\n", 123);

    // This does not go to my callback.
    // If I omit the fclose above then it gets printed to the console.
    printf ("Hello world, this is plain printf.\n");           
    fflush (NULL);                                                          
    return 0;                                                               
}               

Ответы [ 2 ]

0 голосов
/ 18 июля 2019

О растяжке Debian:

setvbuf (f, NULL, _IOLBF, 0);  // line buffered

после того, как сработал вызов create_opencookie.

0 голосов
/ 31 декабря 2018

Это похоже на ошибку в GLIBC.

Причина, по которой printf("simple string") работает не так, как printf("foo %d", 123), заключается в том, что GCC преобразует первые в puts с представлением, что они эквивалентны.

Насколько я могу судить, они должны быть эквивалентными. На этой справочной странице указано, что puts выводит на stdout, точно так же, как printf.

Однако в GLIBC printf выводит на stdout здесь , но puts выводит _IO_stdout здесь , и они не эквивалентны. Об этом уже сообщалось как об ошибке glibc (вышестоящая ошибка).

Чтобы обойти эту ошибку, вы можете создать с флагом -fno-builtin-printf.Это не позволяет GCC преобразовать printf в puts, и в моей системе выдается:

$ ./a.out
my_write_func received 126 bytes
Text is: "This is a long string, fprintf'd to stdout
Hello world, this is a printf with a digit: 123
Hello world, this is plain printf.
"

Этот обходной путь, конечно, не завершен: если вы вызываете puts напрямую или ссылаетесь в объектных файлах, которые вызываютprintf("simple string") и если не скомпилировано с -fno-builtin-printf (возможно, из сторонней библиотеки), то у вас все равно будет проблема.

К сожалению, вы не можете назначить _IO_stdout (который является макросом).Единственное, что вы можете сделать (о чем я могу подумать) - это ссылка в вашем собственном puts, которая просто возвращает printf("%s", arg).Это должно работать, если вы ссылаетесь на libc.so.6, но может вызвать проблемы, если вы ссылаетесь на libc.a.

...