1. 写在最前面
作为一只弱小可怜又无助的 go 开发,在接到要调用 c++ 的接口需求的时候,我的内心是忐忑的。但是作为一个种成熟的语言,go 的 GGO 特性已经支持了调用 C/C++,撒花。
注: CGO 特性支持在 go 语言中调用 C 语言,这一特性使得 go 能够站在 C 的肩膀上,直接可以使用 C 沉淀多年的库。
调用 C++ 的库可以采用通过 C 语言包装的方式
2. CGO 调用方式
CGO 在调用 C 的时候主要有两种方式:
- 引入 C 源码的方式
- 引入动态或者静态链接库的方式
2.1 引入 C 源码
2.1.1 将 C 源码嵌入 go 文件
package main/*
#include <stdio.h>
static void SayHello(const char* s) {puts(s);
}
*/
import "C"func main() {C.SayHello(C.CString("hello, CGO \n"))
}
2.1.2 将 C 的源文件嵌入到 go 项目
go 项目包括以下文件
void SayHello(const char* s);
#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, CGO \n"))
}
2.2 引入链接库的方式
此处以动态链接库为例子,go 项目下包括以下文件 — number.h、number.c、main.go。
2.2.1 链接库源文件的定义
int number_add_mod(int a, int b, int mod);
#include "number.h"int number_add_mod(int a, int b, int mod){return (a+b)%mod;
}
gcc -shared -o libnumber.so number.c
2.2.2 在 go 源文件中引用动态链接库
package main//#cgo CFLAGS: -I./
//#cgo LDFLAGS: -L./ -lnumber
//
//#include "number.h"
import "C"
import "fmt"func main() {fmt.Println("number_add_mod", C.number_add_mod(10, 5, 12))
}
go run .go build
3. CGO 调用 C++
CGO 是 C 语言和 go 语言之间的桥梁,无法直接支持 C++ 的类。但 C++ 是兼容 C 语言的,所以可通过增加一组 C 函数作为 C++ 类和 CGO 之间的桥梁,间接的支持 C++ 和 go 之间的互联。
3.1 demo
demo 包括以下内容:
- C++ 部分:student.h、student.cpp、interface.h、interface.cpp
- C 部分:mian.c
- go 部分:main.go
3.1.1 C++ 部分定义
#include <iostream>
using namespace std;class Student {
public:Student(){}~Student(){}void Operation();void SetName(string name);string name;
};
using namespace std;
void Student::Operation()
{cout << "Hi my name is " << name <<endl;
}
void Student::SetName(string name1)
{name = name1;
#ifdef __cplusplus
extern "C"{
#endifvoid *stuCreate();
void initName(void *, char* name);
void getStuName(void *);
void getName();#ifdef __cplusplus
}
#endif
#include "student.h"
#include "interface.h"#ifdef __cplusplus
extern "C"{
#endifvoid *stuCreate()
{return new Student();
}void getStuName(void *p)
{static_cast<Student *>(p)->Operation();
}void initName(void *p, char* name1)
{static_cast<Student *>(p)->SetName(name1);
}void getName()
{Student obj;obj.Operation();
}#ifdef __cplusplus
}
#endif
注:生成动态库的命令
g++ student.cpp interface.cpp -fPIC -shared -o libstu.so
3.1.2 C 部分定义
#include "interface.h"int main()
{void *p = stuCreate();char *name = "test";initName(p, name);getStuName(p);getName();return 0;
}
注:
$ ./a.out
Hi my name is test
Hi my name is
3.1.3 go 部分定义
// +build linux
// +build amd64
// +build !noptloginpackage main/*
#cgo CFLAGS: -I./
#cgo LDFLAGS: -L./ -lstu
#include <stdlib.h>
#include <stdio.h>
#include "interface.h" //非标准c头文件,所以用引号
*/
import "C"import ("unsafe"
)func main() {name := "test!"cStr := C.CString(name)defer C.free(unsafe.Pointer(cStr))obj := C.stuCreate()C.initName(obj, cStr)C.getStuName(obj)
}
注:
$ go run main.go
Hi my name is test!
4. 坑点
4.1 内存
- C 的内存需要用户控制申请和释放的时机
- go 中用户申请内存后,由 GC 机制控制内存释放的策略
所以,在 C、go 互相调用的时候,如果涉及指针传递一定要注意内存申请/释放的问题。
Memory allocations made by C code are not known to Go's memory manager. When you create a C string with C.CString (or any C memory allocation) you must remember to free the memory when you're done with it by calling C.free.
释放内存的例子:
func Print(s string) {cs := C.CString(s)defer C.free(unsafe.Pointer(cs))C.fputs(cs, (*C.FILE)(C.stdout))
}
4.2 栈
ulimit -a
4.3 线程模型
package main//#include<unistd.h>
import "C"
import ("flag""log""net/http"_ "net/http/pprof""runtime/debug""sync""time"
)const sleepTime = 60func init() {debug.SetMaxThreads(10) //设置 go 程序允许开启的最大线程数量go func() {log.Println(http.ListenAndServe("localhost:6060", nil))}()
}func cSleep() {C.sleep(sleepTime)println("完成 c-sleep 睡眠")
}func goSleep() {time.Sleep(sleepTime * time.Second)println("完成 go-sleep 睡眠")
}func initGoroutines(isCSleep bool) {var wg sync.WaitGroupfor i := 0; i < 20; i++ {wg.Add(1)go func(isCSleep bool) {defer wg.Done()if isCSleep {cSleep()} else {goSleep()}}(isCSleep)}wg.Wait()
}func main() {isCSleep := flag.Bool("isCSleep", true, "确认是否调用 c 提供 sleep 函数")flag.Parse()initGoroutines(*isCSleep)
}
5. 碎碎念
啦啦啦,踩着尾巴写好啦。
- 尽全力在不愉快的日子里搜刮生活藏下的所有温柔
- 别气馁呀,你的好运正在披荆斩棘的向你跑过来哦。
6. 参考资料
- cgo 命令详解
- C?Go?Cgo?
- Command cgo
- How to use C++ in Go
- 全面总结:Golang 调用 c/c++
- 有没有方法调用 cgo 时不开线程
- 深入理解Go - CGO - 9.1 预备知识
- Golang cgo 调用 C++ 动态库 so 文件
- 如何在 GOLANG 中制造 STACK OVERFLOW 故障
- 关于“#ifdef __cplusplus” 和 “extern C”的问题