将Python ctypes功能从Python 2迁移到Python 3

2024-09-24 00:36:08 发布

您现在位置:Python中文网/ 问答频道 /正文

如果这是XY问题,我想做的是:

我有一个wxPython应用程序,它必须使用WM_COPYDATA windows消息与另一个进程通信。虽然使用ctypes模块发送消息非常容易,但接收答案需要覆盖wx循环,因为wx没有为这种情况提供特定的事件

在python2上,我使用了ctypes.windll.user32.SetWindowLongPtrWctypes.windll.user32.CallWindowProcW函数来获得所需的行为。然而,在python3中,相同的代码导致OSError: exception: access violation writing

据我所知,python2ctypes模块和python3ctypes模块之间的唯一区别在于它们处理字符串的方式

我还读到,这两个版本在内存布局上存在差异,但由于我不是C专家,我在代码中找不到问题

我已经用python3.7(64位)、python2.7(64位)和wx4.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上工作,我很好奇这里发生了什么


Tags: 模块代码messagectypeswxuinthwndwndproc
1条回答
网友
1楼 · 发布于 2024-09-24 00:36:08

这里有一个问题:

_LONG_PTR = ctypes.c_long
_LRESULT = _LONG_PTR

类型LONG_PTR是“指针大小的整数”,它在32位和64位进程之间变化。由于您使用的是64位Python,指针是64位的,LONG_PTR应该是:

_LONG_PTR = ctypes.c_long

如果您希望为32位和64位Python提供更多的可移植代码,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版本来检索和调用旧的窗口过程。我做了那个更改,下面的代码可以正常工作

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 documentation中有一个关于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. Mixing usage of the encoding-neutral alias with code that not encoding-neutral can lead to mismatches that result in compilation or runtime errors. For more information, see Conventions for Function Prototypes.

相关问题 更多 >