New submission from Rocco Matano <rocco.mat...@web.de>:
When using ctypes under Windows, I have observed a baffling behavior which I am not sure if it is the result of a bug in ctypes, or is simply due to false expectations on my part. Environment: - Windows 10 Pro, 21H1, 19043.1586, x64 - Python versions 3.6 up to 3.10, x64 What I was trying to do was to enumerate all the icons in the resource section of an executable file. The Windows-API for such a case is EnumResourceNamesW, which requires the pointer to a callback function as an argument. I wanted to hide the details of the corresponding code so that a user of that code does not have to deal with ctypes directly and can supply a regular Python function for the callback. So I added an extra level of indirection. When I ran my code, I was surprised to observe that the program was stuck. After several tries, I found a solution that differed from the original code only in one small detail: Use different types when describing the C callback. Below is a self contained sample that can be used to show the successful case as well as to trigger the blocking. Unfortunately it is quit long. import sys import ctypes from ctypes.wintypes import HMODULE, LPVOID, LPWSTR, BOOL # context structure that is used to use regular python functions as callbacks # where external C code expects C callbacks with a certain signature. class CallbackContext(ctypes.Structure): _fields_ = ( ("callback", ctypes.py_object), ("context", ctypes.py_object), ) CallbackContextPtr = ctypes.POINTER(CallbackContext) # quote from # https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nc-libloaderapi-enumresnameprocw # # BOOL Enumresnameprocw( # [in, optional] HMODULE hModule, # LPCWSTR lpType, # LPWSTR lpName, # [in] LONG_PTR lParam # ) # Note that the arguments lpType and lpName are declared as pointers to strings. if len(sys.argv) > 1 and sys.argv[1].lower() == "fail": # Declaring them as pointers to strings in the ctypes prototype does NOT work. EnumResNameCallback_prototype = ctypes.WINFUNCTYPE( BOOL, HMODULE, LPWSTR, LPWSTR, CallbackContextPtr ) else: # Declaring them as void pointers does work! EnumResNameCallback_prototype = ctypes.WINFUNCTYPE( BOOL, HMODULE, LPVOID, LPVOID, CallbackContextPtr ) # this is the ctypes callback function that mimics the required C call signature @EnumResNameCallback_prototype def EnumResNameCallback(hmod, typ, name, ctxt): cbc = ctxt.contents return cbc.callback(hmod, typ, name, cbc.context) kernel32 = ctypes.windll.kernel32 EnumResourceNames = kernel32.EnumResourceNamesW EnumResourceNames.restype = BOOL EnumResourceNames.argtypes = ( HMODULE, LPWSTR, EnumResNameCallback_prototype, CallbackContextPtr ) # Get a module handle for an executable that contains icons GetModuleHandle = kernel32.GetModuleHandleW GetModuleHandle.restype = HMODULE GetModuleHandle.argtypes = (LPWSTR,) hmod = GetModuleHandle(sys.executable) if hmod == 0: raise ctypes.WinError() RT_GROUP_ICON = ctypes.cast(14, LPWSTR) # this is the 'regular' callback function that does not have to care about # the C call signature def enum_callback(hmod, typ, name, unused_context): print(hmod, typ, name) return True cbc = CallbackContext(enum_callback, None) rcbc = ctypes.byref(cbc) print("Trying to enumerate icons.") print("In the case of failure, this WILL BLOCK indefinitely!") EnumResourceNames(hmod, RT_GROUP_ICON, EnumResNameCallback, rcbc) ---------- components: ctypes messages: 415029 nosy: rocco.matano priority: normal severity: normal status: open title: deadlock in ctypes? type: behavior versions: Python 3.10, Python 3.7, Python 3.8, Python 3.9 _______________________________________ Python tracker <rep...@bugs.python.org> <https://bugs.python.org/issue47001> _______________________________________ _______________________________________________ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com