CGO中处理C中的回调函数
假设有以下 C语言的接口
api.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #ifndef API_H #define API_H
#ifdef __cplusplus extern "C" { #endif
typedef void (*IntCallback)(void *, int);
void SetIntCallback(IntCallback cb, void *data);
void DoIntCallback(int value);
#ifdef __cplusplus } #endif #endif
|
此时如果我们想要通过CGO来将上述的两个(尤其是设置回调函数的SetIntCallback
)导入到golang
中来使用,该怎么做呢?
思考
C语言
中的void *
与 GO
中的什么类型对应呢?
C语言
中的函数指针与GO
中的函数怎么对应呢?
思考结果如下
C语言
中的 void *
和 Go
中的unsafe.Pointer
对应
C语言
中的函数和 Go
中的函数可以通过 //export NAME
的方式来建立对应关系
实际操作
1. 先在CGO
中声明一下回调函数需要用到的C函数
1 2 3 4 5 6 7
| package main
import "C"
|
2. 在Golang
中通过 //export
方式实现上述的C函数
1 2 3 4 5 6
|
func cgoCall(p unsafe.Pointer, number C.int) { }
|
3. 在Golang
中定义一个interface
来接收上面的函数里的C.int
类型的参数
1 2 3 4 5
|
type Caller interface { Call(int) }
|
此时我们可以将Caller类型
作为步骤2
中的p传进去作为参数了
4. 在完善一下步骤2
中的cgoCall
1 2 3 4 5 6 7
|
func cgoCall(p unsafe.Pointer, number C.int) { caller := *(*Caller)(p) caller.Call(int(number)) }
|
说明:
我们在这里将p
参数转化为 一个 Caller
的interface
类型,再调用 Caller
类型的Call(int)
函数。表明我们在调用 C语言
中的SetIntCallback
时, data
参数给的是一个 Caller
类型的指针
5. 定义一个具体的类型实现 Caller接口测试一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
type OneCaller struct {} type AnotherCaller struct {}
func (o OneCaller) Call(value int) { println("one:", value) }
func (o AnotherCaller) Call(value int) { println("another:", value) }
func SetCallback(caller Caller) { C.SetIntCallback(C.IntCallback(C.cgoCall), unsafe.Pointer(&caller)) }
func DoCallback(value int) { C.DoIntCallback(C.int(value)) }
func main() { one := OneCaller {}
SetCallback(one) DoCallback(1234) another := AnotherCaller {} SetCallback(another) DoCallback(5678) }
|
完整的运行测试一下, 发现可以输出:
总结
为了使用C语言
中的回调函数, 我们使用到了以下技术来实现
- unsafe.Pointer:将
Go
中的指针传入到C语言
中的 void *
- //export XXX: 在
GO
中实现 C语言
中声明的函数
- 通过
interface
技术将 C语言
中的回调函数类型绑定实现了多态
或泛型