前言:这也是我记录cgo笔记的初衷,我偶尔需要把同事的c代码拿来用(白嫖),又不想展示自己可怜的C代码水平(毕竟没时间研究代码),so...
C代码模块化
这还是使用原作者(go语言高级编程)的一个例子,抽象一个名为hello的模块,模块的全部接口函数都在hello.h头文件定义:
//hello.h
void SayHello(const char* s);
// hello.c
#include "hello.h"
#include <stdio.h>
void SayHello(const char* s) {
puts(s);
}
package main
/*
#include <hello.h>
*/
import "C"
func main() {
C.SayHello(C.CString("Hello, Worldn"))
}
使用 go run . 进行运行发现程序可以正常运行。go mod可能会报错,初始化go mod即可。
静态库
如果有源码的情况下最好,但是也有时候,同事不给源码,只给库文件,这个时候就需要我们做相关调用了
我们先用纯C语言构造一个简单的静态库。我们要构造的静态库名叫number,库中只有一个number_add_mod函数,用于表示数论中的模加法运算。number库的文件都在number目录下。
number/number.h头文件只有一个纯C语言风格的函数声明:
//源码:《go语言高级编程》
//number/number.h
int number_add_mod(int a, int b, int mod);
number/number.c对应函数的实现:
//源码:《go语言高级编程》
#include "number.h"
int number_add_mod(int a, int b, int mod) {
return (a+b)%mod;
}
因为CGO使用的是GCC命令来编译和链接C和Go桥接的代码。因此静态库也必须是GCC兼容的格式。
通过以下命令可以生成一个叫libnumber.a的静态库:
$ cd ./number
$ gcc -c -o number.o number.c
$ ar rcs libnumber.a number.o
创建main.go文件如下:
package main
//#cgo CFLAGS: -I./number
//#cgo LDFLAGS: -L${SRCDIR}/number -lnumber
//
//#include "number.h"
import "C"
import "fmt"
func main() {
fmt.Println(C.number_add_mod(10, 5, 12))
}
其中有两个#cgo命令,分别是编译和链接参数。CFLAGS通过-I./number将number库对应头文件所在的目录加入头文件检索路径。LDFLAGS通过-L${SRCDIR}/number将编译后number静态库所在目录加为链接库检索路径,-lnumber表示链接libnumber.a静态库。需要注意的是,在链接部分的检索路径不能使用相对路径(C/C++代码的链接程序所限制),我们必须通过cgo特有的${SRCDIR}变量将源文件对应的当前目录路径展开为绝对路径(因此在windows平台中绝对路径不能有空白符号)
因为我们有number库的全部代码,所以我们可以用go generate工具来生成静态库,或者是通过Makefile来构建静态库。因此发布CGO源码包时,我们并不需要提前构建C静态库。
动态库
上面的源码,编译成动态库
$ cd number
$ gcc -shared -o libnumber.so number.c
因为动态库和静态库的基础名称都是libnumber,只是后缀名不同而已。因此Go语言部分的代码和静态库版本完全一样:
package main
//#cgo CFLAGS: -I./number
//#cgo LDFLAGS: -L${SRCDIR}/number -lnumber
//
//#include "number.h"
import "C"
import "fmt"
func main() {
fmt.Println(C.number_add_mod(10, 5, 12))
}
编译时GCC会自动找到libnumber.a或libnumber.so进行链接。当然根据我的实际情况,我基本上不需要动态库链接。
网友评论