Golang为Python编写模块
前言
由于公司的Python项目中有比较关于支付签名与验签的模块, 基于安全性考虑, 希望改用C/C++或者Go 来重构该部分模块,做到加解签过程透明,上层代码只需要关心结果. 整个过程都是边踩坑边完成,下面以简易代码来记录一下整个过程的思路.
记录
Go里面需要显示的引入C
模块, 让编译器支持生成动态链接库, 并且在代码中可以使用C语言的数据类型,这个至关重要. Calling Go code from Python code 摘取一个最简单例子
1 | //libadd.go |
1 | go build -buildmode=c-shared -o libadd.so libadd.go |
1 | from ctypes import cdll |
The cgo export command is documented in
go doc cgo
, section “C references to Go”. Essentially, write//export FUNCNAME
before the function definition
需要显式注释//export add
把 add函数公开给C调用
本以为很简单的的我, 兴致满满地把例子改一下, 改为简单的处理字符串的时候, 发现怕跑不起来了.
1 | //libadd.go |
1 | from ctypes import CDLL |
这时候运行是出错的
再次翻看资料发现这么一句话:
The python code is really short and this is only passing an integer back and forth (more complex string and struct cases are much more challenging).
这说明处理字符串的时候并不是简单改成string
类型就可以.这时候翻开了BUILDING PYTHON MODULES WITH GO 1.5 , 这时能找到的最全面的资料, 可惜里面的过程都过于复杂, 整个思路是用Go去写C code, 类似写解释器一样, 去抽象出PyObject然后按照API标准来注册、处理、返回.我仅是希望以动态链接库
的方式来能调用就可以了.
我开始思考, 为何例子中使用int
类型就可以, 我改成一个简单的接收string
返回string
却一直失败. py是利用ctypes
来跟so模块进行交互, 这里存在一个代码的翻译过程 Py -> C -> Go
, 我能想到的对于字符串数据类型的处理不一样原因引起(后面事实证明了我的猜想).那么思考一下, Py中的字符串传递到Go里面去使用什么类型来接收呢? 所有答案在Python Doc 官网关于ctypes
模块中有能找到.我们来参考对应表格:
ctypes type | C type | Python type |
---|---|---|
c_bool | _Bool |
bool (1) |
c_char | char |
1-character bytes object |
c_wchar | wchar_t |
1-character string |
c_byte | char |
int |
c_ubyte | unsigned char |
int |
c_short | short |
int |
c_ushort | unsigned short |
int |
c_int | int |
int |
c_uint | unsigned int |
int |
c_long |
long |
int |
c_ulong |
unsigned long |
int |
c_longlong |
__int64 or long long |
int |
c_ulonglong |
unsigned __int64 or unsigned long long |
int |
c_size_t |
size_t |
int |
c_ssize_t |
ssize_t or Py_ssize_t |
int |
c_float |
float |
float |
c_double |
double |
float |
c_longdouble |
long double |
float |
c_char_p |
char * (NUL terminated) |
bytes object or None |
c_wchar_p |
wchar_t * (NUL terminated) |
string or None |
c_void_p |
void * |
int or None |
这里可以很清楚的看到Python3 ctypes
中字符串 bytes
和 string
是对应的两种指针类型.同时提供了argtypes
和 restype
来显式转换动态链接库中函数的参数和返回类型.(参考StackOverFlow)
这这时候再改一下代码
1 | //libadd.go |
重新编译
1 | go build -buildmode=c-shared -o libadd.so libadd.go |
Python中引用
1 | import ctypes |
正确输出结果:
1 | b"HelloWorld" |
那么这时候我就可以开始模块的编写了, 只要关注传入参数和返回结果的数据类型处理, Go模块中函数内部实现该怎么写还是怎么写.关于 cgo更多的信息, 可以查阅Golang.org
总结
- Python与Go之间的参数传递, 处理非INT型时需要都转为对应的C类型
- ctypes需要显式地声明DLL函数的参数和返回期望的数据类型
- 注意在Python3中字符串bytes和string的区别
- Go模块需要
//export
声明外部可调用 - Go处理C的类型是需要显式转换