浅谈go中cgo的几种使用方式
作者:Yuan_sr 发布时间:2024-02-15 06:55:51
最简单的CGO程序
//cgo.go
package main
import "C"
func main(){
println("hello cgo")
}
上述代码是一个完整的CGO程序,通过import "C"语句启动了CGO特性,go build命令会在编译和链接阶段启动gcc编译器
源码方式调用C函数
cgoTest.h
void SayHello(const char* s);
cgoTest.c
#include <stdio.h>
#include "cgoTest.h"
void SayHello(const char* s) {
puts(s);
}
main.go
package main
/*
#include <cgoTest.h>
*/
import "C"
func main(){
C.SayHello(C.CString("Hello world\n"))
}
上述.c文件也可以是.cpp文件,前提是编译时需要g++
cgoTest.cpp
#include <iostream>
extern "C" {
#include "cgo01.h"
}
void SayHello(const char* s) {
std::cout << s;
}
上述.c和.cpp的不同实现都实现了SayHello函数,说明解放了函数的实现者,那如果是这种情况,可不可以使用go实现SayHello函数呢?
答案是可以的,这种技术也称为面向C语言接口(.h中的接口声明)的编程技术,该技术不仅仅可以解放函数的实现者,同时也可以简化函数的使用者。
cgoTest.go
package main
import "C"
import "fmt"
//export SayHello
func SayHello(s *C.char){
fmt.Print(C.GoString(s)) //注意:这里是C.GoString
}
注意:上述main.go文件在使用C函数CString后在程序退出前没有释放C.CString创建的字符串会导致内存泄漏,但是对于这个小程序来说,这样是没有问题的,因为程序推出后操作系统会自动回收程序的所有资源
改进后的main.go代码
package main
/*
#include <cgoTest.h>
#include <stdlib.h>
*/
import "C"
import "unsafe"
func main(){
cs := C.CString("CPP Hello world\n")
C.SayHello(cs)
C.free(unsafe.Pointer(cs))
}
当然也有其他方法可以避免这种麻烦的情况出现,而且只需要一个go文件就可以实现面向C语言的编程
main.go (只有这一个文件)
//+build go1.10
package main
//void SayHello(_GoString_ s); //Go1.10中CGO新增的预定义C语言类型,用来表示Go语言字符串
import "C"
import "fmt"
//export SayHello
func SayHello(s string){ //注意这里变量类型为Go 中的string
fmt.Print(s)
}
func main(){
C.SayHello("Hello CGO\n")
}
上面代码执行时先从Go语言的main函数开始,到CGO自动生成的C语言版本SayHello桥接函数,最后到Go语言环境的SayHello函数,是不是有一种合久必分、分久必合的感觉,这也是CGO编程的精华所在。
内部机制
如果在一个go文件中出现了import "C" 指令则表示将调用cgo命令生成的对应的中间文件,下图是cgo生成的中间文件的示意图:
在保证go build 没问题的情况下执行如下命令就可以生成中间文件
go tool cgo main.go
生成的中间文件在_obj目录下
为了在C语言中使用Go语言定义的函数,我们需要将Go代码编译为一个C静态库
go build -buildmode=c-archive -o SayHello.a cgoTest.go
如果没有错误的话,会生成一个SayHello.a静态库和SayHello.h头文件
既然提到了静态库的生成,顺便也说一下Go生成C动态库
go build -buildmode=c-shared -o SayHello.so cgoTest.go
编译和链接参数
编译和链接参数是每一个C/C++程序员需要经常面对的问题。构建每一个C/C++应用均需要经过编译和链接两个步骤,CGO也是如此
编译参数:CFLAGS/CPPFLAGS/CXXFLAGS
编译参数主要是头文件的检索路径,预定义的宏等参数。理论上来说C和C++是完全独立的两个编程语言,它们可以有着自己独立的编译参数。 但是因为C++语言对C语言做了深度兼容,甚至可以将C++理解为C语言的超集,因此C和C++语言之间又会共享很多编译参数。 因此CGO提供了CFLAGS/CPPFLAGS/CXXFLAGS三种参数,其中CFLAGS对应C语言编译参数(以.c后缀名)、 CPPFLAGS对应C/C++ 代码编译参数(.c,.cc,.cpp,.cxx)、CXXFLAGS对应纯C++编译参数(.cc,.cpp,*.cxx)
链接参数:LDFLAGS
链接参数主要包含要链接库的检索目录和要链接库的名字。因为历史遗留问题,链接库不支持相对路径,我们必须为链接库指定绝对路径。 cgo 中的 ${SRCDIR} 为当前目录的绝对路径。经过编译后的C和C++目标文件格式是一样的,因此LDFLAGS对应C/C++共同的链接参数
CGO在使用C/C++资源的时候一般有三种形式:直接使用源码;链接静态库;链接动态库。直接使用源码就是在import "C"之前的注释部分包含C代码,或者在当前包中包含C/C++源文件。链接静态库和动态库的方式比较类似,都是通过在LDFLAGS选项指定要链接的库方式链接
通过静态库的方式调用C函数
如果CGO中引入的C/C++资源有代码而且代码规模也比较小,直接使用源码是最理想的方式,但很多时候我们并没有源代码,或者从C/C++源代码开始构建的过程异常复杂,这种时候使用C静态库也是一个不错的选择。静态库因为是静态链接,最终的目标程序并不会产生额外的运行时依赖,也不会出现动态库特有的跨运行时资源管理的错误。不过静态库对链接阶段会有一定要求:静态库一般包含了全部的代码,里面会有大量的符号,如果不同静态库之间出现了符号冲突则会导致链接的失败
假设dirname 下有filename.c文件和filename.h文件,则生成静态库的命令为
$ cd ./dirname
$ gcc -c -o filename.o filename.c
$ ar rcs libfilename.a filename.o
使用静态库中的C函数
package main
//#cgo CFLAGS: -I./dirname
//#cgo LDFLAGS: -L${SRCDIR}/dirname -lfilename
//
//#include "filename.h"
import "C"
import "fmt"
func main() {
fmt.Println(C.filename_func())
}
通过动态库的方式调用C函数
动态库出现的初衷是对于相同的库,多个进程可以共享同一个,以节省内存和磁盘资源。但是在磁盘和内存已经白菜价的今天,这两个作用已经显得微不足道了,那么除此之外动态库还有哪些存在的价值呢?从库开发角度来说,动态库可以隔离不同动态库之间的关系,减少链接时出现符号冲突的风险。而且对于windows等平台,动态库是跨越VC和GCC不同编译器平台的唯一的可行方式
动态库的生成
gcc -shared -o libfinename.so filename.c
对于CGO 来说,使用动态库和静态库是一样的
package main
//#cgo CFLAGS: -I./dirname
//#cgo LDFLAGS: -L${SRCDIR}/dirname -lfilename
//
//#include "filename.h"
import "C"
import "fmt"
func main() {
fmt.Println(C.filename_func())
}
来源:https://blog.csdn.net/weixin_38299404/article/details/118189622


猜你喜欢
- 本文实例讲述了js实现向右横向滑出的二级菜单效果。分享给大家供大家参考。具体如下:这是一个网页上的横向滑出二级菜单,菜单是竖向排列的,但二级
- 以XML格式查看查询结果通过使用传统—xml 选项调用MySQL命令行客户程序,你可以以XML格式(而不是传统的列表形式
- 这篇文章主要介绍了Python爬取豆瓣视频信息代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋
- 本文的文字及图片来源于网络,仅供学习、交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理。以下文章来源于数据STUDIO,作者龙哥
- 一、爬虫的流程开始学习爬虫,我们必须了解爬虫的流程框架。在我看来爬虫的流程大概就是三步,即不论我们爬取的是什么数据,总是可以把爬虫的流程归纳
- 终于皇天不负有心人,答案还是让我找到了。 网上的都是这样用的 $content = iconv("utf-8",&quo
- <script type="text/javascript"> </script>
- 在《永远强大的函数》那一讲中,老齐我已经向看官们简述了一下变量,之后我们就一直在使用变量,每次使用变量,都要有一个操作,就是赋值。本讲再次提
- Mac版Python3安装/升级Mac系统自带Python,但都是2.X版本,非常老的版本了。如果我们需要安装Python3版本,怎么能快速
- 本文实例为大家分享了TensorFlow实现卷积神经网络的具体代码,供大家参考,具体内容如下代码(源代码都有详细的注释)和数据集可以在git
- 一、爬虫是什么? 在进行大数据分析或者进行数据挖掘的时候,数据源可以从某些提供数据统计的网站获得,也可以从某些文献或内部资料中获得
- c3p0是什么c3p0的出现,是为了大大提高应用程序和数据库之间访问效率的。它的特性:编码的简单易用连接的复用连接的管理说到c3p0,不得不
- python修改图像分辨率大小图像分辨率指图像中存储的信息量,是每英寸图像内有多少个像素点,分辨率的单位为PPI(Pixels Per In
- 通过结构体生成jsonbuf, err := json.MarshalIndent(s, "", " &quo
- 一、条件简化我们编写的查询语句的搜索条件本质上是一个表达式,这些表达式可能比较繁杂,或者不能高效的执行,MySQL的查询优化器会为我们简化这
- 前言酷狗、网抑云和 QQ 音乐都有桌面歌词功能,这篇博客也将使用 pyqt 实现桌面歌词功能,效果如下图所示:代码实现桌面歌词部件 
- 本文实例讲述了PHP面向对象程序设计类的定义与用法。分享给大家供大家参考,具体如下:<?phpclass Person {  
- 人的大脑通过双眼来辨别视觉图形获取信息。大脑根据储存的经验,将所看到的视觉图形建立起优先级。由此可见,一个良好的视觉设计可以帮助大脑迅速有效
- 本文实例讲述了纯JS实现本地图片预览的方法。分享给大家供大家参考。具体如下:刚突然看到,网上已经有很多类似的代码,但没找到一个合适的。就拿自
- 本篇文章面向的读者: 已经基本掌握Go中的 协程(goroutine),通道(channel),互斥锁(sync.Mutex),读