内容简介:当然这并不是Hello World。我们不首先输出Hello World是有原因的,接下来就会讲到。不过首先我们分析一下现在这个程序。 首先从结构上来看可以知道这就是一个普通的Go程序,第一行这三行是Go调C才这样的,接下来我们看看Hello World。
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的栈。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。