假设(并非完全假设,这里有 demo)你正在编写一个程序包,用于连接 Go 和其它一些提供大量 C 结构体内存的程序。这些结构可能是系统调用的结果,也可能是一个库给你提供的纯粹信息性内容。无论哪种情况,你都希望将这些结构传递给你的程序包的用户,以便他们可以使用这些结构执行操作。在你的包中,你可以直接使用 cgo 提供的 C. 类型。但这有点恼人(这些整型它们没有对应的原生 Go 类型,使得与常规 Go 代码交互需要乱七八糟的强制转换),并且对于其它导入你的包的代码没有帮助。因此,你需要以某种方式使用原生的 Go 结构体。

一种方式是手动为这些 C 结构体的定义你自己的 Go 版本。这有两个缺点。这太枯燥了(还很容易出错),并且不能保证你能获得与 C 完全相同的内存布局(后者通常但并非总是很重要)。幸运的是有一种更好的方法,那就是使用 cgo 的 -godefs 功能或多或少地为你自动生成结构体声明。生成结果并不总是完美的,但可能会为你带来最大的收益。

使用 -godefs 的起点是特殊的 cgo Go 源文件,该文件需要将某些 Go 类型声明为某些 C 类型。例如:

// +build ignore
package kstat
// #include <kstat.h>
import "C" type IO C.kstat_io_t
type Sysinfo C.sysinfo_t const Sizeof_IO = C.sizeof_kstat_io_t
const Sizeof_SI = C.sizeof_sysinfo_t

这些常量对于喜欢较真的人很有用,可以用来在后面对比检查 Go 类型的 unsafe.Sizeof() 和 C 类型的大小是否一致。

运行 go tool cgo -godefs .go ,它将打印一系列带有导出字段和所有内容的标准 Go 类型到标准输出。然后,你可以将其保存到文件中并使用。如果你认为 C 类型可能会更改,则应将生成的文件保留下来,这样就避免重新生成文件遇到的很多麻烦。如果 C 类型基本上是固定的,则可以使用 godoc 对生成的输出进行注释。 cgo 会考虑类型匹配问题,它会把原始的 C 结构中存在的 padding 也插入到输出中。

我不知道如果原始的 C 结构体不可能在 Go 中重建出来,cgo 会怎么办。 比如 Go 需要 padding,但是 C 不需要。希望它会指出错误。这是你以后可能要检查这些 sizeof 的原因之一。

-godefs 最大的限制是与 cgo 通常具有的限制相同:它没有对 C 联合类型(union)的真正支持,因为 Go 确实没有这个。如果你的 C 结构体中有联合,你得自己弄清楚如何处理它们;我相信 cgo 把这些转换为大小合适的 uint8 数组,但这对于实际访问内容不是很有用。

这里有两个问题。假设你有一个嵌入了另一个结构体类型的结构体:

struct cpu_stat {
struct cpu_sysinfo cpu_sysinfo;
struct cpu_syswait cpu_syswait;
struct vminfo cpu_vminfo;
}

在这里,你必须给 cgo 一些帮助,方式是在主结构体类型之前创建嵌入结构类型的 Go 版本:

type Sysinfo C.struct_cpu_sysinfo
type Syswait C.struct_cpu_syswait
type Vminfo C.struct_cpu_vminfo type CpuStat C.struct_cpu_stat

然后 cgo 才能生成正确的内嵌的 Go 结构的 CpuStat 结构。如果不这样做,你将获得一个 CpuStat 结构类型,该结构类型具有不完整的类型信息,其中的 Sysinfo 等字段将引用名为 _Ctype_… 的未在任何地方定义的类型。

顺便说一句,我在这确实是指 Sysinfo ,而不是 Cpu_sysinfo 。cgo 足够聪明,可以从结构字段名称中删除这种常见的前缀。我不知道它的算法是怎样的,但至少是有用的。

第二个问题是嵌入了匿名结构:

struct mntinfo_kstat {
....
struct {
uint32_t srtt;
uint32_t deviate;
} m_timers[4];
....
}

不幸的是,cgo 根本无法处理这种问题。具体可以去看 issue 5253 ,你有两个选择,第一种是使用建议的 CL 修复,这个目前仍然适用于 src/cmd/cgo/gcc.go 并且能够工作(对我来说)。如果你不想构建自己的 Go 工具链(或者如果 CL 不再适用或无法工作),另一种解决方案是创建一个新的 C 头文件,该文件具有整个结构体的变体,通过创建具名结构体去除结构体的匿名化。

struct m_timer {
uint32_t srtt;
uint32_t deviate;
} struct mntinfo_kstat_cgo {
....
struct m_timer m_timers [4];
....
}

然后,在你的 Go 文件中,

...
// #include "myhacked.h"
... type MTimer C.struct_m_timer
type Mntinfo C.struct_mntinfo_kstat_cgo

除非你搞错了,否则两个 C 结构体应具有完全相同的大小和布局,并且彼此完全兼容。现在你可以在你的版本上使用 -godefs 了,记住按照前面问题 1 的处理,需要为 m_timer 创建明确的 Go 类型。 如果你飘了(你认为你不在需要重新生成这些内容了),你可以在生成的 Go 文件中逆转这个过程,重新将 MTimer 类型匿名化到结构体中(因为 Go 对匿名结构体有很好的支持)。因为你没有更改实际内容,只是改了类型声明,所以结果应该与原始的布局相同。

PS:-godefs 的输入文件被设置为不被正常 go build 过程构建,因为它只用于 godefs 生成。如果这个文件也被包含在 go build 构建的源码中,你会得到关于 Go 类型多处定义的构建错误。必然的结果是,你不需要将此文件和任何相关 .h 文件与软件包的常规 .go 文件放在同一目录。你可以把他们放在子目录,或者放在完全独立的位置。

(我认为该 package 行在 godefs .go 文件中唯一要做的就是设置 cgo 将在输出中打印的软件包名称。)


via: https://utcc.utoronto.ca/~cks/space/blog/programming/GoCGoCompatibleStructs

作者:ChrisSiebenmann 译者:befovy 校对:polaris1119

本文由 GCTT 原创编译,Go语言中文网 荣誉推出

用 cgo 生成用于 cgo 的 C 兼容的结构体的更多相关文章

  1. 生成用于ROM初始化的coe文件---使用matlab

    生成用于ROM初始化的coe文件---使用matlab t=0:2*pi/2^12:2*pi; y=0.5*sin(t)+0.5; r=ceil(y*(2^8-1)); fid = fopen('si ...

  2. 莫烦python教程学习笔记——使用波士顿数据集、生成用于回归的数据集

    # View more python learning tutorial on my Youtube and Youku channel!!! # Youtube video tutorial: ht ...

  3. [GO]通过结构体生成json

    package main import ( "encoding/json" "fmt" ) type IT struct { //一定要注意这里的成员变量的名字 ...

  4. 内核中用于数据接收的结构体struct msghdr(转)

    内核中用于数据接收的结构体struct msghdr(转) 我们从一个实际的数据包发送的例子入手,来看看其发送的具体流程,以及过程中涉及到的相关数据结构.在我们的虚拟机上发送icmp回显请求包,pin ...

  5. go语言之进阶篇通过结构体生成json

    1.通过结构体生成json 示例: package main import ( "encoding/json" "fmt" ) //成员变量名首字母必须大写 t ...

  6. golang 兼容不同json结构体解析实践

    线上服务器,同一个web接口有时需要兼容不同版本的结构体.这种情况思路是使用interface{}接收任意类型数据,结合reflect包处理. 如下,http接口调用者会传入不同的json结构数据(单 ...

  7. go 从表结构生成结构体

    package main import ( "fmt" "github.com/gohouse/converter" ) func main() { // 初始 ...

  8. python脚本将json文件生成C语言结构体

    1.引言 以前用过python脚本根据excel生成相关C语言代码,其实本质就是文件的读写,主要是逻辑问题,这次尝试将json文件生成C语言的结构体. 2.代码 这是一个json文件,生成这个结构体的 ...

  9. CGO 类型(CGO Types) 一

    CGO Types C作为一种混合编程语言已经很久了,无论那些广泛使用的包是用何种语言实现的,都导出了和C兼容的API.Go程序调用C程序,可以借助两种工具实现,一种是cgo,另一种是SWIG工具.C ...

随机推荐

  1. java-把生成的随机数,指定范围(如:100-200),指定打印次数(如:50次),并进行去重。

    package main.demo; public class Demo4 { /** * 随机指定范围内N个不重复的数 * 最简单最基本的方法 * @param min 指定范围最小值 * @par ...

  2. 如果你还不知道如何控制springboot中bean的加载顺序,那你一定要看此篇

    1.为什么需要控制加载顺序 springboot遵从约定大于配置的原则,极大程度的解决了配置繁琐的问题.在此基础上,又提供了spi机制,用spring.factories可以完成一个小组件的自动装配功 ...

  3. 理解Linux的硬链接与软链接-转载

    理解Linux的硬链接与软链接 来自:https://www.ibm.com/developerworks/cn/linux/l-cn-hardandsymb-links/index.html

  4. 大汇总 | 一文学会八篇经典CNN论文

    本文主要是回顾一下一些经典的CNN网络的主要贡献. 论文传送门 [google团队] [2014.09]inception v1: https://arxiv.org/pdf/1409.4842.pd ...

  5. Spring集成CXF发布WebService并在客户端调用

    Spring集成CXF发布WebService 1.导入jar包 因为官方下载的包里面有其他版本的sprring包,全导入会产生版本冲突,所以去掉spring的部分,然后在项目根目录下新建了一个CXF ...

  6. LIMS/QMS产品索引

    Starlims https://www.cnblogs.com/mahongbiao/p/12863304.html 客户申请门户/客户服务门户 https://www.cnblogs.com/ma ...

  7. Python while 中简单的语句组

    Python while 中简单的语句组: 只使用 while: # 简单的语句组 a = 4 b = 8 num = 0 while a < b: print("a 比 b 小&qu ...

  8. PHP sleep() 函数

    实例 延迟执行当前脚本 5 秒: <?phpecho date('h:i:s') . "<br>"; //sleep for 5 secondssleep(5); ...

  9. PHP print() 函数

    实例 输出一些文本: <?php print "Hello world!"; ?>高佣联盟 www.cgewang.com 定义和用法 print() 函数输出一个或多 ...

  10. Promise核心基础

    基础 Promise 抽象表达:是js中进行异步编程的新的解决方案 具体解释:1.从语法上来说是一个构造函数 2.从功能上来说promise对象用来封装一个异步操作并可以获取其结果 状态改变:0.ne ...