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