使用ctypes呼叫系統C API函式需要注意的問題,函式引數中有指標或結構體的情況下最好不要修改argtypes

YinKaisheng發表於2019-05-31

有人向我反應,在程式碼裡同時用我的python模組uiautomation和其它另一個模組後,指令碼執行時會報錯,但單獨使用任意一個模組時都是正常的,沒有錯誤。issue連結

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

假設我需要寫一個module,這個module需要提供獲取當前滑鼠游標下視窗控制程式碼的功能,這需要呼叫系統C API來實現。

實現如下:

module1.py


#!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)

呼叫的程式碼如下

test.py

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


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


if __name__ == '__main__':
    main()

 

執行結果如下

the handle under cursor is 1839250

這時複製一份module1.py,重新命名為module2.py,他們的程式碼是完全一樣的

在test.py同時呼叫這兩個module,程式碼如下

#!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,只要有一個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就不會報錯了。

作為庫的作者,需要主要一下,最好不要設定系統C函式的argtypes。

  

相關文章