前言
? 由于公司的Python項(xiàng)目中有關(guān)于支付簽名與驗(yàn)簽的模塊,是自定的一些內(nèi)部邏輯,基于安全性考慮, 希望改用C/C++或者Go 來重構(gòu)該部分模塊,做到加解簽過程透明,上層代碼只需要關(guān)心結(jié)果. 由于最近開始了Golang的學(xué)習(xí),就嘗試完成這部分工作,整個(gè)過程都是邊踩坑邊完成,下面以樣例代碼來分享一下整個(gè)過程的思路.
記錄
? Go里面需要顯示的引入C
模塊, 讓編譯器支持生成動(dòng)態(tài)鏈接庫, 并且在代碼中可以使用C語言的數(shù)據(jù)類型,這個(gè)至關(guān)重要. Calling Go code from Python code 摘取一個(gè)最簡單例子
//libadd.go
package main
import "C"
//export add
func add(left, right int) int {
return left + right
}
func main() {
}
go build -buildmode=c-shared -o libadd.so libadd.go
from ctypes import cdll
lib = cdll.LoadLibrary('./libadd.so')
print("Loaded go generated SO library")
result = lib.add(2, 3)
print(result)
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函數(shù)公開給C調(diào)用
本以為很簡單的就能用, 興致滿滿地把例子改一下, 改為簡單的處理字符串的時(shí)候, 卻發(fā)現(xiàn)跑不起來了.
//libadd.go
package main
import "C"
//export add
func add(left, right string) string {
return left + right
}
func main() {
}
from ctypes import CDLL
lib = CDLL('./libadd.so')
print("Loaded go generated SO library")
result = lib.add("Hello", "World")
print(result)
- 這時(shí)候運(yùn)行是出錯(cuò)的
再次翻看資料發(fā)現(xiàn)這么一句話:
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).
這說明處理字符串的時(shí)候并不是簡單改成string
類型就可以.這時(shí)候翻開了BUILDING PYTHON MODULES WITH GO 1.5 , 這時(shí)能找到的最全面的資料, 可惜里面的過程都過于復(fù)雜, 整個(gè)思路是用Go去寫C code, 類似寫解釋器一樣, 去抽象出PyObject然后按照API標(biāo)準(zhǔn)來注冊(cè)、處理项秉、返回.我僅是希望以動(dòng)態(tài)鏈接庫
的方式來能調(diào)用就可以了.
我開始思考, 為何例子中使用int
類型就可以, 我改成一個(gè)簡單的接收string
返回string
卻一直失敗. py是利用ctypes
來跟so模塊進(jìn)行交互, 這里存在一個(gè)代碼的翻譯過程 Py -> C -> Go
, 我能想到的對(duì)于字符串?dāng)?shù)據(jù)類型的處理不一樣原因引起(后面事實(shí)證明了我的猜想).那么思考一下, Py中的字符串傳遞到Go里面去使用什么類型來接收呢? 翻閱了大量資料, 所有答案在Python Doc 官網(wǎng)關(guān)于ctypes
模塊中有能找到.我們來看一下這圖:
這里可以很清楚的看到Python3
ctypes
中字符串 bytes
和 string
是對(duì)應(yīng)的兩種指針類型.同時(shí)提供了argtypes
和 restype
來顯式轉(zhuǎn)換動(dòng)態(tài)鏈接庫中函數(shù)的參數(shù)和返回類型.(參考StackOverFlow)
這時(shí)候按照思考的流程來修改代碼
//libadd.go
package main
import "C"
//export add
func add(left, right *C.char) *C.char {
// bytes對(duì)應(yīng)ctypes的c_char_p類型,翻譯成C類型就是 char *指針
merge := C.GoString(left) + C.GoString(right)
return C.CString(merge)
}
func main() {}
重新編譯
go build -buildmode=c-shared -o libadd.so libadd.go
Python中引用
import ctypes
add = ctypes.CDLL('./libadd.so').add
# 顯式聲明參數(shù)和返回的期望類型
add.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
add.restype = ctypes.c_char_p
left = b"Hello"
right = b"World"
print(add(left, right))
正確輸出結(jié)果:
b"HelloWorld"
就這樣, 一個(gè)基本的模塊就完成, 只要關(guān)注傳入?yún)?shù)和返回結(jié)果的數(shù)據(jù)類型處理, 我只需要豐富函數(shù)的處理邏輯,Go模塊中函數(shù)內(nèi)部實(shí)現(xiàn)對(duì)于Python是透明,只要參數(shù)正確即可.其中關(guān)于 cgo更多的信息, 大家可以自行查閱Golang.org
總結(jié)
- Python與Go之間的參數(shù)傳遞, 處理非INT型時(shí)需要都轉(zhuǎn)為對(duì)應(yīng)的C類型
- ctypes需要顯式地聲明DLL函數(shù)的參數(shù)和返回期望的數(shù)據(jù)類型
- 注意在Python3中字符串bytes和string的區(qū)別
- Go模塊需要
//export
聲明外部可調(diào)用 - Go處理C的類型是需要顯式轉(zhuǎn)換