【问题标题】:Migrating ctypes function from Python 2 to Python 3将 ctypes 函数从 Python 2 迁移到 Python 3
【发布时间】:2021-04-07 06:02:33
【问题描述】:

如果这是一个 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, 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,我很好奇这里发生了什么。

【问题讨论】:

    标签: python python-3.x python-2.7 wxpython ctypes


    【解决方案1】:

    这里有个问题:

    _LONG_PTR = ctypes.c_long
    _LRESULT = _LONG_PTR
    

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

    _LONG_PTR = ctypes.c_longlong
    

    如果您想要 32 位和 64 位的更多可移植代码,LPARAM 在 Windows 标头中也被定义为 LONG_PTR,因此下面的定义将为 32 位和 64 位 Python 正确定义 LONG_PTR,因为 @ 987654328@ 已经根据 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 类似)的注释暗示了这个解决方案:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-09-06
      • 1970-01-01
      • 1970-01-01
      • 2023-03-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-24
      相关资源
      最近更新 更多