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

Reply via email to