0

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!

Community
  • 1
  • 1
Zack Yoshyaro
  • 2,056
  • 6
  • 24
  • 46
  • This isn't the exact same code. One of these has a `print` the other doesn't, as well as a weird list sitting around that doesn't do anything. – user2357112 Sep 06 '13 at 16:34
  • @user2357112 Whoop! Thought I removed those. It's fixed now. Code is the same. – Zack Yoshyaro Sep 06 '13 at 16:45
  • Run both versions again to make sure you get the output you've posted. If anything is different from the output you've posted (including formatting), update the question. Many bugs mysteriously vanish when you do that. – user2357112 Sep 06 '13 at 16:46
  • @user2357112 Updated the question code. Ran it on my end with the same results. The code is copy/pasted, and identical sans one being a function. – Zack Yoshyaro Sep 06 '13 at 17:02
  • The text `'return code: '` doesn't appear in any of the output you've shown. That's strange. – user2357112 Sep 06 '13 at 17:17

1 Answers1

1

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()
Eryk Sun
  • 33,190
  • 5
  • 92
  • 111