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.scharsigned char
C.ucharunsigned char
C.shortshort
C.ushortunsigned short
C.intint
C.uintunsigned int
C.longlong
C.ulongunsigned long
C.longlonglong long
C.ulonglongunsigned long long
C.floatfloat
C.doubledouble
unsafe.Pointervoid*
C.struct_xxxstruct
C.union_xxxunion
  • 示例
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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值