本文总阅读量 本站访客数人次 本站总访问量
gongluck's blog
C/C++ Golang 音视频流媒体
CGO类型转换

由于CGO和C的数据类型不是完全等价匹配, 所有在使用CGO的过程中需要做类型转换。

代码仓库

https://github.com/gongluck/CGO-DEMO.git

_cgo_export.h

使用 go tool cgo .\hello.go 命令可以生成 cgo 的中间文件,其中 _cgo_export.h 声明了导出函数和C与GO直接类型的关系。

C-CGO-GO类型映射

C语言类型 CGO类型 Go语言类型
char C.char byte
singed char C.schar int8
unsigned char C.uchar uint8
short C.short int16
unsigned short C.ushort uint16
int C.int int32
unsigned int C.uint uint32
long C.long int32
unsigned long C.ulong uint32
long long int C.longlong int64
unsigned long long int C.ulonglong uint64
float C.float float32
double C.double float64
size_t C.size_t uint
int8_t C.int8_t int8
uint8_t C.uint8_t uint8
int16_t C.int16_t int16
uint16_t C.uint16_t uint16
int32_t C.int32_t int32
uint32_t C.uint32_t uint32
int64_t C.int64_t int64
uint64_t C.uint64_t uint64

GO字符串和切片

Go语言的字符串、切片、字典、接口和管道等特有的数据类型生成对应的C语言类型:

typedef struct { const char *p; ptrdiff_t n; } GoString;
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;

GoString 类型会对 _cgo_export.h 头文件产生依赖,可以用 GoString 代替。以下两个函数用于获取字符串结构中的长度和指针信息:

size_t _GoStringLen(_GoString_ s);
const char *_GoStringPtr(_GoString_ s);

GO访问C的结构体、联合、枚举类型

package main

/*
#include <stdint.h>

struct A{
    int i;
    float f;
    int type;
    int   size:10;	// The bit field cannot be accessed
    float arr[];	// Zero-length arrays are also inaccessible
};

union B1 {
    int i;
    float f;
};
union B2 {
    int8_t i8;
    int64_t i64;
};

enum C {
    ONE,
    TWO,
};
*/
import "C"

import (
    "fmt"
    "unsafe"
)

func main() {
    var a C.struct_A
    fmt.Println(a.i)
    fmt.Println(a.f)
    fmt.Println(a._type)

    var b1 C.union_B1
    fmt.Printf("%T\n", b1) // [4]uint8
    var b2 C.union_B2
    fmt.Printf("%T\n", b2) // [8]uint8
    fmt.Println("b1.i:", *(*C.int)(unsafe.Pointer(&b1)))
    fmt.Println("b1.f:", *(*C.float)(unsafe.Pointer(&b1)))

    var c C.enum_C = C.TWO
    fmt.Println(c)
    fmt.Println(C.ONE)
    fmt.Println(C.TWO)
}

通过 C.struct_xxx 来访问C语言中定义的 struct xxx 结构体类型。结构体的内存布局按照C语言的通用对齐规则,在32位Go语言环境C语言结构体也按照32位对齐规则,在64位Go语言环境按照64位的对齐规则。对于指定了特殊对齐规则的结构体,无法在CGO中访问。如果结构体的成员名字中碰巧是Go语言的关键字,可以通过在成员名开头添加下划线来访问。

C语言结构体中位字段对应的成员无法在Go语言中访问,如果需要操作位字段成员,需要通过在C语言中定义辅助函数来完成。对应零长数组的成员,无法在Go语言中直接访问数组的元素,但其中零长的数组成员所在位置的偏移量依然可以通过 unsafe.Offsetof(a.arr) 来访问。

在C语言中,我们无法直接访问Go语言定义的结构体类型。

通过 C.union_xxx 来访问C语言中定义的 union xxx 类型。但是Go语言中并不支持C语言联合类型,它们会被转为对应大小的字节数组。可以使用unsafe包强制转型为对应类型(这是性能最好的方式)访问联合类型成员;对于复杂的联合类型,推荐通过在C语言中定义辅助函数的方式处理。

可以通过 C.enum_xxx 来访问C语言中定义的 enum xxx 结构体类型。在C语言中,枚举类型底层对应int类型,支持负数类型的值。我们可以通过 C.ONEC.TWO 等直接访问定义的枚举值。

GO访问C的字符串、数组

根据在reflect包中有字符串和切片的定义:

type StringHeader struct {
 Data uintptr
 Len  int
}

type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}

不单独分配内存,可以在Go语言中直接访问C语言的内存空间。因为Go语言的字符串是只读的,用户需要自己保证Go字符串在使用期间,底层对应的C字符串内容不会发生变化、内存不会被提前释放掉。

package main

/*
#include <string.h>
char arr[10];
char *s = "Hello";
*/
import "C"
import (
    "fmt"
    "reflect"
    "unsafe"
)

func main() {
    // C array -> Go slice
    var arr0 []byte
    var arr0Hdr = (*reflect.SliceHeader)(unsafe.Pointer(&arr0))
    arr0Hdr.Data = uintptr(unsafe.Pointer(&C.arr[0]))
    arr0Hdr.Len = 10
    arr0Hdr.Cap = 10

    arr1 := (*[31]byte)(unsafe.Pointer(&C.arr[0]))[:10:10]
    fmt.Println(arr1)

    // C string -> Go string
    var s0 string
    var s0Hdr = (*reflect.StringHeader)(unsafe.Pointer(&s0))
    s0Hdr.Data = uintptr(unsafe.Pointer(C.s))
    s0Hdr.Len = int(C.strlen(C.s))

    sLen := int(C.strlen(C.s))
    s1 := string((*[31]byte)(unsafe.Pointer(C.s))[:sLen:sLen])
    fmt.Println(s1)
}

在C语言中可以通过 GoStringGoSlice 来访问Go语言的字符串和切片。但由于GC的存在,可能会潜藏着危险。

GO指针和数值转换

为了实现X类型指针到Y类型指针的转换,需要借助 unsafe.Pointer 作为中间桥接类型实现不同类型指针之间的转换。unsafe.Pointer 指针类型类似C语言中的 void* 类型的指针。 任何类型的指针都可以通过强制转换为 unsafe.Pointer 指针类型去掉原有的类型信息,然后再重新赋予新的指针类型而达到指针间的转换的目的。

Go语言针对 unsafe.Pointr 指针类型特别定义了一个 uintptr 类型。可以 uintptr 为中介,实现数值类型到unsafe.Pointr指针类型到转换。


Last modified on 2020-04-11