簡體   English   中英

將 Python ctypes 函數從 Python 2 遷移到 Python 3

[英]Migrating Python ctypes Funtion from Python 2 to Python 3

如果這是一個 XY 問題,這就是我想要做的:

我有一個 wxPython 應用程序,它必須使用 WM_COPYDATA windows 消息與另一個進程通信。 雖然使用ctypes模塊發送消息非常容易,但接收答案需要我覆蓋 wx 循環,因為 wx 沒有為這種情況提供特定事件。

在 python2 上,我使用ctypes.windll.user32.SetWindowLongPtrWctypes.windll.user32.CallWindowProcW函數來獲得所需的行為。 但是,在 python3 中,同樣的代碼會導致OSError: exception: access violation writing

據我所知,python2 ctypes模塊和 python3 ctypes模塊之間的唯一區別是它們如何處理字符串。

我還讀到,兩個版本的 memory 布局方式存在差異,但由於我不是 C 專家,我無法在我的代碼中找到問題。

我已經使用 python3.7 (64Bit) 和 python2.7(64Bit) 和 wx 4.0.7 測試了代碼(盡管它也適用於 wx2.8 和 python2)

這是最小的可重現示例:

import ctypes, ctypes.wintypes, win32con, wx, sys


_LPARAM = ctypes.wintypes.LPARAM
_WPARAM = ctypes.wintypes.WPARAM
_HWND = ctypes.wintypes.HWND
_UINT = ctypes.wintypes.UINT
_LPCWSTR = ctypes.wintypes.LPCWSTR
_LONG_PTR = ctypes.c_long
_LRESULT = _LONG_PTR
_LPCWSTR = ctypes.wintypes.LPCWSTR

_WNDPROC = ctypes.WINFUNCTYPE(_LPARAM,   # return Value
                              _HWND,     # First Param, the handle
                              _UINT,     # second Param, message id
                              _WPARAM,   # third param, additional message info (depends on message id)
                              _LPARAM,   # fourth param, additional message info (depends on message id)
)


_SetWindowLongPtrW = ctypes.windll.user32.SetWindowLongPtrW
_SetWindowLongPtrW.argtypes = (_HWND, ctypes.c_int, _WNDPROC)
_SetWindowLongPtrW.restypes = _WNDPROC

_CallWindowProc = ctypes.windll.user32.CallWindowProcW
_CallWindowProc.argtypes = (_WNDPROC, _HWND, _UINT, _WPARAM, _LPARAM)
_CallWindowProc.restypes = _LRESULT

def _WndCallback(hwnd, msg, wparam, lparam):
    print(hwnd, msg, wparam, lparam)
    return _CallWindowProc(_old_wndproc, hwnd, msg, _WPARAM(wparam), _LPARAM(lparam))
_mywndproc = _WNDPROC(_WndCallback)


app = wx.App(redirect=False)
frame = wx.Frame(None, title='Simple application')
frame.Show()

_old_wndproc = _WNDPROC( _SetWindowLongPtrW(frame.GetHandle(), win32con.GWL_WNDPROC, _mywndproc ) )
if _old_wndproc == 0:
    print( "Error" )
    sys.exit(1)

app.MainLoop()

編輯:我知道有一個 pywin32 模塊,它可能對我有幫助。 但是,由於代碼適用於 python2,我很好奇這里發生了什么。

這里有一個問題:

_LONG_PTR = ctypes.c_long
_LRESULT = _LONG_PTR

LONG_PTR類型是“一個指針大小的 integer”,它在 32 位和 64 位進程之間變化。 由於您使用的是 64 位 Python,因此指針是 64 位的, LONG_PTR應該是:

_LONG_PTR = ctypes.c_long

如果您想要更多可移植的 32 位和 64 位代碼, LPARAM在 Windows 標頭中也被定義為LONG_PTR ,因此下面的定義將為 32 位和 64 位 Python 正確定義 LONG_PTR,因為ctypes已經基於 Python 正確定義了它建造:

_LONG_PTR = ctypes.wintypes.LPARAM  # or _LPARAM in your case

在那次更改之后,我用 wxPython 測試了你的腳本,但仍然有問題。 我懷疑 wxPython 是在沒有UNICODE/_UNICODE定義的情況下編譯的,因此 SetWindowLongPtr 和 CallWindowProc API 必須使用A版本來檢索和調用舊的 window 過程。 我進行了更改,以下代碼有效。

Full code tested with 64-bit Python 3.8.8:
```py
import ctypes, ctypes.wintypes, win32con, wx, sys


_LPARAM = ctypes.wintypes.LPARAM
_WPARAM = ctypes.wintypes.WPARAM
_HWND = ctypes.wintypes.HWND
_UINT = ctypes.wintypes.UINT
_LPCWSTR = ctypes.wintypes.LPCWSTR
_LONG_PTR = _LPARAM
_LRESULT = _LONG_PTR
_LPCWSTR = ctypes.wintypes.LPCWSTR

_WNDPROC = ctypes.WINFUNCTYPE(_LRESULT,  # return Value
                              _HWND,     # First Param, the handle
                              _UINT,     # second Param, message id
                              _WPARAM,   # third param, additional message info (depends on message id)
                              _LPARAM,   # fourth param, additional message info (depends on message id)
)


_SetWindowLongPtr = ctypes.windll.user32.SetWindowLongPtrA
_SetWindowLongPtr.argtypes = (_HWND, ctypes.c_int, _WNDPROC)
_SetWindowLongPtr.restypes = _WNDPROC

_CallWindowProc = ctypes.windll.user32.CallWindowProcA
_CallWindowProc.argtypes = (_WNDPROC, _HWND, _UINT, _WPARAM, _LPARAM)
_CallWindowProc.restypes = _LRESULT

@_WNDPROC
def _WndCallback(hwnd, msg, wparam, lparam):
    print(hwnd, msg, wparam, lparam)
    return _CallWindowProc(_old_wndproc, hwnd, msg, wparam, lparam)


app = wx.App(redirect=False)
frame = wx.Frame(None, title='Simple application')
frame.Show()

_old_wndproc = _WNDPROC(_SetWindowLongPtr(frame.GetHandle(), win32con.GWL_WNDPROC, _WndCallback))
if _old_wndproc == 0:
    print( "Error" )
    sys.exit(1)

app.MainLoop()

順便說一句, MSDN 文檔中有一條關於 SetWindowLongPtr(和 CallWindowProc 類似)的注釋,暗示了這個解決方案:

The winuser.h header defines SetWindowLongPtr as an alias which automatically selects the ANSI or Unicode version of this function based on the definition of the UNICODE preprocessor constant. 將編碼中性別名與非編碼中性的代碼混合使用可能會導致不匹配,從而導致編譯或運行時錯誤。 有關詳細信息,請參閱 Function 原型的約定。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM