go cgo


原文链接: go cgo

Go语言高级编程(Advanced Go Programming)第2章 CGO编程
鸟窝 CGO 文章整理

(...) cgo的指针传递 - 敦刻尔克的小兵 - SegmentFault 思否
https://github.com/chai2010/advanced-go-programming-book/tree/master/ch2-cgo
第二章 CGO编程-Go语言高级编程
chai2010/gopherchina2018-cgo-talk: GopherChina2018: 深入CGO编程 - 最新修订

winlinvip/go-fdkaac: Golang binding for lib-fdkaac(https://github.com/winlinvip/fdk-aac)
全面总结: Golang 调用 C/C++,例子式教程 - 掘金
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
c++ bindings for go
gocv golang 调用cpp
Examples of calls between Go and C/C++

豆瓣 C++ / #python / #golang #libmc
NVIDIA Management Library (NVML) bindings for Go

https://segmentfault.com/a/1190000017981732

常见问题

  1. CGO构建程序会自动构建当前目录下的C源文件,即是 go 会将当前目录下 .c 文件都编译成 .o目标文件,再链接汇编,这个特点衍生出几个注意事项:

  2. go 代码以静态库或动态库方式引用 C 函数的话,需要将对应的C源文件移出 go源文件所在的目录

  3. 如果想要将 C 函数编译到 go 程序,就需要将 C源文件与 go 文件放在同一目录下

  4. 在C/C++混编下, go 中引用 C 函数,需要将 C 函数名置于全局,即 extern C

  5. 要使用CGO特性,需要安装C/C++构建工具链,在macOS和Linux下是要安装GCC,在windows下是需要安装MinGW工具。同时需要保证环境变量CGO_ENABLED被设置为1,这表示CGO是被启用的状态。在本地构建时CGO_ENABLED默认是启用的,当交叉构建时CGO默认是禁止的。比如要交叉构建ARM环境运行的Go程序,需要手工设置好C/C++交叉构建的工具链,同时开启CGO_ENABLED环境变量。然后通过import "C"语句启用CGO特性。

编译go程序提示undefined reference to xxx ?

解决方法其实在go文件中(配置C的地方),加入参数就行:
添加#cgo LDFLAGS: -lm
同时我还碰到了“undefined reference to `dladdr”错误,😓
解决方案是:
添加:#cgo LDFLAGS:-ldl

C的struct的字段类型 是Go的关键字 ?

struct pcap
{

int type;
char *username;
char *password;

};
然后使用Cgo调用

var a *C.pcap
a.type // 此处会报错,因为type是Go中的关键字

如果C的struct的字段类型是Go的关键字,如 type , 那么在Go代码中可以在字段前加关键字如 x._type

Cgo中使用var声明C结构的变量是否需要释放内存?

func main() {

var pkt_header C.struct_pcap_pkthdr // 声明C struct的变量
var p_header *C.struct_pcap_pkthdr

// 请问是否需要手动释放该变量?求大神解答,谢谢
// defer C.free(unsafe.Pointer(&pkt_header))   ?
// defer C.free(unsafe.Pointer(p_header))

}

pkt_header是在go中分配的,所以不需要手动去释放。

CGO 在GO语言中是桥梁,枢纽,灵魂

unsafe.Pointer 可以让你无视 Go 的类型系统,完成任何类型与内建的 uintptr 类型之间的转化。根据文档,unsafe.Pointer 可以实现四种其他类型不能的操作:

任何类型的指针都可以转化为一个 unsafe.Pointer
一个 unsafe.Pointer 可以转化成任何类型的指针
一个 uintptr 可以转化成一个 unsafe.Pointer
一个 unsafe.Pointer 可以转化成一个 uintptr

GO最重要的概念就是指针: unsafe包中的 unsafe.Pointer和 builtin中的uintptr
任意类型强转成 unsafe.Pointer 的目的是为了做类型转换
数值类型强转成 uintptr 的目的是为了做指针运算

var p unsafe.Pointer = nil         // unsafe    目的:是把一个值变成一个指针 (可以进行指针赋值等操作)
var q uintptr        = uintptr(p)  // builtin   目的:就是把一个uint的值变成一个可以进行指针运算的值 (可以进行地址加减操作)

看下面例子,

  1. 把指针变地址 uintptr(unsafe.Pointer(&x)
  2. 把地址变指针 pb := (*int16)(unsafe.Pointer(uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)))

在C中 对应的就是

void *p     = NULL;                // unsafe.Pointer === void *p
uintptr_t q = (uintptr_t)(p);      // <stdint.h>  uintptr_t === uintptr

II. cgo参数传递

将go中的 []string ==> **C.char

var argv =[]string{"../Models/.../gti_gnet3.model", "../Data/Image_lite/bridge_c20.bin"}
C.main(C.int(len(argv)),(**C.char)(unsafe.Pointer(&argv[0]))) //main(int argc,char**argv)

[]byte ==> *C.char

c_char := (*C.char)(unsafe.Pointer(&bt[0]))

[]byte ==> *C.uint8_t

libfacedetection_capi_result_t* libfacedetection_capi_facedetect_rgba(uint8_t* rgba, int width, int height, int step);

func DetectFaceRGB(rgb []byte, w, h, stride int) []Face {
	rv := C.libfacedetection_capi_facedetect_rgb(
		(*C.uint8_t)(unsafe.Pointer(&rgb[0])),
		C.int(w),
		C.int(h),
		C.int(stride),
	)
	defer C.libfacedetection_capi_result_free(rv)
}

cgo []byte ==> void * 和void ** 传递方式

在C语言中,用void **表达指针数组是自然的方式。

int match(void *feat, void **models, int model_num, float *score, int *index);

func (s *VprLib) Predict(feat []byte, model []byte) (float32, error) {
    cFeat := unsafe.Pointer(&feat[0])               // void *
    cModelNum := C.int(1)
    cModels := make([]uintptr, cModelNum)          
    cModels[0] = uintptr(unsafe.Pointer(&model[0])) // void ** 
    var cScore C.float
    var cIndex C.int
    ret := C.ul_vpr_match(cFeat,
             (*unsafe.Pointer)(unsafe.Pointer(&cModels[0])),
             cModelNum,
             &cScore,
             &cIndex)

return float32(cScore), nil
}

Go语言中开始这么写,是错误的❌

func (s *Lib) Predict(feat []byte, model []byte) (float32, error) {
    cFeat := unsafe.Pointer(&feat[0])            // void * 
    cModelNum := C.int(1)
    cModels := make([]unsafe.Pointer, cModelNum)  //错误
    cModels[0] = unsafe.Pointer(&model[0])
    var cScore C.float
    var cIndex C.int
    ret := C.ul_vpr_match(cFeat,
             (*unsafe.Pointer)(unsafe.Pointer(&cModels[0])),
             cModelNum,
             &cScore,
             &cIndex)
    return float32(cScore), asError(ret, "match")
}

测试问题:
panic: runtime error: cgo argument has Go pointer to Go pointer [recovered]

helloword.go

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
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 libfacedetection_capi_h_
#define libfacedetection_capi_h_

#include <stddef.h>
#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

void Printf(char *s);

#ifdef __cplusplus
}
#endif
#endif // libfacedetection_capi_h_

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里指示链接:g++ -fPIC -shared -o libhelloword.so *.cpp

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。

CGO类型

C和Go中有很多类型是对应的,但是需要我们自行转换类型。例如:

  • C.int
  • C.long
  • C.ulong
    如果是访问struct得这么用 C.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的栈。

Cgo可以创建出能够调用C代码的Go包。

在go命令行中使用cgo

使用cgo书写普通的Go代码,导入一个伪包“C”。Go代码就可以关联到如C.size_t的类型,如C.stdout的变量,或者如C.putchar的方法。

如果“C”包的导入紧接在注释之后,那个这个注释,被称为前导码【preamble】,就会作为编译Go包中的C语言部分的头文件。例如:

// #include
// #include
import "C"
前导码【preamble】可以包含任何C语言代码,包括方法和变量声明及定义。这些代码后续可能会由Go代码引用,就像它们定义在了名为“C”的Go包中。所有在前导码中声明的名字都可能被使用,即使它们以小写字母开头。有一个例外:前导码中的static变量不能被Go代码引用;而static方法则可以被Go引用。

可以查看$GOROOT/misc/cgo/stdio和$GOROOT/misc/cgo/gmp中的例子。也可以在https://golang.org/doc/articles/c_go_cgo.html 中了解使用cgo的介绍。

CFLAGS, CPPFLAGS, CXXFLAGS, FFLAGS,和LDFLAGS可以在注释区域中,使用#cgo伪指令来定义,以改变C,C++,或Fortran编译器的行为。被多个指令定义的值会被联系在一起。指令还可以包含一个构建约束的列表,将其对系统的影响限制为满足其中一个约束条件。可以查看https://golang.org/pkg/go/build/#hdr-Build_Constraints 了解约束语法的详情。例如:

// #cgo CFLAGS: -DPNG_DEBUG=1
// #cgo amd64 386 CFLAGS: -DX86=1
// #cgo LDFLAGS: -lpng
// #include <png.h>
import "C"

或者,CPPFLAGS和LDFLAGS也可通过pkg-config工具获取,使用#cgo pkg-config:指令,后面跟上包名即可。比如:

// #cgo pkg-config: png cairo
// #include <png.h>
import "C"

默认的pkg-config工具可以通过设置PKG_CONFIG环境变量来改变。

当编译时,CGO_CFLAGS,CGO_CPPFLAGS,CGO_CXXFLAGS,CGO_FFLAGS和CGO_LDFLAGS这些环境变量都会从指令中提取出来,并加入到flags中。包特定的flags需要使用指令来设置,而不是通过环境变量,所以这些构建可以在未更改的环境中也能正常运行。

一个包中的所有cgo CPPFLAGS和CFLAGS指令会被连接起来,并用来编译包中的C文件。一个包中的所有CPPFLAGS和CXXFLAGS指令会被连接起来,并用来编译包中的C++文件。一个包中的所有CPPFLAGS和FFLAGS指令会被连接起来,并用来编译包中的Fortran文件。在这个程序中任何包内的所有LDFLAGS指令会被连接起来,并在链接时使用。所有的pkg-config指令会被连接起来,并同时发送给pkg-config,以添加到每个适当的命令行标志集中。

当cgo指令被转化【parse】时,任何出现${SRCDIR}字符串的地方,都会被替换为包含源文件的目录的绝对路径。这就允许预编译的静态库包含在包目录中,并能够正确的链接。例如,如果foo包在/go/src/foo目录下:

// #cgo LDFLAGS: -L${SRCDIR}/libs -lfoo
就会被展开为:

// #cgo LDFLAGS: -L/go/src/foo/libs -lfoo
心得: // #cgo LDFLAGS: 可用来链接静态库。-L指定静态库所在目录,-l指定静态库文件名,注意静态库文件名必须有lib前缀,但是这里不需要写,比如上面的-lfoo实际找的是libfoo.a文件

  1. 当Go tool发现一个或多个Go文件使用了特殊导入“C”包,它就会在当前目录中寻找其它非Go文件,并将其编译为Go包的一部分。
    任何.c, .s, 或者.S文件会被C编译器编译。
    任何.cc, .cpp, 或者.cxx文件会被C++编译器编译。
    任何.f, .F, .for或者.f90文件会被fortran编译器编译。
    任何.h, .hh, .hpp或者.hxx文件不会被分开编译,但是,如果这些头文件修改了,那么C和C++文件就会被重新编译。默认的C和C++编译器有可能会分别被CC和CXX环境变量改变,那些环境变量可能包含命令行选项。

  2. cgo tool对于本地构建默认是启用的。而在交叉编译时默认是禁用的。你可以在运行go tool时,通过设置CGO_ENABLED环境变量来控制它:设为1表示启用cgo, 设为0关闭它。如果cgo启用,go tool将会设置构建约束“cgo”。

  3. 在交叉编译时,你必须为cgo指定一个C语言交叉编译器。你可以在使用make.bash构建工具链时,设置CC_FOR_TARGET环境变量来指定,或者是在你运行go tool时设置CC环境变量来指定。与此相似的还有作用于C++代码的CXX_FOR_TARGET和CXX环境变量。

Go引用C代码

在Go文件中,C的结构体属性名是Go的关键字,可以加上下划线前缀来访问它们:如果x指向一个拥有属性名"type"的C结构体,那么就可以用x._type来访问这个属性。对于那些不能在Go中表达的C结构体字段(例如位字段或者未对齐的数据),会在Go结构体中被省略,而替换为适当的填充,以到达下一个属性,或者结构体的结尾。

标准C数字类型,可以通过如下名字访问:

  1. C的void*类型 由Go的unsafe.Pointer表示。
  2. C的int128_t和uint128_t由[16]byte表示。
  3. 如果想直接访问一个结构体,联合体,或者枚举类型,请加上如下前缀:struct, union, 或者enum_。比如C.struct_stat。
  4. 任何一个C类型T的尺寸大小,都可以通过C.sizeof_T获取,比如C.sizeof_struct_stat。

心得:但是如果C结构体用了typedef struct设置了别名,则就不需要加上前缀,可以直接C.xxx_t 访问该类型。

因为在通常情况下Go并不支持C的联合体类型【union type】,所以C的联合体类型,由一个等长的Go byte数组来表示。

Go结构体不能嵌入具有C类型的字段。

Go代码不能引用发生在非空C结构体末尾的零尺寸字段。如果要获取这个字段的地址(这也是对于零大小字段唯一能做的操作),你必须传入结构体的地址,并加上结构体的大小,才能算出这个零大小字段的地址。

cgo会将C类型转换为对应的,非导出的的Go类型。因为转换是非导出的,一个Go包就不应该在它的导出API中暴露C的类型:在一个Go包中使用的C类型,不同于在其它包中使用的同样C类型。

可以在多个赋值语境中,调用任何C函数(甚至是void函数),来获取返回值(如果有的话),以及C errno变量作为Go error(如果方法返回void,则使用 _ 来跳过返回值)。例如:

n, err = C.sqrt(-1)
_, err := C.voidFunc()
var n, err = C.sqrt(1)

调用C的方法指针目前还不支持,然而你可以声明Go变量来引用C的方法指针,然后在Go和C之间来回传递它。C代码可以调用来自Go的方法指针。例如:

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中,一个方法参数被写为一个固定大小的数组,实际上需要的是一个指向数组第一个元素的指针。C编译器很清楚这个调用习惯,并相应的调整这个调用,但是Go不能这样做。在Go中,你必须显式的传入指向第一个元素的指针:C.f(&C.x[0])。

在Go和C类型之间,通过拷贝数据,还有一些特殊的方法转换。用Go伪代码定义如下:

作为一个特殊例子,C.malloc并不是直接调用C的库函数malloc,而是调用一个Go的帮助函数【helper function】,该函数包装了C的库函数malloc,并且保证不会返回nil。如果C的malloc指示内存溢出,这个帮助函数会崩溃掉程序,就像Go自己运行时内存溢出一样。因为C.malloc从不失败,所以它不会返回包含errno的2值格式。

Go调用C代码,Cgo笔记

参考:

官方文档:

http://golang.org/cmd/cgo/

http://blog.golang.org/2011/03/c-go-cgo.html

一份博文,编译过程讲得比较细:

http://googollee.blog.163.com/blog/static/1159411201031812128593/

Go语言教程:使用C语言函数:

http://chaishushan.blog.163.com/blog/static/130192897201012710273283/

看完上面的教程,基本上知道怎么用Go调用C代码、和需要注意的事项。

至于C调用Go的代码,Go调用汇编代码,以后再研究吧。

以下内容是笔记,列出一些重点等,不解释。

源文件

/*
// 这是注释中的注释
#include <stdio.h>
#include <errno.h>
*/
import "C"
 
// #cgo CFLAGS: -DPNG_DEBUG=1
// #cgo linux CFLAGS: -DLINUX=1
// #cgo LDFLAGS: -lpng
// #include <png.h>
import "C"
 
// #cgo pkg-config: png cairo
// #include <png.h>
import "C"
 ···
 

编译方法

include $(GOROOT)/src/Make.inc
 
TARG=xdb
CGOFILES=xdb.go 
CGO_CFLAGS+=-L/opt/xunsearch/lib/ -I/opt/xunsearch/include
CGO_LDFLAGS+=-lscws -L/opt/xunsearch/lib/
 include $(GOROOT)/src/Make.pkg
 
说明:CFlags等参数,可以写在Go的源代码中
 
 

#### 数据类型转换

C -> Go

int(C.int ) 
// C string to Go string
func C.GoString(*C.char) string 
// C string, length to Go string
func C.GoStringN(*C.char, C.int) string 
// C pointer, length to Go []byte
func C.GoBytes(unsafe.Pointer, C.int) []byte
 
Go -> C 
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
unsafe.Pointer (void*)
// Go string to C string
func C.CString(string) *C.char

var val []byte
(*C.char)(unsafe.Pointer(&val[0]))
 
 

内存释放

cs := C.CString(s)
defer C.free(unsafe.Pointer(cs))
Go创建的对象,可以很好的回收;而C创建的,则需要手动回收

注意事项:

不支持调用像 Printf()
不清楚的地方,问人,或者看别人用Go调用C的代码。

(待完善)
http://googollee.blog.163.com/blog/static/1159411201031812128593/

http://my.oschina.net/zengsai/blog/5138

http://my.oschina.net/zengsai/blog/5139

package main

// First, build Skia this way:
//   ./gyp_skia -Dskia_shared_lib=1 && ninja -C out/Debug

/*
#cgo LDFLAGS: -lm
#cgo LDFLAGS: -lpng
#cgo LDFLAGS: -lstdc++
#cgo LDFLAGS: -L ../../out/Debug/lib
#cgo LDFLAGS: -Wl,-rpath=../../out/Debug/lib
#cgo LDFLAGS: -lskia
#cgo CFLAGS: -I../../include/c
#include "sk_surface.h"
*/
import "C"

import (
	"fmt"
)

func main() {
	p := C.sk_paint_new()
	defer C.sk_paint_delete(p)
	fmt.Println("OK!")
}

// TODO: replace this with an idiomatic interface to Skia.

在很多场景下,在 Go 的程序中需要调用 c 函数或者是用 c 编写的库(底层驱动,算法等,不想用 Go 语言再去造一遍轮子,复用现有的 c 库)。
那么该如何调用呢?Go 可是更好的 C 语言啊,当然提供了和 c 语言交互的功能,称为Cgo
Cgo封装了#cgo伪 c 文法,参数CFLAGS用来传入编译选项,LDFLAGS来传入链接选项。这个用来调用非 c 标准的第三方 c 库。

1)先从最简单的写起吧,Go 代码直接调用 c 函数,下面的示例中在代码注释块调用了标准的 c 库,并写了一个 c 函数 (本例只是简单打印了一句话,在该注释块中可以写任意合法的 c 代码),在 Go 代码部分直接调用该 c 函数hi()

package main

import "fmt"

/*
#include <stdio.h>

void hi() {
    printf("hello world!\n");
}
*/
import "C" //这里可看作封装的伪包C, 这条语句要紧挨着上面的注释块,不可在它俩之间间隔空行!

func main() {
    C.hi()
    fmt.Println("Hi, vim-go")
}

运行结果:

root@slave2:/home/cgo# go run main.go 
hello world!
Hi, vim-go

好,我可以在 Go 代码中写 c 代码了,那么我该如何在 Go 中直接调用已经编译好的第三方 c 库呢?用Cgo
2)本例示范在 Go 代码中调用非标准的 c 的第三方动态库
c 文件

/*
 * hi.c
 * created on: July 1, 2017
 *      author: mark
 */

#include <stdio.h>

void hi() {
    printf("Hello Cgo!\n");
}

h 文件

void hi();

编译成动态库. so

root@slave2:/home/cgo# gcc -c -fPIC -o hi.o hi.c
root@slave2:/home/cgo# gcc -shared -o libhi.so hi.o

Go 文件

package main

import "fmt"

/*
#cgo CFLAGS: -I./
#cgo LDFLAGS: -L./ -lhi
#include "hi.h" //非标准c头文件,所以用引号
*/
import "C"

func main() {
    C.hi()
    fmt.Println("Hi, vim-go")
}

重点来了(敲黑板):
CFLAGS中的-I(大写的 i) 参数表示.h头文件所在的路径
LDFLAGS中的-L(大写) 表示. so 文件所在的路径 -l(小写的 L) 表示指定该路径下的库名称,比如要使用libhi.so,则只需用-lhi (省略了libhi.so中的lib.so字符,关于这些字符所代表的具体含义请自行 google)表示。
运行结果:

root@slave2:/home/cgo# go run main.go 
Hello Cgo!
Hi, vim-go

更深入一些,
1)头文件路径和库文件路径写死的话,一旦第三方库的安装路径变化了,Golang 的代码也要跟着变化,就会很麻烦。这时可以使用cgo命令中使用pk-config,具体请参考这篇博文:Golang 使用 pkg-config 自动获取头文件和链接库的方法
2)当在 Go 中使用了以上的方法后,就要求主机(或者云服务器)上必须有相应的.so文件,如果不存在就会链接报错,导致程序退出。
.so是一些不必要的第三方驱动库(可有可无),那就麻烦了,你不能为了跑这个程序,把每台主机都装上那个不必要的第三方库吧。有没有一种方法可以在 Go 程序运行时才调用这些.so库呢,如果不存在忽略就好(就不启用那个库提供的功能了,而不是链接报错直接异常退出)?当然有!敬请期待一下一篇。:)

// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This file contains tests for the cgo checker.

package a

// void f(void *ptr) {}
import "C"

import "unsafe"

func CgoTests() {
	var c chan bool
	C.f(*(*unsafe.Pointer)(unsafe.Pointer(&c))) // want "embedded pointer"
	C.f(unsafe.Pointer(&c))                     // want "embedded pointer"

	var m map[string]string
	C.f(*(*unsafe.Pointer)(unsafe.Pointer(&m))) // want "embedded pointer"
	C.f(unsafe.Pointer(&m))                     // want "embedded pointer"

	var f func()
	C.f(*(*unsafe.Pointer)(unsafe.Pointer(&f))) // want "embedded pointer"
	C.f(unsafe.Pointer(&f))                     // want "embedded pointer"

	var s []int
	C.f(*(*unsafe.Pointer)(unsafe.Pointer(&s))) // want "embedded pointer"
	C.f(unsafe.Pointer(&s))                     // want "embedded pointer"

	var a [1][]int
	C.f(*(*unsafe.Pointer)(unsafe.Pointer(&a))) // want "embedded pointer"
	C.f(unsafe.Pointer(&a))                     // want "embedded pointer"

	var st struct{ f []int }
	C.f(*(*unsafe.Pointer)(unsafe.Pointer(&st))) // want "embedded pointer"
	C.f(unsafe.Pointer(&st))                     // want "embedded pointer"

	var st3 S
	C.f(*(*unsafe.Pointer)(unsafe.Pointer(&st3))) // want "embedded pointer"
	C.f(unsafe.Pointer(&st3))                     // want "embedded pointer"

	// The following cases are OK.
	var i int
	C.f(*(*unsafe.Pointer)(unsafe.Pointer(&i)))
	C.f(unsafe.Pointer(&i))

	C.f(*(*unsafe.Pointer)(unsafe.Pointer(&s[0])))
	C.f(unsafe.Pointer(&s[0]))

	var a2 [1]int
	C.f(*(*unsafe.Pointer)(unsafe.Pointer(&a2)))
	C.f(unsafe.Pointer(&a2))

	var st2 struct{ i int }
	C.f(*(*unsafe.Pointer)(unsafe.Pointer(&st2)))
	C.f(unsafe.Pointer(&st2))

	var st4 S2
	C.f(*(*unsafe.Pointer)(unsafe.Pointer(&st4)))
	C.f(unsafe.Pointer(&st4))

	type cgoStruct struct{ p *cgoStruct }
	C.f(unsafe.Pointer(&cgoStruct{}))

	C.CBytes([]byte("hello"))
}

type S struct{ slice []int }

type S2 struct{ int int }
`