CGO简明教程
Hello World
package main
// #include <stdlib.h>
import "C"
import (
"fmt"
)
func main() {
fmt.Println(int(C.random()))
}
当然这并不是Hello World。我们不首先输出Hello World是有原因的,接下来就会讲到。不过首先我们分析一下现在这个程序。
首先从结构上来看可以知道这就是一个普通的Go程序,第一行 package main
声明这个代码是在main包里。然后下面有 func main
是程序的入口。
// #inlcude <stdlib.h>
import "C"
这三行是Go调C才这样的,import "C"
是为了可以在Go程序里直接使用C里的一些函数,例如main中 C.random()
,而 import "C"
上边的注释叫做preamble,注意必须和 import "C"
紧紧挨着中间不能有空格。注释的风格可以是 // #include...
也可以是 /*#include ...*/
这样的。此外可以在 preamble 中加入 // #cgo
开头的注释,用于指示编译和链接中发生的一些事情,例如链接哪个动态链接库等。
接下来我们看看Hello World。
首先我们需要三个文件,helloworld.h
:
#ifndef __helloworld
#define __helloworld
void Printf(char *s);
#endif
helloworld.c
:
#include <stdio.h>
void Printf(char *s) {
printf("%s", s);
}
main.go
:
package main
// #include "helloworld.h"
import "C"
func main() {
C.Printf("hello world")
}
为啥不直接 C.printf
输出呢,因为在wiki中提到cgo目前暂时还不支持变长参数的C函数,所以要我们自己包装一下。编译:
$ go build
./main.go:7: cannot use "hello world" (type string) as type *_Ctype_char in argument to _Cfunc_Printf
原因是C和Go的字符串不是通用的,我们要把Go的字符串转成C的字符串,但是因为不是在编译的这个过程申请内存,而是在堆里 申请内存存储字符串,而Go的垃圾回收是管不到C申请的内存,所以我们需要自行销毁对应的内存。
package main
// #include <stdlib.h>
// #include "helloworld.h"
import "C"
import (
"unsafe"
)
func main() {
cs := C.CString("hello world\n")
defer C.free(unsafe.Pointer(cs))
C.Printf(cs)
}
也可以我们先把C编译成动态链接库,然后在Go里指示链接:
package main
// #cgo LDFLAGS: -L${SRCDIR}/ -Wl,-rpath,${SRCDIR}/ -lhelloworld
// #include <stdlib.h>
// #include "helloworld.h"
import "C"
import (
"unsafe"
)
func main() {
cs := C.CString("hello world\n")
defer C.free(unsafe.Pointer(cs))
C.Printf(cs)
}
我们先把 helloworld.c
编译成动态链接库 libhelloworld.so
。
类型
C和Go中有很多类型是对应的,但是需要我们自行转换类型。例如:
- C.int
- C.long
- C.ulong
如果是访问struct得这么用 C.struct_<struct的名字>
,enum,union和sizeof也类似。
具体参考:https://golang.org/cmd/cgo/#hdr-Go_references_to_C
此外,对于指针类型,则是该咋用咋用,比如 *C.int
。但是对于 void *
,则需要用 unsafe.Pointer
来表示。
其他知识
如果说需要什么其他知识,那就是编译链接相关的知识了,推荐《程序员的自我修养-链接、装载与库》。这本书非常的好,国产神书, 不过说实话,因为不经常接触,看过然后又忘记了大部分内容🤦♂️
CGO是如何运行的
在Go中调用C函数,cgo生成的代码调用 runtime.cgocall(_cgo_Cfunc_f, frame)
,_cgo_Cfunc_f
就是GCC编译
出来的代码。 runtime.cgocall
会调用 runtime.asmcgocall(_cgo_Cfunc_f, frame)
。
runtime.asmcgocall
会切换到m->go
的栈然后执行代码,因为 g0
的栈是操作系统分配的栈(大小为8k),足够
执行C代码。 _cgo_Cfunc_f
在frame中执行C函数,然后返回到 runtime.asmcgocall
。之后再切回调用它的
G的栈。
- https://github.com/golang/go/wiki/cgo
- https://golang.org/cmd/cgo/
- https://blog.golang.org/c-go-cgo
- https://book.douban.com/subject/3652388/
- https://golang.org/src/runtime/cgocall.go
更多文章
- socks5 协议详解
- zerotier简明教程
- 搞定面试中的系统设计题
- frp 源码阅读与分析(一):流程和概念
- 用peewee代替SQLAlchemy
- Golang(Go语言)中实现典型的fork调用
- DNSCrypt简明教程
- 一个Gunicorn worker数量引发的血案
- Golang validator使用教程
- Docker组件介绍(二):shim, docker-init和docker-proxy
- Docker组件介绍(一):runc和containerd
- 使用Go语言实现一个异步任务框架
- 协程(coroutine)简介 - 什么是协程?
- SQLAlchemy简明教程
- Go Module 简明教程