Go语言高级编程

324课时
1.4K学过
8分

2. 第2章 CGO编程

引言

2.1 快速入门

2.1.1 最简CGO程序

2.1.2 基于C标准库函数输出字符串

2.1.3 使用自己的C函数

2.1.4 C代码的模块化

2.1.5 用Go重新实现C函数

2.1.6 面向C接口的Go编程

2.2 CGO基础

2.2.1 import "C"语句

2.2.2 #cgo语句

2.2.3 build tag 条件编译

2.3 类型转换

2.3.1 数值类型

2.3.2 Go 字符串和切片

2.3.3 结构体、联合、枚举类型

2.3.4 数组、字符串和切片

2.3.5 指针间的转换

2.3.6 数值和指针的转换

2.3.7 切片间的转换

2.4 函数调用

2.4.1 Go调用C函数

2.4.2 C函数的返回值

2.4.3 void函数的返回值

2.4.4 C调用Go导出函数

2.5 内部机制

2.5.1 CGO生成的中间文件

2.5.2 Go调用C函数

2.5.3 C调用Go函数

2.6 实战: 封装qsort

2.6.1 认识qsort函数

2.6.2 将qsort函数从Go包导出

2.6.3 改进:闭包函数作为比较函数

2.6.4 改进:消除用户对unsafe包的依赖

2.7 CGO内存模型

2.7.1 Go访问C内存

2.7.2 C临时访问传入的Go内存

2.7.3 C长期持有Go指针对象

2.7.4 导出C函数不能返回Go内存

2.8 C++ 类包装

2.8.1 C++ 类到 Go 语言对象

2.8.1.1 准备一个 C++ 类

2.8.1.2 用纯C函数接口封装 C++ 类

2.8.1.3 将纯C接口函数转为Go函数

2.8.1.4 包装为Go对象

2.8.2 Go 语言对象到 C++ 类

2.8.2.1 构造一个Go对象

2.8.2.2 导出C接口

2.8.2.3 封装C++对象

2.8.2.4 封装C++对象改进

2.8.3 彻底解放C++的this指针

2.9 静态库和动态库

2.9.1 使用C静态库

2.9.2 使用C动态库

2.9.3 导出C静态库

2.9.4 导出C动态库

2.9.5 导出非main包的函数

2.10 编译和链接参数

2.10.1 编译参数:CFLAGS/CPPFLAGS/CXXFLAGS

2.10.2 链接参数:LDFLAGS

2.10.3 pkg-config

2.10.4 go get 链

2.10.5 多个非main包中导出C函数

课程评价 (0)

请对课程作出评价:
0/300

学员评价

暂无精选评价
20分钟

2.6.2 将qsort函数从Go包导出

为了方便Go语言的非CGO用户使用qsort函数,我们需要将C语言的qsort函数包装为一个外部可以访问的Go函数。

用Go语言将qsort函数重新包装为qsort.Sort函数:

package qsort

//typedef int (*qsort_cmp_func_t)(const void* a, const void* b);
import "C"
import "unsafe"

func Sort(
	base unsafe.Pointer, num, size C.size_t,
	cmp C.qsort_cmp_func_t,
) {
	C.qsort(base, num, size, cmp)
}

因为Go语言的CGO语言不好直接表达C语言的函数类型,因此在C语言空间将比较函数类型重新定义为一个qsort_cmp_func_t类型。

虽然Sort函数已经导出了,但是对于qsort包之外的用户依然不能直接使用该函数——Sort函数的参数还包含了虚拟的C包提供的类型。 在CGO的内部机制一节中我们已经提过,虚拟的C包下的任何名称其实都会被映射为包内的私有名字。比如C.size_t会被展开为_Ctype_size_tC.qsort_cmp_func_t类型会被展开为_Ctype_qsort_cmp_func_t

被CGO处理后的Sort函数的类型如下:

func Sort(
	base unsafe.Pointer, num, size _Ctype_size_t,
	cmp _Ctype_qsort_cmp_func_t,
)

这样将会导致包外部用于无法构造_Ctype_size_t_Ctype_qsort_cmp_func_t类型的参数而无法使用Sort函数。因此,导出的Sort函数的参数和返回值要避免对虚拟C包的依赖。

重新调整Sort函数的参数类型和实现如下:

/*
#include <stdlib.h>

typedef int (*qsort_cmp_func_t)(const void* a, const void* b);
*/
import "C"
import "unsafe"

type CompareFunc C.qsort_cmp_func_t

func Sort(base unsafe.Pointer, num, size int, cmp CompareFunc) {
	C.qsort(base, C.size_t(num), C.size_t(size), C.qsort_cmp_func_t(cmp))
}

我们将虚拟C包中的类型通过Go语言类型代替,在内部调用C函数时重新转型为C函数需要的类型。因此外部用户将不再依赖qsort包内的虚拟C包。

以下代码展示的Sort函数的使用方式:

package main

//extern int go_qsort_compare(void* a, void* b);
import "C"

import (
	"fmt"
	"unsafe"

	qsort "."
)

//export go_qsort_compare
func go_qsort_compare(a, b unsafe.Pointer) C.int {
	pa, pb := (*C.int)(a), (*C.int)(b)
	return C.int(*pa - *pb)
}

func main() {
	values := []int32{42, 9, 101, 95, 27, 25}

	qsort.Sort(unsafe.Pointer(&values[0]),
		len(values), int(unsafe.Sizeof(values[0])),
		qsort.CompareFunc(C.go_qsort_compare),
	)
	fmt.Println(values)
}

为了使用Sort函数,我们需要将Go语言的切片取首地址、元素个数、元素大小等信息作为调用参数,同时还需要提供一个C语言规格的比较函数。 其中go_qsort_compare是用Go语言实现的,并导出到C语言空间的函数,用于qsort排序时的比较函数。

目前已经实现了对C语言的qsort初步包装,并且可以通过包的方式被其它用户使用。但是qsort.Sort函数已经有很多不便使用之处:用户要提供C语言的比较函数,这对许多Go语言用户是一个挑战。下一步我们将继续改进qsort函数的包装函数,尝试通过闭包函数代替C语言的比较函数。

消除用户对CGO代码的直接依赖。