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”的问题