2018年11月27日 星期二

Python 3.6與3.7的c_wchar_p無法接受空字元的處理辦法

最近工作上又遇到需要新的小工具的狀況
所以開始把原本的python win32的程式改一改
只是看著使用的python版本是3.5.3
想說3.7.1都出來了,不然更新一下最新版好了
.......
沒錯,狀況就這樣發生了
在傳遞filter_string給OPENFILENAME的Structure時
跳出了ValueError: embedded null character

上網開始資料後,很快地就發現是python已知的Issue
Issue32745:ctypes string pointer fields should accept embedded null characters
當下看到,第一時間就覺得自己無法處理
只好再換回3.5.3版運作
直到上網討論後,有網友給了新方向
再試!.....唉~~又失敗
再看看線上手冊的ctypes說明
        !
好像有招了
簡單地說,就是利用create_unicode_buffer建立字元陣列
再轉換類型為c_wchar_p就可解決
不過還是有些小細節要處理

詳細說明如下


在線上手冊介紹ctypes的章節
python使用create_string_buffer時
可以建立一串空字串,同時不被舉出ValueError
>>> from ctypes import *
>>> p = create_string_buffer(3)            # create a 3 byte buffer, initialized to NUL bytes
>>> print(sizeof(p), repr(p.raw))
3 b'\x00\x00\x00'
>>> p = create_string_buffer(b"Hello")     # create a buffer containing a NUL terminated string
>>> print(sizeof(p), repr(p.raw))
6 b'Hello\x00'
>>> print(repr(p.value))
b'Hello'
>>> p = create_string_buffer(b"Hello", 10) # create a 10 byte buffer
>>> print(sizeof(p), repr(p.raw))
10 b'Hello\x00\x00\x00\x00\x00'
>>> p.value = b"Hi"
>>> print(sizeof(p), repr(p.raw))
10 b'Hi\x00lo\x00\x00\x00\x00\x00'
>>>
所以使用同類型的create_unicode_buffer後也會可以處理空字元

這樣第一步算是完成了
接著第二步就是型別轉換
因為如果使用下面的程式碼時
>>> from ctypes import *
>>> class TestStruct(Structure):            # create a 3 byte buffer, initialized to NUL bytes
...  _fields_ =[("unicode", c_wchar_p)]
...
>>> t = TestStruct()
>>> u = create_unicode_buffer("foo\0bar", 7)
>>> t.unicode = u
Traceback (most recent call last):
  File "", line 1, in 
TypeError: incompatible types, c_wchar_Array_7 instance instead of c_wchar_p instance
>>>
很明顯地兩個不能互傳
所以要用cast()來轉換
>>> t.unicode = cast(u, c_wchar_p)
>>> print(t.unicode)
'foo'
>>>
不過這看起來有點怕怕的
畢竟後面的資料看來都不見了
然後unicode的Array又沒有像c_char的陣列可以看raw資訊
所以到底有沒有傳過去呢?
實際丟字串"PDF檔案(*.PDF)\0*.pdf\0其它檔案(*.*)\0*.*\0"
打開程式,看起來是沒問題,不過怎麼有點怪怪的...

可以看到後面多掛了兩串亂碼資料在

當下蠻傻眼的
想了一陣子後就懂了
因為c_wchar_p是指標,而windows api要知道c_wchar_p中止字串資料的結尾,需要2個空字元才行
而我在建立c_wchar_Array時,用的長度只有整個字串的長度
filter_string = "PDF檔案(*.PDF)\0*.pdf\0其它檔案(*.*)\0*.*\0"
ofn = OPENFILENAME()
temp_str = create_unicode_buffer(filter_string, len(filter_string))
ofn.lpstrFilter = cast(temp_str, LPCWSTR)
所以在記憶體裡的狀況會是
"PDF檔案(*.PDF)\0*.pdf\0其它檔案(*.*)\0*.*\0亂碼資料\0亂碼資料\0亂碼資料\0亂碼資料\0\0"
多讀了兩段
如果建立字串陣列時,將長度加一之後,在記憶體裡就會變成
"PDF檔案(*.PDF)\0*.pdf\0其它檔案(*.*)\0*.*\0\0亂碼資料\0亂碼資料\0亂碼資料\0亂碼資料\0\0"
資料就會只讀到我們想要的位置
filter_string = "PDF檔案(*.PDF)\0*.pdf\0其它檔案(*.*)\0*.*\0"
ofn = OPENFILENAME()
temp_str = create_unicode_buffer(filter_string, len(filter_string)+1)
ofn.lpstrFilter = cast(temp_str, LPCWSTR)

沒有留言: