乐趣区

关于go:CGO-初步认知和基本数据类型转换

CGO 是什么?

CGO 是 GO 语言外面的一个个性,CGO 属于 GOLANG 的高级用法,次要是通过应用 GOLANG 调用 CLANG 实现的程序库

应用

咱们能够应用

import "C" 来应用 CGO 这个个性

一个最简略的 CGO 应用

package main


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

func main(){C.puts(C.CString("Hello, Cgo\n"))
}

import "C" 的上方能够写须要导入的库 C 库,须要正文起来,CGO 会将此处的正文内容当做 C 的代码,被称为 序言(preamble)

上述代码的性能解释

应用 CGO 包的 C.CString 函数将 Go 语言字符串转为 C 语言字符串

最初调用 CGO 包的 C.puts 函数向规范输入窗口打印转换后的 C 字符串

应用 go build -x main.go 编译一下

加上 -x 能够打印出编译过程中执行的指令

# go build -x main.go
WORK=/tmp/go-build594331603
mkdir -p $WORK/b001/
cat >$WORK/b001/importcfg.link << 'EOF' # internal
packagefile command-line-arguments=/root/.cache/go-build/fb/fbb37eeb6735cb453f6d92e2e3f46f14d9dceb5baa1cdd10aae11d1d47d60e55-d
packagefile runtime/cgo=/usr/local/go/pkg/linux_amd64/runtime/cgo.a
packagefile syscall=/usr/local/go/pkg/linux_amd64/syscall.a
packagefile runtime=/usr/local/go/pkg/linux_amd64/runtime.a
packagefile errors=/usr/local/go/pkg/linux_amd64/errors.a
packagefile internal/bytealg=/usr/local/go/pkg/linux_amd64/internal/bytealg.a
packagefile internal/oserror=/usr/local/go/pkg/linux_amd64/internal/oserror.a
packagefile internal/race=/usr/local/go/pkg/linux_amd64/internal/race.a
packagefile internal/unsafeheader=/usr/local/go/pkg/linux_amd64/internal/unsafeheader.a
packagefile sync=/usr/local/go/pkg/linux_amd64/sync.a
packagefile internal/cpu=/usr/local/go/pkg/linux_amd64/internal/cpu.a
packagefile runtime/internal/atomic=/usr/local/go/pkg/linux_amd64/runtime/internal/atomic.a
packagefile runtime/internal/math=/usr/local/go/pkg/linux_amd64/runtime/internal/math.a
packagefile runtime/internal/sys=/usr/local/go/pkg/linux_amd64/runtime/internal/sys.a
packagefile internal/reflectlite=/usr/local/go/pkg/linux_amd64/internal/reflectlite.a
packagefile sync/atomic=/usr/local/go/pkg/linux_amd64/sync/atomic.a
EOF
mkdir -p $WORK/b001/exe/
cd .
/usr/local/go/pkg/tool/linux_amd64/link -o $WORK/b001/exe/a.out -importcfg $WORK/b001/importcfg.link -buildmode=exe -buildid=Vv0to6CWqbWf5_KTN66F/K36AEO-x4qJ_LJbz5wgG/HVbBbLSaW0sTSwlN8TzN/Vv0to6CWqbWf5_KTN66F -extld=gcc /root/.cache/go-build/fb/fbb37eeb6735cb453f6d92e2e3f46f14d9dceb5baa1cdd10aae11d1d47d60e55-d
/usr/local/go/pkg/tool/linux_amd64/buildid -w $WORK/b001/exe/a.out # internal
mv $WORK/b001/exe/a.out main
rm -r $WORK/b001/

尝试本人写一个 C 函数,让 GO 来调用他

Go 语言环境中调用这个 SayHello 函数

package main

/*
#include <stdio.h>

static void SayHello(const char* s) {puts(s);
}
*/
import "C"

func main(){C.SayHello(C.CString("hello xiaomotong study cgo\n"))
}

尝试本人写一个 C 文件,而后 GO 中进行导入和调用

xmtC.h

void SayHi(const char * str);

xmtC.c

(必须是同级目录下的 .c 文件,cgo 应用 go build 编译的时候,会默认在同级目录下找 .c 文件进行编译,如果咱们是须要将 C 文件做成动态库 或者 动静库的形式,那么就不要将 C 的源码文件放到同级目录下了,防止重名)

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

void SayHi(const char * str){puts(str);
}

main.go

package main

//void SayHi(const char * str);
import "C"

func main(){C.SayHi(C.CString("hello xiaomotong study cgo\n"))
}

间接运行 go build 进行编译,运行可执行程序即可

# go build
# ls
cgo  main.go  xmtC.c
# ./cgo
hello xiaomotong study cgo

通过面向 C 语言接口的编程技术,咱们不仅仅解放了函数的实现者,同时也简化的函数的使用者。当初咱们能够将 SayHi 当作一个规范库的函数应用(和 puts 函数的应用形式相似)

咱们也能够在 go 文件中写成这个样子

package main

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

func main(){C.SayHi(C.CString("hello xiaomotong study cgo\n"))
}

合并 C 和 GO 的代码

Go1.10 中 CGO 新减少了一个 _GoString_ 预约义的 C 语言类型,用来示意 Go 语言字符串

// +build go1.10

package main

//void SayHi(_GoString_ s);
import "C"

import ("fmt")

func main() {C.SayHi("hello xiaomotong study cgo\n")
}

//export SayHi
func SayHi(s string) {fmt.Print(s)
}

上述代码的具体执行逻辑程序是这样的:

CGO 环境

应用 CGO 须要肯定的环境环境反对

  • linux 下 须要有 gcc/g++ 的编译环境
  • windows 下须要有 MinGW 工具
  • 须要把 GO 的环境变量 CGO_ENABLED 置位 1

上述的例子中,咱们有几个须要留神的点:

  • import "C" 语句不能和其余的 import 语句放在一起,须要独自一行搁置
  • 上述咱们在 GO 外面传递的值,例如 C.CString("hello xiaomotong study cgo\n") 是调用了 C 的虚构包,将字符串转换成 C 的字符串传入进去
  • Go 是强类型语言

    所以 cgo 中传递的参数类型必须与申明的类型完全一致,而且传递前必须用”C”中的转化函数转换成对应的 C 类型,不能间接传入 Go 中类型的变量

    通过虚构的 C 包导入的 C 语言符号并 不须要是大写字母结尾,它们不受 Go 语言的导出规则束缚

cgo 用法

咱们能够应用 #cgo 语句设置编译阶段和链接阶段的相干参数

  • 编译阶段的参数

次要用于定义相干宏和指定头文件检索门路

  • 链接阶段的参数

次要是指定库文件检索门路和要链接的库文件

例如咱们能够这样

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

CFLAGS

  • -DPNG_DEBUG

定义宏 PNG_DEBUG,设置为 1

  • -I

定义头文件的检索目录是 ./include

LDFLAGS

  • -L

指定链接时库文件检索目录,能够通过写 ${SRCDIR}来示意以后包的绝对路径

  • -l

指定链接时须要的库,此处是 png 库

条件编译 build tag

就是在咱们 go build 的时候,增加一些条件参数,当然这个条件参数在对应的文件中是须要有的,

例如上述咱们应用 Go1.10 的时候,就在文件中增加了 // +build go1.10

咱们能够这样用:

go build -tags="debug"
go build -tags="debug test"
go build -tags="linux,386"

go build 的时候加上 -tags 参数,若有多个咱们能够一起写,用 空格 距离,示意 ,用 逗号 距离示意

GO 和 C 数据类型互相转换

cgo 官网提供了如下的数据类型转换:

C 语言类型 CGO 类型 Go 语言类型
char C.char byte
singed char C.schar int8
unsigned char C.uchar uint8
short C.short int16
unsigned short C.ushort uint16
int C.int int32
unsigned int C.uint uint32
long C.long int32
unsigned long C.ulong uint32
long long int C.longlong int64
unsigned long long int C.ulonglong uint64
float C.float float32
double C.double float64
size_t C.size_t uint

须要留神 3 个点:

  • CGO 中,C 语言的 intlong类型都是对应 4 个字节的内存大小,size_t 类型能够当作 Go 语言 uint 无符号整数类型看待
  • CGO 中,C 语言的 int 固定为 4 字节的大小,GO 语言的 intuint 却在 32 位和 64 位零碎下别离对应 4 个字节和 8 个字节大小
  • 例如数据类型两头有空格,unsigned int 不能间接通过 C.unsigned int 拜访,能够应用 typedef 关键字提供一个规定的类型命名,这样更利于在 CGO 中拜访

字符串和切片类型

CGO 生成的 _cgo_export.h 头文件中有 GO 外面字符串,切片,通道,字典,接口等数据类型对应的示意形式,然而咱们个别应用有价值的就是字符串和切片了

因为 CGO 没有提供其余数据类型的辅助函数

typedef struct {const char *p; GoInt n;} GoString;

咱们导出函数的时候能够这样写:

应用 _GoString_预约义类型,这样写能够升高在 cgo 代码中可能对 _cgo_export.h 头文件产生的循环依赖的危险

_GoString_ 是 Go1.10 针对 Go 专门加的字符

extern void helloString(_GoString_ p0);

咱们能够应用官网提供的函数 计算字符串的长度 获取字符串的地址

size_t _GoStringLen(_GoString_ s);
const char *_GoStringPtr(_GoString_ s);

struct,union,enum

GO 语言中拜访 C 语言的 struct,union,enum,能够查看下表的对应关系

C 语言 GO 语言
struct xx C.struct_xx
union xx C.union_xx
enum xx C.enum_xx

对于构造体 struct

构造体的内存布局依照 C 语言的通用对齐规定

在 32 位 Go 语言环境 C 语言构造体也依照 32 位对齐规定,在 64 位 Go 语言环境依照 64 位的对齐规定

对于指定了非凡对齐规定的构造体,无奈在 CGO 中拜访

GO 中能够这样拜访 C 的构造体

package main

/*
struct struct_TEST {
    int i;
    float f;
};
*/
import "C"
import "fmt"

func main() {
    var a C.struct_TEST
    a.i = 1
    a.f = 2

    fmt.Println(a.i)
    fmt.Println(a.f)
}

须要留神如下 2 个大点:

  • 构造体成员的名字和 GO 中关键字的名字一样咋解决

例如上述构造体成员名字是这样的

struct struct_TEST {
    int type;
    float f;
};

那么咱们拜访 type 的时候,能够这样拜访 a._type 即可

若构造体是这样的呢?

struct struct_TEST {
    int type;
    float _type;
};

咱们拜访的时候依然是这样拜访,a._type ,不过理论拜访到的是 float _type;,通过 GO 就没有方法拜访到 int type;

GO 中也无法访问 C 中的 零长数组 和 位字段,例如

struct struct_TEST {
    int   size: 10; // 位字段无法访问
    float arr[];    // 零长的数组无法访问};
  • 在 C 语言中,无奈间接拜访 Go 语言定义的构造体类型

对于枚举 enum

枚举类型底层对应 int 类型,反对正数类型的值,咱们能够间接应用 C.xx 来进行拜访

例如枚举类型为:

enum TEST {
    ONE,
    TWO,
};

应用这个类型咱们能够用 c C.enum_TEST

给这个变量复制的时候,咱们能够这样做:c = C.ONE

对于联合体 union

Go 语言中并不反对 C 语言联结类型,它们会被转为对应大小的字节数组

例如

union B1 {
    int i;
    float f;
};

union B1 会被转换成为 4 个字节大小的 字节数组 [4]uint8

GO 中操作联合体变量有 3 种形式:

  • 在 C 语言中定义辅助函数
  • Go 语言的 encoding/binary 手工解码成员(须要留神大端小端问题)
  • 应用 unsafe 包强制转型为对应类型

举个例子

package main

/*
#include <stdint.h>

union TEST {
    int i;
    float f;
};
*/
import "C"
import (
    "fmt"
    "unsafe"
)

func main() {
    var b C.union_TEST

    *(*C.int)(unsafe.Pointer(&b)) = 1

    fmt.Println("b.i:", *(*C.int)(unsafe.Pointer(&b)))
    fmt.Println("b.f:", *(*C.float)(unsafe.Pointer(&b)))
}

咱们读取和写入联合体变量的时候,应用 unsafe 包性能是最好的,通过unsafe 获取指针,而后转成对应的数据类型的指针即可

参考资料:

GO 高级编程

欢送点赞,关注,珍藏

敌人们,你的反对和激励,是我保持分享,提高质量的能源

好了,本次就到这里

技术是凋谢的,咱们的心态,更应是凋谢的。拥抱变动,背阴而生,致力向前行。

我是 小魔童哪吒,欢送点赞关注珍藏,下次见~

退出移动版