golang中cgo中文说明


原文链接: golang中cgo中文说明

cgo

  1. 使用带有go命令的cgo
  2. Go调用C的函数
  3. C引用Go的函数
  4. 指针传递的实现
  5. 特别案例
  6. 直接使用cgo

Cgo支持创建调用C代码的Go包。

1. 使用带有go命令的cgo

要使用cgo,
首先 编写导入伪包“C”的普通Go代码。
然后,在Go代码中就可以引用诸如C.size_t之类的类型,诸如C.stdout()之类的变量,或诸如C.putchar()之类的函数。

如果import "C"后面紧跟注释,则在编译包的C部分时,该注释(称为前导码)将用作标题。例如:

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

前导码可以包含任何C代码,包括函数和变量声明和定义。然后可以从Go代码引用它们,就好像它们是在包“C”中定义的那样。
即使从小写字母开头,也可以使用序言中声明的所有名称。
例外:前导码中的静态变量可能不会从Go代码中引用; 允许静态函数。

有关使用cgo的介绍:https://golang.org/doc/articles/c_go_cgo.html 。

可以在这些注释中使用伪#cgo指令定义CFLAGS,CPPFLAGS,CXXFLAGS,FFLAGS和LDFLAGS,以调整C,C ++或Fortran编译器的行为。
多个指令中定义的值连接在一起。该指令可以包含一个构建约束列表,将其影响限制为满足其中一个约束的系统(有关约束语法的详细信息,请参阅https://golang.org/pkg/go/build/#hdr-Build_Constraints)。例如:

-dl 加载动态库
-stdc++

// #cgo CFLAGS:-DPNG_DEBUG = 1
// #cgo amd64 386 CFLAGS:-DX86 = 1
// #cgo LDFLAGS:-lpng
// #include <png.h>
import "C"

或者,CPPFLAGS和LDFLAGS可以通过pkg-config工具使用#cgo pkg-config:指令,后跟包名称获得。例如:

// #cgo pkg-config:png cairo
// #include <png.h>
import "C"

可以通过设置PKG_CONFIG环境变量来更改默认的pkg-config工具。

出于安全原因,仅允许一组有限的标志,特别是-D,-I和-l。
要允许其他标志,请将CGO_CFLAGS_ALLOW设置为与新标志匹配的正则表达式。
要禁止原本允许的标志,请将CGO_CFLAGS_DISALLOW设置为匹配必须禁止的参数的正则表达式。
在这两种情况下,正则表达式必须匹配一个完整的参数:允许-mfoo = bar,使用CGO_CFLAGS_ALLOW =' - mfoo。*',而不仅仅是CGO_CFLAGS_ALLOW =' - mfoo'。类似命名的变量控制允许的CPPFLAGS,CXXFLAGS,FFLAGS和LDFLAGS。

同样出于安全原因,仅允许有限的一组字符,特别是字母数字字符和一些符号,例如“。”,不会以意外的方式解释。尝试使用禁用字符会出现“格式错误的#cgo参数”错误。

构建时,CGO_CFLAGS,CGO_CPPFLAGS,CGO_CXXFLAGS,CGO_FFLAGS和CGO_LDFLAGS环境变量将添加到从这些指令派生的标志中。应使用指令而不是环境变量来设置特定于程序包的标志,以便构建在未修改的环境中工作。从环境变量获得的标志不受上述安全限制的约束。

包中的所有cgo CPPFLAGS和CFLAGS指令都连接在一起,用于编译该包中的C文件。程序包中的所有CPPFLAGS和CXXFLAGS指令都连接在一起,用于编译该程序包中的C ++文件。程序包中的所有CPPFLAGS和FFLAGS指令都连接在一起,用于编译该程序包中的Fortran文件。程序中任何包中的所有LDFLAGS指令都被连接并在链接时使用。所有pkg-config指令都连接在一起并同时发送到pkg-config,以添加到每组适当的命令行标志。

变量 ${SRCDIR} 用来指代原文件所在的文件夹的绝对路径,这允许你将预先编译好的静态库放在本地文件夹中,让编译器可以找到这些库以便正确的链接。比如包foo在文件夹/go/src/foo下:

package foo
// #cgo LDFLAGS: -L${SRCDIR}/libs -lzip
将展开为:
// #cgo LDFLAGS: -L/go/src/foo/libs -lzip

当Go工具看到一个或多个Go文件使用特殊import "C"时,它将在目录中查找其他非Go文件,并将它们编译为Go包的一部分。任何.c,.s或.S文件都将使用C编译器进行编译。任何.cc,.cpp或.cxx文件都将使用C ++编译器进行编译。任何.f,.F,.for或.f90文件都将使用fortran编译器进行编译。任何.h,.hh,.hpp或.hxx文件都不会单独编译,但是,如果更改了这些头文件,则将重新编译包(包括其非Go源文件)。请注意,对其他目录中的文件的更改不会导致重新编译程序包,因此程序包的所有非Go源代码都应存储在程序包目录中,而不是存储在子目录中。CC和CXX环境变​​量可能会更改默认的C和C ++编译器,分别; 这些环境变量可能包括命令行选项。

默认情况下,cgo工具针对预期可在其中工作的系统上的本机构建启用。CGO_ENABLED=1
交叉编译时默认禁用它。您可以通过在运行go工具时设置CGO_ENABLED=1环境变量来控制此操作:将其设置为1以启用cgo,并将其设置为0以禁用它。
如果启用了cgo,go工具将设置构建约束“cgo”。特殊import "C"意味着“cgo”构建约束,好像文件也说“// + build cgo”。
因此,如果禁用cgo,则go工具不会构建import "C"的文件。(有关构建约束的更多信息,请参阅https://golang.org/pkg/go/build/#hdr-Build_Constraints)。

交叉编译时,必须为cgo指定C交叉编译器才能使用。您可以通过在使用make.bash构建工具链时设置通用CC_FOR_TARGET或更具体的CC_FOR _ $ {GOOS} _ $ {GOARCH}(例如,CC_FOR_linux_arm)环境变量来执行此操作,或者您可以随时设置CC环境变量你运行go工具。

对于C ++CXX_FOR_TARGET, CXXFOR${GOOS}_${GOARCH}和CXX环境变​​量的工作方式类似。

Go 调用C

在Go文件中,作为Go中关键字的C结构字段名称可以通过在下面添加下划线来访问:如果x指向带有名为“type”的字段的C结构,则x._type访问该字段。Go结构中省略了不能用Go表示的C结构字段,例如位字段或未对齐的数据,替换为适当的填充以到达下一个字段或结构的结尾。

  1. 标准C数字类型以名称C.char, *C.schar(signed char *), C.uchar(unsigned char), C.short, C.ushort(unsigned short), C.int, C.uint(名称)提供。 unsigned int), C.long, C.ulong(unsigned long), C.longlong(long long), C.ulonglong(unsigned long long), C.float, C.double, C.complexfloat(complex float), and C.complexdouble(复杂的双重)。
  2. C类型void * 由Go的 unsafe.Pointer表示。C类型int128_t和uint128_t由[16]byte字节表示。

  3. 一些特殊的C类型通常由Go中的指针类型表示,而是由uintptr表示。请参阅下面的特殊情况部分。

  4. 要直接访问struct,union或enum类型,请在其前面加上struct_,union_或enum_,如C.struct_stat中所示。

  5. 任何C类型T的大小都可以作为C.sizeof_T使用,如C.sizeof_struct_stat中所示。

  6. 可以在Go文件中使用特殊名称_GoString_的参数类型声明AC函数。可以使用普通的Go字符串值调用此函数。可以通过调用C函数来访问字符串长度和指向字符串内容的指针

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

这些功能仅在前导码中可用,而不在其他C文件中。C代码不得修改_GoStringPtr返回的指针的内容。请注意,字符串内容可能没有尾随NUL字节。

由于Go在一般情况下不支持C的联合类型,因此C的联合类型表示为具有相同长度的Go字节数组。

Go结构不能嵌入具有C类型的字段。

Go代码不能引用非空C结构末尾出现的零大小字段。要获取此类字段的地址(这是您可以使用零大小字段执行的唯一操作),您必须获取结构的地址并添加结构的大小。

Cgo将C类型转换为等效的未导出的Go类型。由于转换是未导出的,因此Go包不应在其导出的API中公开C类型:一个Go包中使用的C类型与另一个中使用的相同C类型不同。

可以在多个赋值上下文中调用任何C函数(甚至是void函数)以将返回值(如果有)和C errno变量都检索为错误(如果函数返回void,则使用_跳过结果值)。例如:

n,err = C.sqrt(-1)
_,错误:= C.voidFunc()
var n,err = C.sqrt(1)
目前不支持调用C函数指针,但是您可以声明包含C函数指针的Go变量并在Go和C之间来回传递.C代码可以调用从Go接收的函数指针。例如:

package main

// typedef int(* intFunc)();
//
// int
// bridge_int_func(intFunc f)
// {
// return f();
//}
//
// int fortytwo()
// {
//返回42;
//}
import "C"
导入“fmt”

func main(){
	f:= C.intFunc(C.fortytwo)
	fmt.Println(INT(C.bridge_int_func(F)))
	//输出:42
}

在C中,写为固定大小数组的函数参数实际上需要指向数组第一个元素的指针。C编译器知道这个调用约定并相应地调整调用,但Go不能。在Go中,必须明确地将指针传递给第一个元素:Cf(&C.x [0])。

不支持调用可变参数C函数。通过使用C函数包装器可以避免这种情况。例如:

package main

// #include <stdio.h>
// #include <stdlib.h>
//
// static void myprint(char * s){
// printf(“%s \ n”,s);
//}
import "C"
导入“不安全”

func main(){
	cs:= C.CString(“来自stdio的Hello”)
	C.myprint(CS)
	C.free(unsafe.Pointer(CS))
}

一些特殊功能通过复制数据在Go和C类型之间进行转换。在伪Go定义中:
```go
//将字符串转到C字符串
//使用malloc在C堆中分配C字符串。
//调用者有责任安排它
//释放,例如通过调用C.free(确保包含stdlib.h
//如果需要C.free)
func C.CString(string)* C.char

//将[]字节切片转到C数组
//使用malloc在C堆中分配C数组。
//调用者有责任安排它
//释放,例如通过调用C.free(确保包含stdlib.h
//如果需要C.free)
func C.CBytes([] byte)unsafe.Pointer

// C string to Go字符串
func C.GoString(* C.char)string

//具有显式长度的C数据到Go字符串
func C.GoStringN(* C.char,C.int)string

//具有显式长度的C数据到Go []字节
func C.GoBytes(unsafe.Pointer,C.int)[] byte
作为一个特例,C.malloc不会直接调用C库malloc,而是调用包含C库malloc的Go辅助函数,但保证永远不会返回nil。如果C的malloc指示内存不足,则辅助函数会崩溃程序,就像Go本身内存不足一样。因为C.malloc不会失败,所以它没有返回errno的双结果形式。

C引用Go
可以通过以下方式导出Go函数以供C代码使用:

//导出MyFunction
func MyFunction(arg1,arg2 int,arg3 string)int64 {...}

//导出MyFunction2
func MyFunction2(arg1,arg2 int,arg3 string)(int64,* C.char){...}
它们将在C代码中提供:

extern int64 MyFunction(int arg1,int arg2,GoString arg3);
extern struct MyFunction2_return MyFunction2(int arg1,int arg2,GoString arg3);
在从cgo输入文件复制的任何前导后,在_cgo_export.h生成的头文件中找到。具有多个返回值的函数被映射到返回结构的函数。

并非所有Go类型都可以以有用的方式映射到C类型。不支持Go结构类型; 使用C结构类型。不支持Go数组类型; 使用C指针。

可以使用C类型_GoString_调用带有类型字符串参数的Go函数,如上所述。_GoString_类型将在前导码中自动定义。请注意,C代码无法创建此类型的值; 这仅用于将字符串值从Go传递到C并返回Go。

在文件中使用//导出会对前导码设置限制:因为它被复制到两个不同的C输出文件中,所以它不能包含任何定义,只能包含声明。如果文件包含定义和声明,则两个输出文件将生成重复的符号,链接器将失败。为避免这种情况,必须将定义放在其他文件或C源文件中的前导码中。

传递指针
Go是一种垃圾收集语言,垃圾收集器需要知道Go内存的每个指针的位置。因此,在Go和C之间传递指针存在限制。

在本节中,术语Go指针表示指向Go分配的内存的指针(例如通过使用&运算符或调用预定义的新函数),术语C指针表示指向由C分配的内存的指针(例如通过调用C.malloc)。指针是Go指针还是C指针是一个动态属性,由内存的分配方式决定; 它与指针的类型无关。

请注意,除了类型的零值之外,某些Go类型的值始终包含Go指针。字符串,切片,界面,通道,贴图和函数类型都是如此。指针类型可以包含Go指针或C指针。数组和结构类型可能包含也可能不包含Go指针,具体取决于元素类型。下面关于Go指针的所有讨论不仅适用于指针类型,还适用于包含Go指针的其他类型。

Go代码可以将Go指针传递给C,前提是它指向的Go内存不包含任何Go指针。C代码必须保留此属性:它不能在Go内存中存储任何Go指针,即使是临时的。将指针传递给结构中的字段时,所讨论的Go内存是字段占用的内存,而不是整个结构。将指针传递给数组或切片中的元素时,所讨论的Go内存是整个数组或切片的整个后备数组。

调用返回后,C代码可能无法保留Go指针的副本。这包括_GoString_类型,如上所述,它包含一个Go指针; C代码可能不保留_GoString_值。

C代码调用的Go函数可能不会返回Go指针(这意味着它可能不会返回字符串,切片,通道等)。由C代码调用的Go函数可以将C指针作为参数,并且它可以通过这些指针存储非指针或C指针数据,但是它可能不会将Go指针存储在C指针指向的内存中。由C代码调用的Go函数可以将Go指针作为参数,但它必须保留它指向的Go内存不包含任何Go指针的属性。

Go代码可能不会在C内存中存储Go指针。C代码可以将Go指针存储在C存储器中,遵循上述规则:当C函数返回时,它必须停止存储Go指针。

在运行时动态检查这些规则。检查由GODEBUG环境变量的cgocheck设置控制。默认设置是GODEBUG = cgocheck = 1,它实现了相当便宜的动态检查。可以使用GODEBUG = cgocheck = 0完全禁用这些检查。通过GODEBUG = cgocheck = 2可以在运行时以某种代价完成对指针处理的检查。

通过使用不安全的软件包可以打败这种强制执行,当然没有什么能阻止C代码做任何它喜欢的事情。但是,违反这些规则的程序可能会以意想不到的,不可预测的方式失败。

注意:当前的实现有一个bug。虽然允许Go代码将nil或C指针(但不是Go指针)写入C存储器,但如果C存储器的内容看起来像是Go指针,则当前实现有时可能会导致运行时错误。因此,如果Go代码要在其中存储指针值,请避免将未初始化的C内存传递给Go代码。在将其传递给Go之前将C中的内存清零。

特别案例
一些特殊的C类型通常由Go中的指针类型表示,而是由uintptr表示。其中包括:

1.达尔文上的* Ref类型,源于CoreFoundation的CFTypeRef类型。

2.来自Java的JNI接口的对象类型:

jobject
JCLASS
jthrowable
的jstring
jarray
jbooleanArray
jbyteArray
jcharArray
jshortArray
jintArray
jlong​​Array
jfloatArray
jdoubleArray
jobjectArray
jweak
3.来自EGL API的EGLDisplay类型。

这些类型在Go端是uintptr,因为它们会混淆Go垃圾收集器; 它们有时不是指针,而是以指针类型编码的数据结构。对这些类型的所有操作必须在C中进行。初始化空的此类引用的适当常量是0,而不是nil。

这些特殊情况在Go 1.10中引入。要从Go 1.9及更早版本自动更新代码,请使用Go修复工具中的cftype或jni重写:

去工具修复-r cftype
去工具修复-r jni
它将在适当的位置用0替换nil。

EGLDisplay案例在Go 1.12中引入。使用egl rewrite自动更新Go 1.11及更早版本的代码:

去工具修复-r egl
直接使用cgo
用法:

go tool cgo [cgo options] [ - 编译器选项] gofiles ...
Cgo将指定的输入Go源文件转换为多个输出Go和C源文件。

在调用C编译器以编译包的C部分时,编译器选项将通过未解释的方式传递。

直接运行cgo时可以使用以下选项:

-V

打印cgo版本并退出。

-debug界定

调试选项。打印#defines。

-debug-GCC

调试选项。跟踪C编译器执行和输出。

-dynimport文件

编写由文件导入的符号列表。写给
-dynout参数或标准输出。go使用
构建cgo包时构建。

-dynlinker

将动态链接器写为-dynimport输出的一部分。

-dynout文件

将-dynimport输出写入文件。

-dynpackage包

将Go包设置为-dynimport输出。

-exportheader文件

如果有任何导出的函数,请写入
生成导出声明到文件。
C代码可以#include这个来查看声明。

-importpath字符串

Go包的导入路径。可选的; 用于
生成的文件中更好的注释。

-import_runtime_cgo

如果设置(默认为),则导入runtime / cgo
生成输出。

-import_syscall

如果设置(默认为),则导入系统调用
生成输出。

-gccgo

为gccgo编译器生成输出而不是
gc编译器。

-gccgoprefix前缀

与gccgo一起使用的-fgo-prefix选项。

-gccgopkgpath路径

与gccgo一起使用的-fgo-pkgpath选项。

-godefs

用Go语法替换C包写出输入文件
具有实际价值的名称。用于生成文件
引导新目标时的syscall包。

-objdir目录

将所有生成的文件放在目录中。

-srcdir目录

`