Go / Cgo – 如何访问 C 结构体的字段?

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

Go / Cgo - How to access a field of a Cstruct?

问题

我正在开发一个使用Go语言编写的应用程序,用于将音频文件从一种格式转码为另一种格式:

我使用了goav库,该库使用Cgo来绑定FFmpeg的C库:
https://github.com/giorgisio/goav/


goav库中的package avformat有一个typedef,用于将原始的FFmpeg lib C-Struct AVOutputFormat进行类型转换:

type ( 
   OutputFormat               C.struct_AVOutputFormat
)

在我的代码中,我有一个名为outputF的变量,类型为OutputFormat,它是一个C.struct_AVOutputFormat

真正的C中的AVOutputFormat结构体有以下字段:

name, long_name, mime_type, extensions, audio_codec, video_codec, subtitle_codec,..

还有许多其他字段。

参考:https://ffmpeg.org/doxygen/2.6/structAVOutputFormat.html


我通过fmt.Println(outputF)验证了情况,并得到了以下结果:

{0x7ffff7f23383 0x7ffff7f23907 0x7ffff7f13c33 0x7ffff7f23383 86017 61 0 128 <nil> 0x7ffff7f8cfa0 <nil> 3344 0x7ffff7e3ec10 0x7ffff7e3f410 0x7ffff7e3ecc0 <nil> 0x7ffff7e3dfc0 <nil> <nil> <nil> <nil> <nil> <nil> 0 0x7ffff7e3e070 0x7ffff7e3e020 <nil>}

音频编解码器字段位于位置5,包含86017

我使用reflect包验证了字段名称:

val := reflect.Indirect(reflect.ValueOf(outputF))
fmt.Println(val)
fmt.Println("Fieldname: ", val.Type().Field(4).Name)

输出
Fieldname:  audio_codec

我尝试访问原始AVOutputFormat的字段audio_codec,使用了以下代码:

fmt.Println(outputF.audio_codec)
错误outputF.audio_codec未定义无法引用未公开的字段或方法audio_codec

fmt.Println(outputF._audio_codec)
错误outputF._audio_codec未定义类型*avformat.OutputFormat没有字段或方法_audio_codec

根据我在Cgo文档中的阅读:
在Go文件中,如果C的结构体字段名称在Go中是关键字,可以在其前面加上下划线来访问它们:如果x指向一个具有名为"type"的字段的C结构体,x._type可以访问该字段。无法在Go中表示的C结构体字段,例如位字段或不对齐的数据,在Go结构体中被省略,用适当的填充替换以达到下一个字段或结构体末尾。

但是我不知道我做错了什么。

编辑:
好吧,肯定不需要下划线,因为audio_codec在Go中不是关键字。这一点我现在明白了。但是仍然有一个问题,为什么我无法访问C结构体字段"audio_codec"。

英文:

I develope an application in Go for transcode an audio file from one format to another one:

I use the goav library that use Cgo to bind the FFmpeg C-libs:
https://github.com/giorgisio/goav/


The goav library; package avformat has a typedef that cast the original FFmpeg lib C-Struct AVOutputFormat:

type ( 
   OutputFormat               C.struct_AVOutputFormat
)

In my code i have a variable called outputF of the type OutputFormat that is a C.struct_AVOutputFormat.

The C real AVOutputFormat struct has fields:

name, long_name, mime_type, extensions, audio_codec, video_codec, subtitle_codec,..

and many fields more.

See: https://ffmpeg.org/doxygen/2.6/structAVOutputFormat.html


I verified the situation by fmt.Println(outputF) and reached:

{0x7ffff7f23383 0x7ffff7f23907 0x7ffff7f13c33 0x7ffff7f23383 86017 61 0 128 <nil> 0x7ffff7f8cfa0 <nil> 3344 0x7ffff7e3ec10 0x7ffff7e3f410 0x7ffff7e3ecc0 <nil> 0x7ffff7e3dfc0 <nil> <nil> <nil> <nil> <nil> <nil> 0 0x7ffff7e3e070 0x7ffff7e3e020 <nil>}

The audio codec field is on position 5 and contains 86017

I verified the field name by using the package reflect:

val := reflect.Indirect(reflect.ValueOf(outputF))
fmt.Println(val)
fmt.Println("Fieldname: ", val.Type().Field(4).Name)

Output:
Fieldname:  audio_codec

I try to access the field audio_codec of the original AVOutputFormat using:

fmt.Println(outputF.audio_codec)
ERROR: outputF.audio_codec undefined (cannot refer to unexported field or method audio_codec)


fmt.Println(outputF._audio_codec)
ERROR: outputF._audio_codec undefined (type *avformat.OutputFormat has no field or method _audio_codec)

>As i read in the Cgo documentation:
Within the Go file, C's struct field names that are keywords in Go can be accessed by prefixing them with an underscore: if x points at a C struct with a field named "type", x._type accesses the field. C struct fields that cannot be expressed in Go, such as bit fields or misaligned data, are omitted in the Go struct, replaced by appropriate padding to reach the next field or the end of the struct.

But I have no idea what im doing wrong.

Edit:
Okay for sure no underscore is required as audio_codec is not a keyword in Go. This i understood for now. But still there is the question why im not able to access the CStruct field "audio_codec".

答案1

得分: 2

这里涉及到GO/CGO的一些特殊性:

type OutputFormat C.struct_AVOutputFormat 是一个go的类型声明,而不是一个别名。可以将其视为一个薄包装而不是别名。因此,OutputFormat != C.struct_AVOutputFormat,并且C.struct_AVOutputFormat的字段没有被导出,这就是为什么会出现ERROR: outputF.audio_codec undefined (cannot refer to unexported field or method audio_codec)的原因。

如果字段被称为Audio_codec,它将符合go的导出标识符的定义,我们可以访问它,但实际上并不是这样。

有一种方法可以解决这个问题,但我建议在继续之前三思而后行,因为它使用了不安全指针,并且存在程序在运行时可能失去可移植性和/或稳定性的风险。这是一个关于不安全指针的好介绍,如果你想了解更多。

现在,如果你确实确定要这样做,解决方案是将指向OutputFormat的指针转换为不安全指针,然后将其转换为指向C.struct_AVOutputFormat的指针。请注意,这需要加载FFMPEG头文件以获取C.struct_AVOutputFormat的定义。

//#cgo pkg-config: libavformat
//#include <libavformat/avformat.h>
import "C"
import (
	"fmt"
	"unsafe"
	"github.com/giorgisio/goav/avformat"
)

func getOutput() *avformat.OutputFormat {
  // 调用avformat并获取一个OutputFormat返回
}

func main() {
	outputF := getOutput()
	coutput := *(*C.struct_AVOutputFormat)(unsafe.Pointer(outputF))

	fmt.Println(coutput.audio_codec) // 现在应该可以工作了
}

注意:我没有测试cgo包配置和<libavformat/avformat.h>导入是否正确,但这在我用一个简单的C库进行尝试时是有效的。

英文:

There are some idiosyncrasies to GO/CGO that you're bumping into here:

type OutputFormat C.struct_AVOutputFormat is a go type declaration, not an alias. It might help to think of it as a thin wrapper rather than an alias. Therefore OutputFormat != C.struct_AVOutputFormat and the fields of C.struct_AVOutputFormat are not exported which is why you're getting the ERROR: outputF.audio_codec undefined (cannot refer to unexported field or method audio_codec)

If the field was called Audio_codec it would fit go's definition of an exported identifier and we could access it, but it isn't.

There is a way to get around this, but I'd recommend thinking twice before going ahead with it as it uses unsafe pointers and there are risks that your program may lose portability and/or stability at runtime. This is a good intro to unsafe pointers if you'd like to learn more.

Now if you're really sure you want to do this, the solution is to convert the pointer to OutputFormat into an unsafe pointer then convert it into a pointer to C.struct_AVOutputFormat. Note that this requires you to load the FFMPEG headers to get the definition of C.struct_AVOutputFormat

//#cgo pkg-config: libavformat
//#include &lt;libavformat/avformat.h&gt;
import &quot;C&quot;
import (
	&quot;fmt&quot;
	&quot;unsafe&quot;
	&quot;github.com/giorgisio/goav/avformat&quot;
)

func getOutput() *avformat.OutputFormat {
  // calls out to avformat and gets an OutputFormat back
}

func main() {
	outputF := getOutput()
	coutput := *(*C.struct_AVOutputFormat)(unsafe.Pointer(outputF))

	fmt.Println(coutput.audio_codec) // This should now work
}

Caveat: I've not tested that the cgo package config and &lt;libavformat/avformat.h&gt; import are correct but this worked with a simple C library I stood up to try it out with.

huangapple
  • 本文由 发表于 2021年8月15日 19:20:13
  • 转载请务必保留本文链接:https://go.coder-hub.com/68791044.html
匿名

发表评论

匿名网友

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

确定