1. 程式人生 > >Python2 & Python3 ctypes 字串編碼型別轉換大坑

Python2 & Python3 ctypes 字串編碼型別轉換大坑

ctypes介紹

python標準庫自帶的模組,支援呼叫C的動態連結庫檔案(windows下為*.dll,linux下為*.so)
示例程式碼:

# 載入動態連結庫檔案
ll = cdll.LoadLibrary
lib = ll("lib/libTest.so")
# 生成函式指標
# 假設庫中有函式int foo(char*)
foo = lib.foo
# 設定引數格式
foo.argtypes = [c_char_p]
# 設定返回值格式
foo.restype = c_int

事件的起源是,和學長一起做專案,學長留給我一個2700行的C++檔案並表示”你要的介面我都已經寫好了,你拿去直接用就行”。開心的我拿回去封裝了就跑
其中有一個C++函式funcA

,接受一個const char* rfile作為輸入檔名,返回一個char* res作為結果。於是第一次就寫了如下程式碼:

ll = cdll.LoadLibrary
lib = ll("lib/libTest.so")
funcA = lib.funcA
funcA.restype = c_char_p

res = funcA("test.dat")
arr = res.split(".")

在實驗室用python2讀了幾個測試資料跑了一遍,沒有問題
可是!!!回去以後在本機上用python3跑!!!res永遠是None!!!

第一次嘗試

在C++函式funcA中,cout<<rfile<<endl;


驚訝的發現,輸出結果是t…………
當時的第一反應是,只取到第一個字元,可能是在傳參過程中,引數被解引用了。於是添加了一行funcA.argtypes = [c_char_p],說明傳入引數是個指標
不出所料,沒點卵用……

第二次嘗試

網上找到類似案例,Converting python string object to c char* using ctypes-stackoverflow
根據高票答案指示,傳參之前先對引數字串進行編碼:
res = funcA("test.dat".encode("utf-8"))
此時檔案讀取正常
可是下一行,對返回結果split的時候,又報錯TypeError: a bytes-like object is required, not 'str'


聯想起之前python2跑這段程式碼並沒問題,python3和python2的一大區別就是字串str預設採用unicode編碼,這個問題就慢慢變得清晰了起來

結果

python3中,字串str全部採用unicode編碼。str提供一個encode(type) 函式,可以按照type指定的編碼型別,將字串轉換為一個bytes 物件,即位元組流
然而python2中,所謂的str ,其實是bytes !!!
更操蛋的是python2的str 居然還有一個encode() 方法……至今不明白他是想幹什麼……
所以問題就解決啦,歸根結底是由於python3和python2中,字串的預設編碼不同導致的
最終程式碼:

ll = cdll.LoadLibrary
lib = ll("lib/libTest.so")
funcA = lib.funcA
funcA.argtypes = [c_char_p]
funcA.restype = c_char_p

res = funcA("test.dat".encode("utf-8")).decode()
arr = res.split(".")