看了下上一次提交还是去年刚搭建这个博客的时候,之前在博客园维护过一个博客,三天打鱼半年晒网写过几篇,后面打算转战到此争取能多写几篇,没想到还是没做到。今天难得有时间,先总结一篇cgo相关的吧。
目前公司的技术栈已经全面转go,而一些业务还需要依赖到一些老的C\C++ so,或者一些项目必须调用C/C++的so,比如ffmpeg。所以工作中经常会使用到cgo来调用这些C\C++ so,把cgo调用方法和遇到的问题简单记录下来。
cgo简单调用
在go中如果要调用C接口,要采用cgo来实现。比如下面的代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
package main
/*
#include <stdio.h>
int add(int a, int b) {
return a + b
}
#cgo CFLAGS: -g
*/
import "C"
func main() {
sum := C.add(C.int(1), C.int(1))
}
|
如果要使用cgo,在go源文件中必须加入import “C"来标识,用于导入C实现的代码,而C源码则在import “C”上面以注释的方式加入。导入以后C源码可以在go代码中直接调用只要在函数、类型以C.开头即可。这里需要注意的一点是C代码和import “C”之间不能有空行
调用so
在实际应用中,一般会把C实现封装成so提供cgo调用,通过LDFLAGS指定lib即可。
1
2
3
4
5
6
7
8
9
|
package main
/*
#cgo CFLAGS: -I ../lib
#cgo LDFLAGS: -L ${SRCDIR}/../lib -lxl_stat -lxl_thunder_sdk
#include "call_so_interface.h"
#include <stdlib.h>
*/
import "C"
|
注意,在编译时指定lib或者include的路径,不能直接使用相对路径,但可以使用一个变量${SRCDIR},这个变量指的是源码路径。
类型转换
对于字符串,提供了CString、GoString两个函数进行转换,看下面的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
package main
import (
"fmt"
"unsafe"
)
/*
#include <stdio.h>
#include <stdlib.h>
char* pass_char_array(char* in) {
printf("print in c is %s\n",in);
char* out = malloc(64);
sprintf(out, "%s", "hello world return from c");
return out;
}
#cgo CFLAGS: -g
*/
import "C"
func main() {
str := "hello world pass from go"
in := C.CString(str)
out := C.pass_char_array(in)
outStr := C.GoString(out)
C.free(unsafe.Pointer(in)) //注意需要手动调用free
C.free(unsafe.Pointer(out)) //注意需要手动调用free
fmt.Println("print in go ", outStr)
}
|
运行结果为
1
2
|
print in c is hello world pass from go
print in go hello world return from c
|
唯一需要注意的是,对于C类型的字符串要记得手动调用C.free防止内存泄漏。
对于其他数组类型的转换,最近在工作中遇到一个问题,先看我的代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
package main
import "unsafe"
/*
#include <stdio.h>
#include <stdlib.h>
int pass_int_array(int* in, int len) {
int i = 0;
for (i = 0; i < len; i++) {
printf("%d ", in[i]);
}
return 0;
}
#cgo CFLAGS: -g
*/
import "C"
func main() {
a := []int{1, 2, 3, 4}
C.pass_int_array((*C.int)(unsafe.Pointer(&a[0])), 4)
}
|
我们可以看到,对于数组的转换是采用unsafe.Pointer
取go数组a的首地址显式转换成C int指针类型。OK,运行一下,看看打印结果是什么。
1 0 2 0
这是什么情况,没毛病啊,说好的1 2 3 4呢?我猜想是不是两边int占用的内存大小不一致呢?go int 8个字节,C int 4个字节,通过一段代码来验证一下吧。
1
2
3
4
|
g := 1
c := C.int(1)
fmt.Println("sizeof go int", unsafe.Sizeof(g))
fmt.Println("sizeof c int", unsafe.Sizeof(c))
|
运行结果
1
2
|
sizeof go int 8
sizeof c int 4
|
果然有毛病啊老铁,于是把go中数组改成a := []int32{1, 2, 3, 4}
再次运行,完美通过。
所以要注意,在这种跨语言调用中,对于int、double等类型的传递一定要明确指定字节大小
调用C++代码
无法直接调用C++代码,需要再封装一层,导出为C类型接口才行。