【一文通】C/C++与Go语言混合编程入门级教程(Windows平台完成)

news2025/1/16 8:14:16

在这里插入图片描述

一、概述

Go语言可以通过自带的 cgo 工具进行 C+GO 混合编程,这个工具放在go安装目录的 pkg\tool 下,其源代码则在 src\runtime\cgo 里面,当然作为入门教程本文不打算对cgo的实现原理进行深入研究,仅从 Hello World 的角度来实际体验一下 cgo(文末有我收集的各种资源可供深入学习)。


二、从最简单开始 (内联C代码)

默认的 Go 编译器是关闭交叉编译功能的,因为开启了 cgo 会让 Go 程序的移植性变差且部署变得很麻烦。纯粹的 Go 代码固然更好,但有时候当需要用到的软件库找不到 Go 版本的时候则只能采用这种方式来应对。正面而言,没有cgo,Go 就不会有今天的地位,因为通过它可以继承 C/C++ 将近半个世纪的软件遗产,此外 cgo 也是在 Android 和 iOS 上运行 Go 程序的关键。

打开cgo很简单,将环境变量 CGO_ENABLED 设为 1 就可以了:
Windows:set CGO_ENABLED=1
类Linux平台: export CGO_ENABLED=1

输入这段程序:

main.go:

package main

/*
int Add(int a, int b){
    return a+b;
}
*/
import "C"

import "fmt"

func main() {
	a := C.int(10)
	b := C.int(20)
	c := C.Add(a, b)
	fmt.Println(c) // 30
}

上面这段代码,最前面有一段 “注释”,下一行是import "C",注意这段注释并非真正的注释,而是C语言代码,在Go语言的规范中,在 import "C" 之前的若干注释行叫做序文(preamble),而实际上Go语言也没有“C”这个包,这是一个虚拟包,它的作用可以理解为一条分割线,让C和Go代码分离,在 import "C"之上的注释部分是C代码,之下则是普通的 Go 代码。注意C代码和 import "C" 之间不可存在空行,“C”这个包也不能被常规 import 语句所引用,只能以独占一行的方式存在。此后在普通Go代码部分,我们就可以通过 C.Add 来引用这个C语言函数了。这种通过内联方式来引用C代码的方法很简单,在编译环节和普通纯Go相比没有差异,直接输入go build main.go 或者 go run main.go 就可以了。


三、引用C语言编写的库

大多数情况下,我们的目的是通过cgo来引用第三方软件库,无论开源闭源基本上都是以库的方式存在的,下面就来讲一下在Go代码中如何引用C语言编译好的静态链接库里的函数。为了简单起见我们先模拟一个C语言静态链接库,创建以下2个文件:

hello.c:

#include <stdio.h>
#include "hello.h"

void SayHello()
{
    printf("Hello, world!\n");
}

hello.h:

void SayHello();

用gcc将他们编译为静态链接库(后缀为.a):
$ gcc -c hello.c
$ ar -crv libhello.a hello.o
输入以上两行命令后,我们得到了一个 libhello.a 的静态链接库。

PS:关于gcc和ar命令,是包含在MinGW软件里的,关于它的安装这里不展开讨论,可以翻阅我之前的博文,过程也很简单。

再创建1个Go文件:
main.go:

package main

/*
#cgo CFLAGS: -I${SRCDIR}
#cgo LDFLAGS: -L${SRCDIR} -lhello
#include "hello.h"
*/
import "C"

import (
	"fmt"
)

func main() {
	C.SayHello()
	fmt.Println("Succeed!")
}

CFLAGS和LDFLAGS 是两个C语言的编译和链接开关,在这里CFLAGS指明了头文件的路径,而LDFLAGS则指定了库文件路径和库文件名。

PS: 头文件就是指跟在 #include 后面的后缀为 .h的文件,库文件则是指以 .a 或 .so 结尾的文件,在Windows下则是 .lib 或 .dll 方式存在。${SRCDIR} 则表示当前目录,也就是我们通常使用的 “.” 。库文件不能使用相对路径是C/C++的历史遗留问题,通过${SRCDIR}则可以变相地采用相对路径,例如假设有一个绝对路径c:\test\hello,则${SRCDIR}\lib会自动展开为 c:\test\hello\lib。

CFLAGS 通过 -I 将在当前目录设为头文件(.h)检索路径。LDFLAGS 则通过 -L${SRCDIR} 在当前目录设为库文件(.a)检索路径,-lhello 表示具体链接的是 libhello.a 这个库。

注意:-lhello 表示链接 libhello.a这个库,其实是将 “lib" 和 后缀 “.a” 去掉之后简写为hello的,这是C语言的套路。此外,动态链接库(.so文件)的链接方式是一样的,假设现在我们提供的库文件是一个动态链接库 libhello.so ,在这里的设置是完全一样的都是 -lhello。

输入 go build main.go 或者 go run main.go 运行,显示:

Hello, world!
Succeed!

尽管就只是打印了两行字,但这里的 “Hello, world!” 实际上是调用了 C语言的 libhello.a 库里的 SayHello() 函数的结果,而 “Succeed!” 则是调用普通 Go 标准库 fmt 的结果,两者有着本质区别。


四、引用C++库

相对而言,C++与Go的交叉编码显得要麻烦一些,cgo 是 C 语言和 Go 语言之间的桥梁,但原则上它无法直接支持 C++ 的类,只能增加一组 C 语言函数接口作为 C++ 类和 CGO 之间的桥梁,迂回地让Go和C++对接。这就是我们经常在开源Go项目里面经常看到 “xxx-bridge” 的原因,只要出现这种情况,多半是这个项目引用了C++库。

首先,我们先创造一个C++库,创建一个myLib目录,然后在里面再创建两个C++文件:
$ mkdir myLib
$ cd myLib

hello.cpp:

#include "hello.h"
#include <iostream>

void hello() {
    std::cout << "Hello, World!\nThis message comes from a CPP function!" << std::endl;
}

hello.h:

void hello();

和上例一样,创建一个静态链接库:
$ g++ -c hello.cpp (注意这次使用的是g++而不是gcc)
$ ar crs libhello.a hello.o
然后退回到项目的根目录,再创建两个作为“桥梁”的C++文件:
$ cd ..

hellobridge.cpp :

#include "hellobridge.h"
#include "mylib/hello.h"

void CallHello()
{
    hello(); // 调用库中的hello()函数
}

hellobridge.h :

#ifdef __cplusplus
extern "C" {
#endif

void CallHello();

#ifdef __cplusplus
}
#endif

因为这个文件是用于 CGO,必须采用 C 语言规范的名字修饰规则,在 C++ 源文件包含时需要用 extern "C" 语句说明。

最后创建 Go主程序:

hello.go :

package main

/*
package main

/*
#cgo CXXFLAGS: -std=c++0x
#cgo LDFLAGS: -L${SRCDIR}/mylib -lhello
#cgo CPPFLAGS: -Wno-unused-result
#include "hellobridge.h"
*/
import "C"

func main() {
	C.CallHello()
}

这次我们设置了 CXXFLAGS 编译开关,告诉 cgo 现是 C++ 代码。

注意:go env 环境变量有 CC 和 CXX 之分,分别对应的是C和C++的可执行编译器(需要放到PATH命令里以在任何地方执行),当我们设置CFLAGS的时候,cgo 会自动开启C语言编译器进行工作(默认是gcc),而当CXXFLAGS开关被设置的时候,cgo 会自动选择使用C++编译器(默认是g++)。而我们不必纠结如何“指挥”cgo 用什么编译器去工作,它的逻辑很简单即通过两个编译开关来判定,我们不需要将 CC 设为 g++ 这种方法来实现强制 cgo 使用C++进行编译, 这将弄巧成拙。

关于这三个开关:
CFLAGS :C语言编译参数
CXXFLAGS: C++独有的编译参数
CPPFLAGS:C和C++共有的编译参数
在本例中我们发现一个参数 -Wno-unused-result ,这个参数是告诉编译器取消“未使用”变量的警告,这个参数是C和C++共有的因此放到了 CPPFLAG 里。

上述文件全部创建完毕后,输入 go build -o hello.exe 或者 go run .

Hello, World!
This message comes from a CPP function!

注意:因为这次我们不只有 hello.go 文件参与编译,另外还有两个作为桥梁的文件:hellobridge.cpp 和 hellobridge.h 也要参与编译,因此就不能像上面的例子那样使用 go build hello.go 单独编译 hello.go文件而是需要编译整个文件夹。


五、使用pkg-config

本节并非本文的重点,但鉴于pkg-config这个工具使用很广泛并且 cgo 与它也有相应的对接参数,因此一并在这里告知。

pkg-config 是原生Linux下的工具(也有For Win版),它的主要作用就是简化库文件的引用操作,因为我们引用任何第三方库首先都要知道它的头文件和库文件到底存放位置,然后再来拼接类似 CFLAGS 或 LDFLAGS 这样的参数,这是一件很麻烦的事情。通过pkg-config 在一定程度上能简化这个操作。 其运行逻辑其实非常简单,可以简单地将它理解为一个自动展开器,比如说现在有一个库 hello,它的头文件在存放在 /usr/local/include ,库文件在/usr/local/lib ,过往在编译它的时候我们需要这么做:
gcc hello.cpp -I/usr/local/include -L/usr/local/lib -lhello -o hello.exe

我们需要通过各种方法(比如说whereisfind这一类指令)去查找这个库的实际存放位置,这显得非常麻烦。而采用了pkg-config后,则变成了这样:

gcc hello.cpp `pkg-config -cflags -libs hello` -o hello.exe 

其中用 ` ` 号包裹起来那段内容,在命令执行的时候会自动展开为

-I/usr/local/include -L/usr/local/lib -lhello

这样我们就只需要知道库的名字,而不需要关心这个库到底存放在哪了,省时又省心。

每一个库,都会预先创建一个 .pc 后缀的文件,里面预设了 CFLAGS 以及 LDFLAGS 等等记录,当 pkg-config 检索到这个文件后,将会自动将匹配到的内容展开,而这些 .pc 文件存放的地方称为 PKG_CONFIG_PATH , 下面就用我们的hello库来演示一遍 pkg-config 的用法。

首先,尽管这是一个Linux工具,实际上我们也不必费尽心思去寻找它的所谓 Windows 版,因为在安装 MSYS2 或者 MinGW 的时候就附带了这个工具。我们只需要进入MSYS2就能直接使用,使用命令:
$ echo $PKG_CONFIG_PATH
得知所有的 .pc 文件都放在这两个目录里:
/mingw64/lib/pkgconfig:/mingw64/share/pkgconfig
输入命令:
$ cd /mingw64/lib/pkgconfig
创建一个文件 hello.pc

Name: Hello
Description: Hello World Cgo Test.
Version: 1.0.0
Libs: -Lc:/test/myLib -lhello
Cflags: -Ic:/test/myLib

Name 和 Description 随便输,Libs 表示我们hello库实际存放位置,Cflags 指明了 hello 库的头文件存放位置。
存盘退出,输入;
# pkg-config --list-all
屏幕会显示一堆 package 名字,仔细找找看,我们的Hello库应该也在里面了。
输入:
# pkg-config --cflags --libs hello
显示:
-Ic:/test/myLib -Lc:/test/myLib -lhello
说明pkg-config已经侦测到我们的hello库并且能够自动展开了。

现在,我们用gcc或者g++命令编译的时候就可以这样:

gcc hello.cpp `pkg-config -cflags -libs hello` -o hello.exe

它会自动展开成这样:

gcc hello.cpp -Ic:/test/myLib -Lc:/test/myLib -lhello -o hello.exe

坑提示:在Windows下的两个shell都不能识别 `pkg-config -cflags -libs hello`这种格式,无论是cmd还是powershell,我还尝试过使用 $(pkg-config -cflags -libs hello) 这样的格式,都不能实现展开。但对于已经安装好MSYS2的系统来说,这也不是什么大问题,只是进哪个Shell的差异而已。

回到我们的 Go 这边,采用了 pkg-config 之后,不再需要指定头文件和库文件路径,修改后代码如下:

package main

/*
#cgo pkg-config: hello
#cgo CXXFLAGS: -std=c++0x
#cgo CPPFLAGS: -Wno-unused-result
#include "hellobridge.h"
*/
import "C"

func main() {
	C.CallHello()
}

旧的代码是:#cgo LDFLAGS: -L${SRCDIR}/mylib -lhello
而现在我们只需要:pkg-config: hello 就可以了,对我们自己而言倒也没什么,因为库是我们自己写的当然很清楚它保存在哪,但是换一个角度,这个库假设提供给别人使用,那将会省去他很多麻烦。

六、后记
cgo 门道非常深,本文仅作抛砖引玉之用,且只讲了go如何引用c,而未提及c引用go,以及变量、数组、结构体、指针等各种转换问题。我收集了一些学习资料可供大家深入研究:

官方手册:
https://pkg.go.dev/cmd/cgo
https://go.dev/blog/cgo

CGO:
https://chai2010.cn/advanced-go-programming-book/ch2-cgo/ch2-01-hello-cgo.html
https://www.cntofu.com/book/19/0.13.md
https://www.cnblogs.com/lidabo/p/6068448.html
https://bastengao.com/blog/2017/12/go-cgo-cpp.html
https://fasionchan.com/golang/practices/call-c/

C/C++:
https://blog.51cto.com/u_15091053/2652800
https://www.cnblogs.com/52php/p/5681711.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/678738.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

快速查询银行卡发卡省市和归属银行,了解自己的财务状况!

API接口是现代软件开发的基本组成部分。它们允许应用程序通过互联网连接到其他软件系统&#xff0c;并从这些系统中获取或传输数据。银行卡归属地查询API接口是为开发人员提供的一种工具&#xff0c;可以帮助他们轻松地查询银行卡的归属地信息。在本文中&#xff0c;我们将介绍…

内网横向移动—IPCATschtasks

内网横向移动—IPC&AT&schtasks 1. IPC介绍1.1. IPC利用条件关系1.1.1. IPC$空连接介绍1.1.2. 139与445端口介绍1.1.3. 默认共享 1.2. IPC连接问题1.2.1. IPC连接失败原因1.2.2. IPC连接常见错误 2. 横向移动常用命令2.1. IPC命令介绍2.1.1. IPC常用命令演示2.1.1.1. 建…

docker非root用户下取消sudo前缀

解决非root用户下执行docker命令提示权限不足&#xff0c;必须添加sudo的问题。 第一步&#xff1a;执行 sudo gpasswd aby docker 命令&#xff0c;将当前用户aby加入docker组中。 第二步&#xff1a;执行 sudo chmod arw /var/run/docker.sock 命令修改sock权限

数据结构之堆——算法与数据结构入门笔记(六)

本文是算法与数据结构的学习笔记第六篇&#xff0c;将持续更新&#xff0c;欢迎小伙伴们阅读学习。有不懂的或错误的地方&#xff0c;欢迎交流 引言 当涉及到高效的数据存储和检索时&#xff0c;堆&#xff08;Heap&#xff09;是一种常用的数据结构。上一篇文章中介绍了树和完…

chatgpt赋能python:Python列表转字符串——从新手到大师

Python列表转字符串——从新手到大师 在Python编程中&#xff0c;列表和字符串是非常常用的数据类型。有时候&#xff0c;我们需要将一个列表转换为一个字符串&#xff0c;以方便进行各种操作。幸运的是&#xff0c;Python内置了一些函数和方法&#xff0c;可以轻松地将列表转…

7Z045 引脚功能详解

本文针对7Z045芯片&#xff0c;详细讲解硬件设计需要注意的技术点&#xff0c;可以作为设计和检查时候的参考文件。问了方便实用&#xff0c;按照Bank顺序排列&#xff0c;包含配置Bank、HR Bank、HP Bank、GTX Bank、供电引脚等。 参考文档包括&#xff1a; ds191-XC7Z030-X…

ruoyi-cloud版本(一)项目的下载与本地运行(亲测有效)

目录 1 架构2 架构图3 源码下载4 创建数据库5 下载nacos与运行6 打开运行基础模块&#xff08;启动没有先后顺序&#xff09;7 启动前端 1 架构 com.ruoyi ├── ruoyi-ui // 前端框架 [80] ├── ruoyi-gateway // 网关模块 [8080] ├── ruoyi…

canvas详解00-认识canvas

身为一个WEB开发人员&#xff0c;肯定都是想着能够开发出酷炫和激动人心的应用程序来。可以很多动画特效&#xff0c;例如黑客帝国的数字&#xff0c;彩色炫酷的例子动效。也可以实现各种图画面板&#xff0c;如实现类似于photoshop的web在线图像编辑。各种酷炫的表单等等。 #…

专项练习10

目录 一、选择题 1、执行以下程序&#xff0c;下列说法中&#xff0c;正确的是&#xff08;&#xff09; 2、下面有关JavaScript中系统方法的描述&#xff0c;错误的是&#xff1f; 3、以下 JavaScript 代码&#xff0c;在浏览器中运行的结果是 4、假设DOM结构为 二、编程题 …

[ruby on rails] rails中使用graphQL

1. 添加gem gem graphql’是主要提供server的, gem graphiql-rails’是用来生成一个graphiql查询页面IDE,自己用来测试的group :development dogem graphiql-rails endgem graphql2.使用命令生成模板文件 rails g graphql:install在API only中,routes不会自动填充graphiql路…

chatgpt赋能python:Python的下载方法——从官网到第三方渠道

Python的下载方法——从官网到第三方渠道 Python 是一种翻译式、面向对象的、动态数据类型的高级程序设计语言&#xff0c;被广泛应用于数据分析、人工智能、物联网等领域。相信大多数程序员都知道 Python&#xff0c;并且使用它编写程序。那么&#xff0c;如何下载 Python&am…

人工智能(1):机器学习工作流程

1 什么是机器学习 机器学习是从数据中自动分析获得模型&#xff0c;并利用模型对未知数据进行预测。 2 机器学习工作流程 机器学习工作流程总结 1 获取数据 2 数据基本处理 3 特征工程 4 机器学习(模型训练) 5 模型评估 结果达到要求&#xff0c;上线服务没有达到要求&a…

程序编译连接加载过程详解

程序加载过程详解 可重定位的elf文件格式简介 首先我们打开目标文件看一下 上面的图就是目标文件的格式了&#xff0c;这里使用的是010editer&#xff0c;这个二进制编辑器很好用 可以看到大致分为三部分&#xff0c;首先是header&#xff0c;然后是sectionheader&#xff0…

MIT 6.S081 (BOOK-RISCV-REV1)教材第四章内容 -- Trap -- 下

MIT 6.S081 教材第四章内容 -- Trap -- 下 引言从内核空间陷入页面错误异常Page Fault BasicsLazy page allocationZero Fill On DemandCopy On Write ForkDemand PagingMemory Mapped Files 真实世界 引言 MIT 6.S081 2020 操作系统 本文为MIT 6.S081课程第四章教材内容翻译…

Windows编译OpenSSL Win10系统 vs2010

近期因为项目需要&#xff0c;需要用到openssl动态库&#xff0c;现在将编译的过程记录一下&#xff1b; 操作系统&#xff1a;Win10 64位 编译器&#xff1a;VS2010 编译步骤如下&#xff1a; 1、下载openssl版本&#xff08;下载地址&#xff1a;​http://www.openssl.or…

chatgpt赋能python:Python分割——字符串处理中的必备技能

Python分割——字符串处理中的必备技能 如果你曾经遇到过需要将一个字符串根据一定规则切割成多个子串的情况&#xff0c;那么你一定会发现&#xff0c;Python中的分割功能能够让这个任务变得非常简单。 什么是Python分割&#xff1f; Python中的分割是指将一个字符串根据特…

端午节安康,佬们都了解端午节的哪些知识呢(附粽子大作战小游戏)

前言&#xff1a; 端午节假期&#xff0c;首先祝各位小伙伴儿们端午节安康。参考了一些资料&#xff0c;本篇文章将和大家分享关于端午节的由来&#xff0c;习俗&#xff0c;以及关于端午节的一个代码小游戏–粽子大作战。 希望大家看完此篇文章能对端午节有收获&#xff0c;也…

如何在gitee上托管项目

1、如果想要将一个项目托管到gitee上,第一步找到gitee官网&#xff1a; https://gitee.com/?utm_sourcebaidu&utm_mediumsem&utm_termgitee%CB%BD%D3%D0%B2%BF%CA%F0&utm_campaignenterprise&utm_contentcompetition&wl_kwid260644677393&wl_creativ…

chatgpt赋能python:用Python绘制区域图,探索数据背后的故事

用Python绘制区域图&#xff0c;探索数据背后的故事 随着大数据时代的到来&#xff0c;数据可视化变得越来越受到重视。而区域图&#xff08;Area chart&#xff09;是一种常用的数据可视化图表类型之一。它可以说明一个数量随着时间的变化而发生的趋势&#xff0c;以及各类数…

chatgpt赋能python:Python的不确定尾数:如何处理和解决?

Python的不确定尾数&#xff1a;如何处理和解决&#xff1f; Python是一种流行的编程语言&#xff0c;被广泛用于开发Web应用程序、数据分析、人工智能、机器学习等领域。但是&#xff0c;Python在处理浮点数时可能存在精度问题&#xff0c;尤其是当尾数不确定时&#xff0c;会…