概述
CGO使用
文章目录
- CGO使用
- 1、什么是CGO
- 2、CGO环境基础
- 3、开始使用
- 4、类型转换
- 4.1 数值类型
- 4.2 字符串和切片,结构体、联合、枚举
- 5、静态库的使用
- 5.1. 静态库制作
- 5.2 在golang程序中引入静态库
- 6、C回调Go函数
- 7、更多介绍
1、什么是CGO
CGO是实现Go与C互操作的方式,它是Go语言自带的一个工具来支持C语言函数调用,包括Go调C和C调Go两个过程,C++ 的接口可以用 C 包装一下提供给 golang 调用,被调用的 C 代码可以直接以源代码形式提供或者打包静态库或动态库在编译时链接,推荐使用静态库的方式,这样方便代码隔离,编译的二进制也没有动态库依赖方便发布,其中Go调C的过程比较简单。对于一个在C中定义的函数add,在Go中调用时需要显式的使用C.add调用。其中C是在程序中引入的一个伪包。
2、CGO环境基础
要使用CGO特性,需要安装C/C++构建工具链,在macOS和Linux下是要安装GCC,在windows下是需要安装MinGW工具。同时需要保证环境变量CGO_ENABLED
被设置为1,这表示CGO是被启用的状态。在本地构建时CGO_ENABLED
默认是启用的,当交叉构建时CGO默认是禁止的。比如要交叉构建ARM环境运行的Go程序,需要手工设置好C/C++交叉构建的工具链,同时开启CGO_ENABLED
环境变量。然后通过import "C"
语句启用CGO特性。
3、开始使用
先看一个最简单的通过cgo,使golang调用c例子:
package main
/*
#include <stdio.h>
#include <stdlib.h>
static void Hello(const char* s) {
puts(s);
}
static double Add(int a,double b) {
return a+b;
}
*/
import "C"
import "fmt"
func main() {
msg:=C.CString("Hello, Worldn")
defer C.free(unsafe.Pointer(msg)) //释放内存
C.Hello(msg)
fmt.Println(C.Add(C.int(3),C.double(12.5)))
}
通过以上代码,可以看到C代码可以直接在golang文件中直接书写,但必须注意代码位置要在import "C"
改伪包头部定义,并以全文注释/**/
或以单行注释//
定义C代码范围。import "C"
:启用CGO特性,go build
:命令会在编译和链接阶段启动gcc编译器<stdio.h>
:C语言的头文件,定义了三个变量类型、一些宏和各种函数来执行输入和输出。
注意: /* */
注释与import "C"
之间不得有换行;import "C"
导入语句需要单独一行,不能与其他包一同import。
错误信息:could not determine kind of name for
使用C.CString
创建的C语言字符串没有释放内存会导致内存泄漏,释放内存 defer C.free(unsafe.Pointer(msg))
,使用C.free
释放必须引入标准库#include <stdlib.h>
,否则将无法使用C.free
函数
4、类型转换
先看第一节示例中Add
函数需要两个参数int、double,以及返回一个double类型,在golang中调用该函数时由于类型互通存在差异,cgo中定义了二语言类型之间的转换规则:
4.1 数值类型
golang 的基本数值类型内存模型和 C 语言一样,就是连续的几个字节(1 / 2 / 4 / 8 字节),因此传递数值类型时可以直接将 golang 的基本数值类型转换成对应的 CGO 类型然后传递给 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
、short
等类型没有明确定义内存大小,但是在CGO中它们的内存大小是确定的。在CGO中,C语言的int
和long
类型都是对应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 |
size_t
类型可以当作Go语言uint
无符号整数类型对待;
第一节C.Add(C.int(3),C.double(12.5))
调用了Add
函数,它的返回值根据上表数据类型对应,并非直接是goalng中得float64,而是cgo中C.double
,因此在做后续返回值操作需要注意类型问题。
4.2 字符串和切片,结构体、联合、枚举
参考:更多类型介绍
5、静态库的使用
5.1. 静态库制作
我们首先基于第一节代码从新做一次封装,将第一节代码打包生成一个简单的静态库,
静态库使用需要两个标准文件即可,以.h和.a为后缀,其中 .h文件为头文件,里边主要定义了库向外暴露的接口,.a 文件就是主要的静态库文件,包含函数主要实现以及其他依赖项,在生成过程中需要生成一个中间文件,以 .o后缀。操作过程:
1、定义好需要暴露的接口文件_ num.h_
#include <stdlib.h>
double Add(int a,double b);
void Hello(const char* s);
2、定义函数实现文件 num.c
#include "num.h"
#include "stdio.h"
double Add(int a,double b){
return a+b;
}
void Hello(const char* s){
puts(s);
}
3、因为CGO使用的是GCC命令来编译和链接C和Go桥接的代码。因此静态库也必须是GCC兼容的格式。通过以下命令可以生成一个叫libnum.a的静态库:
gcc -c -o num.o num.c
ar rcs libnum.a num.o
生成后当前项目文件结构如下:
![image.png](https://img-blog.csdnimg.cn/img_convert/80bb6c8c1333ef3259592609985e9ec9.png#align=left&display=inline&height=135&margin=[object Object]&name=image.png&originHeight=135&originWidth=419&size=5700&status=done&style=none&width=419)
后面就可以直接进行链接调用静态库!
5.2 在golang程序中引入静态库
先看一段示例:
package main
//#cgo CFLAGS: -I${SRCDIR}/num
//#cgo LDFLAGS: -L${SRCDIR} -lnum
//#include "num.h"
import "C"
import (
"fmt"
"unsafe"
)
func main() {
msg := C.CString("Hello, Worldn")
defer C.free(unsafe.Pointer(msg)) //释放内存
C.Hello(msg)
fmt.Println(C.Add(C.int(3), C.double(12.5)))
}
发现在伪包头部多了很多参数,也非C代码,它就是注明静态库链接位置与编译参数的声明,CFLAGS通过-I./num
将num库对应头文件所在的目录加入头文件检索路径。LDFLAGS通过-L${SRCDIR}/num
将编译后num静态库所在目录加为链接库检索路径,-lnum
表示链接libnum.a静态库。在链接部分的检索路径不能使用相对路径(C/C++代码的链接程序所限制),${SRCDIR}
变量将源文件对应的当前目录路径展开为绝对路径。
6、C回调Go函数
我们在第五节编写的num.h
文件中新增一个定义好的回调接口:
typedef double(*cut) (int a,double b);
void Call(cut callback);
在num.c文件新增该接口的实现:
void Call(cut callback){
double n=callback(1,1.5);
}
重新生成一个库,下面我们在golang程序中的实现如下:
package main
//#cgo CFLAGS: -I${SRCDIR}/num
//#cgo LDFLAGS: -L${SRCDIR} -lnum
//#include "num.h"
//extern double Cut(int a,double b); //以C代码形式提供
import "C"
import (
"fmt"
"unsafe"
)
func main() {
msg := C.CString("Hello, Worldn")
defer C.free(unsafe.Pointer(msg)) //释放内存
C.Hello(msg)
fmt.Println(C.Add(C.int(3), C.double(12.5)))
C.Call(C.cut(C.Cut))
}
//export Cut
func Cut(a C.int, b C.double) C.double {
fmt.Println("回调....")
return C.double(a) - b
}
关于C引用golang代码,在这段代码片中有几个关键地方:
//export Cut
func Cut(a C.int, b C.double) C.double {
export
导出go函数,在C引用时,Cut
函数会被认为是一个全局可访问的C函数,在这里运用了export
关键字后,其对应的函数不论在go当中访问级别是公有,私有,它在C中都会被当做一个全局访问的C函数。
注意 golang实现了回调函数的同时,一定要注意其参数以及返回值类型是否与在 .h 头文件中定义好的接口形式一致,包括类型与顺序;要在伪包头部将golang暴露的函数以C函数形式声明,否则将出现无法识别该函数的错误!could not determine kind of name for C.Cut
7、更多介绍
参考:官方CGO文档
CGO编程
CGOWiki
最后
以上就是会撒娇台灯为你收集整理的CGO使用CGO使用1、什么是CGO2、CGO环境基础3、开始使用4、类型转换5、静态库的使用6、C回调Go函数7、更多介绍的全部内容,希望文章能够帮你解决CGO使用CGO使用1、什么是CGO2、CGO环境基础3、开始使用4、类型转换5、静态库的使用6、C回调Go函数7、更多介绍所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复