diff --git a/ch2-cgo/ch2-02-cgo-types.md b/ch2-cgo/ch2-02-cgo-types.md index 84dfc2b..ee55f4d 100644 --- a/ch2-cgo/ch2-02-cgo-types.md +++ b/ch2-cgo/ch2-02-cgo-types.md @@ -356,17 +356,23 @@ typedef struct { void *data; GoInt len; GoInt cap; } GoSlice; 在C语言中可以通过`GoString`和`GoSlice`来访问Go语言的字符串和切片。如果是Go语言中数组类型,可以将数组转为切片后再行转换。如果字符串或切片对应的底层内存空间由Go语言的运行时管理,那么在C语言中不能长时间保存Go内存对象。 + 关于CGO内存模型的细节在稍后章节中会详细讨论。 -## 指针和切片 +## 指针间的转换 -C语言和Go语言指针的转换可以看做是两种不同类型的指针之间的转换。在Go语言中我们无法在不同类型之间做转换,因此不同类型的指针之间也存在此限制。由于任意类型的指针均可和`unsafe.Pointer`相互转换,所以我们可以以`unsafe.Pointer`作为中间桥接类型实现不同类型指针之间的转换。 +在C语言中,不同类型的指针是可以显式或隐式转换的,如果是隐式只是会在编译时给出一些警告信息。但是Go语言对于不同类型的转换非常严格,任何C语言中可能出现的警告信息在Go语言中都可能是错误!指针是C语言的灵魂,指针间的自由转换也是cgo代码中经常要解决的第一个重要的问题。 + +在Go语言中两个指针的类型完全一致则不需要转换可以直接通用。如果一个指针类型是用type命令在另一个指针类型基础之上构建的,换言之两个指针底层是相同完全结构的指针,那么我我们可以通过直接强制转换语法进行指针间的转换。但是cgo经常要面对的是2个完全不同类型的指针间的转换,原则上这种操作在纯Go语言代码是严格禁止的。 + +cgo存在的一个目的就是打破Go语言的禁制,恢复C语言应有的指针的自由转换和指针运算。以下代码演示了如何将X类型的指针转化为Y类型的指针: ```go var p *X @@ -376,7 +382,31 @@ q = (*Y)(unsafe.Pointer(p)) // *X => *Y p = (*X)(unsafe.Pointer(q)) // *Y => *X ``` -再结合`reflect.SliceHeader`类型,我们可以实现`[]X`和`[]Y`类型的切片转换: +为了实现X类型指针到Y类型指针的转换,我们需要借助`unsafe.Pointer`作为中间桥接类型实现不同类型指针之间的转换。`unsafe.Pointer`指针类型类似C语言中的`void*`类型的指针。 + +下面是指针间的转换流程的示意图: + +![](../images/ch2-x-ptr-to-y-ptr.uml.png) + +任何类型的指针都可以通过强制转换为`unsafe.Pointer`指针类型去掉原有的类型信息,然后再重新赋予新的指针类型而达到指针间的转换的目的。 + +## 数值和指针的转换 + +不同类型指针间的转换看似复杂,但是在cgo中已经算是比较简单的了。在C语言中经常遇到用普通数值表示指针的场景,也就是说如何实现数值和指针的转换也是cgo需要面对的一个问题。 + +为了严格控制指针的使用,Go语言禁止将数值类型直接转为指针类型!不过,Go语言针对`unsafe.Pointr`指针类型特别定义了一个uintptr类型。我们可以uintptr为中介,实现数值类型到`unsafe.Pointr`指针类型到转换。再结合前面提到的方法,就可以实现数值和指针的转换了。 + +下面流程图演示了如何实现int32类型到C语言的`char*`字符串指针类型的相互转换: + +![](../images/ch2-int32-to-char-ptr.uml.png) + +转换分为几个阶段,在每个阶段实现一个小目标:首先是int32到uintptr类型,然后是uintptr到`unsafe.Pointr`指针类型,最后是`unsafe.Pointr`指针类型到`*C.char`类型。 + +## 切片间的转换 + +在C语言中数组也一种指针,因此两个不同类型数组之间到转换和指针间转换基本类似。但是在Go语言中,数组或数组对应到切片都不再是指针类型,因为我们也就无法直接实现不同类型到切片之间的转换。 + +不过Go语言的reflect包提供了切片类型到底层结构,再结合前面讨论到不同类型之间到指针转换技术就可以实现`[]X`和`[]Y`类型的切片转换: ```go var p []X @@ -390,6 +420,10 @@ pHdr.Len = qHdr.Len * unsafe.Sizeof(q[0]) / unsafe.Sizeof(p[0]) pHdr.Cap = qHdr.Cap * unsafe.Sizeof(q[0]) / unsafe.Sizeof(p[0]) ``` -如果X和Y类型的大小不同,需要重新设置Len和Cap属性。需要注意的是,如果X或Y是空类型,上述代码中可能导致除0错误,实际代码需要根据情况酌情处理。 +不同切片类型之间转换到思路是先为构造一个空的目标切片,然后用原有的切片底层数据填充目标切片。如果X和Y类型的大小不同,需要重新设置Len和Cap属性。需要注意的是,如果X或Y是空类型,上述代码中可能导致除0错误,实际代码需要根据情况酌情处理。 + +下面演示了切片间的转换的具体流程: + +![](../images/ch2-x-slice-to-y-slice.uml.png) 针对CGO中常用的功能,作者封装了 "github.com/chai2010/cgo" 包,提供基本的转换功能,具体的细节可以参考实现代码。