What would cause this code to produce different results when running at the module level versus in the scope of a function?

StackOverflow https://stackoverflow.com/questions/18662430

Question

I'm attempting to enumerate the available display monitors via Windows API's EnumDisplayMonitors. However, I'm getting some very weird behavior that I can't figure out. Namely, the function runs correctly, but only when it's not inside of a function.

If I place it at the module level like so:

Module Code

def _monitorEnumProc(hMonitor, hdcMonitor, lprcMonitor, dwData):
    print 'call result:', hMonitor, hdcMonitor, lprcMonitor, dwData

if __name__ == '__main__':
    # Callback Factory
    MonitorEnumProc = WINFUNCTYPE(
        ctypes.c_bool, 
        ctypes.wintypes.HMONITOR,
        ctypes.wintypes.HDC,
        ctypes.POINTER(RECT),
        ctypes.wintypes.LPARAM
    )

    # Make the callback function
    enum_callback = MonitorEnumProc(_monitorEnumProc)

    # Enumerate the windows
    print 'return code: %d' % windll.user32.EnumDisplayMonitors(
        None, 
        None,
        enum_callback,
        0
        )

Everything runs as expected. It prints out the handles and rects for my two attached monitors.

Output:

>>> call result: 65537 None <__main__.LP_RECT object at 0x02250EE0> 0
>>> call result: 65539 None <__main__.LP_RECT object at 0x02250EE0> 0
[Finished in 0.1s]

All is well. And the EnumDisplayMonitors function returns a non-zero value showing that everything went as planned.

Now, here's the problem, if I stick the exact same code into a function, things go screwy.

Function Code

def _monitorEnumProc(hMonitor, hdcMonitor, lprcMonitor, dwData):
    print 'call result:', hMonitor, hdcMonitor, lprcMonitor, dwData

def enum_mons():
    # Callback Factory
    MonitorEnumProc = WINFUNCTYPE(
        ctypes.c_bool, 
        ctypes.wintypes.HMONITOR,
        ctypes.wintypes.HDC,
        ctypes.POINTER(RECT),
        ctypes.wintypes.LPARAM
    )

    # Make the callback function
    enum_callback = MonitorEnumProc(_monitorEnumProc)

    # Enumerate the windows
    print 'return code: %d' % windll.user32.EnumDisplayMonitors(
        None, 
        None,
        enum_callback,
        0
        )

if __name__ == '__main__':
    enum_mons()

So, exact same code, except now inside of a function.

Output

call result: 65537 None <__main__.LP_RECT object at 0x02250E90> 0
0

Rather than spitting out all the attached monitors and a success code, it spits out only one monitor and a 0, which according to the MSDN doc means the function failed.

Anyone know what would cause this?It's got me stumped!

Was it helpful?

Solution

When you use a function, enum_callback is getting deallocated when the Python frame for enum_mons is garbage collected. So it's a race as to whether it still exists when Windows tries to call it for each monitor. Define the callback globally -- or use a class.

Also your callback should return True to continue the enumeration.

import ctypes
from ctypes import wintypes 

LPRECT = ctypes.POINTER(wintypes.RECT)

# Callback Factory
MonitorEnumProc = ctypes.WINFUNCTYPE(
    ctypes.c_bool, 
    wintypes.HMONITOR,
    wintypes.HDC,
    LPRECT,
    wintypes.LPARAM)

ctypes.windll.user32.EnumDisplayMonitors.restype = wintypes.BOOL
ctypes.windll.user32.EnumDisplayMonitors.argtypes = [
    wintypes.HDC,
    LPRECT,
    MonitorEnumProc,
    wintypes.LPARAM]

def _monitorEnumProc(hMonitor, hdcMonitor, lprcMonitor, dwData):
    print 'call result:', hMonitor, hdcMonitor, lprcMonitor, dwData
    print lprcMonitor[0].right, lprcMonitor[0].bottom
    return True # continue enumeration

# Make the callback function
enum_callback = MonitorEnumProc(_monitorEnumProc)

def enum_mons():   
    '''Enumerate the display monitors.'''
    return ctypes.windll.user32.EnumDisplayMonitors(
        None, 
        None,
        enum_callback,
        0)

if __name__ == '__main__':
    print 'return code: %d' % enum_mons()
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top