In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-15 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/02 Report--
In this issue, Xiaobian will bring you about how to carry out the interoperability implementation of Go and C language. The article is rich in content and analyzed and described from a professional perspective. After reading this article, I hope you can gain something.
Go has a strong C background. In addition to its inherited syntax, its designers and design goals are inextricably linked to C. In terms of interoperability between Go and C language, Go provides strong support. Especially with C in Go, you can even write C directly in Go source files, which is something no other language can do.
There are a few scenarios where Go might interoperate with C:
When improving local code performance, replace some Go code with C. C is to Go what assembly is to C.
2. Go memory GC performance is insufficient, and manually manage application memory by yourself.
3. Implement Go Wrapper for some libraries. For example, Oracle provides the C version of OCI, but Oracle does not provide the Go version and the protocol details for connecting to DB, so it can only be used by Go developers by wrapping the C OCI version.
Go exports functions for C developers (this requirement should be rare at the moment).
5、Maybe more…
I. Go calls C code principle
Here is a short example:
package main
// #include
// #include
/*
void print(char *str) {
printf("%s\n", str);
}
*/
import "C"
import "unsafe"
func main() {
s := "Hello Cgo"
cs := C.CString(s)
C.print(cs)
C.free(unsafe.Pointer(cs))
}
There are several "special" places in the above code compared to the "normal"Go code:
1)In the comments at the beginning, the words include of the C header file appear
2)The C function print is defined in the comment
3)import a "package" named C
4)In the main function, the above C function-print is called.
Yes, this is the procedure for calling C code in Go source code, and we can see that we can write C code directly in Go source files.
First of all, C code in Go source files needs to be wrapped with comments, just like the include header file and print function definition above;
Second, the import "C" statement is required, and it cannot be separated from the C code above by a blank line, but must be closely connected. The "C" here is not a package name, but a concept similar to namespace, or can be understood as a pseudo-package, under which all syntax elements of C language are located;
Finally, access to C syntax elements must be preceded by pseudo-packet prefixes, such as C.uint and C.print, C.free, etc. in the above code.
How do we compile this go source file? In fact, it is no different from the "normal"Go source file, which can still be compiled and executed directly by go build or go run. But in the actual compilation process, go calls a tool called cgo, cgo will identify and read the C elements in the Go source file, extract them and hand them to the C compiler for compilation, and finally link them with the Go source compiled object file into an executable program. It is not difficult to understand why C code in Go source files should be wrapped in comments. These special syntax can be recognized and used by Cgo.
Type C used in Go
1, the original type
* numeric types
C native numeric types can be accessed in Go as follows:
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
The numerical types of Go and C are not one-to-one. Therefore, explicit conversion operations are necessary when using each other's type variables, such as this example in Go doc:
func Random() int {
return int(C.random()) //C.long-> int of Go
}
func Seed(i int) {
C.srandom(C.uint(i))//uint of Go-> uint of C
}
* pointer type
Pointer types of primitive numeric types can be preceded by a * according to Go syntax, such as var p *C. int. void* is special and is represented by unsafe.Pointer in Go. Any type of pointer value can be converted to an unsafe.Pointer type, and an unsafe.Pointer type value can be converted to any type of pointer value. unsafe.Pointer can also be converted to and from uintptr. Since the pointer type of unsafe.Pointer cannot do arithmetic operations, it can be converted to uintptr for arithmetic operations.
* string type
There is no formal string type in C, where strings are represented by arrays of characters ending in '\0'; in Go, string types are primitive types, so string type conversion is necessary for interoperability between the two languages.
With the C.CString function, we can convert the string type of Go to the "string" type of C and pass it to the C function for use. As we used in the example at the beginning of this article:
s := "Hello Cgo\n"
cs := C.CString(s)
C.print(cs)
However, the resulting C string cs cannot be managed by Go's gc, we must manually free the memory occupied by cs, which is why we call C.free to free cs in the end. Memory allocated internally in C, GC in Go is imperceptible, so remember to free it.
C.GoString can convert C string (*C.char) to Go string type, for example:
// #include
// #include
// char *foo = "hellofoo";
import "C"
import "fmt"
func main() {
… …
fmt.Printf("%s\n", C.GoString(C.foo))
}
* array type
Arrays in C differ greatly from arrays in Go, which are value types, while the former and pointers in C can be converted at will in most cases. There seems to be no direct explicit transition between the two, and official documentation does not explain it. But we can convert an array of C to a Slice of Go by writing a conversion function (because arrays in Go are value types and their sizes are static, it is more generic to convert to Slice). Here is an example of an integer array conversion:
// int cArray[] = {1, 2, 3, 4, 5, 6, 7};
func CArrayToGoArray(cArray unsafe.Pointer, size int) (goArray []int) {
p := uintptr(cArray)
for i :=0; i
< size; i++ { j := *(*int)(unsafe.Pointer(p)) goArray = append(goArray, j) p += unsafe.Sizeof(j) } return } func main() { … … goArray := CArrayToGoArray(unsafe.Pointer(&C.cArray[0]), 7) fmt.Println(goArray) } 执行结果输出:[1 2 3 4 5 6 7] 这里要注意的是:Go编译器并不能将C的cArray自动转换为数组的地址,所以不能像在C中使用数组那样将数组变量直接传递给函数,而是将数组第一个元素的地址传递给函数。 2、自定义类型 除了原生类型外,我们还可以访问C中的自定义类型。 * 枚举(enum) // enum color { // RED, // BLUE, // YELLOW // }; var e, f, g C.enum_color = C.RED, C.BLUE, C.YELLOW fmt.Println(e, f, g) 输出:0 1 2 对于具名的C枚举类型,我们可以通过C.enum_xx来访问该类型。如果是匿名枚举,则似乎只能访问其字段了。 * 结构体(struct) // struct employee { // char *id; // int age; // }; id := C.CString("1247") var employee C.struct_employee = C.struct_employee{id, 21} fmt.Println(C.GoString(employee.id)) fmt.Println(employee.age) C.free(unsafe.Pointer(id)) 输出: 1247 21 和enum类似,我们可以通过C.struct_xx来访问C中定义的结构体类型。 * 联合体(union) 这里我试图用与访问struct相同的方法来访问一个C的union: // #include // union bar { // char c; // int i; // double d; // }; import "C" func main() { var b *C.union_bar = new(C.union_bar) b.c = 4 fmt.Println(b) } 不过编译时,go却报错:b.c undefined (type *[8]byte has no field or method c)。从报错的信息来看,Go对待union与其他类型不同,似乎将union当成[N]byte来对待,其中N为union中最大字段的size(圆整后的),因此我们可以按如下方式处理C.union_bar: func main() { var b *C.union_bar = new(C.union_bar) b[0] = 13 b[1] = 17 fmt.Println(b) } 输出:&[13 17 0 0 0 0 0 0] * typedef 在Go中访问使用用typedef定义的别名类型时,其访问方式与原实际类型访问方式相同。如: // typedef int myint; var a C.myint = 5 fmt.Println(a) // typedef struct employee myemployee; var m C.struct_myemployee 从例子中可以看出,对原生类型的别名,直接访问这个新类型名即可。而对于复合类型的别名,需要根据原复合类型的访问方式对新别名进行访问,比如myemployee实际类型为struct,那么使用myemployee时也要加上struct_前缀。 三、Go中访问C的变量和函数 实际上上面的例子中我们已经演示了在Go中是如何访问C的变量和函数的,一般方法就是加上C前缀即可,对于C标准库中的函数尤其是这样。不过虽然我们可以在Go源码文件中直接定义C变量和C函数,但从代码结构上来讲,大量的在Go源码中编写C代码似乎不是那么"专业"。那如何将C函数和变量定义从Go源码中分离出去单独定义呢?我们很容易想到将C的代码以共享库的形式提供给Go源码。 Cgo提供了#cgo指示符可以指定Go源码在编译后与哪些共享库进行链接。我们来看一下例子: package main // #cgo LDFLAGS: -L ./ -lfoo // #include // #include // #include "foo.h" import "C" import "fmt" func main() { fmt.Println(C.count) C.foo() } 我们看到上面例子中通过#cgo指示符告诉go编译器链接当前目录下的libfoo共享库。C.count变量和C.foo函数的定义都在libfoo共享库中。我们来创建这个共享库: // foo.h int count; void foo(); //foo.c #include "foo.h" int count = 6; void foo() { printf("I am foo!\n"); } $>gcc -c foo.c
$> ar rv libfoo.a foo.o
我们首先创建一个静态共享库libfoo.a,不过在编译Go源文件时我们遇到了问题:
$> go build foo.go
# command-line-arguments
/tmp/go-build565913544/command-line-arguments.a(foo.cgo2.)(.text): foo: not defined
foo(0): not defined
提示foo函数未定义。通过-x选项打印出具体的编译细节,也未找出问题所在。不过在Go的问题列表中我发现了一个issue(http://code.google.com/p/go/issues/detail?id=3755),上面提到了目前Go的版本不支持链接静态共享库。
那我们来创建一个动态共享库试试:
$> gcc -c foo.c
$> gcc -shared -Wl,-soname,libfoo.so -o libfoo.so foo.o
再编译foo.go,的确能够成功。执行foo。
$> go build foo.go && go
6
I am foo!
还有一点值得注意,那就是Go支持多返回值,而C中并没不支持。因此当将C函数用在多返回值的调用中时,C的errno将作为err返回值返回,下面是个例子:
package main
// #include
// #include
// #include
// int foo(int i) {
// errno = 0;
// if (i > 5) {
// errno = 8;
// return i - 5;
// } else {
// return i;
// }
//}
import "C"
import "fmt"
func main() {
i, err := C.foo(C.int(8))
if err != nil {
fmt.Println(err)
} else {
fmt.Println(i)
}
}
$> go run foo.go
exec format error
errno为8,其含义在errno.h中可以找到:
#define ENOEXEC 8 /* Exec format error */
的确是"exec format error"。
四、C中使用Go函数
与在Go中使用C源码相比,在C中使用Go函数的场合较少。在Go中,可以使用"export + 函数名"来导出Go函数为C所使用,看一个简单例子:
package main
/*
#include
extern void GoExportedFunc();
void bar() {
printf("I am bar!\n");
GoExportedFunc();
}
*/
import "C"
import "fmt"
//export GoExportedFunc
func GoExportedFunc() {
fmt.Println("I am a GoExportedFunc!")
}
func main() {
C.bar()
}
不过当我们编译该Go文件时,我们得到了如下错误信息:
# command-line-arguments
/tmp/go-build163255970/command-line-arguments/_obj/bar.cgo2.o: In function `bar':
./bar.go:7: multiple definition of `bar'
/tmp/go-build163255970/command-line-arguments/_obj/_cgo_export.o:/home/tonybai/test/go/bar.go:7: first defined here
collect2: ld returned 1 exit status
代码似乎没有任何问题,但就是无法通过编译,总是提示"多重定义"。翻看Cgo的文档,找到了些端倪。原来
There is a limitation: if your program uses any //export directives, then the C code in the comment may only include declarations (extern int f();), not definitions (int f() { return 1; }).
似乎是// extern int f()与//export f不能放在一个Go源文件中。我们把bar.go拆分成bar1.go和bar2.go两个文件:
// bar1.go
package main
/*
#include
extern void GoExportedFunc();
void bar() {
printf("I am bar!\n");
GoExportedFunc();
}
*/
import "C"
func main() {
C.bar()
}
// bar2.go
package main
import "C"
import "fmt"
//export GoExportedFunc
func GoExportedFunc() {
fmt.Println("I am a GoExportedFunc!")
}
编译执行:
$> go build -o bar bar1.go bar2.go
$> bar
I am bar!
I am a GoExportedFunc!
个人觉得目前Go对于导出函数供C使用的功能还十分有限,两种语言的调用约定不同,类型无法一一对应以及Go中类似Gc这样的高级功能让导出Go函数这一功能难于完美实现,导出的函数依旧无法完全脱离Go的环境,因此实用性似乎有折扣。
五、其他
虽然Go提供了强大的与C互操作的功能,但目前依旧不完善,比如不支持在Go中直接调用可变个数参数的函数(issue975),如printf(因此,文档中多用fputs)。
这里的建议是:尽量缩小Go与C间互操作范围。
什么意思呢?如果你在Go中使用C代码时,那么尽量在C代码中调用C函数。Go只使用你封装好的一个C函数最好。不要像下面代码这样:
C.fputs(…)
C.atoi(..)
C.malloc(..)
而是将这些C函数调用封装到一个C函数中,Go只知道这个C函数即可。
C.foo(..)
相反,在C中使用Go导出的函数也是一样。
上述就是小编为大家分享的如何进行Go与C语言的互操作实现了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注行业资讯频道。
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.