1
1
# 2.2. 基础类型转换
2
2
3
- 顾名思义,CGO最初是为了利用C语言资源、是C语言到Go语言、从Go语言函数调用C语言函数的意思。当然,最初因为C语言还会涉及回调函数, 自然也会涉及到从C语言函数调用Go语言函数。现在,CGO已经演变为C语言和Go语言双向通讯的桥梁,它连接了C语言和Go语言两大编程语言 。要想利用好CGO特性,自然需要了解C语言类型和Go语言类型之间的转换 ,这是本节要讨论的问题。
3
+ 最初CGO是为了达到方便从Go语言函数调用C语言函数以复用C语言资源这一目的而出现的(因为C语言还会涉及回调函数, 自然也会涉及到从C语言函数调用Go语言函数) 。现在,它已经演变为C语言和Go语言双向通讯的桥梁 。要想利用好CGO特性,自然需要了解此二语言类型之间的转换规则 ,这是本节要讨论的问题。
4
4
5
5
## 数值类型
6
6
7
- Go语言中访问C语言的符号时 ,一般是通过虚拟的“C”包访问,比如` C.int ` 对应C语言的` int ` 类型。但是, 有些C语言的类型是由多个关键字组成,通过虚拟的 “C”包访问C语言类型时名称部分不能有空格字符,比如C语言的 ` unsigned int ` 不能直接通过` C.unsigned int ` 访问。CGO对基础的数值类型都提供了对应规则 ,比如` C.uint ` 对应C语言的` unsigned int ` 。
7
+ 在Go语言中访问C语言的符号时 ,一般是通过虚拟的“C”包访问,比如` C.int ` 对应C语言的` int ` 类型。有些C语言的类型是由多个关键字组成,但通过虚拟的 “C”包访问C语言类型时名称部分不能有空格字符,比如 ` unsigned int ` 不能直接通过` C.unsigned int ` 访问。因此CGO为C语言的基础数值类型都提供了相应转换规则 ,比如` C.uint ` 对应C语言的` unsigned int ` 。
8
8
9
9
Go语言中数值类型和C语言数据类型基本上是相似的,以下是它们的对应关系表。
10
10
@@ -25,9 +25,9 @@ float | C.float | float32
25
25
double | C.double | float64
26
26
size_t | C.size_t | uint
27
27
28
- 需要注意的是,虽然C语言中 ` int ` 、` short ` 等类型没有明确定义内存大小,但是它们在CGO中的内存大小是确定的 。在CGO中,C语言的` int ` 和` long ` 类型都是对应4个字节的内存大小,` size_t ` 类型可以当作Go语言` uint ` 无符号整数类型对待。
28
+ 需要注意的是,虽然在C语言中 ` int ` 、` short ` 等类型没有明确定义内存大小,但是在CGO中它们的内存大小是确定的 。在CGO中,C语言的` int ` 和` long ` 类型都是对应4个字节的内存大小,` size_t ` 类型可以当作Go语言` uint ` 无符号整数类型对待。
29
29
30
- CGO中,虽然C语言的` int ` 固定为4字节的大小,但是Go语言自己的` int ` 和` uint ` 则在32位和64位系统下分别对应4个字节和8个字节大小 。如果需要在C语言中访问Go语言的` int ` 类型,可以通过` GoInt ` 类型访问,` GoInt ` 类型在CGO工具生成的` _cgo_export.h ` 头文件中定义。其实在` _cgo_export.h ` 头文件中,每个基本的Go数值类型都定义了对应的C语言类型,它们一般都是以单词Go为前缀。下面是64位环境下,` _cgo_export.h ` 头文件生成的Go数值类型的定义,其中` GoInt ` 和` GoUint ` 类型分别对应` GoInt64 ` 和` GoUint64 ` :
30
+ CGO中,虽然C语言的` int ` 固定为4字节的大小,但是Go语言自己的` int ` 和` uint ` 却在32位和64位系统下分别对应4个字节和8个字节大小 。如果需要在C语言中访问Go语言的` int ` 类型,可以通过` GoInt ` 类型访问,` GoInt ` 类型在CGO工具生成的` _cgo_export.h ` 头文件中定义。其实在` _cgo_export.h ` 头文件中,每个基本的Go数值类型都定义了对应的C语言类型,它们一般都是以单词Go为前缀。下面是64位环境下,` _cgo_export.h ` 头文件生成的Go数值类型的定义,其中` GoInt ` 和` GoUint ` 类型分别对应` GoInt64 ` 和` GoUint64 ` :
31
31
32
32
``` c
33
33
typedef signed char GoInt8;
@@ -44,7 +44,7 @@ typedef float GoFloat32;
44
44
typedef double GoFloat64;
45
45
```
46
46
47
- 除了` GoInt ` 和` GoUint ` 之外,我们并不推荐直接访问` GoInt32 ` 、` GoInt64 ` 等类型。更好的做法是通过C语言的C99标准引入的` <stdint.h> ` 头文件。在` <stdint.h> ` 文件中,它是为了提高C语言的可移植性,每个数值类型都提供了明确内存大小 ,而且和Go语言的类型命名更加一致。
47
+ 除了` GoInt ` 和` GoUint ` 之外,我们并不推荐直接访问` GoInt32 ` 、` GoInt64 ` 等类型。更好的做法是通过C语言的C99标准引入的` <stdint.h> ` 头文件。为了提高C语言的可移植性, 在` <stdint.h> ` 文件中,不但每个数值类型都提供了明确内存大小 ,而且和Go语言的类型命名更加一致。
48
48
49
49
C语言类型 | CGO类型 | Go语言类型
50
50
-------- | ---------- | ---------
@@ -57,11 +57,11 @@ uint32_t | C.uint32_t | uint32
57
57
int64_t | C.int64_t | int64
58
58
uint64_t | C.uint64_t | uint64
59
59
60
- 前文说过,如果C语言的类型是由多个关键字组成,无法通过虚拟的 “C”包直接访问。 比如C语言的` unsigned short ` 不能直接通过` C.unsigned short ` 访问。但是,在` <stdint.h> ` 中通过使用C语言的` typedef ` 关键字将` unsigned short ` 重新定义为` uint16_t ` 一个单词的类型后 ,我们就可以通过` C.uint16_t ` 访问原来的` unsigned short ` 类型了。对于比较复杂的C语言类型,推荐使用` typedef ` 关键字提高一个规则的类型命名 ,这样更利于在CGO中访问。
60
+ 前文说过,如果C语言的类型是由多个关键字组成,则无法通过虚拟的 “C”包直接访问( 比如C语言的` unsigned short ` 不能直接通过` C.unsigned short ` 访问) 。但是,在` <stdint.h> ` 中通过使用C语言的` typedef ` 关键字将` unsigned short ` 重新定义为` uint16_t ` 这样一个单词的类型后 ,我们就可以通过` C.uint16_t ` 访问原来的` unsigned short ` 类型了。对于比较复杂的C语言类型,推荐使用` typedef ` 关键字提供一个规则的类型命名 ,这样更利于在CGO中访问。
61
61
62
62
## Go 字符串和切片
63
63
64
- 在CGO生成的` _cgo_export.h ` 头文件中还会为Go语言特有的字符串 、切片、字典、接口和管道等特有的数据类型生成对应的C语言类型:
64
+ 在CGO生成的` _cgo_export.h ` 头文件中还会为Go语言的字符串 、切片、字典、接口和管道等特有的数据类型生成对应的C语言类型:
65
65
66
66
``` c
67
67
typedef struct { const char * p; GoInt n; } GoString;
@@ -71,7 +71,7 @@ typedef struct { void *t; void *v; } GoInterface;
71
71
typedef struct { void * data; GoInt len; GoInt cap; } GoSlice;
72
72
```
73
73
74
- 不过需要注意的是,其中只有Go语言的字符串和切片在CGO中有一定的使用价值,因为字符串和切片可以在Go调用C语言函数时马上使用。而其它的类型在C语言环境并无使用的价值,因为CGO并未针对这些类型提供相关的辅助函数,而且因为Go语言特有的内存模型的原因导致我们无法保持这些由Go语言管理的内存指针 。
74
+ 不过需要注意的是,其中只有字符串和切片在CGO中有一定的使用价值,因为此二者可以在Go调用C语言函数时马上使用;而CGO并未针对其他的类型提供相关的辅助函数,且Go语言特有的内存模型导致我们无法保持这些由Go语言管理的内存指针,所以它们C语言环境并无使用的价值 。
75
75
76
76
在导出的C语言函数中我们可以直接使用Go字符串和切片。假设有以下两个导出函数:
77
77
@@ -90,7 +90,7 @@ extern void helloString(GoString p0);
90
90
extern void helloSlice(GoSlice p0);
91
91
```
92
92
93
- 不过需要注意的是,如果使用了GoString类型会对 `_cgo_export.h`头文件产生依赖,而这个头文件是动态输出的。更严谨的做法是为C语言函数接口定义严格的头文件,然后基于稳定的头文件实现代码。
93
+ 不过需要注意的是,如果使用了GoString类型则会对 `_cgo_export.h`头文件产生依赖,而这个头文件是动态输出的。更严谨的做法是为C语言函数接口定义严格的头文件,然后基于稳定的头文件实现代码。
94
94
95
95
## 结构体、联合、枚举类型
96
96
@@ -115,7 +115,7 @@ func main() {
115
115
}
116
116
```
117
117
118
- 如果结构体的成员名字中有的是Go语言的关键字 ,可以通过在成员名开头添加下划线来访问:
118
+ 如果结构体的成员名字中碰巧是Go语言的关键字 ,可以通过在成员名开头添加下划线来访问:
119
119
120
120
``` go
121
121
/*
@@ -132,7 +132,7 @@ func main() {
132
132
}
133
133
```
134
134
135
- 但是如果有2个成员:一个是以Go语言关键字命名,另一个刚好是以下划线和Go语言关键字命名命名,那么以Go语言关键字命名将无法访问 (被屏蔽):
135
+ 但是如果有2个成员:一个是以Go语言关键字命名,另一个刚好是以下划线和Go语言关键字命名,那么以Go语言关键字命名的成员将无法访问 (被屏蔽):
136
136
137
137
``` go
138
138
/*
@@ -150,7 +150,7 @@ func main() {
150
150
}
151
151
```
152
152
153
- C语言结构体中位字段对应的成员无法在Go语言中访问,如果需要操作位字段成员,需要通过在C语言中定义辅助函数来完成。对应零长数组的成员,无法在Go语言中直接访问数组的元素。其中零长的数组成员所在位置的偏移量依然可以通过 ` unsafe.Offsetof(a.arr) ` 来访问。
153
+ C语言结构体中位字段对应的成员无法在Go语言中访问,如果需要操作位字段成员,需要通过在C语言中定义辅助函数来完成。对应零长数组的成员,无法在Go语言中直接访问数组的元素,但其中零长的数组成员所在位置的偏移量依然可以通过 ` unsafe.Offsetof(a.arr) ` 来访问。
154
154
155
155
``` go
156
156
/*
@@ -199,7 +199,7 @@ func main() {
199
199
}
200
200
```
201
201
202
- 如果需要操作C语言的联合类型变量一般有三种方法:第一种是在C语言中定义辅助函数来完成 ;第二种是通过Go语言的"encoding/binary"手工解码成员,但是需要主要大端小端问题 ;第三种是使用` unsafe ` 包强制转型为对应类型后访问, 这是性能最好的方式。下面是通过 ` unsafe ` 包访问联合类型成员的方式:
202
+ 如果需要操作C语言的联合类型变量,一般有三种方法:第一种是在C语言中定义辅助函数 ;第二种是通过Go语言的"encoding/binary"手工解码成员(需要注意大端小端问题) ;第三种是使用` unsafe ` 包强制转型为对应类型( 这是性能最好的方式)。下面展示通过 ` unsafe ` 包访问联合类型成员的方式:
203
203
204
204
``` go
205
205
/*
@@ -246,7 +246,7 @@ func main() {
246
246
247
247
## 数组、字符串和切片
248
248
249
- 在C语言中,数组名其实对应一个指针 ,指向特定类型特定长度的一段内存,但是这个指针不能被修改;当把数组名传递给一个函数时,实际上传递的是数组第一个元素的地址。为了讨论方便,我们将一段特定长度的内存统称为数组。C语言的字符串是一个char类型的数组,字符串的长度需要根据表示结尾的NULL字符的位置确定。C语言中没有切片类型。
249
+ 在C语言中,数组名其实对应于一个指针 ,指向特定类型特定长度的一段内存,但是这个指针不能被修改;当把数组名传递给一个函数时,实际上传递的是数组第一个元素的地址。为了讨论方便,我们将一段特定长度的内存统称为数组。C语言的字符串是一个char类型的数组,字符串的长度需要根据表示结尾的NULL字符的位置确定。C语言中没有切片类型。
250
250
251
251
在Go语言中,数组是一种值类型,而且数组的长度是数组类型的一个部分。Go语言字符串对应一段长度确定的只读byte类型的内存。Go语言的切片则是一个简化版的动态数组。
252
252
@@ -280,7 +280,7 @@ func C.GoStringN(*C.char, C.int) string
280
280
func C .GoBytes (unsafe.Pointer , C .int ) []byte
281
281
```
282
282
283
- 其中` C.CString ` 针对输入的Go字符串,克隆一个C语言格式的字符串;返回的字符串由C语言的` malloc ` 函数分配,不需要时可以通过C语言的 ` free ` 函数释放。` C.CBytes ` 函数的功能和` C.CString ` 类似,用于将输入的Go语言字节切片克隆一个C语言版本的字节数组,返回的C语言数组不需要时可以通过 ` free ` 函数释放 。` C.GoString ` 用于将以NULL结尾的C语言字符串克隆一个Go语言字符串 。` C.GoStringN ` 用于将一个字符数组克隆一个Go语言字符串 。` C.GoBytes ` 用于将C语言数组 ,克隆一个Go语言字节切片。
283
+ 其中` C.CString ` 针对输入的Go字符串,克隆一个C语言格式的字符串;返回的字符串由C语言的` malloc ` 函数分配,不使用时需要通过C语言的 ` free ` 函数释放。` C.CBytes ` 函数的功能和` C.CString ` 类似,用于从输入的Go语言字节切片克隆一个C语言版本的字节数组,同样返回的数组需要在合适的时候释放 。` C.GoString ` 用于将从NULL结尾的C语言字符串克隆一个Go语言字符串 。` C.GoStringN ` 是另一个字符数组克隆函数 。` C.GoBytes ` 用于从C语言数组 ,克隆一个Go语言字节切片。
284
284
285
285
该组辅助函数都是以克隆的方式运行。当Go语言字符串和切片向C语言转换时,克隆的内存由C语言的` malloc ` 函数分配,最终可以通过` free ` 函数释放。当C语言字符串或数组向Go语言转换时,克隆的内存由Go语言分配管理。通过该组转换函数,转换前和转换后的内存依然在各自的语言环境中,它们并没有跨越Go语言和C语言。克隆方式实现转换的优点是接口和内存管理都很简单,缺点是克隆需要分配新的内存和复制操作都会导致额外的开销。
286
286
@@ -351,7 +351,7 @@ typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
351
351
352
352
## 指针和切片
353
353
354
- C语言和Go语言指针的转换可以看做是两中不同类型的指针之间的转换 。在Go语言中我们无法在不同类型之间做转换,不同类型的指针也是由此限制。但是任意类型的指针均可以和 ` unsafe.Pointer ` 相互转换,我们可以以 ` unsafe.Pointer ` 作为中间桥接类型实现不同类型指针之间的转换。
354
+ C语言和Go语言指针的转换可以看做是两种不同类型的指针之间的转换 。在Go语言中我们无法在不同类型之间做转换,因此不同类型的指针之间也存在此限制。由于任意类型的指针均可和 ` unsafe.Pointer ` 相互转换,所以我们可以以 ` unsafe.Pointer ` 作为中间桥接类型实现不同类型指针之间的转换。
355
355
356
356
``` go
357
357
var p *X
@@ -375,6 +375,6 @@ pHdr.Len = qHdr.Len * unsafe.Sizeof(q[0]) / unsafe.Sizeof(p[0])
375
375
pHdr.Cap = qHdr.Cap * unsafe.Sizeof (q[0 ]) / unsafe.Sizeof (p[0 ])
376
376
```
377
377
378
- 如果X和Y类型的大小不同,需要重新即使Len和Cap属性 。需要注意的是,如果X或Y是空类型,上述代码中可能导致除0的问题 ,实际代码需要根据情况酌情处理。
378
+ 如果X和Y类型的大小不同,需要重新设置Len和Cap属性 。需要注意的是,如果X或Y是空类型,上述代码中可能导致除0错误 ,实际代码需要根据情况酌情处理。
379
379
380
380
针对CGO中常用的功能,作者封装了 "github.com/chai2010/cgo" 包,提供基本的转换功能,具体的细节可以参考实现代码。
0 commit comments