golang为什么可以golang 调用c库代码

6696人阅读
/program/679738.html
Go调用C代码,Cgo笔记
官方文档:
http://golang.org/cmd/cgo/
http://blog.golang.org/2011/03/c-go-cgo.html
一份博文,编译过程讲得比较细:
http://googollee./blog/static//
Go语言教程:使用C语言函数:
http://chaishushan./blog/static//
看完上面的教程,基本上知道怎么用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
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的源代码中&
数据类型转换
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
C.schar (signed char)
C.uchar (unsigned char)
C.ushort (unsigned short)
C.uint (unsigned int)
C.ulong (unsigned long)
C.longlong (long long)
C.ulonglong (unsigned long long)
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/static//
http://my.oschina.net/zengsai/blog/5138
http://my.oschina.net/zengsai/blog/5139
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:6823086次
积分:20491
积分:20491
排名:第314名
原创:267篇
转载:335篇
评论:491条
目录和样章下载:
本人曾健生,家乡是佛山,现在广州工作,《App后台开发运维和架构实践》一书作者,曾经负责社交app &ekeo&和&米信&的后端开发,目前就职于云后端平台bmob从事云服务方面的开发工作(想了解bmob点击)
文章:60篇
阅读:669397
(1)(1)(1)(4)(1)(11)(4)(1)(2)(2)(1)(1)(7)(3)(5)(2)(8)(10)(12)(4)(6)(1)(15)(6)(2)(3)(3)(1)(15)(5)(7)(12)(7)(12)(4)(5)(6)(4)(2)(3)(9)(9)(19)(3)(6)(10)(20)(7)(4)(43)(50)(15)(26)(36)(38)(21)(11)(7)(14)(1)(1)(2)(2)(7)(5)(1)(1)(8)(11)(8)(7)> 博客详情
uintptr(unsafe.Pointer(&pn))
uintptr(unsafe.Pointer(&buf_PN[0])),
人打赏支持
码字总数 17299
支付宝支付
微信扫码支付
打赏金额: ¥
已支付成功
打赏金额: ¥Go与C语言的互操作
| Go语言中文网 | Golang中文社区 | Golang中国
<meta name="author" content="polaris ">
Go与C语言的互操作
Go与C语言的互操作
有强烈的背景,除了语法具有继承性外,其设计者以及其设计目标都与C语言有着千丝万缕的联系。在Go与C语言互操作()方面,Go更是提供了强大的支持。尤其是在Go中使用C,你甚至可以直接在Go源文件中编写C代码,这是其他语言所无法望其项背的。
在如下一些场景中,可能会涉及到Go与C的互操作:
1、提升局部代码性能时,用C替换一些Go代码。C之于Go,好比汇编之于C。
2、嫌Go内存GC性能不足,自己手动管理应用内存。
3、实现一些库的Go Wrapper。比如Oracle提供的C版本OCI,但Oracle并未提供Go版本的以及连接DB的协议细节,因此只能通过包装C
OCI版本的方式以提供Go开发者使用。
4、Go导出函数供C开发者使用(目前这种需求应该很少见)。
5、Maybe more…
一、Go调用C代码的原理
下面是一个短小的例子:
package main
// #include
// #include
void print(char *str) {
printf("%s\n", str);
import "C"
import "unsafe"
func main() {
s := "Hello Cgo"
cs := C.CString(s)
C.print(cs)
C.free(unsafe.Pointer(cs))
与"正常"Go代码相比,上述代码有几处"特殊"的地方:
1) 在开头的注释中出现了C头文件的include字样
2) 在注释中定义了C函数print
3) import的一个名为C的"包"
4) 在main函数中居然调用了上述的那个C函数-print
没错,这就是在Go源码中调用C代码的步骤,可以看出我们可直接在Go源码文件中编写C代码。
首先,Go源码文件中的C代码是需要用注释包裹的,就像上面的include 头文件以及print函数定义;
其次,import "C"这个语句是必须的,而且其与上面的C代码之间不能用空行分隔,必须紧密相连。这里的"C"不是包名,而是一种类似名字空间的概念,或可以理解为伪包,C语言所有语法元素均在该伪包下面;
最后,访问C语法元素时都要在其前面加上伪包前缀,比如C.uint和上面代码中的C.print、C.free等。
我们如何来编译这个go源文件呢?其实与"正常"Go源文件没啥区别,依旧可以直接通过go build或go run来编译和执行。但实际编译过程中,go调用了名为cgo的工具,cgo会识别和读取Go源文件中的C元素,并将其提取后交给C编译器编译,最后与Go源码编译后的目标文件链接成一个可执行程序。这样我们就不难理解为何Go源文件中的C代码要用注释包裹了,这些特殊的语法都是可以被Cgo识别并使用的。
二、在Go中使用C语言的类型
1、原生类型
* 数值类型
在Go中可以用如下方式访问C原生的数值类型:
C.schar (signed char),
C.uchar (unsigned char),
C.ushort (unsigned short),
C.int, C.uint (unsigned int),
C.ulong (unsigned long),
C.longlong (long long),
C.ulonglong (unsigned long long),
Go的数值类型与C中的数值类型不是一一对应的。因此在使用对方类型变量时少不了显式转型操作,如Go doc中的这个例子:
func Random() int {
return int(C.random())//C.long -> Go的int
func Seed(i int) {
C.srandom(C.uint(i))//Go的uint -> C的uint
* 指针类型
原生数值类型的指针类型可按Go语法在类型前面加上*,比如var p *C.int。而void*比较特殊,用Go中的unsafe.Pointer表示。任何类型的指针值都可以转换为unsafe.Pointer类型,而unsafe.Pointer类型值也可以转换为任意类型的指针值。unsafe.Pointer还可以与uintptr这个类型做相互转换。由于unsafe.Pointer的指针类型无法做算术操作,转换为uintptr后可进行算术操作。
* 字符串类型
C语言中并不存在正规的字符串类型,在C中用带结尾'\0'的字符数组来表示字符串;而在Go中,string类型是原生类型,因此在两种语言互操作是势必要做字符串类型的转换。
通过C.CString函数,我们可以将Go的string类型转换为C的"字符串"类型,再传给C函数使用。就如我们在本文开篇例子中使用的那样:
s := "Hello Cgo\n"
cs := C.CString(s)
C.print(cs)
不过这样转型后所得到的C字符串cs并不能由Go的gc所管理,我们必须手动释放cs所占用的内存,这就是为何例子中最后调用C.free释放掉cs的原因。在C内部分配的内存,Go中的GC是无法感知到的,因此要记着释放。
通过C.GoString可将C的字符串(*C.char)转换为Go的string类型,例如:
// #include
// #include
// char *foo = "hellofoo";
import "C"
import "fmt"
func main() {
fmt.Printf("%s\n", C.GoString(C.foo))
* 数组类型
C语言中的数组与Go语言中的数组差异较大,后者是值类型,而前者与C中的指针大部分场合都可以随意转换。目前似乎无法直接显式的在两者之间进行转型,官方文档也没有说明。但我们可以通过编写转换函数,将C的数组转换为Go的Slice(由于Go中数组是值类型,其大小是静态的,转换为Slice更为通用一些),下面是一个整型数组转换的例子:
// int cArray[] = {1, 2, 3, 4, 5, 6, 7};
func CArrayToGoArray(cArray unsafe.Pointer, size int) (goArray []int) {
p := uintptr(cArray)
for i :=0; i < i++ {
j := *(*int)(unsafe.Pointer(p))
goArray = append(goArray, j)
p += unsafe.Sizeof(j)
func main() {
goArray := CArrayToGoArray(unsafe.Pointer(&C.cArray[0]), 7)
fmt.Println(goArray)
执行结果输出:[1 2 3 4 5 6 7]
这里要注意的是:Go编译器并不能将C的cArray自动转换为数组的地址,所以不能像在C中使用数组那样将数组变量直接传递给函数,而是将数组第一个元素的地址传递给函数。
2、自定义类型
除了原生类型外,我们还可以访问C中的自定义类型。
* 枚举(enum)
// enum color {
var e, f, g C.enum_color = C.RED, C.BLUE, C.YELLOW
fmt.Println(e, f, g)
输出:0 1 2
对于具名的C枚举类型,我们可以通过C.enum_xx来访问该类型。如果是匿名枚举,则似乎只能访问其字段了。
* 结构体(struct)
// struct employee {
id := C.CString("1247")
var employee C.struct_employee = C.struct_employee{id, 21}
fmt.Println(C.GoString(employee.id))
fmt.Println(employee.age)
C.free(unsafe.Pointer(id))
和enum类似,我们可以通过C.struct_xx来访问C中定义的结构体类型。
* 联合体(union)
这里我试图用与访问struct相同的方法来访问一个C的union:
// #include
// union bar {
import "C"
func main() {
var b *C.union_bar = new(C.union_bar)
fmt.Println(b)
不过编译时,go却报错:b.c undefined (type *[8]byte has no field or method c)。从报错的信息来看,Go对待union与其他类型不同,似乎将union当成[N]byte来对待,其中N为union中最大字段的size(圆整后的),因此我们可以按如下方式处理C.union_bar:
func main() {
var b *C.union_bar = new(C.union_bar)
fmt.Println(b)
输出:&[13 17 0 0 0 0 0 0]
在Go中访问使用用typedef定义的别名类型时,其访问方式与原实际类型访问方式相同。如:
var a C.myint = 5
fmt.Println(a)
// typedef struct
var m C.struct_myemployee
从例子中可以看出,对原生类型的别名,直接访问这个新类型名即可。而对于复合类型的别名,需要根据原复合类型的访问方式对新别名进行访问,比如myemployee实际类型为struct,那么使用myemployee时也要加上struct_前缀。
三、Go中访问C的变量和函数
实际上上面的例子中我们已经演示了在Go中是如何访问C的变量和函数的,一般方法就是加上C前缀即可,对于C标准库中的函数尤其是这样。不过虽然我们可以在Go源码文件中直接定义C变量和C函数,但从代码结构上来讲,大量的在Go源码中编写C代码似乎不是那么“专业”。那如何将C函数和变量定义从Go源码中分离出去单独定义呢?我们很容易想到将C的代码以共享库的形式提供给Go源码。
Cgo提供了#cgo指示符可以指定Go源码在编译后与哪些进行链接。我们来看一下例子:
package main
// #cgo LDFLAGS: -L ./ -lfoo
// #include
// #include
// #include "foo.h"
import "C"
import "fmt“
func main() {
fmt.Println(C.count)
我们看到上面例子中通过#cgo指示符告诉go编译器链接当前目录下的libfoo共享库。C.count变量和C.foo函数的定义都在libfoo共享库中。我们来创建这个共享库:
void foo();
#include "foo.h"
int count = 6;
void foo() {
printf("I am foo!\n");
$> gcc -c foo.c
$> ar rv libfoo.a foo.o
我们首先创建一个静态共享库libfoo.a,不过在编译Go源文件时我们遇到了问题:
$> go build foo.go
# command-line-arguments
/tmp/go-build/command-line-arguments.a(foo.cgo2.)(.text): foo: not defined
foo(0): not defined
提示foo函数未定义。通过-x选项打印出具体的编译细节,也未找出问题所在。不过在Go的问题列表中我发现了一个issue(/p/go/issues/detail?id=3755),上面提到了目前Go的版本不支持链接静态共享库。
那我们来创建一个动态共享库试试:
$> gcc -c foo.c
$> gcc -shared -Wl,-soname,libfoo.so -o libfoo.so
再编译foo.go,的确能够成功。执行foo。
$> go build foo.go && go
还有一点值得注意,那就是Go支持多返回值,而C中并没不支持。因此当将C函数用在多返回值的调用中时,C的errno将作为err返回值返回,下面是个例子:
package main
// #include
// #include
// #include
// int foo(int i) {
errno = 0;
if (i > 5) {
errno = 8;
return i – 5;
import "C"
import "fmt"
func main() {
i, err := C.foo(C.int(8))
if err != nil {
fmt.Println(err)
fmt.Println(i)
$> go run foo.go
exec format error
errno为8,其含义在errno.h中可以找到:
#define ENOEXEC
/* Exec format error */
的确是“exec format error”。
四、C中使用Go函数
与在Go中使用C源码相比,在C中使用Go函数的场合较少。在Go中,可以使用"export + 函数名"来导出Go函数为C所使用,看一个简单例子:
package main
extern void GoExportedFunc();
void bar() {
printf("I am bar!\n");
GoExportedFunc();
import "C"
import "fmt"
//export GoExportedFunc
func GoExportedFunc() {
fmt.Println("I am a GoExportedFunc!")
func main() {
不过当我们编译该Go文件时,我们得到了如下错误信息:
# command-line-arguments
/tmp/go-build/command-line-arguments/_obj/bar.cgo2.o: In function `bar':
./bar.go:7: multiple definition of `bar'
/tmp/go-build/command-line-arguments/_obj/_cgo_export.o:/home/tonybai/test/go/bar.go:7: first defined here
collect2: ld returned 1 exit status
代码似乎没有任何问题,但就是无法通过编译,总是提示“多重定义”。翻看Cgo的文档,找到了些端倪。原来
There is a limitation: if your program uses any //export directives, then the C code in the comment may only include declarations (extern int f();), not definitions (int f() { return 1; }).
似乎是// extern int f()与//export f不能放在一个Go源文件中。我们把bar.go拆分成bar1.go和bar2.go两个文件:
// bar1.go
package main
extern void GoExportedFunc();
void bar() {
printf("I am bar!\n");
GoExportedFunc();
import "C"
func main() {
// bar2.go
package main
import "C"
import "fmt"
//export GoExportedFunc
func GoExportedFunc() {
fmt.Println("I am a GoExportedFunc!")
编译执行:
$> go build -o bar bar1.go bar2.go
I am a GoExportedFunc!
个人觉得目前Go对于导出函数供C使用的功能还十分有限,两种语言的调用约定不同,类型无法一一对应以及Go中类似Gc这样的高级功能让导出Go函数这一功能难于完美实现,导出的函数依旧无法完全脱离Go的环境,因此实用性似乎有折扣。
虽然Go提供了强大的与C互操作的功能,但目前依旧不完善,比如不支持在Go中直接调用可变个数参数的函数(issue975),如printf(因此,文档中多用fputs)。
这里的建议是:尽量缩小Go与C间互操作范围。
什么意思呢?如果你在Go中使用C代码时,那么尽量在C代码中调用C函数。Go只使用你封装好的一个C函数最好。不要像下面代码这样:
C.fputs(…)
C.atoi(..)
C.malloc(..)
而是将这些C函数调用封装到一个C函数中,Go只知道这个C函数即可。
相反,在C中使用Go导出的函数也是一样。
(C) 2012, . 版权所有.
支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
支持 @ 本站用户;支持表情(输入 : 提示),见
有强烈的背景,除了语法具有继承性外,其设计者以及其设计目标都与C语言有着千丝万缕的联系。在Go与C语言互操作()方面,Go更是提供了强大的支持。尤其是在Go中使用C,你甚至可以直接在Go源文件中编写C代码,这是其他语言所无法望其项背的。
在如下一些场景中,可能会涉及到Go与C的互操作:
1、提升局部代码性能时,用C替换一些Go代码。C之于Go,好比汇编之于C。
2、嫌Go内存GC性能不足,自己手动管理应用内存。
3、实现一些库的Go Wrapper。比如Oracle提供的C版本OCI,但Oracle并未提供Go版本的以及连接DB的协议细节,因此只能通过包装C
OCI版本的方式以提供Go开发者使用。
4、Go导出函数供C开发者使用(目前这种需求应该很少见)。
5、Maybe more…
一、Go调用C代码的原理
下面是一个短小的例子:
package main
// #include
// #include
void print(char *str) {
printf("%s\n", str);
import "C"
import "unsafe"
func main() {
s := "Hello Cgo"
cs := C.CString(s)
C.print(cs)
C.free(unsafe.Pointer(cs))
与"正常"Go代码相比,上述代码有几处"特殊"的地方:
1) 在开头的注释中出现了C头文件的include字样
2) 在注释中定义了C函数print
3) import的一个名为C的"包"
4) 在main函数中居然调用了上述的那个C函数-print
没错,这就是在Go源码中调用C代码的步骤,可以看出我们可直接在Go源码文件中编写C代码。
首先,Go源码文件中的C代码是需要用注释包裹的,就像上面的include 头文件以及print函数定义;
其次,import "C"这个语句是必须的,而且其与上面的C代码之间不能用空行分隔,必须紧密相连。这里的"C"不是包名,而是一种类似名字空间的概念,或可以理解为伪包,C语言所有语法元素均在该伪包下面;
最后,访问C语法元素时都要在其前面加上伪包前缀,比如C.uint和上面代码中的C.print、C.free等。
我们如何来编译这个go源文件呢?其实与"正常"Go源文件没啥区别,依旧可以直接通过go build或go run来编译和执行。但实际编译过程中,go调用了名为cgo的工具,cgo会识别和读取Go源文件中的C元素,并将其提取后交给C编译器编译,最后与Go源码编译后的目标文件链接成一个可执行程序。这样我们就不难理解为何Go源文件中的C代码要用注释包裹了,这些特殊的语法都是可以被Cgo识别并使用的。
二、在Go中使用C语言的类型
1、原生类型
* 数值类型
在Go中可以用如下方式访问C原生的数值类型:
C.schar (signed char),
C.uchar (unsigned char),
C.ushort (unsigned short),
C.int, C.uint (unsigned int),
C.ulong (unsigned long),
C.longlong (long long),
C.ulonglong (unsigned long long),
Go的数值类型与C中的数值类型不是一一对应的。因此在使用对方类型变量时少不了显式转型操作,如Go doc中的这个例子:
func Random() int {
return int(C.random())//C.long -> Go的int
func Seed(i int) {
C.srandom(C.uint(i))//Go的uint -> C的uint
* 指针类型
原生数值类型的指针类型可按Go语法在类型前面加上*,比如var p *C.int。而void*比较特殊,用Go中的unsafe.Pointer表示。任何类型的指针值都可以转换为unsafe.Pointer类型,而unsafe.Pointer类型值也可以转换为任意类型的指针值。unsafe.Pointer还可以与uintptr这个类型做相互转换。由于unsafe.Pointer的指针类型无法做算术操作,转换为uintptr后可进行算术操作。
* 字符串类型
C语言中并不存在正规的字符串类型,在C中用带结尾'\0'的字符数组来表示字符串;而在Go中,string类型是原生类型,因此在两种语言互操作是势必要做字符串类型的转换。
通过C.CString函数,我们可以将Go的string类型转换为C的"字符串"类型,再传给C函数使用。就如我们在本文开篇例子中使用的那样:
s := "Hello Cgo\n"
cs := C.CString(s)
C.print(cs)
不过这样转型后所得到的C字符串cs并不能由Go的gc所管理,我们必须手动释放cs所占用的内存,这就是为何例子中最后调用C.free释放掉cs的原因。在C内部分配的内存,Go中的GC是无法感知到的,因此要记着释放。
通过C.GoString可将C的字符串(*C.char)转换为Go的string类型,例如:
// #include
// #include
// char *foo = "hellofoo";
import "C"
import "fmt"
func main() {
fmt.Printf("%s\n", C.GoString(C.foo))
* 数组类型
C语言中的数组与Go语言中的数组差异较大,后者是值类型,而前者与C中的指针大部分场合都可以随意转换。目前似乎无法直接显式的在两者之间进行转型,官方文档也没有说明。但我们可以通过编写转换函数,将C的数组转换为Go的Slice(由于Go中数组是值类型,其大小是静态的,转换为Slice更为通用一些),下面是一个整型数组转换的例子:
// int cArray[] = {1, 2, 3, 4, 5, 6, 7};
func CArrayToGoArray(cArray unsafe.Pointer, size int) (goArray []int) {
p := uintptr(cArray)
for i :=0; i < i++ {
j := *(*int)(unsafe.Pointer(p))
goArray = append(goArray, j)
p += unsafe.Sizeof(j)
func main() {
goArray := CArrayToGoArray(unsafe.Pointer(&C.cArray[0]), 7)
fmt.Println(goArray)
执行结果输出:[1 2 3 4 5 6 7]
这里要注意的是:Go编译器并不能将C的cArray自动转换为数组的地址,所以不能像在C中使用数组那样将数组变量直接传递给函数,而是将数组第一个元素的地址传递给函数。
2、自定义类型
除了原生类型外,我们还可以访问C中的自定义类型。
* 枚举(enum)
// enum color {
var e, f, g C.enum_color = C.RED, C.BLUE, C.YELLOW
fmt.Println(e, f, g)
输出:0 1 2
对于具名的C枚举类型,我们可以通过C.enum_xx来访问该类型。如果是匿名枚举,则似乎只能访问其字段了。
* 结构体(struct)
// struct employee {
id := C.CString("1247")
var employee C.struct_employee = C.struct_employee{id, 21}
fmt.Println(C.GoString(employee.id))
fmt.Println(employee.age)
C.free(unsafe.Pointer(id))
和enum类似,我们可以通过C.struct_xx来访问C中定义的结构体类型。
* 联合体(union)
这里我试图用与访问struct相同的方法来访问一个C的union:
// #include
// union bar {
import "C"
func main() {
var b *C.union_bar = new(C.union_bar)
fmt.Println(b)
不过编译时,go却报错:b.c undefined (type *[8]byte has no field or method c)。从报错的信息来看,Go对待union与其他类型不同,似乎将union当成[N]byte来对待,其中N为union中最大字段的size(圆整后的),因此我们可以按如下方式处理C.union_bar:
func main() {
var b *C.union_bar = new(C.union_bar)
fmt.Println(b)
输出:&[13 17 0 0 0 0 0 0]
在Go中访问使用用typedef定义的别名类型时,其访问方式与原实际类型访问方式相同。如:
var a C.myint = 5
fmt.Println(a)
// typedef struct
var m C.struct_myemployee
从例子中可以看出,对原生类型的别名,直接访问这个新类型名即可。而对于复合类型的别名,需要根据原复合类型的访问方式对新别名进行访问,比如myemployee实际类型为struct,那么使用myemployee时也要加上struct_前缀。
三、Go中访问C的变量和函数
实际上上面的例子中我们已经演示了在Go中是如何访问C的变量和函数的,一般方法就是加上C前缀即可,对于C标准库中的函数尤其是这样。不过虽然我们可以在Go源码文件中直接定义C变量和C函数,但从代码结构上来讲,大量的在Go源码中编写C代码似乎不是那么“专业”。那如何将C函数和变量定义从Go源码中分离出去单独定义呢?我们很容易想到将C的代码以共享库的形式提供给Go源码。
Cgo提供了#cgo指示符可以指定Go源码在编译后与哪些进行链接。我们来看一下例子:
package main
// #cgo LDFLAGS: -L ./ -lfoo
// #include
// #include
// #include "foo.h"
import "C"
import "fmt“
func main() {
fmt.Println(C.count)
我们看到上面例子中通过#cgo指示符告诉go编译器链接当前目录下的libfoo共享库。C.count变量和C.foo函数的定义都在libfoo共享库中。我们来创建这个共享库:
void foo();
#include "foo.h"
int count = 6;
void foo() {
printf("I am foo!\n");
$> gcc -c foo.c
$> ar rv libfoo.a foo.o
我们首先创建一个静态共享库libfoo.a,不过在编译Go源文件时我们遇到了问题:
$> go build foo.go
# command-line-arguments
/tmp/go-build/command-line-arguments.a(foo.cgo2.)(.text): foo: not defined
foo(0): not defined
提示foo函数未定义。通过-x选项打印出具体的编译细节,也未找出问题所在。不过在Go的问题列表中我发现了一个issue(/p/go/issues/detail?id=3755),上面提到了目前Go的版本不支持链接静态共享库。
那我们来创建一个动态共享库试试:
$> gcc -c foo.c
$> gcc -shared -Wl,-soname,libfoo.so -o libfoo.so
再编译foo.go,的确能够成功。执行foo。
$> go build foo.go && go
还有一点值得注意,那就是Go支持多返回值,而C中并没不支持。因此当将C函数用在多返回值的调用中时,C的errno将作为err返回值返回,下面是个例子:
package main
// #include
// #include
// #include
// int foo(int i) {
errno = 0;
if (i > 5) {
errno = 8;
return i – 5;
import "C"
import "fmt"
func main() {
i, err := C.foo(C.int(8))
if err != nil {
fmt.Println(err)
fmt.Println(i)
$> go run foo.go
exec format error
errno为8,其含义在errno.h中可以找到:
#define ENOEXEC
/* Exec format error */
的确是“exec format error”。
四、C中使用Go函数
与在Go中使用C源码相比,在C中使用Go函数的场合较少。在Go中,可以使用"export + 函数名"来导出Go函数为C所使用,看一个简单例子:
package main
extern void GoExportedFunc();
void bar() {
printf("I am bar!\n");
GoExportedFunc();
import "C"
import "fmt"
//export GoExportedFunc
func GoExportedFunc() {
fmt.Println("I am a GoExportedFunc!")
func main() {
不过当我们编译该Go文件时,我们得到了如下错误信息:
# command-line-arguments
/tmp/go-build/command-line-arguments/_obj/bar.cgo2.o: In function `bar':
./bar.go:7: multiple definition of `bar'
/tmp/go-build/command-line-arguments/_obj/_cgo_export.o:/home/tonybai/test/go/bar.go:7: first defined here
collect2: ld returned 1 exit status
代码似乎没有任何问题,但就是无法通过编译,总是提示“多重定义”。翻看Cgo的文档,找到了些端倪。原来
There is a limitation: if your program uses any //export directives, then the C code in the comment may only include declarations (extern int f();), not definitions (int f() { return 1; }).
似乎是// extern int f()与//export f不能放在一个Go源文件中。我们把bar.go拆分成bar1.go和bar2.go两个文件:
// bar1.go
package main
extern void GoExportedFunc();
void bar() {
printf("I am bar!\n");
GoExportedFunc();
import "C"
func main() {
// bar2.go
package main
import "C"
import "fmt"
//export GoExportedFunc
func GoExportedFunc() {
fmt.Println("I am a GoExportedFunc!")
编译执行:
$> go build -o bar bar1.go bar2.go
I am a GoExportedFunc!
个人觉得目前Go对于导出函数供C使用的功能还十分有限,两种语言的调用约定不同,类型无法一一对应以及Go中类似Gc这样的高级功能让导出Go函数这一功能难于完美实现,导出的函数依旧无法完全脱离Go的环境,因此实用性似乎有折扣。
虽然Go提供了强大的与C互操作的功能,但目前依旧不完善,比如不支持在Go中直接调用可变个数参数的函数(issue975),如printf(因此,文档中多用fputs)。
这里的建议是:尽量缩小Go与C间互操作范围。
什么意思呢?如果你在Go中使用C代码时,那么尽量在C代码中调用C函数。Go只使用你封装好的一个C函数最好。不要像下面代码这样:
C.fputs(…)
C.atoi(..)
C.malloc(..)
而是将这些C函数调用封装到一个C函数中,Go只知道这个C函数即可。
相反,在C中使用Go导出的函数也是一样。
(C) 2012, . 版权所有.
记住登录状态
还不是会员

我要回帖

更多关于 golang 调用python 的文章

 

随机推荐