【问题标题】:Redirect printf to fopencookie将 printf 重定向到 fopencookie
【发布时间】:2019-05-27 13:16:19
【问题描述】:

在 Linux(Raspberry Pi 上的 Raspbian)上,我希望我的 C 应用程序使用 printf 打印的任何内容都会在回调中发回给我。

(不,我不是在谈论使用 > some_file.txt 的 shell 重定向。我说的是 C 程序自行决定发送 stdout(因此是 printf ) 到同一程序内的回调。)

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

我觉得这应该很容易。 StackOverflow 上已经有这个问题的变体,但我找不到完全相同的。

我可以使用fopencookie 来获取FILE*,它最终会调用我的回调。到目前为止,一切都很好。挑战是让stdoutprintf 去那里。

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

不能使用dup2,因为来自fopencookieFILE* 没有文件描述符(fileno 返回-1)。

glibc documentation 表明我可以简单地将 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;                                                               
}               

【问题讨论】:

  • 听起来像是 glibc printf 扩展的问题。您是否考虑过只使用#define printf cookie_printf 或类似的东西。编写自己的函数非常容易,它与printf 具有相同的签名,然后调用vfprintfstackoverflow.com/a/1737675/1028434

标签: c printf raspbian glibc stdio


【解决方案1】:

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

printf("simple string")printf("foo %d", 123) 工作方式不同的原因是 GCC 将前者转换为 puts,并认为它们是等价的。

据我所知,它们应该是等价的。 This man page 声明 puts 输出到 stdout,就像 printf 一样。

但是,在 GLIBC 中,printf 输出到 stdouthere,但 puts 输出到 _IO_stdouthere,并且它们等效。 This has already been reported as a glibc bug(upstream bug).

要解决此错误,您可以使用-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 编译的目标文件(可能来自第 3 方库),那你还是有问题。

很遗憾,您不能分配给_IO_stdout(这是一个宏)。你唯一能做的(我能想到的)是链接到你自己的puts,它只返回printf("%s", arg)。如果您链接到libc.so.6,这应该可以工作,但如果您链接到libc.a,则可能会导致麻烦。

【讨论】:

    【解决方案2】:

    您可以改为重定向到管道并在单独的线程中处理写入的数据。

    #include <pthread.h>
    #include <ctype.h>
    #include <unistd.h>
    #include <stdio.h>
    
    // this is the original program which you can't change
    void print(void) {
        printf("Hello, %d\n", 123);
        puts("world");
        printf("xyz");
    }
    
    int p[2];
    
    void *render(void *arg) {
        int nread;
        char buf[1];
        while((nread = read(p[0], buf, sizeof buf)) > 0) {
            // process the written data, in this case - make it uppercase and write to stderr
            for(int i = 0; i < nread; i++)
                buf[i] = toupper(buf[i]);
            write(2, buf, nread);
        }
        return NULL;
    }
    
    int main() {
        setvbuf(stdout, NULL, _IONBF, 0);
        pipe(p);
        dup2(p[1], 1);
        close(p[1]);
        pthread_t t;
        pthread_create(&t, NULL, render, NULL);
        print();
        close(1);
        pthread_join(t, NULL);
    }
    

    【讨论】:

      【解决方案3】:

      关于 debian 拉伸:

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

      create_opencookie 调用成功后。

      【讨论】:

      • 欢迎使用 StackOverflow!请使用代码标签使您的答案更具可读性。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-25
      • 2021-12-20
      相关资源
      最近更新 更多