英文:
How to capture the contents of stderr in a C function call from golang?
问题
这是一个调用C函数并将输出定向到stderr
的Golang程序。在实际程序中,函数调用位于一个无法更改的库中:
package main
// #include <stdio.h>
// #include <stdlib.h>
//
// // 假设我们无法更改此函数
// static void library_call(char* s) {
// fprintf(stderr, "%s\n", s);
// }
import "C"
import (
"fmt"
"io"
"os"
"unsafe"
)
func main() {
// 尝试将os.Stderr重定向到一个管道以进行写入;它确实阻止了向stderr写入,但无法成功捕获输出
r, w, _ := os.Pipe()
_ = os.Stderr.Close()
os.Stderr = w
// 调用一个写入stderr的C函数;我想在缓冲区中捕获这个输出
cs := C.CString("This should be the contents of the buffer")
C.library_call(cs)
C.free(unsafe.Pointer(cs))
// 如果取消注释,管道将捕获此输出
// fmt.Fprintf(os.Stderr, "testing")
// 关闭管道写入器,以便我们可以从中读取
w.Close()
out, _ := io.ReadAll(r)
// 我希望这将输出
// "got output: This should be the contents of the buffer",
// 但实际上是空的
fmt.Printf("got output: %s\n", string(out))
}
(您可以将其保存为test.go
,并使用go build test.go && ./test
进行编译和运行)
我想要做的是在缓冲区中捕获C.library_call
的输出(它写入stderr),然后在调用完成后将os.Stderr
恢复为其正常功能。
我无法弄清楚如何做到这一点,你能帮忙吗?
(额外加分的话,这应该在Windows上也能工作!)
英文:
Here is a golang program that calls a C function which outputs to stderr
. In the real program, the function call is inside a library that I cannot change:
package main
// #include <stdio.h>
// #include <stdlib.h>
//
// // assume that we cannot change this function
// static void library_call(char* s) {
// fprintf(stderr, "%s\n", s);
// }
import "C"
import (
"fmt"
"io"
"os"
"unsafe"
)
func main() {
// attempt to redirect os.Stderr to write to a pipe; it does prevent
// writing to stderr, but does not successfully capture the output
r, w, _ := os.Pipe()
_ = os.Stderr.Close()
os.Stderr = w
// call a C function that writes to stderr; I would like to capture this
// output in a buffer
cs := C.CString("This should be the contents of the buffer")
C.library_call(cs)
C.free(unsafe.Pointer(cs))
// the pipe will capture this output if you uncomment it
// fmt.Fprintf(os.Stderr, "testing")
// close the pipe writer, so we can read from it
w.Close()
// Question: how would I restore os.Stderr here to its normal value?
out, _ := io.ReadAll(r)
// I'd like this to output
// "got output: This should be the contents of the buffer",
// but it's empty instead
fmt.Printf("got output: %s\n", string(out))
}
(You can save this to test.go
and compile and run it with: go build test.go && ./test
)
What I would like to do is to capture the output of C.library_call
, which writes to stderr, in a buffer, and then restore os.Stderr
to its normal function after the call is complete.
I can't figure out how to do so, can you help?
(For bonus points, this should work on windows too!)
答案1
得分: 1
我找到了Eli Bendersky在GitHub上的代码(https://github.com/eliben/code-for-blog/blob/9920db50ab8e61718943e6ea355944b393b20772/2020/go-fake-stdio/snippets/redirect-cgo-stdout.go)和一篇相关文章(https://eli.thegreenplace.net/2020/faking-stdin-and-stdout-in-go/),这些帮助我找到了一个解决方案,至少在Unix上是有效的。我相当确定这在Windows上不会起作用:
package main
// #include <stdio.h>
// #include <stdlib.h>
//
// // assume that we cannot change this function
// static void library_call(char* s) {
// fprintf(stderr, "%s\n", s);
// }
import "C"
import (
"fmt"
"io"
"os"
"syscall"
"unsafe"
)
// Solution based on eli's work at:
// https://github.com/eliben/code-for-blog/blob/9920db50ab8e61718943e6ea355944b393b20772/2020/go-fake-stdio/snippets/redirect-cgo-stdout.go
func main() {
// use syscall.Dup to get a copy of stderr
origStderr, err := syscall.Dup(syscall.Stderr)
if err != nil {
panic(err)
}
r, w, _ := os.Pipe()
// Clone the pipe's writer to the actual Stderr descriptor; from this point
// on, writes to Stderr will go to w.
if err = syscall.Dup2(int(w.Fd()), syscall.Stderr); err != nil {
panic(err)
}
// call the C function, and flush the output
cs := C.CString("This should be the contents of the buffer")
C.library_call(cs)
C.free(unsafe.Pointer(cs))
C.fflush(nil)
// close the pipe, restore original stderr, and close our dup'ed handle
w.Close()
syscall.Dup2(origStderr, syscall.Stderr)
syscall.Close(origStderr)
// read the data that was written to the pipe; Eli does this with a
// goroutine in the background but this is simpler and seems to work here
// for my purposes. Pipes have limited capacity and this will fail if
// the output is longer than what we see here; see Eli's code and blog
// for details
b, _ := io.ReadAll(r)
fmt.Printf("got output: %s\n", string(b))
fmt.Fprintf(os.Stderr, "stderr works normally\n")
}
这个代码可以解决一部分问题,但如果你知道如何在Windows上解决这个问题(或者这个代码在Windows上也能工作),那就太好了!
英文:
I found code from Eli Bendersky on github and an accompanying article that helped me figure out a solution, at least on unix. I'm pretty sure this won't work on windows:
package main
// #include <stdio.h>
// #include <stdlib.h>
//
// // assume that we cannot change this function
// static void library_call(char* s) {
// fprintf(stderr, "%s\n", s);
// }
import "C"
import (
"fmt"
"io"
"os"
"syscall"
"unsafe"
)
// Solution based on eli's work at:
// https://github.com/eliben/code-for-blog/blob/9920db50ab8e61718943e6ea355944b393b20772/2020/go-fake-stdio/snippets/redirect-cgo-stdout.go
func main() {
// use syscall.Dup to get a copy of stderr
origStderr, err := syscall.Dup(syscall.Stderr)
if err != nil {
panic(err)
}
r, w, _ := os.Pipe()
// Clone the pipe's writer to the actual Stderr descriptor; from this point
// on, writes to Stderr will go to w.
if err = syscall.Dup2(int(w.Fd()), syscall.Stderr); err != nil {
panic(err)
}
// call the C function, and flush the output
cs := C.CString("This should be the contents of the buffer")
C.library_call(cs)
C.free(unsafe.Pointer(cs))
C.fflush(nil)
// close the pipe, restore original stderr, and close our dup'ed handle
w.Close()
syscall.Dup2(origStderr, syscall.Stderr)
syscall.Close(origStderr)
// read the data that was written to the pipe; Eli does this with a
// goroutine in the background but this is simpler and seems to work here
// for my purposes. Pipes have limited capacity and this will fail if
// the output is longer than what we see here; see Eli's code and blog
// for details
b, _ := io.ReadAll(r)
fmt.Printf("got output: %s\n", string(b))
fmt.Fprintf(os.Stderr, "stderr works normally\n")
}
This gets me farther, but if you know of how to solve this on Windows too (or that this would work on windows?) that would be great!
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论