广告
返回顶部
首页 > 资讯 > 后端开发 > GO >Go语言开发快速学习CGO编程
  • 296
分享到

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

Go语言开发CGO编程CGO编程 2023-05-14 17:05:47 296人浏览 八月长安
摘要

目录快速上手 CGo 程序基于 C 标准库实现最简单的 CGO 程序基于自己写的 C 函数构建 CGO 程序模块化以上例子用 Go 实现 C 函数并导出用 C 接口的方式实现 Go

快速上手 CGO 程序

真实的 CGO 程序原理一般都比较复杂,但是在使用层面上来说,其实没有想象的那么难。

今天我们可以由浅入深来看看一个 CGO 程序该是怎么样实现的?

如果要构造一个简单的 CGO 程序,首先要忽视一些复杂的 CGO 特性,下面我们来快速上手一个 CGO 程序。

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

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

// hello.go
package 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.go
package main

import "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.go
package main
//void SayHello(const char* s);
import "C"
func main() {
    C.SayHello(C.CString("Hello, World\n"))
}

模块化以上例子

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

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

// hello.h
void 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.h
void SayHello(char* s);

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

// hello.go
package main
import "C"
import "fmt"
//export SayHello
func SayHello(s *C.char) {
    fmt.Print(C.GoString(s))
}

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

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

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

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

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

// main.go
package main
//void SayHello(char* s);
import "C"
import (
    "fmt"
)
func main() {
    C.SayHello(C.CString("Hello, World\n"))
}
//export SayHello
func 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 main

import "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 main

import "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 main

import "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.Consumer
	oConsumer.Init(Mqdns, MqexchangeName, Mqqueue, Mqroute)
	msgs, err := oConsumer.StartConsume(Mqqueue,false)
	if err != nil{
		fmt.Printf("oConsumer.StartConsume failed:%+v, arg:%+v \n",err, Mq
		return
	}
	for msg := range msgs{
		strMsg := string(msg.Body)
		msg.Ack(true)
    // 调用 C++ 处理消息的函数
		C.RecvAndDealMessage(C.CString(strMsg), C.int(len(strMsg))) //c++ 处理mq消息
	}
}

总结

通过以上实例说明,可以知道CGO其实是C语言和Go语言混合编程的技术,因此要想熟练地使用CGO是非常有必要要了解这两门语言的。

任何技术和语言都有它自身的优点和不足,Go语言不是银弹,它无法解决全部问题。而通过CGO可以做到以下几点:

  • 通过CGO可接入C/C++的世纪软件遗产
  • 通过CGO可以用Go给其它系统写C接口的共享库
  • 通过CGO技术也可以让Go语言编写的代码可以很好地融入现有的软件生态

而现在的软件确实大多数是建立在C/C++语言之上的。因此CGO可以说是一个统筹兼备的技术,是Go的一个重量级的技术,也是值得任何一个Go语言开发人员学习的。

以上就是Go语言开发快速学习CGO编程的详细内容,更多关于Go语言开发CGO编程的资料请关注编程网其它相关文章!

您可能感兴趣的文档:

--结束END--

本文标题: Go语言开发快速学习CGO编程

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

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

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

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

下载Word文档
猜你喜欢
  • Go语言开发快速学习CGO编程
    目录快速上手 CGO 程序基于 C 标准库实现最简单的 CGO 程序基于自己写的 C 函数构建 CGO 程序模块化以上例子用 Go 实现 C 函数并导出用 C 接口的方式实现 Go ...
    99+
    2023-05-14
    Go语言开发CGO编程 CGO编程
  • 学习Go语言编程技巧,快速提高学习效率
    提升学习效率的方法,迅速掌握Go语言编程技巧在当今科技高速发展的时代,学习成为了人们不可或缺的一部分。尤其是在计算机领域,编程语言的繁多让人眼花缭乱。而其中一门备受关注的编程语言就是Go语言。作为一种简洁高效的语言,Go语言不仅有着广泛的应...
    99+
    2023-12-30
    Go语言 编程技巧 学习效率
  • Go语言编程入门指南,从零开始快速学习编程
    从零开始学习Go语言,快速入门编程世界编程语言是现代科技领域不可或缺的一部分。Go语言作为一门简洁高效的编程语言,近年来在开发领域逐渐崭露头角。对于想要快速入门编程世界的初学者来说,学习Go语言是一个不错的选择。本文将指导你从零开始学习Go...
    99+
    2023-12-30
    编程 Go语言 入门
  • 怎么快速学习web编程语言
    本篇内容主要讲解“怎么快速学习web编程语言”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“怎么快速学习web编程语言”吧!编程分析世界上有许多编程语言,一下就能列举出5000多种,但编程语言排行...
    99+
    2023-06-16
  • GO语言实时开发技术,如何快速学习笔记?
    随着互联网的迅速发展,越来越多的企业开始选择GO语言作为其开发语言,因为GO语言具有高效、快速、可靠等特点。但是,很多人在学习GO语言时都遇到了一些困难,因此如何快速学习GO语言实时开发技术,是我们需要探讨的问题。本文将介绍一些GO语言实...
    99+
    2023-11-12
    实时 开发技术 学习笔记
  • GO语言学习笔记:如何快速掌握开发技术?
    GO语言是一种相对较新的编程语言,它吸收了许多其他编程语言的优点,如C语言的速度和Python的简洁性。GO语言的语法简单明了,适合初学者学习。本文将介绍一些学习GO语言的技巧和建议,以帮助你快速掌握开发技术。 一、安装GO语言 首先,你...
    99+
    2023-06-28
    学习笔记 教程 开发技术
  • GO语言编程算法学习笔记,如何快速加载?
    作为一门快速发展的编程语言,GO语言在算法学习中也有着广泛的应用。但是,对于初学者来说,GO语言的算法学习难度较大,而且编译速度慢,加载速度慢等问题也让人头疼。那么,如何才能让GO语言编程算法学习更快速地加载呢?下面就来分享一些小技巧。 ...
    99+
    2023-08-18
    学习笔记 编程算法 load
  • 学习Go语言的技巧和方法,快速掌握编程技能
    学习一门编程语言是现代社会中越来越重要的技能之一。Go语言作为一种相对年轻但非常受欢迎的编程语言,它具有简洁、高效和可扩展的特点,因此对于想要提升自己的编程能力的人来说,学习Go语言是一个不错的选择。本文将介绍如何高效学习Go语言,轻松掌握...
    99+
    2023-12-30
    编程技能 Go语言学习 高效学习
  • Go语言编程学习golang配置golint
    目录下载golint打开setting对话框设置一个快捷键下载golint 下载golang 的 lint,下载地址:https://github.com/golang/lint ...
    99+
    2022-11-12
  • GO语言学习笔记:如何快速掌握语言的基础知识?
    随着技术的不断发展,越来越多的程序员开始学习GO语言。GO语言是Google于2009年推出的一种编程语言,它的设计目标是提供一种简单、高效、可靠的编程语言。GO语言在性能、并发性和可维护性方面表现出色,因此受到了众多开发者的青睐。本文将...
    99+
    2023-06-28
    学习笔记 教程 开发技术
  • GO语言开发技术:学习笔记分享!
    GO语言是一门高效、可靠、可扩展的编程语言,由Google开发。它的出现解决了许多其他编程语言的问题,例如C++中的指针问题、Java中的垃圾回收等等。 GO语言在现代化的软件开发中,已经被广泛应用,并成为了云计算领域的事实标准。本篇文章...
    99+
    2023-08-02
    学习笔记 开发技术 linux
  • 从零开始学习Go语言、Git和JavaScript异步编程
    在现代编程中,Go语言、Git和JavaScript异步编程都是非常重要的工具。Go语言是一种快速、高效、并发的编程语言,Git是一个版本控制工具,而JavaScript异步编程则是编写现代Web应用程序所必需的技能。在本文中,我们将从零...
    99+
    2023-11-04
    git javascript 异步编程
  • GO语言学习笔记之编程算法,如何load更加快捷?
    在编写GO语言代码时,很多开发人员都会遇到一个问题:在加载和解析程序时,速度会变慢。这个问题对于大型程序来说尤其严重,因为它们需要加载的代码量更大。所以,如何让编程算法load更加快捷呢? 首先,我们需要了解一下GO语言是如何加载和解析程...
    99+
    2023-08-18
    学习笔记 编程算法 load
  • “Go语言实时开发技术教程,让你快速掌握开发技能!”
    Go语言实时开发技术教程,让你快速掌握开发技能! Go语言是一种由谷歌开发的高效编程语言,它的设计目标是提高程序员的工作效率和代码可读性,同时保证代码的执行效率。由于其卓越的特性,Go语言已经成为了云计算、分布式系统、网络编程等领域的首选语...
    99+
    2023-10-23
    教程 开发技术 实时
  • 从零开始学习 Windows 编程:Go 语言实现算法 API
    作为一名程序员,我们需要不断地学习新的技能和知识。而在 Windows 平台上开发程序是一个必须掌握的技能。本文将介绍如何使用 Go 语言实现算法 API,帮助你从零开始学习 Windows 编程。 一、Windows 程序开发环境搭建 ...
    99+
    2023-08-15
    windows 编程算法 api
  • “Go语言实时开发技术:如何快速掌握?”
    Go语言是一种相对年轻的编程语言,由于其高效、高并发、易于学习等优点,越来越多的开发者开始使用它来开发各种应用程序。但是,对于初学者来说,Go语言的学习曲线可能比较陡峭,需要一些实时开发技巧来帮助他们快速掌握这门语言。 本文将介绍一些Go语...
    99+
    2023-10-23
    教程 开发技术 实时
  • 从零开始学习GO语言分布式开发技术
    随着互联网技术的不断发展,分布式开发已成为现代软件开发的重要趋势。而GO语言作为一种快速、高效、并发的编程语言,也越来越受到开发者的青睐。本文将从零开始,介绍GO语言分布式开发技术的基本概念和实践经验,并结合代码演示,帮助读者更好地理解和...
    99+
    2023-09-04
    开发技术 教程 分布式
  • Shell 和 Python 编程,哪种语言更适合快速开发?
    在现代编程的世界中,我们有许多不同的编程语言可供选择,其中 Shell 和 Python 可谓是两个备受欢迎的语言。虽然它们都可以用于快速开发,但它们在使用场景和功能方面有着不同的特点。那么,哪种语言更适合快速开发呢?让我们来深入探讨一下...
    99+
    2023-08-21
    bash 编程算法 shell
  • GO语言编程算法学习笔记,如何提高加载速度?
    GO语言编程是当前最流行的一种编程语言,它的优点在于其简单、高效、可靠、安全等特性,因此越来越多的开发者开始选择GO语言来进行开发。在GO语言的编程中,算法是一个非常重要的部分,不仅对于程序的性能有着重要的影响,而且对于程序的正确性也有着...
    99+
    2023-08-18
    学习笔记 编程算法 load
  • GO语言实时开发技术,开发者必备学习笔记
    随着人工智能和物联网技术的发展,实时应用的需求越来越多。GO语言是一种现代化的高性能编程语言,它的运行速度快、并发性能好、内存占用小、语法简单易懂,这些特性使得GO语言成为实时应用开发的优秀选择。本文将介绍一些GO语言实时开发的技术和工具...
    99+
    2023-11-12
    实时 开发技术 学习笔记
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作