微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

如何解决将 Python ctypes 功能从 Python 2 迁移到 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 模块之间的唯一区别是它们处理字符串的方式。

我还读到,两个版本的内存布局方式有所不同,但由于我不是 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,lparam)
    return _CallWindowProc(_old_wndproc,hwnd,_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 是“一个指针大小的整数”,它在 32 位和 64 位进程之间变化。由于您使用的是 64 位 Python,因此指针是 64 位的,LONG_PTR 应该是:

_LONG_PTR = ctypes.c_long

如果您想要更多可移植的 32 位和 64 位代码,LPARAM 在 Windows 标头中也定义为 LONG_PTR,因此以下定义将为 32 位和 64 位正确定义 LONG_PTR位 Python,因为 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,lparam)
    return _CallWindowProc(_old_wndproc,hwnd,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 类似)的注释,暗示了这个解决方案:

winuser.h 头文件将 SetWindowLongPtr 定义为别名,它根据 UNICODE 预处理器常量的定义自动选择此函数的 ANSI 或 Unicode 版本。将编码中立的别名与非编码中立的代码混合使用会导致不匹配,从而导致编译或运行时错误。有关详细信息,请参阅函数原型约定。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。