将 printf 重定向到 fopencookie
Redirect printf to fopencookie
On Linux (Raspbian on a Raspberry Pi) 我想这样做,以便我的 C 应用程序使用 printf
打印的任何内容都在回调。
(不,我不是在谈论 shell 与 > some_file.txt
的重定向。我在谈论一个 C 程序 自己将 stdout
(因此 printf
)发送到同一程序中的回调。)
(是的,我真的很想这样做。我正在使用 OpenGL 制作一个全屏程序,并希望在该程序中使用我自己的 printf
向用户显示任何文本渲染代码。用其他东西替换所有 printf
调用是不可行的。)
我觉得这应该很容易。 Whosebug 上已经有这个问题的变体,但我发现 none 完全相同。
我可以使用 fopencookie
得到一个 FILE*
最终调用我的回调。到目前为止,一切都很好。挑战是让 stdout
和 printf
去那里。
我不能使用freopen
,因为它需要一个字符串路径。我要重定向到的 FILE*
不是文件系统上的文件,而是仅存在于 运行 时间。
我不能使用dup2
因为fopencookie
的FILE*
没有文件描述符(fileno
returns-1).
glibc documentation 建议我可以简单地将 stdout
重新分配给我的新 FILE*
:"stdin, stdout, and stderr are normal variables which you can set just like any others."。这 几乎可以工作 。使用 fprintf (stdout, "whatever")
打印的任何内容都会 转到我的回调,任何具有任何格式说明符的 printf
也是如此。但是,使用完全没有格式说明符的字符串对 printf
的任何调用仍会转到 "original" 标准输出。
我怎样才能实现我想要做的事情?
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("simple string")
与 printf("foo %d", 123)
工作方式不同的原因是 GCC 将前者转换为 puts
,并认为它们是等价的。
据我所知,它们应该是等价的。 This man page 表示 puts
输出到 stdout
,就像 printf
一样。
但是,在 GLIBC 中 printf
输出到 stdout
here, but puts
outputs to _IO_stdout
here, and these are not equivalent. 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")
并且 不是 [=50= 的对象文件中调用 link ] 使用 -fno-builtin-printf
编译(可能来自第 3 方库),那么你仍然会遇到问题。
不幸的是你不能分配给_IO_stdout
(这是一个宏)。您唯一可以做的(我能想到的)是在您自己的 puts
中 link,也就是 returns printf("%s", arg)
。如果您 link 反对 libc.so.6
,这应该有效,但如果您 link 反对 libc.a
。
可能会造成麻烦
关于 debian 拉伸:
setvbuf (f, NULL, _IOLBF, 0); // line buffered
在 create_opencookie 调用成功后。
您可以改为重定向到管道,并在单独的线程中处理写入的数据。
#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);
}
On Linux (Raspbian on a Raspberry Pi) 我想这样做,以便我的 C 应用程序使用 printf
打印的任何内容都在回调。
(不,我不是在谈论 shell 与 > some_file.txt
的重定向。我在谈论一个 C 程序 自己将 stdout
(因此 printf
)发送到同一程序中的回调。)
(是的,我真的很想这样做。我正在使用 OpenGL 制作一个全屏程序,并希望在该程序中使用我自己的 printf
向用户显示任何文本渲染代码。用其他东西替换所有 printf
调用是不可行的。)
我觉得这应该很容易。 Whosebug 上已经有这个问题的变体,但我发现 none 完全相同。
我可以使用 fopencookie
得到一个 FILE*
最终调用我的回调。到目前为止,一切都很好。挑战是让 stdout
和 printf
去那里。
我不能使用freopen
,因为它需要一个字符串路径。我要重定向到的 FILE*
不是文件系统上的文件,而是仅存在于 运行 时间。
我不能使用dup2
因为fopencookie
的FILE*
没有文件描述符(fileno
returns-1).
glibc documentation 建议我可以简单地将 stdout
重新分配给我的新 FILE*
:"stdin, stdout, and stderr are normal variables which you can set just like any others."。这 几乎可以工作 。使用 fprintf (stdout, "whatever")
打印的任何内容都会 转到我的回调,任何具有任何格式说明符的 printf
也是如此。但是,使用完全没有格式说明符的字符串对 printf
的任何调用仍会转到 "original" 标准输出。
我怎样才能实现我想要做的事情?
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("simple string")
与 printf("foo %d", 123)
工作方式不同的原因是 GCC 将前者转换为 puts
,并认为它们是等价的。
据我所知,它们应该是等价的。 This man page 表示 puts
输出到 stdout
,就像 printf
一样。
但是,在 GLIBC 中 printf
输出到 stdout
here, but puts
outputs to _IO_stdout
here, and these are not equivalent. 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")
并且 不是 [=50= 的对象文件中调用 link ] 使用 -fno-builtin-printf
编译(可能来自第 3 方库),那么你仍然会遇到问题。
不幸的是你不能分配给_IO_stdout
(这是一个宏)。您唯一可以做的(我能想到的)是在您自己的 puts
中 link,也就是 returns printf("%s", arg)
。如果您 link 反对 libc.so.6
,这应该有效,但如果您 link 反对 libc.a
。
关于 debian 拉伸:
setvbuf (f, NULL, _IOLBF, 0); // line buffered
在 create_opencookie 调用成功后。
您可以改为重定向到管道,并在单独的线程中处理写入的数据。
#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);
}