使用ctypes調用系統C API函數須要注意的問題,函數參數中有指針或結構體的狀況下最好不要修改argtypes

有人向我反應,在代碼裏同時用個人python模塊uiautomation和其它另外一個模塊後,腳本運行時會報錯,但單獨使用任意一個模塊時都是正常的,沒有錯誤。issue連接python

我用一個例子來演示下這個問題是如何出現的。git

假設我須要寫一個module,這個module須要提供獲取當前鼠標光標下窗口句柄的功能,這須要調用系統C API來實現。github

實現以下:函數

module1.pyui

 
#!python3
# -*- coding: utf-8 -*-
import ctypes
import ctypes.wintypes


class POINT(ctypes.Structure):
    _fields_ = [("x", ctypes.wintypes.LONG),
                ("y", ctypes.wintypes.LONG)]


ctypes.windll.user32.WindowFromPoint.argtypes = (POINT, )
ctypes.windll.user32.WindowFromPoint.restype = ctypes.c_void_p

ctypes.windll.user32.GetCursorPos.argtypes = (ctypes.POINTER(POINT), )


def WindowFromPoint(x, y):
    return ctypes.windll.user32.WindowFromPoint(POINT(x, y))


def GetCursorPos():
    point = POINT(0, 0)
    ctypes.windll.user32.GetCursorPos(ctypes.byref(point))
    return point.x, point.y


def WindowFromCursor():
    x, y = GetCursorPos()
    return WindowFromPoint(x, y)
 

調用的代碼以下spa

test.py指針

#!python3
# -*- coding:utf-8 -*-
import module1


def main():
    print('the handle under cursor is', module1.WindowFromCursor())


if __name__ == '__main__':
    main()

 

運行結果以下rest

the handle under cursor is 1839250

這時複製一份module1.py,重命名爲module2.py,他們的代碼是徹底同樣的code

在test.py同時調用這兩個module,代碼以下blog

#!python3
# -*- coding:utf-8 -*-
import module1
import module2


def main():
    print('the handle under cursor is', module1.WindowFromCursor())
    print('the handle under cursor is', module2.WindowFromCursor())


if __name__ == '__main__':
    main()

運行就會報錯了

ctypes.ArgumentError: argument 1: <class 'TypeError'>: expected LP_POINT instance instead of pointer to POINT

但分開單獨調用任一個模塊就是正常的,不會出錯。

 

這是由於,module1,module2調用的同一個C函數,在設置argtypes的時候,後面的修改會覆蓋前面的設置。

執行

import module1
import module2

後,C函數中的POINT參數必須是module2.POINT纔是合法的。

在用module1調用時,傳入的參數類型是module1.POINT,運行時就會報錯了。

這種錯誤應該只有在參數中有結構體或結構體指針時纔會出現。

假設module1, module2分別是兩我的寫,而這兩個module都會調用同一個C函數,C函數參數裏有你自定義的ctypes.Structure類型,你又要同時用這兩個module,只要有一個module設置了argtypes,運行時可能就會出錯。

 

解決方法是,在module1, module2中註釋兩行代碼

#ctypes.windll.user32.WindowFromPoint.argtypes = (POINT, )
ctypes.windll.user32.WindowFromPoint.restype = ctypes.c_void_p

#ctypes.windll.user32.GetCursorPos.argtypes = (ctypes.POINTER(POINT), )

不要修改argtypes,再運行test.py就不會報錯了。

相關文章
相關標籤/搜索