go语言的cgo简单教程

目前Go语言有2套编译器:GC和gccgo。其中GC提供的cgo支持C语言,gccgo支持C/C++。
此外,SWIG从2.0.1之后也对go语言提供支持,可以支持C++的类和回调。Go官方提供cmd/go命令,
可以很好的支持cgo,swig支持目前还在完善之中。

本文将简要介绍基于cgo集成C/C++库,适用平台: Linux/Windows。

1. Hello, 世界

// hello.go
package main

import "fmt"

func main() {
fmt.Printf("Hello, 世界\n")
}

由于go编译速度很快,并且cmd/go支持远程get第三方库,go可以方便的作为脚本语言使用。
运行上面的程序, 可以直接输入: go run hello.go。

2. 基于CGO的 Hello, 世界

package main

/*
#include <stdio.h>
*/
import "C"

func main() {
C.puts(C.CString("Hello, 世界\n"))
}

其中 import "C" 前注释中可以导入C语言 函数/变量/宏 等到一个虚拟的 "C" 包中。
我们可以直接使用C.前缀访问C中的函数。

当前的例子中,使用C的puts函数输出"Hello, 世界"。之所以没有使用C语言经典的printf函数,
是因为printf的参数是可变的,而cgo不支持访问你可变参数的函数。

如果需要使用printf函数,需要再做一次包装。

3. 基于C语言printf的 Hello, 世界

package main

/*
#include <stdio.h>

static void myPrint(const char* msg) {
printf("myPrint: %s", msg);
}
*/
import "C"

func main() {
//C.puts(C.CString("Hello, 世界\n"))
//C.printf(C.CString("Hello, 世界\n")) // error
C.myPrint(C.CString("Hello, 世界\n"))
}

我们通过myPrint来屏蔽printf的可变参数特性。在 import "C" 定义的函数一般建议定义为static。

4. CGO常用的函数

CGO内置了一些常用的函数:

// 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.
func C.CString(string) *C.char

// 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

我们前面的例子就是使用了 C.CString 将go的字符串转换为C语言的char*类型。
C.CString 返回的空间由C语言的malloc分配,使用完毕后需要用free释放。

5. 释放C.CString分配的空间

package main

/*
#include <stdio.h>
#include <stdlib.h>
*/
import "C"
import "unsafe"

func main() {
cStr := C.CString("Hello, 世界\n")
defer C.free(unsafe.Pointer(cStr))
C.puts(C.CString("Hello, 世界\n"))
}

C.CString返回的是C语言的char*类型,对应go语言的*C.char。 C语言的free参数是void*类型,
对应go语言的unsafe.Pointer。由于go语言禁止2种不同类型的隐式转换,因此用C.free时需要手工转换
类型,对应代码 unsafe.Pointer(cStr)。这样cStr的空间就不会出现内存泄露了。

另外,使用CGO时,一般都会导入"unsafe"包。"unsafe"中提供了一些类似C语言中的底层工具:

func Alignof(v ArbitraryType) uintptr
func Offsetof(v ArbitraryType) uintptr
func Sizeof(v ArbitraryType) uintptr
type ArbitraryType
type Pointer

6. 常见的C/go类型转换

使用CGO时,c和go之间的基本数据类型转换是经常遇到的一个问题。

整数/浮点数 转换:

// c -> go
i_go = int(C.i_c)
f32_fo = float32(C.float_c)
f64_fo = float64(C.double_c)

// go -> c
i_c = C.int(i_go)
float_c = C.float(f32_go)
double_c = C.double(f64_go)

字符串转换:

// c -> go
str_go = C.GoString(C.str_c)

// go -> c
str_c = C.CString(str_go)

指针转换:
// c -> go
p_int_go = (*int)(unsafe.Pointer(C.p_int))
// go -> c
p_int_c = (*C.int)(unsafe.Pointer(&myIntSlice[0]))

比如要调用C语言的main函数(代码不能运行,只是为了说明字符串数组转换):

func CallMain(args []string) int {
argc := C.int(len(args))
argv := make([]*C.char, len(args))
for i := 0; i < len(args); i++ {
argv[i] = C.CString(args[i])
}
rv := C.main(argc, (**C.char)(unsafe.Pointer(&argv)))
return int(rv)
}

如果是回调函数,需要在C中作一层封装(相对比较麻烦)。

7. 连接选项

可以使用#cgo扩展预处理命令指定CFLAGS/LDFLAGS。

比如go-gdal库的连接参数为:
/*
#include "go_gdal.h"

#cgo linux  pkg-config: gdal
#cgo darwin pkg-config: gdal
#cgo windows LDFLAGS: -lgdal.dll
*/
import "C"

#cgo之后可以跟系统命令,指定参数对应生效的系统。对于Linux等系统,可以直接使用pkg-config。

8. 集成C++库

目前cgo不识别C++语法。如果需要使用C++库,可以将C++库的API封装为C语言形式,然后编译为动态库。
根据动态库生成对于的导出lib和一个只有C API说明的头文件,然后可以导入go环境使用。

注:
CGO的内容比较杂,而且需要搭建完备的开发环境,现整理一点,以后慢慢补充。
目前,笔者尝试过2个C库的go帮定, 读者可以参考。网址是:

http://code.google.com/p/go-opencv/


原文转自:http://chaishushan.blog.163.com/blog/static/1301928972012799345127/

发布了19 篇原创文章 · 获赞 16 · 访问量 14万+

猜你喜欢

转载自blog.csdn.net/u010884123/article/details/60872980