Python and Ctypes - Magnification DLL

2019-08-11 12:22发布

问题:

my goal is to get the magnification.dll from Windows running in Python 3.6 with Ctypes. I'm able to zoom the screen, but I can't get the transformation of the input working. I hope someone knows how to fix this and can explain me, what I did wrong. Thanks. ( Magnification API (Windows) )

P.s. It can happen that you need to close the python process manually because zooming won't stop. (At least in Visual Studio Code)

magnification_api.py

import ctypes

class RECT(ctypes.Structure):
    _fields_ = [("left", ctypes.c_long),
                ("top", ctypes.c_long),
                ("right", ctypes.c_long),
                ("bottom", ctypes.c_long)]

class Magnification:
    def __init__(self):
        self.dll = ctypes.CDLL("magnification.dll")

        BOOL = ctypes.c_bool
        FLOAT = ctypes.c_float
        INT = ctypes.c_int
        self.LPRECT = LPRECT = ctypes.POINTER(RECT)
        self.PBOOL = PBOOL = ctypes.POINTER(ctypes.c_bool)

        # MagInitialize
        self.dll.MagInitialize.restype = BOOL

        # MagUninitialize 
        self.dll.MagUninitialize.restype = BOOL

        # MagSetFullscreenTransform 
        self.dll.MagSetFullscreenTransform.restype = BOOL
        self.dll.MagSetFullscreenTransform.argtypes = (FLOAT, INT, INT)

        # MagGetInputTransform 
        self.dll.MagGetInputTransform.restype = BOOL
        self.dll.MagGetInputTransform.argtypes = (PBOOL, LPRECT, LPRECT)

    def MagInitialize(self):
        return self.dll.MagInitialize()

    def MagUninitialize(self):
        return self.dll.MagUninitialize()

    def MagSetFullscreenTransform(self, magLevel, xOffset, yOffset):
        return self.dll.MagSetFullscreenTransform(magLevel, xOffset, yOffset)

    def MagGetInputTransform(self, pfEnabled, prcSource, prcDest):
        return self.dll.MagGetInputTransform(pfEnabled, prcSource, prcDest)

zoomer.py

import ctypes
from magnification_api import Magnification
import time

class Main:
    def __init__(self):
        self.mag = Magnification()

    def zoom(self, factor, x, y):
        if factor > 1.0:
            while True:
                if self.mag.MagInitialize():
                    result = self.mag.MagSetFullscreenTransform(factor, 0, 0)

                    if result:    
                        fInputTransformEnabled = self.mag.PBOOL()
                        rcInputTransformSource = self.mag.LPRECT()
                        rcInputTransformDest = self.mag.LPRECT()

                        if self.mag.MagGetInputTransform(fInputTransformEnabled, rcInputTransformSource, rcInputTransformDest):
                        # fails here
                            print("Success")
                        else:
                            print("Failed")

                time.sleep(1)


if __name__ == "__main__":
    m = Main()
    m.zoom(1.05, 0, 0)

回答1:

I want to start by pointing out other stuff that's not right in the code (not necessarily related to the error):

  • Seems like reinventing the wheel here. Use the wintypes module (only mentioned in the documentation), it has everything needed for this scenario:

    >>> from ctypes import wintypes
    
    >>> dir(wintypes)
    ['ATOM', 'BOOL', 'BOOLEAN', 'BYTE', 'CHAR', 'COLORREF', 'DOUBLE', 'DWORD', 'FILETIME', 'FLOAT', 'HACCEL', 'HANDLE', 'HBITMAP', 'HBRUSH', 'HCOLORSPACE', 'HDC', 'HDESK', 'HDWP', 'HENHMETAFILE', 'HFONT', 'HGDIOBJ', 'HGLOBAL', 'HHOOK', 'HICON', 'HINSTANCE', 'HKEY', 'HKL', 'HLOCAL', 'HMENU', 'HMETAFILE', 'HMODULE', 'HMONITOR', 'HPALETTE', 'HPEN', 'HRGN', 'HRSRC', 'HSTR', 'HTASK', 'HWINSTA', 'HWND', 'INT', 'LANGID', 'LARGE_INTEGER', 'LCID', 'LCTYPE', 'LGRPID', 'LONG', 'LPARAM', 'LPBOOL', 'LPBYTE', 'LPCOLESTR', 'LPCOLORREF', 'LPCSTR', 'LPCVOID', 'LPCWSTR', 'LPDWORD', 'LPFILETIME', 'LPHANDLE', 'LPHKL', 'LPINT', 'LPLONG', 'LPMSG', 'LPOLESTR', 'LPPOINT', 'LPRECT', 'LPRECTL', 'LPSC_HANDLE', 'LPSIZE', 'LPSIZEL', 'LPSTR', 'LPUINT', 'LPVOID', 'LPWIN32_FIND_DATAA', 'LPWIN32_FIND_DATAW', 'LPWORD', 'LPWSTR', 'MAX_PATH', 'MSG', 'OLESTR', 'PBOOL', 'PBOOLEAN', 'PBYTE', 'PCHAR', 'PDWORD', 'PFILETIME', 'PFLOAT', 'PHANDLE', 'PHKEY', 'PINT', 'PLARGE_INTEGER', 'PLCID', 'PLONG', 'PMSG', 'POINT', 'POINTL', 'PPOINT', 'PPOINTL', 'PRECT', 'PRECTL', 'PSHORT', 'PSIZE', 'PSIZEL', 'PSMALL_RECT', 'PUINT', 'PULARGE_INTEGER', 'PULONG', 'PUSHORT', 'PWCHAR', 'PWIN32_FIND_DATAA', 'PWIN32_FIND_DATAW', 'PWORD', 'RECT', 'RECTL', 'RGB', 'SC_HANDLE', 'SERVICE_STATUS_HANDLE', 'SHORT', 'SIZE', 'SIZEL', 'SMALL_RECT', 'UINT', 'ULARGE_INTEGER', 'ULONG', 'USHORT', 'VARIANT_BOOL', 'WCHAR', 'WIN32_FIND_DATAA', 'WIN32_FIND_DATAW', 'WORD', 'WPARAM', '_COORD', '_FILETIME', '_LARGE_INTEGER', '_POINTL', '_RECTL', '_SMALL_RECT', '_ULARGE_INTEGER', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'ctypes', 'tagMSG', 'tagPOINT', 'tagRECT', 'tagSIZE']
    

    As a subnote, BOOL's custom definition doesn't match Win's one:

    >>> ctypes.sizeof(ctypes.c_bool)
    1
    >>> ctypes.sizeof(wintypes.BOOL)
    4
    

    which is a candidate for Access violation

  • Stuff like self.LPRECT = LPRECT = ctypes.POINTER(RECT) just looks odd. Types shouldn't be members of an instance
  • Try to follow [Python]: PEP 8 -- Style Guide for Python Code
  • Other small issues that don't worth being mentioned


Going back to the error: it's ERROR_INVALID_PARAMETER, because all 3 arguments passed to MagGetInputTransform are NULL pointers, as [Python 3]: Pointers states:

Calling the pointer type without an argument creates a NULL pointer.

To fix it (this is just one way - which I find the most straightforward) change:

  • The 3 variables initialization:

    fInputTransformEnabled = wintypes.BOOL()
    rcInputTransformSource = wintypes.RECT()
    rcInputTransformDest = wintypes.RECT()
    
  • The way they are passed to MagGetInputTransform:

    if self.mag.MagGetInputTransform(ctypes.byref(fInputTransformEnabled), ctypes.byref(rcInputTransformSource), ctypes.byref(rcInputTransformDest)):
    

and everything should be fine (however all 3 variables have been populated with 0s).

NB: When you encounter an error on Win, [MSDN]: GetLastError function is your best friend!