cgo基础(代码片段)

binhome binhome     2022-12-14     798

关键词:

CGO基础

要使用CGO特性,需要安装C/C++构建工具链,在macOS和Linux下是要安装GCC,在windows下是需要安装MinGW工具。同时需要保证环境变量CGO_ENABLED被设置为1,这表示CGO是被启用的状态。在本地构建时CGO_ENABLED默认是启用的,当交叉构建时CGO默认是禁止的。比如要交叉构建ARM环境运行的Go程序,需要手工设置好C/C++交叉构建的工具链,同时开启CGO_ENABLED环境变量。然后通过import "C"语句启用CGO特性。

import "C"语句

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

举个最简单的例子:

package main

/*
#include <stdio.h>

void printint(int v) 
    printf("printint: %d
", v);

*/
import "C"

func main() 
    v := 42
    C.printint(C.int(v))

这个例子展示了cgo的基本使用方法。开头的注释中写了要调用的C函数和相关的头文件,头文件被include之后里面的所有的C语言元素都会被加入到”C”这个虚拟的包中。需要注意的是,import "C"导入语句需要单独一行,不能与其他包一同import。向C函数传递参数也很简单,就直接转化成对应C语言类型传递就可以。如上例中C.int(v)用于将一个Go中的int类型值强制类型转换转化为C语言中的int类型值,然后调用C语言定义的printint函数进行打印。

需要注意的是,Go是强类型语言,所以cgo中传递的参数类型必须与声明的类型完全一致,而且传递前必须用”C”中的转化函数转换成对应的C类型,不能直接传入Go中类型的变量。同时通过虚拟的C包导入的C语言符号并不需要是大写字母开头,它们不受Go语言的导出规则约束。
cgo将当前包引用的C语言符号都放到了虚拟的C包中,同时当前包依赖的其它Go语言包内部可能也通过cgo引入了相似的虚拟C包,但是不同的Go语言包引入的虚拟的C包之间的类型是不能通用的。这个约束对于要自己构造一些cgo辅助函数时有可能会造成一点的影响。

比如我们希望在Go中定义一个C语言字符指针对应的CChar类型,然后增加一个GoString方法返回Go语言字符串:

package cgo_helper

//#include <stdio.h>
import "C"

type CChar C.char

func (p *CChar) GoString() string 
    return C.GoString((*C.char)(p))


func PrintCString(cs *C.char) 
    C.puts(cs)

现在我们可能会想在其它的Go语言包中也使用这个辅助函数:

package main

//static const char* cs = "hello";
import "C"
import "./cgo_helper"

func main() 
    cgo_helper.PrintCString(C.cs)

这段代码是不能正常工作的,因为当前main包引入的C.cs变量的类型是当前main包的cgo构造的虚拟的C包下的*char类型(具体点是*C.char,更具体点是*main.C.char),它和cgo_helper包引入的*C.char类型(具体点是*cgo_helper.C.char)是不同的。在Go语言中方法是依附于类型存在的,不同Go包中引入的虚拟的C包的类型却是不同的(main.C不等cgo_helper.C),这导致从它们延伸出来的Go类型也是不同的类型(*main.C.char不等*cgo_helper.C.char),这最终导致了前面代码不能正常工作。

有Go语言使用经验的用户可能会建议参数转型后再传入。但是这个方法似乎也是不可行的,因为cgo_helper.PrintCString的参数是它自身包引入的*C.char类型,在外部是无法直接获取这个类型的。换言之,一个包如果在公开的接口中直接使用了*C.char等类似的虚拟C包的类型,其它的Go包是无法直接使用这些类型的,除非这个Go包同时也提供了*C.char类型的构造函数。因为这些诸多因素,如果想在go test环境直接测试这些cgo导出的类型也会有相同的限制。

#cgo语句

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

// #cgo CFLAGS: -DPNG_DEBUG=1 -I./include
// #cgo LDFLAGS: -L/usr/local/lib -lpng
// #include <png.h>
import "C"

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

因为C/C++遗留的问题,C头文件检索目录可以是相对目录,但是库文件检索目录则需要绝对路径。在库文件的检索目录中可以通过$SRCDIR变量表示当前包目录的绝对路径:

// #cgo LDFLAGS: -L$SRCDIR/libs -lfoo

上面的代码在链接时将被展开为:

// #cgo LDFLAGS: -L/go/src/foo/libs -lfoo

#cgo语句主要影响CFLAGSCPPFLAGSCXXFLAGSFFLAGSLDFLAGS几个编译器环境变量。LDFLAGS用于设置链接时的参数,除此之外的几个变量用于改变编译阶段的构建参数(CFLAGS用于针对C语言代码设置编译参数)。

对于在cgo环境混合使用C和C++的用户来说,可能有三种不同的编译选项:其中CFLAGS对应C语言特有的编译选项CXXFLAGS对应是C++特有的编译选项CPPFLAGS则对应C和C++共有的编译选项。但是在链接阶段,C和C++的链接选项是通用的,因此这个时候已经不再有C和C++语言的区别,它们的目标文件的类型是相同的。

#cgo指令还支持条件选择,当满足某个操作系统或某个CPU架构类型时后面的编译或链接选项生效。比如下面是分别针对windows和非windows下平台的编译和链接选项:

// #cgo windows CFLAGS: -DX86=1
// #cgo !windows LDFLAGS: -lm

其中在windows平台下,编译前会预定义X86宏为1;在非widnows平台下,在链接阶段会要求链接math数学库。这种用法对于在不同平台下只有少数编译选项差异的场景比较适用。

如果在不同的系统下cgo对应着不同的c代码,我们可以先使用#cgo指令定义不同的C语言的宏,然后通过宏来区分不同的代码:

package main

/*
#cgo windows CFLAGS: -DCGO_OS_WINDOWS=1
#cgo darwin CFLAGS: -DCGO_OS_DARWIN=1
#cgo linux CFLAGS: -DCGO_OS_LINUX=1

#if defined(CGO_OS_WINDOWS)
    const char* os = "windows";
#elif defined(CGO_OS_DARWIN)
    const char* os = "darwin";
#elif defined(CGO_OS_LINUX)
    const char* os = "linux";
#else
#    error(unknown os)
#endif
*/
import "C"

func main() 
    print(C.GoString(C.os))

这样我们就可以用C语言中常用的技术来处理不同平台之间的差异代码。

build标志位条件编译

build tag 是在Go或cgo环境下的C/C++文件开头的一种特殊的注释。条件编译类似于前面通过#cgo指令针对不同平台定义的宏,只有在对应平台的宏被定义之后才会构建对应的代码。但是通过#cgo指令定义宏有个限制,它只能是基于Go语言支持的windows、darwin和linux等已经支持的操作系统。如果我们希望定义一个DEBUG标志的宏,#cgo指令就无能为力了。而Go语言提供的build tag 条件编译特性则可以简单做到。

// +build debug

package main

var buildMode = "debug"

可以用以下命令构建:

$ go build -tags="debug"
$ go build -tags="windows debug"

我们可以通过-tags命令行参数同时指定多个build标志,它们之间用空格分隔。

当有多个build tag时,我们将多个标志通过逻辑操作的规则来组合使用。比如以下的构建标志表示只有在”linux/386“或”darwin平台下非cgo环境“才进行构建。

// +build linux,386 darwin,!cgo

其中linux,386中linux和386用逗号链接表示AND的意思;而linux,386和darwin,!cgo之间通过空白分割来表示OR的意思。

build Demo:

main.go

package main

import "fmt"

func main() 
	fmt.Printf("%s
", s)

s1.go

// +build S1

package main

var s = "S1"

s2.go

// +build S1

package main

var s = "S2"
$ go build -tag="S1"
$ ./test -> S1
$ go build -tag="S2"
$ ./test -> S2

深入学习cgo(代码片段)

深入学习CGO快速入门基础知识import"C"语句`#cgo`语句GO与C的类型转换CGO函数调用CGO内部机制CGO内存模型C++类封装成CAPICGO调用在goruntime层面的处理CGO的静态/动态库封装以及编译链接参数CGO定位内存泄露CGO性能CGO最... 查看详情

cgo内部机制(代码片段)

...录并且在构建完成时保留中间文件。如果是比较简单的cgo代码我们也可以直接通过手工调用gotoolcgo命令来查看生成的中间文件。在一个Go源文件中,如果出现了import"C"指令则表示将调用cgo命令生成对应的 查看详情

cgo静态库和动态库(代码片段)

...直接使用源码就是在import"C"之前的注释部分包含C代码,或者在当前包中包含C/C++源文件。链接静态库和动态库的方式比较类似,都是通过在LDFLAGS选项指定要链接的库方式链接。本节我们主要关注在CGO中如何使用静态库和... 查看详情

cgo编译和链接参数(代码片段)

CGO编译和链接参数编译和链接参数是每一个C/C++程序员需要经常面对的问题。构建每一个C/C++应用均需要经过编译和链接两个步骤,CGO也是如此。本节我们将简要讨论CGO中经常用到的编译和链接参数的用法。编译参数:CFLAGS/CPPFLAG... 查看详情

cgo类型转换(代码片段)

类型转换最初CGO是为了达到方便从Go语言函数调用C语言函数(用C语言实现Go语言声明的函数)以复用C语言资源这一目的而出现的(因为C语言还会涉及回调函数,自然也会涉及到从C语言函数调用Go语言函数(用Go语言实现C语言声... 查看详情

gitadd.和gitadd*区别(代码片段)

gitadd.会把本地所有untrack的文件都加入暂存区,并且会根据.gitignore做过滤;gitadd*会忽略.gitignore把任何文件都加入.一个.gitignore文件例子:*.o*.a*.so_obj_test*.[568vq][568vq].out*.cgo1.go*.cgo2.c_cgo_defun.c_cgo_gotypes.go_cgo_exp 查看详情

cgo实战-封装qsort函数(代码片段)

qsort快速排序函数是C语言的高阶函数,支持用于自定义排序比较函数,可以对任意类型的数组进行排序。本节我们尝试基于C语言的qsort函数封装一个Go语言版本的qsort函数。认识qsort函数qsort快速排序函数有<stdlib.h>标准库提供... 查看详情

cgo:ccompiler“gcc“notfound:exec:“gcc“:executablefilenotfoundin%path%(代码片段)

cgo:Ccompiler"gcc"notfound:exec:"gcc":executablefilenotfoundin%PATH%问题描述原因分析解决方案参考链接问题描述场景描述win10系统搭建的golang开发环境,版本是goversiongo1.17.2windows/amd64,采用gongland作为开发工具, 查看详情

golangcgo使用总结(代码片段)

...,C++的接口可以用C包装一下提供给golang调用。被调用的C代码可以直接以源代码形式提供或者打包静态库或动态库在编译时链接。推荐使用静态库的方式,这样方便代码隔离,编译的二进制也没有动态库依赖方便 查看详情

golang对不同系统的编译(代码片段)

Golang支持在一个平台下生成另一个平台可执行程序的交叉编译功能。Mac下编译#mac编译linux执行文件CGO_ENABLED=0GOOS=linuxGOARCH=amd64gobuildmain.go#mac编译windows执行文件CGO_ENABLED=0GOOS=windowsGOARCH=amd64gobuildmain.goLinux下编译#编译mac执行文件CGO_EN... 查看详情

golang交叉编译(代码片段)

...ang的交叉编译要保证golang版本在1.5以上,本解决方案实例代码1.9版本执行的。GOOS=linuxGOARCH=amd64gobuildhello.go这里用到了两个变量:GOOS:目标操作系统GOARCH:目标操作系统的架构OSARCHOSVersionlinux386/amd64/arm>=Linux2.6darwin386/amd64OSX(SnowL... 查看详情

我的go+语言初体验——goplus基础语法学习(代码片段)

...容Go的特性(包括部分支持cgo)在GoPlus中用到的Go基础,具体应用到的基础如下数据定义布尔型布尔型的值只可以是常量true或者false。一个简单的例子:varbbool=true。数字类型整型int和浮点型float32、float64,Go... 查看详情

aconcurrent-safecentralizedpointermanagingfacility(代码片段)

AConcurrent-safeCentralizedPointerManagingFacilityAuthor:ChangkunOu[1]Permalink:https://golang.design/research/cgo-handle[2]IntheGo1.17release,wecontributedanewcgofacilityruntime/cgo.Handle[3]inordert 查看详情

cgo之类型转换

...unsignedint不能直接通过C.unsignedint访问。因此CGO为C语言的基础数值类型都提供了相应转换规则,比如C.uint对应C语言的unsignedint。packagemain/*s 查看详情

[go语言]cgo用法演示

...成的库已经非常丰富。通过cgo,可以在Go语言中使用C语言代码,充分利用好现有的“轮子”。本文所有代码,在下述环境中调试通过: Windows8.164-bitGo1.3.364-bitGCC4.8.164-bit 要想使用cgo,要导入C“包”: import"C"这行代码... 查看详情

cgo之调用静态库

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

go初接触之缩略图(代码片段)

...,前几天写的demo发现确实效率高了不少,所以用cgo把C的代码封装成库,已经基本完成。当成是一个对go、cgo的学习和对C的一些复习吧。1#ifndefCompressImage_C2#defineCompressImage_C34#include<stdio.h>5#include<math.h 查看详情

如何用 Go 语言的 cgo 编译 Cuda 源代码?

】如何用Go语言的cgo编译Cuda源代码?【英文标题】:howtocompileCudasourcewithGolanguage\'scgo?【发布时间】:2015-09-1514:51:06【问题描述】:我用cuda-c写了一个简单的程序,它可以在eclipsensight上运行。这是源代码:#include<iostream>#includ... 查看详情