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

huangapple go评论75阅读模式
英文:

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 &lt;stdio.h&gt;
// #include &lt;stdlib.h&gt;
//
// // assume that we cannot change this function
// static void library_call(char* s) {
//   fprintf(stderr, &quot;%s\n&quot;, s);
// }
import &quot;C&quot;

import (
	&quot;fmt&quot;
	&quot;io&quot;
	&quot;os&quot;
	&quot;unsafe&quot;
)

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(&quot;This should be the contents of the buffer&quot;)
	C.library_call(cs)
	C.free(unsafe.Pointer(cs))

	// the pipe will capture this output if you uncomment it
	// fmt.Fprintf(os.Stderr, &quot;testing&quot;)

	// 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&#39;d like this to output
	// &quot;got output: This should be the contents of the buffer&quot;,
	// but it&#39;s empty instead
	fmt.Printf(&quot;got output: %s\n&quot;, string(out))
}

(You can save this to test.go and compile and run it with: go build test.go &amp;&amp; ./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 &lt;stdio.h&gt;
// #include &lt;stdlib.h&gt;
//
// // assume that we cannot change this function
// static void library_call(char* s) {
//   fprintf(stderr, &quot;%s\n&quot;, s);
// }
import &quot;C&quot;

import (
	&quot;fmt&quot;
	&quot;io&quot;
	&quot;os&quot;
	&quot;syscall&quot;
	&quot;unsafe&quot;
)

// Solution based on eli&#39;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&#39;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(&quot;This should be the contents of the buffer&quot;)
	C.library_call(cs)
	C.free(unsafe.Pointer(cs))
	C.fflush(nil)

	// close the pipe, restore original stderr, and close our dup&#39;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&#39;s code and blog
    // for details
	b, _ := io.ReadAll(r)

	fmt.Printf(&quot;got output: %s\n&quot;, string(b))
	fmt.Fprintf(os.Stderr, &quot;stderr works normally\n&quot;)
}

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!

huangapple
  • 本文由 发表于 2023年6月27日 20:45:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/76565007.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定