一、调用步骤:
将go代码编译成so库 -> python中通过ctypes引用so库并指定需要调用的函数(同时可指定传入参数类型和返回值类型) -> 指定后按python使用函数方式调用。
需要注意的是:python和go之间参数传递是需要经过C的数据类型转换的,因此需要了解python中ctypes数据类型和python数据类型以及C的数据类型对应关系
三种数据类型使用场景:1. ctypes数据类型为指定调用函数时的传入参数和返回值的数据类型
2. python数据类型为调用函数时传入的参数的数据类型
3. C的数据类型为go代码中定义的函数所需的参数和返回值数据类型
类型对应如下:文档传送地址
由此举例:当python传入的参数需是string时,ctypes中指定的传参参数类型需为c_wchar_p,go中需要指定接收的参数数据类型为 *C.wchar_t。
由于不知道go中如何将字符串和*C.wchar_t互相转化,因此我这里将python传入的参数指定为bytes,即ctypes中指定的传参参数类型需为c_char_p,go中需要指定接收的参数数据类型为*C.char。
二、下面开始实践:
1. 编写go代码
写一个rocketmq的producer函数(main.go),封装成Send函数如下:
1 package main 2 3 import ( 4 "C" 5 "context" 6 "github.com/apache/rocketmq-client-go/v2" 7 "github.com/apache/rocketmq-client-go/v2/primitive" 8 "github.com/apache/rocketmq-client-go/v2/producer" 9 "os" 10 ) 11 12 var ( 13 nameservs = []string{"192.168.2.1:9876"} 14 group = "demo.xy" 15 topic = "test" 16 ) 17 18 //export Send 19 func Send(cid, message *C.char) *C.char { 20 p, err := rocketmq.NewProducer( 21 producer.WithNsResolver(primitive.NewPassthroughResolver(nameservs)), 22 producer.WithRetry(2), 23 producer.WithGroupName(group), 24 ) 25 if err != nil { 26 return C.CString("create producer failed") 27 os.Exit(-1) 28 } 29 30 err = p.Start() 31 if err != nil { 32 return C.CString("start producer failed") 33 os.Exit(-1) 34 } else { 35 defer p.Shutdown() 36 } 37 38 msg := &primitive.Message{ 39 Topic: topic, 40 Body: []byte(C.GoString(message)), 41 } 42 msg.WithTag(C.GoString(cid)) 43 44 _, err = p.SendSync(context.Background(), msg) 45 if err != nil { 46 return C.CString("producer send message failed") 47 } else { 48 return C.CString("producer send message success") 49 } 50 } 51 52 func main() {}
需要注意:1). Go里面将C的char指针类型数据通过C.GoString()转化成go中的字符串;反之通过C.CString()将go中的字符串转化为C的char指针类型数据。具体类型转化方式可查阅相关文档(待补充)
2). python需要调用的函数必须在函数上方用 //export [函数名称] 加上说明,不然python中会报AttrbuteError错误(symbol not found)
2. 将go代码编译成so动态链接库
go build --buildmode=c-shared -o producer.so main.go
编译好后会生成两个文件:producer.so和producer.h
3. python中导入so文件并调用对应的函数
1 import ctypes 2 import json 3 4 # 指定go中的Send函数 5 SendSync = ctypes.CDLL("./producer.so").Send 6 # 指定调用函数时传入的参数数据类型 7 SendSync.argtypes = [ctypes.c_char_p, ctypes.c_char_p] 8 # 指定调用函数返回值的数据类型 9 SendSync.restype = ctypes.c_char_p 10 11 cid = "123454321" 12 message = { 13 "cid": "123454321", 14 "cname": "指南朝北枪", 15 "age": 18, 16 "height": 1.88 17 } 18 19 result = SendSync(cid.encode("utf-8"), json.dumps(message).encode("utf-8")) 20 print(result, type(result))
需要注意的是:1). python2中的字节串是str;字符串是unicode。因此如果是python2调用SendSync函数时不需要使用encode,直接传入str即可
2). python3中字节串是bytes;字符串是str。因此调用SendSync函数时需要将字符串str通过encode转换成bytes
3). 指定的参数类型和传入参数类型一定要一致,否者报ctypes.ArgumentError错误(wrong type)