我是靠谱客的博主 高贵长颈鹿,最近开发中收集的这篇文章主要介绍CGO 从入门到放弃,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

文章目录

    • 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 从入门到放弃所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(63)

评论列表共有 0 条评论

立即
投稿
返回
顶部