python, ctypes and GetIconInfo issue

eryk sun eryksun at gmail.com
Thu May 5 20:09:21 EDT 2016


On Thu, May 5, 2016 at 3:47 PM,  <mymyxin at gmail.com> wrote:
>
> I try to make the GetIconInfo function work, but I can't figure out
> what I'm doing wrong.
>
> From the MSDN documentation the function is
>
> https://msdn.microsoft.com/en-us/library/windows/desktop/ms648070%28v=vs.85%29.aspx
>
>     # BOOL WINAPI GetIconInfo(
>     # _In_  HICON     hIcon,
>     # _Out_ PICONINFO piconinfo
>     # );
>
> which I defined as
>
>     GetIconInfo = windll.user32.GetIconInfo
>     GetIconInfo.argtypes   = [HICON, POINTER(ICONINFO)]
>     GetIconInfo.restype    = BOOL
>     GetIconInfo.errcheck   = ErrorIfZero

Please avoid windll. It caches the loaded library, which in turn
caches function pointers. So all packages that use windll.user32 are
potentially stepping on each others' toes with mutually incompatible
function prototypes. It also doesn't allow configuring
use_last_error=True to enable ctypes.get_last_error() for WinAPI
function calls.

> The structure piconinfo is described as
> https://msdn.microsoft.com/en-us/library/windows/desktop/ms648052%28v=vs.85%29.aspx
>
>     # typedef struct _ICONINFO {
>     # BOOL    fIcon;
>     # DWORD   xHotspot;
>     # DWORD   yHotspot;
>     # HBITMAP hbmMask;
>     # HBITMAP hbmColor;
>     # } ICONINFO, *PICONINFO;
>
> my implementation is
>
>     class ICONINFO(Structure):
>         __fields__ = [
>                       ('fIcon',     BOOL),
>                       ('xHotspot',  DWORD),
>                       ('yHotspot',  DWORD),
>                       ('hbmMask',   HBITMAP),
>                       ('hbmColor',  HBITMAP),
>                      ]

The attribute name is "_fields_", not "__fields__", so you haven't
actually defined any fields and sizeof(ICONINFO) is 0. When you pass
this empty struct to GetIconInfo, it potentially overwrites and
corrupts existing data on the heap that can lead to a crash later on.

Here's the setup I created to test GetIconInfo and GetIconInfoEx.
Maybe you can reuse some of this code, but if you're using XP this
won't work as written because GetIconInfoEx was added in Vista.

Note the use of a __del__ finalizer to call DeleteObject on the
bitmaps. Otherwise, in a real application, calling GetIconInfo would
leak memory. Using __del__ is convenient, but note that you can't
reuse an instance without manually calling DeleteObject on the
bitmaps.

    import ctypes
    from ctypes import wintypes

    kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
    user32 = ctypes.WinDLL('user32', use_last_error=True)
    gdi32 = ctypes.WinDLL('gdi32')

    MAX_PATH = 260
    IMAGE_ICON = 1

    class ICONINFO_BASE(ctypes.Structure):
        def __del__(self, gdi32=gdi32):
            if self.hbmMask:
                gdi32.DeleteObject(self.hbmMask)
                self.hbmMask = None
            if self.hbmColor:
                gdi32.DeleteObject(self.hbmColor)
                self.hbmColor = None

    class ICONINFO(ICONINFO_BASE):
        _fields_ = (('fIcon',    wintypes.BOOL),
                    ('xHotspot', wintypes.DWORD),
                    ('yHotspot', wintypes.DWORD),
                    ('hbmMask',  wintypes.HBITMAP),
                    ('hbmColor', wintypes.HBITMAP))

    class ICONINFOEX(ICONINFO_BASE):
        _fields_ = (('cbSize',    wintypes.DWORD),
                    ('fIcon',     wintypes.BOOL),
                    ('xHotspot',  wintypes.DWORD),
                    ('yHotspot',  wintypes.DWORD),
                    ('hbmMask',   wintypes.HBITMAP),
                    ('hbmColor',  wintypes.HBITMAP),
                    ('wResID',    wintypes.WORD),
                    ('szModName', wintypes.WCHAR * MAX_PATH),
                    ('szResName', wintypes.WCHAR * MAX_PATH))

        def __init__(self, *args, **kwds):
            super(ICONINFOEX, self).__init__(*args, **kwds)
            self.cbSize = ctypes.sizeof(self)

    PICONINFO = ctypes.POINTER(ICONINFO)
    PICONINFOEX = ctypes.POINTER(ICONINFOEX)

    def check_bool(result, func, args):
        if not result:
            raise ctypes.WinError(ctypes.get_last_error())
        return args

    kernel32.GetModuleHandleW.errcheck = check_bool
    kernel32.GetModuleHandleW.restype = wintypes.HMODULE
    kernel32.GetModuleHandleW.argtypes = (
        wintypes.LPCWSTR,) # _In_opt_ lpModuleName

    # DeleteObject doesn't call SetLastError
    gdi32.DeleteObject.restype = wintypes.BOOL
    gdi32.DeleteObject.argtypes = (
       wintypes.HGDIOBJ,) # _In_ hObject

    user32.LoadImageW.errcheck = check_bool
    user32.LoadImageW.restype = wintypes.HANDLE
    user32.LoadImageW.argtypes = (
        wintypes.HINSTANCE, # _In_opt_ hinst
        wintypes.LPCWSTR,   # _In_     lpszName
        wintypes.UINT,      # _In_     uType
        ctypes.c_int,       # _In_     cxDesired
        ctypes.c_int,       # _In_     cyDesired
        wintypes.UINT,)     # _In_     fuLoad

    user32.DestroyIcon.errcheck = check_bool
    user32.DestroyIcon.restype = wintypes.BOOL
    user32.DestroyIcon.argtypes = (
        wintypes.HICON,) # _In_ hIcon

    user32.GetIconInfo.errcheck = check_bool
    user32.GetIconInfo.restype = wintypes.BOOL
    user32.GetIconInfo.argtypes = (
        wintypes.HICON, # _In_  hIcon
        PICONINFO,)     # _Out_ piconinfo

    # requires Vista+
    user32.GetIconInfoExW.errcheck = check_bool
    user32.GetIconInfoExW.restype = wintypes.BOOL
    user32.GetIconInfoExW.argtypes = (
        wintypes.HICON, # _In_  hIcon
        PICONINFOEX,)   # _Out_ piconinfoex

    if __name__ == '__main__':
        hMain = kernel32.GetModuleHandleW(None)
        hIcon = user32.LoadImageW(hMain, wintypes.LPCWSTR(1),
                                  IMAGE_ICON, 0, 0, 0)
        try:
            info = ICONINFOEX()
            user32.GetIconInfoExW(hIcon, ctypes.byref(info))
            print('fIcon    : %d' % info.fIcon)
            print('wResID   : %d' % info.wResID)
            print('szModName: %s' % info.szModName)
        finally:
            user32.DestroyIcon(hIcon)


The __main__ test outputs the following for me in 3.5 and 2.7:

    fIcon    : 1
    wResID   : 1
    szModName: C:\Program Files\Python35\python.exe

    fIcon    : 1
    wResID   : 1
    szModName: C:\Program Files\Python27\python.exe



More information about the Python-list mailing list