概述
文章目录
- 1. 写在最前面
- 2. CGO 调用方式
- 2.1 引入 C 源码
- 2.1.1 将 C 源码嵌入 go 文件
- 2.1.2 将 C 的源文件嵌入到 go 项目
- 2.2 引入链接库的方式
- 2.2.1 链接库源文件的定义
- 2.2.2 在 go 源文件中引用动态链接库
- 3. CGO 调用 C++
- 3.1 demo
- 3.1.1 C++ 部分定义
- 3.1.2 C 部分定义
- 3.1.3 go 部分定义
- 4. 坑点
- 4.1 内存
- 4.2 栈
- 4.3 线程模型
- 5. 碎碎念
- 6. 参考资料
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 文件
-
hello.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")) }
注:
- import “C” 这句话要紧跟在注释之后,不能换行,否则报错
- 运行 go 文件的方式为
go run hello.go
2.1.2 将 C 的源文件嵌入到 go 项目
go 项目包括以下文件
-
hello.h
void SayHello(const char* s);
-
hello.c
#include "hello.h" #include <stdio.h> void SayHello(const char* s) { puts(s); }
-
hello.go
package main //#include "hello.h" import "C" func main() { C.SayHello(C.CString("hello, CGO n")) }
注
- 将 C 的源码模块化,在 go 中通过引入头文件的方式进行函数调用
- 此处须使用
go run "your/package"
或go build "your/package"
才可以。若本就在包路径下的话,也可以直接运行go run .
或go build
。
2.2 引入链接库的方式
此处以动态链接库为例子,go 项目下包括以下文件 — number.h、number.c、main.go。
2.2.1 链接库源文件的定义
-
number.h 的定义
int number_add_mod(int a, int b, int mod);
-
number.c 的定义
#include "number.h" int number_add_mod(int a, int b, int mod){ return (a+b)%mod; }
注:编译 number 的动态库 gcc -shared -o libnumber.so number.c
2.2.2 在 go 源文件中引用动态链接库
-
mian.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++ 部分定义
-
student.h
#include <iostream> using namespace std; class Student { public: Student(){} ~Student(){} void Operation(); void SetName(string name); string name; };
-
student.cpp
using namespace std; void Student::Operation() { cout << "Hi my name is " << name <<endl; } void Student::SetName(string name1) { name = name1;
-
interface.h
#ifdef __cplusplus extern "C"{ #endif void *stuCreate(); void initName(void *, char* name); void getStuName(void *); void getName(); #ifdef __cplusplus } #endif
-
interface.cpp
#include "student.h" #include "interface.h" #ifdef __cplusplus extern "C"{ #endif void *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 部分定义
-
mian.c
#include "interface.h" int main() { void *p = stuCreate(); char *name = "test"; initName(p, name); getStuName(p); getName(); return 0; }
注:
-
编译 main.c
gcc main.c -L. -lstu
-
运行 a.out,将会得到以下内容
$ ./a.out Hi my name is test Hi my name is
3.1.3 go 部分定义
-
main.go
// +build linux // +build amd64 // +build !noptlogin package 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 代码
go run main.go
,得到以下结果$ 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 栈
- C 栈是固定大小(ps 使用 pthread_create() 创建线程的时候,如果不指定分配堆栈大小,则会直接使用系统提供默认值,通过
ulimit -a
命令可以查看 - go 栈能够动态调整(ps go 1.3 之前使用 分段栈,1.4 之后使用 连续栈
4.3 线程模型
-
go 采用 GMP 的调度模型,会控制使用的内核线程的数量
-
CGO 在并发调用 c 语言的阻塞函数时,会导致线程数不受 GMP 控制,进而导致线程数暴增,例子 code ????
package main //#include<unistd.h> import "C" import ( "flag" "log" "net/http" _ "net/http/pprof" "runtime/debug" "sync" "time" ) const sleepTime = 60 func 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.WaitGroup for 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) }
注:
- 此处开启 go 程序使用的线程最大数量限制。
- 在调用 cgo 阻塞 sleep 函数的时候,会提示
runtime: program exceeds 10-thread limit fatal error: thread exhaustion
- 在调用 go 的 sleep 函数的时候,则不会提示该问题
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”的问题
最后
以上就是高贵长颈鹿为你收集整理的CGO 从入门到放弃的全部内容,希望文章能够帮你解决CGO 从入门到放弃所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复