本文由以下 4 篇合并而成:
- cgo使用报告(主体,含完整示例)
- TIPTOP GP 设置 CGo 环境
- Redhat 6.5 GCC 升级到 4.8
- BDL 调用 golang(cgo) 简单示例
第一部分:CGo 使用报告
环境要求
gcc 版本 ≥4.7.1
示例
最简单 Cgo
Go 中调用 C 语言的println函数
import ”C” 表示启用 CGo
1
2
3
4
5
| import "C"
func main() {
println("hello cgo")
}
|
导入头文件
import “C” 与 C 代码之间无空行
1
2
3
4
5
6
| // #include <stdio.h>
import "C"
func main() {
C.puts(C.CString("你好, GopherChina 2018!\n"))
}
|
调用自定义函数
1
2
3
4
5
6
7
8
9
10
11
12
| /*
#include <stdio.h>
static void SayHello(const char* s) {
puts(s);
}
*/
import "C"
func main() {
C.SayHello(C.CString("Hello, World\n"))
}
|
重写 C 模块
1
2
3
4
5
6
7
8
9
10
11
| // #include "hello.h"
import "C"
import "fmt"
//export SayHello
func SayHello(s *C.char) {
fmt.Print(C.GoString(s))
}
func main() {
C.SayHello(C.CString("ok"))
}
|
Go 面向 C 开放接口
1
2
3
4
5
6
7
8
9
10
11
12
| // extern void SayHello(char* s);
import "C"
import "fmt"
func main() {
C.SayHello(C.CString("Hello, World\n"))
}
//export SayHello
func SayHello(s *C.char) {
fmt.Print(C.GoString(s))
}
|
使用GoString代替 char*
1
2
3
4
5
6
7
8
9
10
11
12
| // extern void SayHello(_GoString_ s);
import "C"
import "fmt"
func main() {
C.SayHello("Hello, World\n")
}
//export SayHello
func SayHello(s string) {
fmt.Print(s)
}
|
Go 调用 C 函数
- 无返回值调用
1
2
3
4
5
6
7
8
9
10
| /*
static int add(int a, int b) {
return a+b;
}
*/
import "C"
func main() {
C.add(1, 1)
}
|
- C 函数可以有两个返回值,第二个返回值为错误代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| /*
static int add(int a, int b) {
return a+b;
}
*/
import "C"
import "fmt"
func main() {
v, err := C.add(1, 1)
// _, err := C.add(1, 1)
// v, _ := C.add(1, 1)
fmt.Println(v, err)
// Output:
// 4 <nil>
}
|
导出 Go 函数
1
2
3
4
5
6
7
| import "C"
//export GoAdd
func GoAdd(a, b C.int) C.int {
return a+b
}
// 可以导出私有函数
|
1
2
3
4
5
6
7
8
9
10
| // add.h
int c_add(int a, int b);
// add.c
#include "add.h"
#include "_cgo_export.h"
int c_add(int a, int b) {
return GoAdd(a, b)
}
|
- _GoString_ 类型导出
1
2
3
4
5
6
7
8
9
| // +build go1.10
// extern void SayHello(_GoString_ s);
import "C"
//export SayHello
func SayHello(s string) {
fmt.Print(s)
}
|
Go 中调用 C 的 qsort 函数
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
| /*
#include <stdlib.h>
#define DIM(x) (sizeof(x)/sizeof((x)[0]))
static int compare(const void* a, const void* b) {
return ( *(int*)a - *(int*)b );
}
static void qsort_proxy(int* values, size_t len, size_t elemsize) {
qsort(values, len, sizeof(values[0]), compare);
}
*/
import "C"
import "unsafe"
import "fmt"
func main() {
values := []int32{ 42, 9, 101, 95, 27, 25 };
C.qsort_proxy(
(*C.int)(unsafe.Pointer(&values[0])),
C.size_t(len(values)),
C.size_t(unsafe.Sizeof(values[0])),
)
fmt.Println(values)
}
|
在以上基础上比较函数替换为 Go 的函数
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
31
32
33
34
| /*
#include <stdlib.h>
#define DIM(x) (sizeof(x)/sizeof((x)[0]))
extern int go_qsort_compare(void* a, void* b);
static int compare(const void* a, const void* b) {
return go_qsort_compare((void*)(a), (void*)(b));
}
static void qsort_proxy(int* values, size_t len, size_t elemsize) {
qsort(values, len, sizeof(values[0]), compare);
}
*/
import "C"
import "unsafe"
import "fmt"
//export go_qsort_compare
func go_qsort_compare(a, b unsafe.Pointer) C.int {
pa := (*C.int)(a)
pb := (*C.int)(b)
return C.int(*pa - *pb)
}
func main() {
values := []int32{42, 9, 101, 95, 27, 25}
C.qsort_proxy(
(*C.int)(unsafe.Pointer(&values[0])),
C.size_t(len(values)),
C.size_t(unsafe.Sizeof(values[0])),
)
fmt.Println(values)
}
|
Go 函数作为参数传入,需要函数类型转换
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
| /*
#include <stdlib.h>
typedef int (*qsort_cmp_func_t)(const void* a, const void* b);
extern int go_qsort_compare(void* a, void* b);
*/
import "C"
import (
"fmt"
"unsafe"
)
func main() {
values := []int32{42, 9, 101, 95, 27, 25}
C.qsort(unsafe.Pointer(&values[0]),
C.size_t(len(values)), C.size_t(unsafe.Sizeof(values[0])),
(C.qsort_cmp_func_t)(unsafe.Pointer(C.go_qsort_compare)),
)
fmt.Println(values)
}
//export go_qsort_compare
func go_qsort_compare(a, b unsafe.Pointer) C.int {
pa := (*C.int)(a)
pb := (*C.int)(b)
return C.int(*pb - *pa)
}
|
Go→C→GO
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| //main.go
package main
/*
#include <stdlib.h>
void print_str(const char *s);
*/
import "C"
import "unsafe"
func Print(s string) {
cs := C.CString(s)
C.print_str(cs)
C.free(unsafe.Pointer(cs))
}
func main() {
str1 := "hi how are you\n"
Print(str1)
}
//export HelloByGo
func HelloByGo(name string) *C.char {
return C.CString("greeting " + name)
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| //exp.c
#include "stdio.h"
#include "stdlib.h"
#include "_cgo_export.h"
void print_str(const char *s)
{
printf("%s\n", s?s:"nil");
// C语言调用 Go语言函数
GoString name;
name.p = "123";
name.n = 3;
char *greeting = HelloByGo(name);
printf("call go: %s\n", greeting);
free(greeting);
}
|
1
2
3
4
| > go run .
hi how are you
call go: greeting 123
|
C→GO→C
1
2
3
4
5
6
| //hello.c
#include <stdio.h>
int helloFromC() {
printf("Hi from C\n");
return 0;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| //main.go
package main
/*
extern int helloFromC();
*/
import "C"
import "fmt"
//export HelloFromGo
func HelloFromGo() {
fmt.Printf("Hello from Go!\n")
C.helloFromC()
}
func main() {
}
|
1
| go build -o hello.so -buildmode=c-shared .
|
1
2
3
4
5
6
7
8
9
10
| x/main.c
#include <stdio.h>
#include "hello.h"
int main() {
printf("use hello lib from C:\n");
HelloFromGo();
return 0;
}
|
1
2
| cp hello.h x/ && cp hello.so x/
gcc -o main main.c hello.so
|
总结
函数
1
2
3
| C.CString("") // 将go string转为char*
C.puts() // 调用C语言的puts函数输出 C 字符串
C.GoString() // char* 转为 go string
|
数据类型
1
2
| *C.char // string
_GoString_ // == string
|
类型转换
Go 类型
1
2
| var p unsafe.Pointer = nil // unsafe
var q uintptr = uintptr(p) // builtin
|
C 类型
1
2
| void *p = NULL;
uintptr_t q = (uintptr_t)(p); // <stdint.h>
|
Go unsafe
1
2
3
4
5
6
| type ArbitraryType int
type Pointer *ArbitraryType //面向编译器无法保证安全的指针类型转换
func Sizeof(x ArbitraryType) uintptr //值所对应变量在内存中的大小
func Alignof(x ArbitraryType) uintptr //值所对应变量在内存中地址几个字节对齐
func Offsetof(x ArbitraryType) uintptr //结构体中成员的偏移量
|
C unsafe
1
2
3
4
5
| typedef void* Pointer; // C指针的安全性永远需要自己负责
sizeof(type or expression); // C 关键字, 语义和 Go 基本一致
offsetof(type, member); // <stddef.h> // 宏, 展开为表达式, 语义和 Go 基本一致
alignof(type-id); // C++ 11 // 新特性, 可忽略
|
字符串和切片结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| // Go
type reflect.StringHeader struct {
Data uintptr
Len int
}
type reflect.SliceHeader struct {
Data uintptr
Len int
Cap int
}
// C
typedef struct { const char *p; GoInt n; } GoString;
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
// reflect 包定义的结构和CGO生成的C结构是一致的
// GoString 和 GoSlice 和头部结构是兼容的
|
int32 与 char*之间转换
1
2
3
4
5
6
7
| // int32 => *C.char
var x = int32(9527)
var p *C.char = (*C.char)(unsafe.Pointer(uintptr(x)))
// *C.char => int32
var q int32 = int32(uintptr(unsafe.Pointer(p)))
fmt.Printf("%d \n", q)
|
两个不同类型之间转换
1
2
3
4
5
| var p *X
var q *Y
q = (*Y)(unsafe.Pointer(p)) // *X => *Y
p = (*X)(unsafe.Pointer(q)) // *Y => *X
|
不同数组类型的转换
1
2
3
4
5
6
7
8
9
10
11
| // 所有切片拥有相同的头部 reflect.SliceHeader
// 重新构造切片头部即可完成转换
var p []X
var q []Y // q = p
pHdr := (*reflect.SliceHeader)(unsafe.Pointer(&p))
qHdr := (*reflect.SliceHeader)(unsafe.Pointer(&q))
pHdr.Data = qHdr.Data
pHdr.Len = qHdr.Len * unsafe.Sizeof(q[0]) / unsafe.Sizeof(p[0])
pHdr.Cap = qHdr.Cap * unsafe.Sizeof(q[0]) / unsafe.Sizeof(p[0])
|
导出
C 导出 so
1
2
3
4
5
6
7
8
| // number/number.h
int number_add_mod(int a, int b, int mod);
// number/number.c
int number_add_mod(int a, int b, int mod) {
return (a+b)%mod;
}
$ gcc -shared -o libnumber.so number.c
|
Go 导出静态库和动态库
1
2
3
4
5
6
7
8
9
10
11
12
| package main
import "C"
func main() {}
//export number_add_mod
func number_add_mod(a, b, mod C.int) C.int {
return (a + b) % mod
}
$ go build -buildmode=c-archive -o number.a
$ go build -buildmode=c-shared -o number.so
|
第二部分:TIPTOP GP 设置 CGo 环境
C 编译器
GCC 版本确认
RedHat6.5 C 编译器是 GCC,只要版本大于 4.7 即可。
使用 gcc -v 查看 gcc 版本。

GCC 版本升级
如果你的版本低于 4.7,无法使用 CGo,需要至少升级到 4.7 才可以。建议直接升级到 4.8。
升级步骤见此 TODO,需要有一些 linux 使用经验才可升级,请谨慎,如果有测试主机在测试主机先升级试用。
Go 编译器
Go 编译器设置比较简单,只要下载上传到服务器解压即可,不需要编译。
官网下载
官网下载地址

下载 linux 版本,程序版本大于 go1.10 即可。建议 go1.18 版本以上,可以正常使用 go work
解压安装
1
2
3
| <topprod:/u1/usr/tiptop> ls -l go1.22*
-rw-r--r-- 1 tiptop tiptop 68958945 May 28 11:41 go1.22.3.linux-amd64.tar.gz
<topprod:/u1/usr/tiptop> tar -zxvf go1.22.3.linux-amd64.tar.gz -C ./go
|
./go 是你要安装的目录位置
1
2
3
4
| <topprod:/u1/usr/tiptop/go/bin> ls -l ~/go/bin/
total 14572
-rwxr-xr-x 1 tiptop tiptop 12403672 Aug 5 2023 go
-rwxr-xr-x 1 tiptop tiptop 2513259 Aug 5 2023 gofmt
|
~/go/bin/go,这个文件就是 go 的编译器
~/go/bin/go version 查看当前版本
1
2
| <topprod:/u1/usr/tiptop> ~/go/bin/go version
go version go1.21.0 linux/amd64
|
💡
你可以将 go 编译器设置到$PATH 路径,避免每次都要输入路径
环境变量
C 语言和 CGo 编译后的文件,都是 so 文件,程序调用时是根据几个环境变量进行查询。
所以有一些环境变量需要知道或者重新设置。
- $FGLLDPATH BDL 运行时会在此路径遍历 so 文件,最终需要 BDL 调用的 so 文件必须包含在此目录下。
- $FGLDIR 目录,此目录是 C 语言 TIPTOP GP 环境下,共享包的位置,最终编译的 so 文件的依赖 so 文件需要放到此目录下。
💡
使用 ln -s 软连接可以不移动文件,将链接文件放到对应目录
测试环境设置是否成功
第三部分:Redhat 6.5 GCC 升级到 4.8
升级前版本信息
red hat
1
2
| [root@ERP-AP02 ~]# cat /etc/redhat-release
Red Hat Enterprise Linux Server release 6.5 (Santiago)
|
GCC
1
2
3
4
5
6
| [root@ERP-AP02 ~]# gcc -v
Using built-in specs.
Target: x86_64-redhat-linux
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-bootstrap --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-languages=c,c++,objc,obj-c++,java,fortran,ada --enable-java-awt=gtk --disable-dssi --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-1.5.0.0/jre --enable-libgcj-multifile --enable-java-maintainer-mode --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --disable-libjava-multilib --with-ppl --with-cloog --with-tune=generic --with-arch_32=i686 --build=x86_64-redhat-linux
Thread model: posix
gcc version 4.4.7 20120313 (Red Hat 4.4.7-4) (GCC)
|
1
2
3
4
5
6
| [root@ERP-AP02 ~]# gcc -v
Using built-in specs.
Target: x86_64-redhat-linux
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-bootstrap --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-languages=c,c++,objc,obj-c++,java,fortran,ada --enable-java-awt=gtk --disable-dssi --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-1.5.0.0/jre --enable-libgcj-multifile --enable-java-maintainer-mode --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --disable-libjava-multilib --with-ppl --with-cloog --with-tune=generic --with-arch_32=i686 --build=x86_64-redhat-linux
Thread model: posix
gcc version 4.4.7 20120313 (Red Hat 4.4.7-4) (GCC)
|
升级顺序
安装SCL
centos-release-scl-rh-1-1.el6.centos.noarch.rpm
rpm -ivh centos-release-scl-rh-1-1.el6.centos.noarch.rpm
下载源码
wget http://ftp.gnu.org/gnu/gcc/gcc-4.8.0/gcc-4.8.0.tar.bz2
tar -jxvf gcc-4.8.0.tar.bz2
下载依赖库
cd gcc-4.8.0
./contrib/download_prerequisites
cd ..
建立输出目录
mkdir gcc-build-4.8.0
产生makefile文件
cd gcc-build-4.8.0
../gcc-4.8.0/configure –enable-checking=release –enable-languages=c,c++ –disable-multilib
编译
make -j4
安装
make install
切换新版本
updatedb
locate gcc-4.8|tail
ls /usr/local/bin | grep gcc
update-alternatives –install /usr/bin/gcc gcc /usr/local/bin/i686-pc-linux-gnu-gcc 40
第四部分:BDL 调用 golang(cgo) 简单示例
golang 编译
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| package main
import "C"
import "fmt"
//export hello
func hello(){
fmt.Println("hello world")
}
//export add
func add(a,b int) int {
return a+b
}
func main(){
}
|
golang中要导出的函数前要增加备注//export hello,且必须写有main函数。
import "C"也是必须导入的。
使用命令
go build -buildmode=c-shared -o xxx.so yyy 进行编译产生.so和.h文件,其中xxx是.so的文件名,yyy是项目名称。
如果编译之后未产生.h 文件,说明没有导出任何函数,请检查go源码。
编译产生的.so文件运行时需要,需要加载到$FGLDIR/lib目录下,也可以ln -s创建软链接。
C语言封装
BDL调用C语言可以见BDL调用C语言
源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| #include <stdio.h>
#include "f2c/fglExt.h"
#include "libsum.h"
int sum(int);
GoInt Sum(GoInt,GoInt);
UsrFunction usrFunctions[]={
{ "sum", sum, 2, 1 },
{ 0,0,0,0 }
};
int sum(int in_num){
int s,a=0,b=0;
popint(&a);
popint(&b);
s = Sum(a,b);
pushint(s);
return 1;
}
|
- 产生.o 文件
1
| gcc -c -I $FGLDIR/include -fPIC demo.c
|
- 产生.so文件
1
| gcc -shared -o libsuma.so demo.o -L $FGLDIR/lib -lfgl -lsum
|
- 将产生的.so文件放到
$FGLLDPATH的路径中
BDL调用
1
2
3
4
5
6
7
| import libsuma
function main()
define s integer
call sum(2,3) returning s
display sfmt("\\n2+3=%1",s)
end function
|
BDL调用时只要调用libsuma即可,libsum会在libsuma调用后自动加载。
直接调用的.so 在$FGLLDPATH目录,c调用的.so在$FGLDIR/lib目录。