基本使用
有时候我们需要使用golang去调用一些c的类库,因为使用golang重复实现一遍比较耗时,一些成熟的功能直接调用更好。当然前提是要先安装该c库。CGO可以直接用C的代码,或者C的静态库,或者动态库,当然C++也是可以的。
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"
一旦关闭就会影响 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"
基本数据类型
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);
}
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);
}
#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 切片还包含cap和len。
由于底层内存模型的差异,不能直接将 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(&b[0])), C.int32_t(len(b)))
fmt.Println(b) // [255 255 255 255 255]
}
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
看着一个线程安全的全局变量,可以用于记录最近一次错误的状态码。
/*
#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进行投诉反馈,一经查实,立即删除!