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

7cjasjjr  于 2023-06-27  发布在  C#
关注(0)|答案(1)|浏览(252)

下面是一个golang程序,它调用一个输出到stderr的C函数。在真实的的程序中,函数调用在一个我无法更改的库中:

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可以将其保存到test.go并编译和运行它:go build test.go && ./test
我想做的是在缓冲区中捕获C.library_call的输出(它写入stderr),然后在调用完成后将os.Stderr恢复到其正常功能。
我不知道怎么做,你能帮我吗?
(For加分,这也应该在windows上工作!)

gr8qqesn

gr8qqesn1#

我找到了code from Eli Bendersky on github和一个accompanying article,它帮助我找到了解决方案,至少在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上?)那就太好了!

相关问题