第一节、 go中内嵌原生c代码
-
编译命令:go build -o 生成后文件名 main.go;
– 规则一:调用C函数时,必须是C.开头;
– 规则二:调用C函数时,参数也需要转为C类型; -
注意:
– CGO 会保留序文中的宏定义,但是并不会保留注释,也不支持#program,C 代码块中的#program 语句极可能产生未知错误;
– CGO 中使用 #cgo 关键字可以设置编译阶段和链接阶段的相关参数,可以使用 ${SRCDIR} 来表示 Go 包当前目录的绝对路径。
– 使用 C.结构名 或 C.struct_结构名 可以在 Go 代码段中定义 C 对象,并通过成员名访问结构体成员。 -
示例一
package main
/*
#include <stdio.h>
static void SayHello(const char* s) {
puts(s);
}
void myprintf(const char* s) {
printf("%s\n", s);
}
*/
import "C"
func main() {
C.SayHello(C.CString("Hello, World"))
C.myprintf(C.CString("haha"))
}
- 示例二
// test3.go
package main
/*
#cgo LDFLAGS: -L/usr/local/lib
#include <stdio.h>
#include <stdlib.h>
#define REPEAT_LIMIT 3 // CGO会保留C代码块中的宏定义
typedef struct{ // 自定义结构体
int repeat_time;
char* str;
}blob;
int SayHello(blob* pblob) { // 自定义函数
for ( ;pblob->repeat_time < REPEAT_LIMIT; pblob->repeat_time++){
puts(pblob->str);
}
return 0;
}
*/
import "C"
import (
"fmt"
"unsafe"
)
func main() {
cblob := C.blob{} // 在GO程序中创建的C对象,存储在Go的内存空间
cblob.repeat_time = 0
cblob.str = C.CString("Hello, World\n") // C.CString 会在C的内存空间申请一个C语言字符串对象,再将Go字符串拷贝到C字符串
ret := C.SayHello(&cblob) // &cblob 取C语言对象cblob的地址
fmt.Println("ret", ret)
fmt.Println("repeat_time", cblob.repeat_time)
C.free(unsafe.Pointer(cblob.str)) // C.CString 申请的C空间内存不会自动释放,需要显示调用C中的free释放
}
- 示例二中使用 C.CString 将 Go 字符串对象转化为 C 字符串对象,并将其传入 C 程序空间进行使用,由于 C 的内存空间不受 Go 的 GC 管理,因此需要显示的调用 C 语言的 free 来进行回收。
第二节、 go调用 C 源文件中的函数
- 在 Go 代码中,声明 SayHello() 函数,再引用 hello.c 源文件,就可以调起外部 C 源文件中的函数了。
// demo/hello.c
#include <stdio.h>
int SayHello() {
puts("Hello World");
return 0;
}
// demo/main.go
package main
/*
#include "hello.c"
int SayHello();
*/
import "C"
import (
"fmt"
)
func main() {
ret := C.SayHello()
fmt.Println(ret)
}
第三节、 go调用 C 语言so动态库
- 第一步:创建C语言动态库,源码如下
- 第二步:生成动态库 gcc -fPIC -shared -o libtest.so test.c
- 第三步:将动态库生效,否则会找不到该so库,命令如下:
---------- export LD_LIBRARY_PATH=./
---------- 备注:路径为动态库所在路径
// test.c
#include <stdio.h>
int add(int a, int b) {
return a+b;
}
// test.h
int add(int a, int b);
- 第一步:导入C动态库头文件;
- 第二步:导入动态库文件;
- 第三步:编译命令 go build -o main main.go;
---------- 也可以使用-x查看编译过程 go build -x -o main main.go; - 扩展:也可以通过go tool cgo main.go生成能够调用 C 语言代码的 Go 语言源文件,也就是说所有启用了 CGO 特性的 Go 代码,都会首先经过 cgo 的"预处理"。
// main.go
package main
/*
#cgo CFLAGS: -I ./test.h
#cgo LDFLAGS: -L./ -ltest
#include "test.h"
*/
import "C"
import (
"fmt"
)
func main() {
d := C.add(C.int(10), C.int(20))
fmt.Println(d)
}
在如上例子中,库文件是编译成动态库的,main程序链接的时候也是采用的动态库,可通过ldd main查看。
第四节、 Go 调用 C++
// demo/hello.h
int SayHello();
- 使用 C++来重新实现这个 C 函数
// demo/hello.cpp
#include <iostream>
extern "C" {
#include "hello.h"
}
int SayHello() {
std::cout<<"Hello World";
return 0;
}
- 最后再在 Go 代码中,引用 hello.h 头文件,就可以调用 C++实现的 SayHello 函数了
// demo/test6.go
package main
/*
#include "hello.h"
*/
import "C"
import (
"fmt"
)
func main() {
ret := C.SayHello()
fmt.Println(ret)
}