cgo调用
前言
cgo调用是go语言提供的一种机制,它允许go程序调用C语言的函数和使用C语言的库。在cgo调用中,go程序可以通过import "C"语句来引入c语言的头文件和函数,并且可以使用c语言的数据类型和指针类型。但在使用时需要注意安全性和可移植性,可能会涉及到内存管理和平台相关性的问题。
一、c调go的实现方法
1.实现思路
将go文件编译成静态库或者动态库,c文件编译时链接go的库
# 把main.go编译成libgomain.a
go build -o libgomain.a -buildmode=c-archive main.go
# 根据go文件生成cgo类型的文件,这里主要为了导出头文件_cgo_export.h
go tool cgo -exportheader=_cgo_export.h main.go
# 编译c文件,链接go的库
gcc -g main.c -o main -L. -lgomain
2.具体步骤
- 编写go文件
package main
import (
"fmt"
)
import "C"
func main() {}
//export HelloWorld
func HelloWorld() {
fmt.Println("hello world")
}
需要注意的是,代码中必须添加以下两部分内容
import “C” – 支持cgo交互
//export HelloWorld – 需要导出的函数,可被c文件调用
- 导出cgo头文件
[root@]# go tool cgo -exportheader=_cgo_export.h main.go
[root@]# tree ./
./
├── _cgo_export.h
├── main.c
├── main.go
└── _obj
├── _cgo_export.c
├── _cgo_export.h
├── _cgo_flags
├── _cgo_gotypes.go
├── _cgo_main.c
├── main.cgo1.go
└── main.cgo2.c
1 directory, 10 files
_obj目录下的就是生成的cgo中间文件,_cgo_export.h是导出的头文件,可以被c代码引用
- 编写c文件
#include "_cgo_export.h"
int main()
{
HelloWorld();
return 0;
}
- 编译运行
[root@]# go build -o libgomain.a -buildmode=c-archive main.go
[root@]# gcc -g main.c -o main -L. -lgomain
[root@]# ./main
hello world
二、go调c的实现方法
go调c相比c调go简单很多,需要在go的序文中加入c代码,用import "C"标识,并以C.***的方式调用
1.具体步骤
- 编写go文件
package main
/*
#include <stdio.h>
void HelloWorld()
{
printf("hello world\n");
}
*/
import "C"
func main() {
C.HelloWorld();
}
- 运行
[root@]# go run main.go
hello world
三、数据类型转换
1.参数传递中的类型转换
下表cgo中常用的数据类型的对应关系,在函数调用时对应的类型可以直接进行参数传递
cgo类型 | c类型 |
---|---|
C.char, C.schar | signed char |
C.uchar | unsigned char |
C.short | short |
C.ushort | unsigned short |
C.int | int |
C.uint | unsigned int |
C.long | long |
C.ulong | unsigned long |
C.longlong | long long |
C.ulonglong | unsigned long long |
C.float | float |
C.double | double |
unsafe.Pointer | void* |
C.struct_xxx | struct |
C.union_xxx | union |
- 示例
package main
import (
"fmt"
"unsafe"
)
/*
#include <stdio.h>
struct myStruct {
int i;
char c;
void* p;
};
void TestConvertType(int i, short s, unsigned int ui, float f, double d, struct myStruct ss) {
printf("int: %d, short: %hd, uintL %u, float: %f, double: %lf\n", i, s, ui, f, d);
printf("struct: %d, %c, %p\n", ss.i, ss.c, ss.p);
}
*/
import "C"
func main() {
var i C.int = 0
var s C.short = 1
var ui C.uint = 2
var f C.float = 3.14159
var d C.double = 3.14159265453
var ss C.struct_myStruct
ss.i = C.int(0)
ss.c = C.char('a')
ss.p = unsafe.Pointer(&s)
C.TestConvertType(i, s, ui, f, d, ss)
}
- 运行结果
[root@]# go run main.go
int: 0, short: 1, uintL 2, float: 3.141590, double: 3.141593
struct: 0, a, 0xc00001a0d8
2.常用的类型转换函数
在函数内部如果要将cgo类型互相转换则需要使用类型转换函数,常用的类型转换函数如下
// Go string to C string
func C.CString(string) *C.char
// Go []byte slice to C array
func C.CBytes([]byte) unsafe.Pointer
// C string to Go string
func C.GoString(*C.char) string
// C data with explicit length to Go string
func C.GoStringN(*C.char, C.int) string
// C data with explicit length to Go []byte
func C.GoBytes(unsafe.Pointer, C.int) []byte
四、其他
1.获取c类型的长度
直接使用C.sizeof_type获取
- 示例
package main
import (
"fmt"
)
/*
#include <stdio.h>
struct myStruct {
int i;
char c;
void* p;
};
*/
import "C"
func main() {
fmt.Println(C.sizeof_int)
fmt.Println(C.sizeof_struct_myStruct)
}
- 运行结果
[root@]# go run main.go
4
16
2.遍历c类型数组
对于使用C.malloc申请的c类型数组,使用指针和下标访问数组元素时,需要使用 unsafe.Pointer
将指针类型转换为 uintptr
类型,然后使用 uintptr
类型进行指针运算,举例如下
- 示例
package main
import (
"fmt"
"unsafe"
)
import "C"
func main() {
size := 10
intArray := (*C.int)(C.malloc(C.ulong(C.sizeof_int * size)))
for i := 0; i < size; i++ {
elem := (*C.int)(unsafe.Pointer(uintptr(unsafe.Pointer(intArray)) + uintptr(i * C.sizeof_int)))
*elem = C.int(i)
}
for i := 0; i < size; i++ {
elem := (*C.int)(unsafe.Pointer(uintptr(unsafe.Pointer(intArray)) + uintptr(i * C.sizeof_int)))
fmt.Println(*elem)
}
}
- 运行结果
[root@]# go run main.go
0
1
2
3
4
5
6
7
8
9