提问人:llimllib 提问时间:6/27/2023 最后编辑:llimllib 更新时间:6/27/2023 访问量:66
如何在 golang 的 C 函数调用中捕获 stderr 的内容?
How to capture the contents of stderr in a C function call from golang?
问:
这是一个 golang 程序,它调用一个 C 函数,该函数输出到 .在实际程序中,函数调用位于我无法更改的库中:stderr
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))
}
(您可以将其保存到并使用以下命令编译和运行它:test.go
go build test.go && ./test
)
我想做的是捕获 的输出,该输出写入 stderr,在缓冲区中,然后在调用完成后恢复到其正常功能。C.library_call
os.Stderr
我不知道该怎么做,你能帮忙吗?
(对于奖励积分,这也应该适用于 Windows!
答:
1赞
llimllib
6/27/2023
#1
我在 github 上找到了 Eli Bendersky 的代码,并随附了一篇文章,帮助我找到了解决方案,至少在 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 上也能解决?),那就太好了!
评论