将printf重定向到fopencookie

pet*_*rpi 3 c printf glibc stdio raspbian

在Linux上(Raspberry Pi上的Raspbian)我想这样做,以便我的C应用程序打印使用的任何内容都会printf在回调中发回给我.

(不,我不是在谈论shell重定向> some_file.txt.我正在谈论一个C程序自己决定发送stdout(因此printf)在同一个程序中的回调.)

(是的,我确实想要这样做.我正在使用OpenGL制作全屏程序,并希望printf使用我自己的渲染代码在该程序中向用户呈现任何文本.printf用其他东西替换所有调用是不可行.)

我觉得这应该很容易.StackOverflow上已经存在这个问题的变体,但我找不到的都是完全一样的.

我可以fopencookie用来获得一个FILE*最终调用我的回调.到现在为止还挺好.挑战是去stdoutprintf去那里.

无法使用,freopen因为它需要一个字符串路径.在FILE*我想重定向到是不是文件系统中的文件,而只是在运行时存在.

无法使用,dup2因为FILE*from fopencookie没有文件描述符(fileno返回-1).

glibc的文件表明,我可以简单地重新分配stdout给我的新FILE*:"标准输入,标准输出,和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;                                                               
}               
Run Code Online (Sandbox Code Playgroud)

Emp*_*ian 6

这似乎是GLIBC中的一个错误.

与之printf("simple string")不同的原因printf("foo %d", 123)是GCC将前者转换为a puts,并认为它们是等价的.

据我所知,它们应该是等价的.这个手册页说明了puts输出stdout,就像printf那样.

但是,在GLIBC printf输出到stdout 这里,但puts输出到_IO_stdout 这里,这些并不相同.这已被报告为glibc错误 (上游错误).

要解决此错误,您可以使用-fno-builtin-printf标志进行构建.这可以防止GCC转换printfputs我的系统产生:

$ ./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.
"
Run Code Online (Sandbox Code Playgroud)

这种解决方法当然是不完整的:如果你puts直接调用,或链接调用printf("simple string")编译的对象文件-fno-builtin-printf(可能来自第三方库),那么你仍然会遇到问题.

不幸的是你无法分配_IO_stdout(这是一个宏).你可以做的唯一的另一件事(我能想到的)是你自己的链接puts,它只是返回printf("%s", arg).如果您要链接libc.so.6,这应该有效,但如果您链接,可能会造成麻烦libc.a.