這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
前言
由於公司的Python項目中有關於支付簽名與驗簽的模組,是自定的一些內部邏輯,基於安全性考慮, 希望改用C/C++或者Go 來重構該部分模組,做到加解簽過程透明,上層代碼只需要關心結果. 由於最近開始了Golang的學習,就嘗試完成這部分工作,整個過程都是邊踩坑邊完成,下面以範例代碼來分享一下整個過程的思路.
記錄
Go裡面需要顯示的引入C
模組, 讓編譯器支援產生動態連結程式庫, 並且在代碼中可以使用C語言的資料類型,這個至關重要. Calling Go code from Python code 摘取一個最簡單例子
//libadd.gopackage mainimport "C"//export addfunc add(left, right int) int { return left + right}func main() {}
go build -buildmode=c-shared -o libadd.so libadd.go
from ctypes import cdlllib = 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函數公開給C調用
本以為很簡單的就能用, 興緻滿滿地把例子改一下, 改為簡單的處理字串的時候, 卻發現跑不起來了.
//libadd.gopackage mainimport "C"//export addfunc add(left, right string) string { return left + right}func main() {}
from ctypes import CDLLlib = CDLL('./libadd.so')print("Loaded go generated SO library")result = lib.add("Hello", "World")print(result)
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
模組中有能找到.我們來看一下這圖:
001.png
這裡可以很清楚的看到Python3 ctypes
中字串 bytes
和 string
是對應的兩種指標類型.同時提供了argtypes
和 restype
來顯式轉換動態連結程式庫中函數的參數和傳回型別.(參考StackOverFlow)
這時候按照思考的流程來修改代碼
//libadd.gopackage mainimport "C"//export addfunc add(left, right *C.char) *C.char { // bytes對應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 ctypesadd = ctypes.CDLL('./libadd.so').add# 顯式聲明參數和返回的期望類型add.argtypes = [ctypes.c_char_p, ctypes.c_char_p]add.restype = ctypes.c_char_pleft = b"Hello"right = b"World"print(add(left, right))
正確輸出結果:
b"HelloWorld"
就這樣, 一個基本的模組就完成, 只要關注傳入參數和返回結果的資料類型處理, 我只需要豐富函數的處理邏輯,Go模組中函數內部實現對於Python是透明,只要參數正確即可.其中關於 cgo更多的資訊, 大家可以自行查閱Golang.org
總結
- Python與Go之間的參數傳遞, 處理非INT型時需要都轉為對應的C類型
- ctypes需要顯式地聲明DLL函數的參數和返回期望的資料類型
- 注意在Python3中字串bytes和string的區別
- Go模組需要
//export
聲明外部可調用
- Go處理C的類型是需要顯式轉換