基本使用

时候我们需要使用golang调用一些c的类库,因为使用golang重复实现一遍比较耗时,一些成熟的功能直接调用更好。当然前提是要先安装该c库。CGO可以直接用C的代码,或者C的静态库,或者动态库,当然C++也是可以的。

golang中的CGO特性,能够创建调用C代码的Go包。

package main

import "C"

func main() {

}

然后编译时候需要指定CGO_ENABLED=1,当然,默认值就是1,因此不用指定

> go env |grep CGO
GCCGO="gccgo"
CGO_ENABLED="1"
CGO_CFLAGS="-O2 -g"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-O2 -g"
CGO_FFLAGS="-O2 -g"
CGO_LDFLAGS="-O2 -g"

默认的C编译器gcc

一旦关闭就会影响 CGO 编译需要特别留意,交叉编译时会默认关闭 CGO。

package main

/*

#include <stdio.h>
#include <stdlib.h>

*/
import "C"

func main() {
	s := C.CString("hello world.")
	C.puts(s)
    C.free(unsafe.Pointer(s))
}
> go run main.go
hello world.

注意,注释要紧挨着import "C",否则就是普通的注释,而不是指令

C.puts(s)是调用stdio.h中的puts()函数,但是,需要先将go字符串转换成c字符串,所以C.CString()是由伪包C提供的函数。

cgo当前引用的C语言符号都放到了虚拟的C包中,同时当前依赖的其它Go语言内部可能也通过cgo引入了相似的虚拟C包,但是不同的Go语言引入的虚拟的C包之间的类型是不能通用的。这个约束对于要自己构造一些cgo辅助函数时有可能会造成一点的影响

import "C"语句前的注释可以通过#cgo语句设置编译阶段链接阶段相关参数编译阶段的参数主要用于定义相关宏和指定头文件检索路径链接阶段的参数主要是指定文件检索路径和要链接的库文件

// #cgo CFLAGS: -DPNG_DEBUG=1 -I./include
// #cgo LDFLAGS: -L/usr/local/lib -lpng
// #include <png.h>
import "C"
基本数据类型

golang 和 C 的基本数值类型转换对照表如下

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

注意 C 中的整形比如 int 在标准中是没有定义具体字长的,但一般默认认为是 4 字节对应 CGO 类型中 C.int 则明确定义了字长是 4 ,但 golang 中的 int 字长则是 8 ,因此对应的 golang 类型不是 int 而是 int32 。为了避免误用,C 代码最好使用 C99 标准数值类型对应转换关系如下

C语言类型 CGO类型 Go语言类型
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

C类型是使用在C代码中的,CGO类型和GO类型都是用在GO代码中的,CGO类型是在GO代码中GO类型转换成C类型然后传递给C函数使用。然后在GO代码中C函数的返回值也是CGO类型的。

package main

/*

#include <stdint.h>
static int32_t add(int32_t a, int32_t b) {
	return a + b;
}

*/
import "C"
import "fmt"

func main() {
	var a, b int32 = 1, 2
	var c int32 = int32(C.add(C.int32_t(a), C.int32_t(b)))
	fmt.Println(c)
}
字符串

c语言没有字符串类型,使用char数组代替,于是CGO提供了用于转换 Go 和 C 类型的字符串的函数,都是通过复制数据实现

// Go string to C string
// The C string is allocated in the C heap using malloc.
// It is the caller's responsibility to arrange for it to be
// freed, such as by calling C.free (be sure to include stdlib.h
// if C.free is needed).
func C.CString(string) *C.char

// Go []byte slice to C array
// The C array is allocated in the C heap using malloc.
// It is the caller's responsibility to arrange for it to be
// freed, such as by calling C.free (be sure to include stdlib.h
// if C.free is needed).
func C.CBytes([]byte) unsafe.Pointer

// C string to Go string
func C.GoString(*C.char) string

// C data with explicit length to Go string
func C.GoStringN(*C.char, C.int) string

// C data with explicit length to Go []byte
func C.GoBytes(unsafe.Pointer, C.int) []byte

注意,如果程序常驻内存的话需要使用C.free(unsafe.Pointer(s))来释放由C.CString()创建的内存。

package main

/*

#include <stdio.h>
#include <stdlib.h>

static void SayHello(const char* s) {
	puts(s);
}
*/
import "C"
import "unsafe"

func main() {
	s := C.CString("hello world.")
	C.SayHello(s)
	C.free(unsafe.Pointer(s))
}

我们可以SayHello函数放到当前目录下的一个C语言源文件中。因为是编写独立的C文件中,为了允许外部引用,所以需要去掉函数的static修饰符,因为static函数只能在声明它的文件中可见,其他文件不能引用该函数。hello.c

#include <stdio.h>

void SayHello(const char* s) {
	puts(s);
}

然后在CGO部分声明SayHello函数

package main

/*
#include "hello.c"
*/
import "C"

func main() {
	C.SayHello(C.CString("hello world."))
}

或者模块化

hello.h

void SayHello(const char* s);

hello.c

#include "hello.h"
#include <stdio.h>

void SayHello(const char* s) {
	puts(s);
}

可以使用c++来实现hello.h接口

#include <iostream>

extern "C" {
	#include "hello.h"
}

void SayHello(const char* s) {
	std::cout << s;
}

或者golang实现

hello.h

void SayHello(char* s);

main.go

//export SayHello
func SayHello(s *C.char) {
	fmt.Print(C.GoString(s))
}

CGO不仅仅用于Go语言中调用C语言函数,还可以用于导出Go语言函数给C语言函数调用。我们通过CGO的//export SayHello指令将Go语言实现的函数SayHello导出为C语言函数。为了适配CGO导出的C语言函数,我们禁止了在函数的声明语句中的const修饰符

或者简化一下

package main

//void SayHello(char* s);
import "C"

import (
	"fmt"
)

func main() {
	C.SayHello(C.CString("Hello, Worldn"))
}

//export SayHello
func SayHello(s *C.char) {
	fmt.Print(C.GoString(s))
}

在Go1.10中CGO新增加了一个_GoString_定义的C语言类型,用来表示Go语言字符串。下面是改进后的代码:

package main

//void SayHello(_GoString_ s);
import "C"

import (
	"fmt"
)

func main() {
	C.SayHello("Hello, World")
}

//export SayHello
func SayHello(s string) {
	fmt.Print(s)
}

切片

golang 中切片用起来有点像 C 中的数组,但实际的内存模型还是有点区别的。C 中的数组就是一段连续的内存,数组的值实际上就是这段内存的首地址。golang 切片包含caplen

由于底层内存模型的差异,不能直接将 golang 切片指针传给 C 函数调用,而是需要存储切片数据的内部缓冲区的首地址切片长度取出传递,然后在C代码里面重新构建数组

package main

/*
#include <stdint.h>

static void fill_255(char* buf, int32_t len) {
	int32_t i;
	for (i = 0; i < len; i++) {
		buf[i] = 255;
	}
}
*/
import "C"
import (
	"fmt"
	"unsafe"
)

func main() {
	b := make([]byte, 5)
	fmt.Println(b) // [0 0 0 0 0]
	C.fill_255((*C.char)(unsafe.Pointer(&amp;b[0])), C.int32_t(len(b)))
	fmt.Println(b) // [255 255 255 255 255]
}
C函数的返回

对于有返回值的C函数,我们可以正常获取返回值。

/*
static int div(int a, int b) {
	return a/b;
}
*/
import "C"
import "fmt"

func main() {
	v := C.div(6, 3)
	fmt.Println(v) // 2
}

上面的div函数实现了一个整数除法的运算然后通过返回返回除法的结果

不过如果对于除数为0的情形并没有特殊处理。如果希望在除数为0的时候返回一个错误,其余时候返回正常的结果。因为C语言不支持返回多个结果,因此<errno.h>标准库提供了一个errno宏用于返回错误状态我们可以近似地将errno看着一个线程安全全局变量,可以用于记录最近一次错误状态码。

改进后的div函数实现如下

/*
#include <errno.h>

static int div(int a, int b) {
	if(b == 0) {
		errno = EINVAL;
		return 0;
	}
	return a/b;
}
*/
import "C"
import "fmt"

func main() {
	v0, err0 := C.div(2, 1)
	fmt.Println(v0, err0) // 2 <nil>

	v1, err1 := C.div(1, 0)
	fmt.Println(v1, err1) // 0 The device does not recognize the command.
}

CGO也针对<errno.h>标准库的errno宏做的特殊支持:在CGO调用C函数时如果有两个返回值,那么第二个返回值将对应errno错误状态

void函数的返回值

C语言函数还有一种没有返回值类型的函数,用void表示返回值类型。一般情况下,我们无法获取void类型函数的返回值,因为没有返回值可以获取。前面例子中提到,cgo对errno做了特殊处理,可以通过第二个返回值来获取C语言的错误状态。对于void类型函数,这个特性依然有效。

//static void noreturn() {}
import "C"
import "fmt"

func main() {
	_, err := C.noreturn()
	fmt.Println(err)
}

我们也可以尝试获取第一个返回值,它对应的是C语言的void对应的Go语言类型:

//static void noreturn() {}
import "C"
import "fmt"

func main() {
	v, _ := C.noreturn()
	fmt.Printf("%#v", v) // main._Ctype_void{}
}

关于cgo的内部实现机制可以阅读 https://www.cntofu.com/book/73/ch2-cgo/ch2-05-internal.md

链接静态库和动态https://www.cntofu.com/book/73/ch2-cgo/ch2-09-static-shared-lib.md

原文地址:https://blog.csdn.net/raoxiaoya/article/details/130579103

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_23740.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注