iis服务器助手广告广告
返回顶部
首页 > 资讯 > 精选 >Go语言怎么实现CGO编程
  • 696
分享到

Go语言怎么实现CGO编程

2023-07-05 22:07:59 696人浏览 安东尼
摘要

本文小编为大家详细介绍“Go语言怎么实现CGO编程”,内容详细,步骤清晰,细节处理妥当,希望这篇“Go语言怎么实现CGO编程”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。基于 C 标准库实现最简单的 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编程”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注编程网精选频道。

    --结束END--

    本文标题: Go语言怎么实现CGO编程

    本文链接: https://www.lsjlt.com/news/355730.html(转载时请注明来源链接)

    有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

    本篇文章演示代码以及资料文档资料下载

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

    下载Word文档
    猜你喜欢
    • Go语言怎么实现CGO编程
      本文小编为大家详细介绍“Go语言怎么实现CGO编程”,内容详细,步骤清晰,细节处理妥当,希望这篇“Go语言怎么实现CGO编程”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。基于 C 标准库实现最简单的 CGO 程序...
      99+
      2023-07-05
    • Go语言开发快速学习CGO编程
      目录快速上手 CGO 程序基于 C 标准库实现最简单的 CGO 程序基于自己写的 C 函数构建 CGO 程序模块化以上例子用 Go 实现 C 函数并导出用 C 接口的方式实现 Go ...
      99+
      2023-05-14
      Go语言开发CGO编程 CGO编程
    • Go语言中CGO的使用实践
      目录1. Go语言调用C函数例子:2. Go语言调用C库函数:3. Go语言导出函数给C语言使用:4. Go语言导出函数指针给c语言使用:   &nbs...
      99+
      2024-04-02
    • go语言多线程怎么实现
      在Go语言中,可以使用goroutine来实现多线程。goroutine是Go语言中的轻量级线程,可以同时运行在一个操作系统线程上。...
      99+
      2023-10-21
      go语言
    • go语言是什么编程语言
      go语言是是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。Go是一种过程编程语言,可用于快速机器代码编译;它提供了并发机制,可以轻松开发多核和联网的机器级程序;它提供对接口和类型嵌入的支持。本教程操作环境:...
      99+
      2023-05-14
      Golang go语言
    • go语言是编程语言吗
      本教程操作环境:windows7系统、GO 1.18版本、Dell G3电脑。Go(又称 Golang)是 Google 的 Robert Griesemer,Rob Pike 及 Ken Thompson 开发的一种静态强类型、编译型、并...
      99+
      2022-11-28
      Go Golang go语言
    • 如何在Go语言中实现并发编程?
      Go语言是一种现代化的编程语言,它的并发编程特性使其成为开发高性能网络应用和分布式系统的理想选择。本文将介绍Go语言中的并发编程,包括协程、通道和锁等重要概念,以及如何使用它们来实现高效的并发编程。 协程 协程是Go语言中并发编程的核心概...
      99+
      2023-06-21
      并发 ide npm
    • Go语言:轻量、高效的现代编程语言
      go语言是一种由谷歌开发的开源编程语言,以其轻量、高效、高并发性和简洁语法而著称。它广泛应用于云计算、网络编程和机器学习等领域。安装go语言时,需要执行以下步骤:1. 下载安装程序;2....
      99+
      2024-04-08
      go语言 高效 轻量 linux golang macos 网络编程
    • go语言是不是编程语言
      本篇内容介绍了“go语言是不是编程语言”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!go语言是编程语言。go语言又称Golang,是Goog...
      99+
      2023-07-04
    • Go 语言实时编程教程:如何使用 JavaScript 实现?
      随着互联网技术的不断发展,实时编程越来越受到人们的重视。实时编程的主要特点是要求程序能够在规定的时间内响应用户的请求,如在毫秒级别内处理数据、生成响应等。Go 语言作为一门高效的编程语言,越来越受到开发者的青睐。本文将介绍如何使用 Jav...
      99+
      2023-09-08
      实时 教程 javascript
    • GO语言怎么实现协程池管理
      本篇内容介绍了“GO语言怎么实现协程池管理”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!使用channel实现协程池通过 Channel 实...
      99+
      2023-06-20
    • 如何在Go语言中实现实时异步编程?
      Go语言是一种非常流行的开发语言,其强大的并发和并行能力使其成为编写高性能应用程序的首选语言之一。在Go语言中,实现实时异步编程是一项非常重要的任务。在本文中,我们将介绍如何在Go语言中实现实时异步编程,并提供一些实用的示例代码。 了解G...
      99+
      2023-07-01
      实时 linux 异步编程
    • go语言怎么进行网络编程
      go语言进行网络编程的步骤如下:1、导入net包和其他需要的包;2、创建服务器或客户端连接;3、处理连接,为每个连接创建一个新的“goroutine”来处理;4、处理数据,使用“conn.Read()”读取数据,使用“conn.Write(...
      99+
      2023-12-13
      Go语言网络编程 go语言 Golang
    • Go语言是怎样的一种编程语言?
      Go语言是一种由Google开发的开源编程语言,也被称为Golang。它的设计目标是提高开发人员的工作效率,同时保持高性能和可靠性。Go语言的设计借鉴了许多其他编程语言的优点,包括静态...
      99+
      2024-03-07
      静态类型 速度快 并发强 go语言 标准库
    • 怎么用go语言编程实现二维码生成及识别
      本文小编为大家详细介绍“怎么用go语言编程实现二维码生成及识别”,内容详细,步骤清晰,细节处理妥当,希望这篇“怎么用go语言编程实现二维码生成及识别”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。安装 go-qrc...
      99+
      2023-06-30
    • Go语言如何实现实时函数编程算法?
      随着云计算、大数据、物联网等技术的发展,实时数据处理的需求越来越迫切。实时函数编程算法能够帮助我们处理实时数据,帮助我们更好地理解和应用这些数据。那么,Go语言如何实现实时函数编程算法呢?本文将为大家介绍。 一、Go语言实现实时函数编程算...
      99+
      2023-07-04
      实时 函数 编程算法
    • Go编程语言究竟是不是编程型语言?
      Go编程语言究竟是不是编程型语言? 近年来,Go编程语言在编程领域备受瞩目,许多开发者对其性能、简洁和高效的特点赞不绝口。然而,有人对于Go语言是否真正算是一种编程型语言存在质疑。本文...
      99+
      2024-04-02
    • C语言中怎么实现泛型编程
      这篇文章给大家介绍C语言中怎么实现泛型编程,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。泛型编程(generic  programming)是程序设计语言的一种风格或范式。泛型允许程序员在强类型程序设计语言中...
      99+
      2023-06-15
    • Go 语言编程算法:如何在 LeetCode 中实现?
      LeetCode 是一款非常受欢迎的在线算法编程平台,拥有大量的算法题目和编程挑战,让程序员们可以在这里锻炼算法能力和编程技巧。而 Go 语言作为一种高效、简洁、安全的编程语言,也越来越受到程序员们的青睐。在本文中,我们将介绍如何在 Le...
      99+
      2023-08-20
      leetcode javascript 编程算法
    • 如何在GO语言中实现响应式编程?
      随着现代软件开发的不断发展,越来越多的开发人员开始关注响应式编程。响应式编程是一种编程范式,它强调程序应该根据数据流的变化做出响应,而不是通过手动编写代码来实现数据流的变化。这种编程范式可以帮助开发人员更好地处理异步事件和数据流,从而提高...
      99+
      2023-06-21
      响应 容器 npm
    软考高级职称资格查询
    编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
    • 官方手机版

    • 微信公众号

    • 商务合作