如何在 golang 的 C 函数调用中捕获 stderr 的内容?

How to capture the contents of stderr in a C function call from golang?

提问人:llimllib 提问时间:6/27/2023 最后编辑:llimllib 更新时间:6/27/2023 访问量:66

问:

这是一个 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.gogo build test.go && ./test)

我想做的是捕获 的输出,该输出写入 stderr,在缓冲区中,然后在调用完成后恢复到其正常功能。C.library_callos.Stderr

我不知道该怎么做,你能帮忙吗?

(对于奖励积分,这也应该适用于 Windows!

C Go Unix IO FFI

评论


答:

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 上也能解决?),那就太好了!