Cgo,详细介绍Go与C交互详细过程

文章目录

  • Cgo,详细介绍Go与C交互详细过程
    • 概念解释
    • 使用Cgo在Go中直接编写C代码
    • Cgo工作的基本过程
    • 基础类型转换
    • 关于字符串的两个特别的方法
    • 结构体
    • 类型转换方法
    • 函数指针和回调
      • go调用C的函数指针
      • C回调go的函数
    • 关于C数据作为参数
    • 可变参数
    • C引用Go
    • 动态库和静态库

概念解释

  • Cgo是Go语言提供的一个工具,它本身是一个可执行文件,当我们调用go build指令编译项目的时候,Cgo会在需要处理C代码的时候被自动使用
  • Cgo依赖Gcc工作
  • Cgo本身可以被直接执行,并提供了一系列可选指令选项帮助程序员查找问题

使用Cgo在Go中直接编写C代码

package main
/*
#include <stdio.h>
void PrintHello()
{printf("hello world")
}
*/
import "C"func main() {C.PrintHello()
}

上面这段代码通过调用C标准库中的printf函数向标准输出输出hello world字符串

/*
#include <stdio.h>
void PrintHello()
{printf("hello world")
}
*/
  • 这段被注释的内容被称之为 “序言”,或是"序文"(preamble),可以在序言中直接编写任意的C代码,或引入标准库的头文件,或是要使用的库文件的头文件
  • import "C"其中的C并不是一个真正的go包,称为伪包,用来帮助Cgo识别C代码,需要注意的是在序文结束的后的 import “C” 必须紧跟在序言后面,不能有空行,否则会编译出错
  • 序言中声明的C函数在Go中进行调用的时候要用C.xxx的形式,所有引入的C函数,变量,以及类型,在使用的时候都要以大写的C.作为前缀
  • 所有的C类型都应该局限在使用了 import "C"的包中,避免暴露在包外

Cgo工作的基本过程

对上面的helloworld示例程序执行 go tool cgo main.go 会看到Cgo生成的一系列中间文件


打开main.cgo2.c可以找到上面的代码片段,首先cgo把我们定义在序言中的c代码完整的搬运到了这里,包括引入的头文件和我们定义的PrintHello函数

在文件的末尾,cgo为我们生成了新的函数 _cgo_bd85ba2d6721_Cfunc_PrintHello 并在这个函数中调用我们之前在序言中定义的PrintHello函数

打开_cgo_gotypes.go文件,这个文件属于main包,所以其中定义函数可以被 go 的 main 函数调用,可以看到在第32行,编译制导语句go:cgo_import_static 将上文中提到的 cgo 生成的c函数 _cgo_bd85ba2d6721_Cfunc_PrintHello 引入并和字节型变量__cgofn__cgo_bd85ba2d6721_Cfunc_PrintHello 对齐,并用一个新的变量 _cgo_bd85ba2d6721_Cfunc_PrintHello 保存这个字节型变量的地址,从而实现在Go中拿到C函数的地址,在37行定义了一个新的go函数 func _Cfunc_PrintHello,并在其中调用了之前通过字节变量拿到的C函数

再对 main.cgo1.go 文件进行考察,这是被cgo改写后的go文件,这个被改写的go文件是最终交给go编译器的文件,可以看出 import “C” 语句已经被移除,并引入了 unsafe 包, 在main函数中调用了在上文中提到的 _Cfunc_PrintHello 函数

总结一下:
针对我们这个例子,cgo首先扫描 import “C” 的文件,并将序言部分的C代码拷贝到一个C文件中,这个C文件应该会被gcc编译成 .o 等待后续链接操作,使用 nm _cgo.o 指令,可以看到其中有 PrintHello 函数的定义可以证实我们的猜测,cgo同时会生成一个中间的Go文件,使用编译制导指令在链接期获取C函数的地址,并使用一个新的go函数包装对C函数的调用,cgo会生成一个新的main文件,这个文件是真正被Go编译器处理的main文件,并将其中 import “C” 语句移除,并将以 C.xxx 形式调用的C函数替换为cgo自己生成的Go函数

基础类型转换

这个表展示了常见的数据类型在C和Go中名称

Go name C name
go name c name
C.char, C.schar signed char
C.uchar unsigned char
C.short, C.ushort unsigned short
C.int, C.uint unsigned int
C.long,C.ulong unsigned long
C.longlong long long
C.ulonglong unsigned long long
C.float, C.double, C.complexfloat complex float
C.complexdouble omplex double
unsafe.Pointer void*
__int128_t and __uint128_t [16]byte
C.struct_xxx struct
C.union_xxx union

关于字符串的两个特别的方法

可以在序言中声明以下两个特别的C方法

size_t _GoStringLen(_GoString_ s);
const char *_GoStringPtr(_GoString_ s);

他们的参数类型是_GoString_ s,第一个方法返回Go字符串的长度,第二个方法返回指向这个字符串的char*指针,下面为示例代码

package main
/*
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// 这两个函数要声明在序言中
size_t _GoStringLen(_GoString_ s);
const char *_GoStringPtr(_GoString_ s);
void PrintGoStringInfo(char* s, size_t count)
{// 注意,s尾部没有"\0", 在C中直接当字符串处理会出错char* buf = malloc((count + 1) * sizeof(char)); //memset(buf, 0, count + 1);memcpy(buf, s, count);printf("%s\n", buf);printf("sizeof goString: %ld\n", count);free(buf);
}
*/
import "C"func main() {str := "hello world"C.PrintGoStringInfo(C._GoStringPtr(str), C._GoStringLen(str))
}
  • 需要注意的是_GoStringPtr返回的char*尾部是不包含的\0的在C中直接当字符串处理会出错

  • 这两个函数仅可在序言中使用,不能在其他的C文件中使用,C代码绝不能通过_GoStringPtr返回的指针修改其指向的内容

注意:这两个函数可以很方便的将Go string转换为C的char*,如果使用_GoStringPtr传入一个临时的string到C中,在C中应拷贝一份副本到C内存中,尤其是在一些异步调用的过程中,从官方关于cgo的文档看来,这两个函数似乎并不保证传入的临时Go string类型不会被gc回收

结构体

  • C中定义的结构体字段名有可能和Go中的关键字冲突,这些发生冲突的字段会被自动加上下划线作为前缀,访问这种字段的时候要用这样的形式:x._type
  • 在C中的一些字段无法在Go中表达,如位域和未对其的结构,在Go的结构体中,这些字段会被忽略,但会在下一个字段之前或者结构体的结尾之前留下相应的空白空间

需要给结构体中的数组进行赋值可以用以下方法

/*
typedef struct {int a[32];
} STRU_A
// 须包含这两个库文件
#include <stdlib.h>
#include <stringlh>
*/
import "C"
name = "abcd"
struA := C.STRU_A{}
cName = C.CString(name)
defer C.free(unsafe.Pointer(cName))
C.memcpy(unsafe.Pointer(&struA.cName), unsafe.Pointer(cName), C.size_t(len(name)))

类型转换方法

// 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).
// 这个方法会在C的堆上分配内存,需要使用C.free释放,需要包含stdlib.h
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).
// 这里同样需要使用C.free释放内存
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.malloc并不是直接调用C中的malloc,而是调用了一个包装了一个Go的辅助函数,其包装了C库中的malloc,并保证永远不会返回nil。如果C的malloc表示用尽内存,这个辅助函数就会使程序崩溃,就像Go自身用尽内存发生崩溃,因为C的malloc不能失败,所以他没有返回errno的两个结果的形式

c中的sizeof并不能以C.sizeof的形式使用,而是应该用C.size_T的形式使用,T是C中的类型名

函数指针和回调

go调用C的函数指针

go不能直接调用C的函数指针,但可以调用C的函数,也可以持有C的函数指针,如果go想调用一个C的函数指针,可以将C的指针传入go中,go再将这个指针通过一个C接口送到C侧,然后由C侧执行并返回结果

package main
// typedef int (*intFunc) ();
//
// int
// bridge_int_func(intFunc f)
// {//      return f();
// }
//
// int fortytwo()
// {//      return 42;
// }
import "C"
import "fmt"func main() {f := C.intFunc(C.fortytwo)fmt.Println(int(C.bridge_int_func(f)))// Output: 42
}

C回调go的函数

C可以调用go中被//export标记的导出的函数

C文件

typedef void(*cbtype)();
void registerCallback(cbtype cb);

go文件

/*
// 在序言中声明一次,这是为了让cgo能够 “看到” 这个C函数,否则无法通过编译
void callbackFunc();
*/
import "C"func foo() {C.registerCallback(C.cbtype(C.callbackFunc))
}//export callbackFunc
func callbackFunc() {fmt.Println("go callback func")
}

关于C数据作为参数

在C中,向函数传入一个固定大小的数组作为参数,需要一个指向数组第一个元素的指针。C编译器知到这样的调用约定,并相应的调整调用方式。但在Go中并非如此,你必须明确的传入指向数组第一个元素的指针C.f(&C.x[0])
译者注:这里指的是C中数组名传入函数后变为指向首元素的指针

可变参数

调用可变参C函数是不被支持的,但可以通过使用C函数包装的方法来规避这个问题,如下

package main// #include <stdio.h>
// #include <stdlib.h>
//
// static void myprint(char* s) {//   printf("%s\n", s);
// }
import "C"
import "unsafe"func main() {cs := C.CString("Hello from stdio")C.myprint(cs)C.free(unsafe.Pointer(cs))
}

C引用Go

Go方法可以按照以下方法导出给C代码使用

//export MyFunction
func MyFunction(arg1, arg2 int, arg3 string) int64 {...}//export MyFunction2
func MyFunction2(arg1, arg2 int, arg3 string) (int64, *C.char) {...}

他们带C代码中以如下形式使用

extern GoInt64 MyFunction(int arg1, int arg2, GoString arg3);
extern struct MyFunction2_return MyFunction2(int arg1, int arg2, GoString arg3);

在生成的 _cogo_export.h 头文件,在序言以及所有拷贝自cgo导入的文件内容之后。有多返回值的函数会被映射为返回一个结构体
并不是所有的go类型都可以被映射到C类型,Go的 struct 不被支持;使用C的Struct,Go的array类型不被支持,使用C的指针
可以使用C类型 GoString 调用一个需要传入go字符串的go函数,如上文所述。GoString 类型会在序言中被自动定义, 注意,C类型无法创建这种类型的值,这种方式只有在从Go向C传递string值,和返回Go中时有用

动态库和静态库

cgo只能引入纯c语法的头文件,C的头文件中只能有C语言的语法,不能出现C++特性的语法,比如重载,默认参数等,在C侧的接口实现如果使用C++写,需要使用extern "C"将要实现的接口声明一次,这样可以告诉编译器不要将接口按照C++规则进行符号修饰。

Cgo,Go与C交互的详细介绍相关推荐

  1. javascript(JS与css交互)详细介绍

    一,JS与css交互基本概述 css有三种设置样式:行内样式,内部样式及外部样式 JavaScript获取css样式分两种情况:行内样式获取法和非行内样式获取法. 行内样式 通过element.sty ...

  2. autosar中com模块_详细介绍AUTOSAR各个模块作用PART1(OS,SYS)

    这片文章中我们详细讲解下每个模块的功能,上图是vector的autosar方案,每个模块的详细介绍后续会有单独文章进行讲解,请关注.以下是各个模块的简介 1.VHSM hardware Securit ...

  3. python流程控制语句-Python中流程控制语句的详细介绍

    除了刚才介绍的while语句之外,Python也从其他语言借鉴了其他流程控制语句,并做了相应改变.Python中流程控制语句的详细介绍 4.1 ifStatements 或许最广为人知的语句就是if语 ...

  4. Linux小工具(3)之/proc目录详细介绍(上)

    目录 前言 一.平台说明 二./proc目录概述 三.准备使用 (1)进程描述文件夹 (2)/proc目录下常见的文件夹 前言 Linux系统的使用有别于Windows系统,无论是系统信息还是应用软件 ...

  5. 绝地求生国际服服务器维护到几点,绝地求生更新维护到今天几点?更新内容详细介绍...

    绝地求生更新维护到今天几点?更新内容详细介绍 2021-04-14 09:09:38 绝地求生在4月14日的早上八点进行停机维护,此次的维护将会持续八个小时,也就是将会在当天下午四点半左右结束维护,此 ...

  6. 【转】运输层TCP协议详细介绍

    [转]运输层TCP协议详细介绍 TCP是TCP/IP协议族中非常复杂的一个协议.它具有以下特点: 1:面向连接的运输层协议.在使用TCP协议之前,首先需要建立TCP连接.传送数据完毕后,必须释放已经建 ...

  7. Hadoop生态系统的详细介绍

    hadoop生态系统的详细介绍 简介 Hadoop是一个开发和运行处理大规模数据的软件平台,是Appach的一个用java语言实现开源软件框架,实现在大量计算机组成的集群中对海量数据进行分布式计算.今 ...

  8. Detection and Classification of Acoustic Scenes and Events(DCASE2013详细介绍)

    在DCASE 2013官网上,了解到在本篇文章中,详细描述了DCASE2013挑战赛的结果.下面加上个人的理解做了相应的翻译,可能有不对的地方,在之后的会慢慢改善. 摘要 对于智能系统来说,使用音频形 ...

  9. u-boot 详细介绍 .

    Bootloader 对于计算机系统来说,从开机上电到操作系统启动需要一个引导过程.嵌入式Linux系统同样离不开引导程序,这个引导程序就叫作Bootloader. 6.1.1  Bootloader ...

最新文章

  1. linux搭建--centos使用qemu-kvm,libvirt搭建虚拟机,并搭建libvmi来虚拟机自省(四)
  2. mysql锁的一些理解简书_MySQL锁系列之锁的种类和概念
  3. iphone 软键盘
  4. 处理器映射器(HandlerMapping)及处理器适配器(HandlerAdapter)详解(一)
  5. Java删除list
  6. zookeeper和eureka的对比
  7. 在线文本并集计算工具
  8. 父元素浮动子元素会浮动吗_为什么quot;overflow:hiddenquot;能清除浮动的影响
  9. 在CentOS6.5上安装/启动PostgreSQL
  10. 开始防破解--该死的杀毒软件
  11. 苹果itunes下载_苹果正在杀死iTunes,但不是在Windows上
  12. 程序员的键盘 ikbc poker3 使用心得
  13. 第七周项目2建立链队算法库
  14. 三色螺旋线 -《跟小海龟学Python》案例代码
  15. FileOperatorWSInstaller.zip——上海一网通登录社保下载提示,却无法下载
  16. BI神器Power Query(8)-- PQ从文本文件导入数据(2/2)
  17. Win10恢复账户默认半透明头像
  18. ppt 计算机图标不见了,我PPT的图标变成这样了,为什么
  19. 笔记本Win7系统 设置WIFI热点共享无线网络
  20. mysql 小球_c语言编程实例——小球跳动

热门文章

  1. 上市后股价一泻千里,快手会有拐点吗?
  2. 计算机控制plc应用论文,PLC技术在机械电气控制装置的应用论文
  3. 华东之旅--杭州第一天
  4. woindows rabbitmq 新建账号密码
  5. 关于Collabora Office DzzOffice NextCloud安装记录
  6. node + socket + NetAPP 实现多人在线聊天
  7. python培训机构 可以谈价格
  8. 疫情下的供应链:AI助力应对呆滞物料管理挑战
  9. 2020软考信息系统项目管理师_项目管理(下) 视频培训教程-任铄-专题视频课程
  10. java类和类之间的小练习1