我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Go语言怎么实现CGO编程

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Go语言怎么实现CGO编程

本文小编为大家详细介绍“Go语言怎么实现CGO编程”,内容详细,步骤清晰,细节处理妥当,希望这篇“Go语言怎么实现CGO编程”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。

    基于 C 标准库实现最简单的 CGO 程序

    下面是我们构建的最简 CGO 程序:

    // hello.gopackage main//#include <stdio.h>import "C"func main() {    C.puts(C.CString("Hello, this is a CGO demo.\n"))}

    基于自己写的 C 函数构建 CGO 程序

    上面就是使用了C标准库中已有的函数来实现的一个简单的 CGO 程序。

    下面我们再来看个例子。先自定义一个叫 SayHello 的 C 函数来实现打印,然后从 Go 语言环境中调用这个 SayHello 函数:

    // hello.gopackage mainimport "C"func main() {    C.SayHello(C.CString("Hello, World\n"))}

    除了 SayHello 函数是我们自己实现的之外,其它的部分和前面的例子基本相似。

    我们也可以将 SayHello 函数放到当前目录下的一个 C 语言源文件中(后缀名必须是.c)。因为是编写在独立的 C 文件中,为了允许外部引用,所以需要去掉函数的 static 修饰符。

    // hello.c#include <stdio.h>void SayHello(const char* s) {    puts(s);}

    然后在 CGO 部分先声明 SayHello 函数,其它部分不变:

    // hello.gopackage main//void SayHello(const char* s);import "C"func main() {    C.SayHello(C.CString("Hello, World\n"))}

    模块化以上例子

    在编程过程中,抽象和模块化是将复杂问题简化的通用手段。当代码语句变多时,我们可以将相似的代码封装到一个个函数中;当程序中的函数变多时,我们将函数拆分到不同的文件或模块中。

    在前面的例子中,我们可以抽象一个名为 hello 的模块,模块的全部接口函数都声明在 hello.h 头文件中:

    // hello.hvoid SayHello(const char* s);

    下面是 SayHello 函数的 C 语言实现,对应 hello.c 文件:

    // hello.c#include "hello.h"#include <stdio.h>void SayHello(const char* s) {    puts(s);}

    我们也可以用 C++语言来重新实现这个 C 语言函数:

    // hello.cpp#include <iostream>extern "C" {    #include "hello.h"}void SayHello(const char* s) {    std::cout << s;}

    用 Go 实现 C 函数并导出

    其实 CGO 不仅仅用于 Go 语言中调用 C 语言函数,还可以用于导出 Go 语言函数给 C 语言函数调用。

    在前面的例子中,我们已经抽象一个名为 hello 的模块,模块的全部接口函数都在 hello.h 头文件中定义:

    // hello.hvoid SayHello(char* s);

    现在我们创建一个 hello.go 文件,用 Go 语言重新实现 C 语言接口的 SayHello 函数:

    // hello.gopackage mainimport "C"import "fmt"//export SayHellofunc SayHello(s *C.char) {    fmt.Print(C.GoString(s))}

    我们通过 CGO 的 //export SayHello 指令将 Go 语言实现的函数 SayHello 导出为 C 语言函数。为了适配 CGO 导出的 C 语言函数,我们禁止了在函数的声明语句中的 const 修饰符。

    通过面向 C 语言接口的编程技术,我们不仅仅解放了函数的实现者,同时也简化的函数的使用者。现在我们可以将 SayHello 当作一个标准库的函数使用,如下:

    // main.gopackage main//#include <hello.h>import "C"func main() {    C.SayHello(C.CString("Hello, World\n"))}

    用 C 接口的方式实现 Go 编程

    简单来说就是将上面例子中的几个文件重新合并到一个 Go 文件实现,如下:

    // main.gopackage main//void SayHello(char* s);import "C"import (    "fmt")func main() {    C.SayHello(C.CString("Hello, World\n"))}//export SayHellofunc SayHello(s *C.char) {    fmt.Print(C.GoString(s))}

    虽然看起来全部是 Go 语言代码,但是执行的时候是先从 Go 语言的 main 函数,到 CGO 自动生成的 C 语言版本 SayHello 桥接函数,最后又回到了 Go 语言环境的 SayHello 函数。这个代码包含了 CGO 编程的精华。

    CGO 的主要基础参数

    import "C" 语句说明

    如果在 Go 代码中出现了 import "C" 语句则表示使用了 CGO 特性,紧跟在这行语句前面的注释是一种特殊语法,里面包含的是正常的 C 语言代码。当确保 CGO 启用的情况下,还可以在当前目录中包含 C/C++对应的源文件。比如上面的例子。

    #cgo 语句说明

    import "C" 语句前的注释中可以通过 #cgo 语句设置编译阶段和链接阶段的相关参数。编译阶段的参数主要用于定义相关宏和指定头文件检索路径。链接阶段的参数主要是指定库文件检索路径和要链接的库文件。

    比如:

    // #cgo CFLAGS: -DADDR_DEBUG=1 -I./include// #cgo LDFLAGS: -L/usr/local/lib -linet_addr// #include <inet_addr.h>import "C"

    上面的代码中,CFLAGS 部分,-D 部分定义了宏 ADDR_DEBUG,值为 1;-I 定义了头文件包含的检索目录。LDFLAGS 部分,-L 指定了链接时库文件检索目录,-l 指定了链接时需要链接 inet_addr 库。

    因为 C/C++遗留的问题,C 头文件检索目录可以是相对目录,但是库文件检索目录则需要绝对路径。

    为什么要引入 CGO

    突破 Go 创建切片的内存限制

    由于 Go 语言实现的限制,我们无法在 Go 语言中创建大于 2GB 内存的切片(可参考 makeslice 实现源码)。不过借助 cgo 技术,我们可以在 C 语言环境创建大于 2GB 的内存,然后转为 Go 语言的切片使用:

    package mainimport "C"import "unsafe"func makeByteSlize(n int) []byte {    p := C.makeslice(C.size_t(n))    return ((*[1 << 31]byte)(p))[0:n:n]}func freeByteSlice(p []byte) {    C.free(unsafe.Pointer(&p[0]))}func main() {    s := makeByteSlize(1<<32+1)    s[len(s)-1] = 255    print(s[len(s)-1])    freeByteSlice(s)}

    例子中我们通过 makeByteSlize 来创建大于 4G 内存大小的切片,从而绕过了 Go 语言实现的限制。而 freeByteSlice 辅助函数则用于释放从 C 语言函数创建的切片。

    因为 C 语言内存空间是稳定的,基于 C 语言内存构造的切片也是稳定的,不会因为 Go 语言栈的变化而被移动。

    方便在 Go 语言中接入使用 C/C++的软件资源

    CGO 提供了 golang 和 C 语言相互调用的机制。而在某些第三方库可能只有 C/C++ 的实现,也没有必要用纯 golang 重新实现,因为可能工作量比较大,比较耗时,这时候 CGO 就派上用场了。

    被调用的 C 代码可以直接以源代码形式提供或者打包静态库或动态库在编译时链接。

    这里推荐使用静态库的方式,这样方便代码隔离,也符合 Go 的哲学。

    CGO 带来的问题

    构建时间变长

    当你在 Go 包中导入 "C" 时,go build 需要做更多的工作来构建你的代码。

    • 需要调用 cgo 工具来生成 C 到 Go 和 Go 到 C 的相关代码。

    • 系统中的 C 编译器会为软件包中的每个 C 文件进行调用处理。

    • 各个编译单元被合并到一个 .o 文件中。

    • 生成的 .o 文件会通过系统的链接器,对其引用的共享对象进行修正。

    构建变得复杂

    在引入了 cgo 之后,你需要设置所有的环境变量,跟踪可能安装在奇怪地方的共享对象和头文件等。

    另外需要注意,Go 支持许多的平台,而 cgo 并不是。需要安装 C 编译器,而不仅仅是 Go 编译器。而且可能还需要安装你的项目所依赖的 C 语言库,这也是需要技术成本的。

    Go 和 C 内存模型不同

    内存管理变得复杂,C 是没有垃圾收集的,而 go 有,两者的内存管理机制不同,可能会带来内存泄漏。

    CGO 是 Go 语言和 C 语言的桥梁,它使二者在二进制接口层面实现了互通,但是我们要注意因两种语言的内存模型的差异而可能引起的问题。

    如果在 CGO 处理的跨语言函数调用时涉及到了指针的传递,则可能会出现 Go 语言和 C 语言共享某一段内存的场景。

    我们知道 C 语言的内存在分配之后就是稳定的,但是 Go 语言因为函数栈的动态伸缩可能导致栈中内存地址的移动(这是 Go 和 C 内存模型的最大差异)。如果 C 语言持有的是移动之前的 Go 指针,那么以旧指针访问 Go 对象时会导致程序崩溃。

    使用 C 静态库实现

    CGO 在使用 C/C++资源的时候一般有三种形式:

    • 直接使用源码;

    • 链接静态库;

    • 链接动态库。

    直接使用源码就是在 import "C" 之前的注释部分包含 C 代码,或者在当前包中包含 C/C++源文件。

    链接静态库和动态库的方式比较类似,都是通过在 LDFLAGS 选项指定要链接的库方式链接。这里主要关注在 CGO 中如何使用静态库的问题。

    具体实现

    如果 CGO 中引入的 C/C++资源有代码而且代码规模也比较小,直接使用源码是最理想的方式,但很多时候我们并没有源代码,或者从 C/C++源代码开始构建的过程异常复杂,这种时候使用 C 静态库也是一个不错的选择。

    静态库因为是静态链接,最终的目标程序并不会产生额外的运行时依赖,也不会出现动态库特有的跨运行时资源管理的错误。

    我们先用纯 C 语言构造一个简单的静态库。我们要构造的静态库名叫 sum,库中只有一个 sum_add 函数,用于表示数论中的模加法运算。sum 库的文件都在 sum 目录下。

    sum/sum.h 头文件只有一个纯 C 语言风格的函数声明:

    int sum_add(int a, int b);

    sum/sum.c 对应函数的实现:

    #include "sum.h"int sum_add(int a, int b) {    return a+b;}

    通过以下命令可以生成一个叫 libsum.a 的静态库:

    $ cd ./sum$ gcc -c -o sum.o sum.c$ ar rcs libsum.a sum.o

    生成 libsum.a 静态库之后,放到当前的lib目录下,我们就可以在 CGO 中使用该资源了。

    创建 main.go 文件如下:

    package mainimport "C"import "fmt"func main() {    fmt.Println(C.sum_add(10, 5))}

    其中有两个 #cgo 命令,分别是编译和链接参数。

    CFLAGS 通过 -I./sum 将 sum 库对应头文件所在的目录加入头文件检索路径。

    LDFLAGS 通过 -L./lib 将编译后 sum 静态库所在目录加为链接库检索路径,-lsum 表示链接 libsum.a 静态库。

    需要注意的是,在链接部分的检索路径不能使用相对路径(C/C++代码的链接程序所限制)

    实战应用

    这里以一个实际案例(分两块代码)来说明 CGO 如何使用静态库的。案例实现的功能说明:

    • c++ 代码实现初始化配置、解析传入的 mq 消息,并处理具体的逻辑

    • go 代码实现初始化相关配置(mq 等)、监听订单消息等工作

    C++ 代码主要实现

    #include <iostream>extern "C"{    int init(int logLevel, int disId);    void RecvAndDealMessage(char* sbuf, int len);}// 初始化int init(int logLevel, int disId){    g_xmfDisId = disId;     // 服务初始化    if(CCGI_STUB_CNTL->Initialize() != 0)    {        printf("CCGI_STUB_CNTL->Init failed\n");        return -1;    }    CCGI_STUB_CNTL->setTimeout(5);    // 日志初始化    std::string strModuleName = "xxxxxx";    int iRet = MD_LOG->QuickInitForAPP(strModuleName.c_str(), MD_LOG_FILE_PATH, logLevel);    if (iRet != 0)    {        printf("log init failed. module:%s logswitch:%d ret:%d", strModuleName.c_str(), logLevel, iRet);        return 1;    }    else    {        printf("Init log Ok\n");    }    MD_COMM_LOG_DEBUG("Log Init Finished. level:%d", logLevel);    return iRet;}// 处理消息数据void RecvAndDealMessage(char* sbuf, int len){    MD_COMM_LOG_DEBUG("Begin receive message...");    MessageContainer oMsgCon;  char strbuf[1024];  if(len > 1024)  {  MD_COMM_LOG_ERR(MESSAGE_TOO_LONG, "len = %d, message too long.", len);  return ;  }    snprintf(strbuf, 1024, "%s", sbuf);    MD_COMM_LOG_DEBUG("recvmessage:[%s] len:[%d]", strbuf, len);    //解析并处理收到的消息    DealMsg(strbuf, oMsgCon);}

    Go 代码主要实现

    main 函数实现:

    package mainimport "C"func main()  {  //解析参数if Init() {defer func() {if err := recover(); err != nil {md_log.Errorf(-100, nil, "panic:%v, stack:%v", err, string(debug.Stack()))}}()for {//业务处理run()}}}

    init 函数实现:

    func Init() bool {iniFile, err := ini.LoadFile(os.Args[1])if err != nil {fmt.Println("load config faild, config:", os.Args[1])return false}logswitch := iniFile.GetInt("biz","logswitch",255)md_log.Init(DAEMON_NAME, iniFile.GetInt("biz","logswitch",255))md_log.Debugf("log init success!")  // cgo 调用c++初始化函数ret := C.init(C.int(logswitch),C.int(xmf_dis_id))if  ret != 0 {fmt.Printf("init failed ret:%v \n", ret)return false}fmt.Println("initial success!")return true}

    run 函数代码:

    func run()  {var oConsumer rabbitmq.ConsumeroConsumer.Init(Mqdns, MqexchangeName, Mqqueue, Mqroute)msgs, err := oConsumer.StartConsume(Mqqueue,false)if err != nil{fmt.Printf("oConsumer.StartConsume failed:%+v, arg:%+v \n",err, Mqreturn}for msg := range msgs{strMsg := string(msg.Body)msg.Ack(true)    // 调用 C++ 处理消息的函数C.RecvAndDealMessage(C.CString(strMsg), C.int(len(strMsg))) //c++ 处理mq消息}}

    读到这里,这篇“Go语言怎么实现CGO编程”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注编程网行业资讯频道。

    免责声明:

    ① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

    ② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

    Go语言怎么实现CGO编程

    下载Word文档到电脑,方便收藏和打印~

    下载Word文档

    猜你喜欢

    Go语言怎么实现CGO编程

    本文小编为大家详细介绍“Go语言怎么实现CGO编程”,内容详细,步骤清晰,细节处理妥当,希望这篇“Go语言怎么实现CGO编程”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。基于 C 标准库实现最简单的 CGO 程序
    2023-07-05

    Go语言开发快速学习CGO编程

    这篇文章主要为大家介绍了Go语言开发之快速学习CGO编程,看了本文你就会发现CGO编程其实没有想象的那么难,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-05-14

    go语言多线程怎么实现

    在Go语言中,可以使用goroutine来实现多线程。goroutine是Go语言中的轻量级线程,可以同时运行在一个操作系统线程上。要创建一个goroutine,只需要在函数调用前加上关键字go即可,例如:func main() {go
    2023-10-21

    go语言是什么编程语言

    go语言是是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。Go是一种过程编程语言,可用于快速机器代码编译;它提供了并发机制,可以轻松开发多核和联网的机器级程序;它提供对接口和类型嵌入的支持。
    2023-05-14

    GO语言怎么实现协程池管理

    本篇内容介绍了“GO语言怎么实现协程池管理”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!使用channel实现协程池通过 Channel 实
    2023-06-20

    go语言怎么进行网络编程

    go语言进行网络编程的步骤如下:1、导入net包和其他需要的包;2、创建服务器或客户端连接;3、处理连接,为每个连接创建一个新的“goroutine”来处理;4、处理数据,使用“conn.Read()”读取数据,使用“conn.Write(
    go语言怎么进行网络编程
    2023-12-13

    怎么用go语言编程实现二维码生成及识别

    本文小编为大家详细介绍“怎么用go语言编程实现二维码生成及识别”,内容详细,步骤清晰,细节处理妥当,希望这篇“怎么用go语言编程实现二维码生成及识别”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。安装 go-qrc
    2023-06-30

    Go语言是怎样的一种编程语言?

    Go语言是一种由Google开发的开源编程语言,也被称为Golang。它的设计目标是提高开发人员的工作效率,同时保持高性能和可靠性。Go语言的设计借鉴了许多其他编程语言的优点,包括静态类型、垃圾回收、并发编程等特性,使得它成为一种功能丰富且
    Go语言是怎样的一种编程语言?
    2024-03-07

    C语言中怎么实现泛型编程

    这篇文章给大家介绍C语言中怎么实现泛型编程,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。泛型编程(generic programming)是程序设计语言的一种风格或范式。泛型允许程序员在强类型程序设计语言中编写代码时
    2023-06-15

    Go语言:轻量、高效的现代编程语言

    go语言是一种由谷歌开发的开源编程语言,以其轻量、高效、高并发性和简洁语法而著称。它广泛应用于云计算、网络编程和机器学习等领域。安装go语言时,需要执行以下步骤:1. 下载安装程序;2. 运行安装程序。Go语言:轻量、高效的现代编程语言简
    Go语言:轻量、高效的现代编程语言
    2024-04-08

    简化逻辑流程:Go语言实现无else编程实践

    Go语言实现无else编程实践在编程中,我们经常会使用if-else语句来实现条件判断。然而,在一些情况下,过多的嵌套if-else语句会使代码变得复杂难懂。本文将介绍一种使用Go语言实现无else编程实践的方法,通过简化逻辑流程来提高代
    简化逻辑流程:Go语言实现无else编程实践
    2024-03-13

    编程热搜

    • Python 学习之路 - Python
      一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
      Python 学习之路 - Python
    • chatgpt的中文全称是什么
      chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
      chatgpt的中文全称是什么
    • C/C++中extern函数使用详解
    • C/C++可变参数的使用
      可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
      C/C++可变参数的使用
    • css样式文件该放在哪里
    • php中数组下标必须是连续的吗
    • Python 3 教程
      Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
      Python 3 教程
    • Python pip包管理
      一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
      Python pip包管理
    • ubuntu如何重新编译内核
    • 改善Java代码之慎用java动态编译

    目录