If the Python garbage collector happens to run while win32ui is loading, this can cause a crash which manifests as:
Traceback (most recent call last):
File "import.py", line 5, in <module>
import win32ui
ImportError: DLL load failed: Invalid access to memory location.
</module>
This normally happens very rarely, but if the code executed prior to the win32ui import generates precisely the correct amount of garbage, this happens consistently for a given program.
This is reproducible on both Python 2.6 pywin32-214 and
Python 2.7 pywin32-218 using the following script:
import gc
gc.set_debug(gc.DEBUG_STATS)
gc.set_threshold(1)
import win32process
import win32ui
This results in frequent GC at global construction time, eventually causing a
crash:
[...]
gc: collecting generation 0...
gc: objects in each generation: 4 0 3898
gc: done, 0.0000s elapsed.
gc: collecting generation 0...
gc: objects in each generation: 0 3 3898
gc: done, 0.0000s elapsed.
gc: collecting generation 0...
gc: objects in each generation: 2 3 3898
Traceback (most recent call last):
File "import.py", line 5, in <module>
import win32ui
ImportError: DLL load failed: Invalid access to memory location.
</module>
The stack points to allocation of some other, unrelated object:
python27!visit_decref(struct _object * op = 0x1e322000, void * data = 0x0...
python27!tupletraverse(struct PyTupleObject * o = 0x00b6e850, <function> ...
python27!subtract_refs(union _gc_head * containers = 0x00b6e860)+0x1a
python27!collect(int generation = 0n505416936)+0x120
python27!collect_generations(void)+0x53
python27!_PyObject_GC_Malloc(unsigned int basicsize = 0x14)+0x73
python27!_PyObject_GC_NewVar(struct _typeobject * tp = 0x1e227be8, int ni...
python27!PyTuple_New(int size = 0n2)+0xdd
python27!do_mktuple(char ** p_format = 0x0021f078, char ** p_va = 0x0021f...
python27!va_build_value(char * format = 0x00000000 "", char * va = 0x0021...
python27!Py_BuildValue(char * format = 0x1e2e83d4 "OO")+0x11
win32ui!MakeResourceFromDlgList+0x972f
win32ui!MakeResourceFromDlgList+0x1e67
win32ui!MakeResourceFromDlgList+0x202f
win32ui!MakeResourceFromDlgList+0x20fc
ntdll!LdrpCallInitRoutine+0x14
ntdll!LdrpRunInitializeRoutines+0x344
ntdll!LdrpLoadDll+0x3e5
ntdll!LdrLoadDll+0x230
kernel32!LoadLibraryExW+0x18e
kernel32!LoadLibraryExA+0x1f
python27!_PyImport_GetDynLoadFunc(char * fqname = 0x00b2ac80 "win32ui", ...
</function>
op is win32ui!PyCWnd::type, which is not yet constructed:
0:000> ln esi
Exact matches:
win32ui!PyCWnd::type (<no parameter="" info="">)
0:000> dd esi
1e322000 00000001 00000000 00000000 00000000
1e322010 00000000 00000000 00000000 00000000
1e322020 00000000 00000000 00000000 00000000
[...]
</no>
visit_decref tries to determine whether the object should be considered for GC,
but crashes on op->ob_type->tp_flags because op->ob_type is NULL.
Theoretical sequence of events:
- win32ui is loaded, CRT begins executing global constructors.
(PyCWnd::type has not been constructed yet at this point.)
- Some global constructor makes the GC aware of PyCWnd::type. This part is
unclear. Perhaps this happens when a subclass type object is created, or
perhaps someone instantiates a PyCWnd. tupletraverse in the call stack above
suggests PyCWnd::type is placed into a tuple using Py_BuildValue (a previous
Py_BuildValue to the one requesting an allocation above).
- Some other global constructor allocates memory for an unrelated object (this
is the stack above), triggers the GC, which seeks to visit PyCWnd::type,
which being partially constructed, causes visit_decref to crash.
Workaround: disable GC while loading win32ui, and re-enable afterwards:
import gc
threshold0, , _ = gc.get_threshold()
gc.set_threshold(0)
import win32process
import win32ui
# other pywin32 imports
gc.set_threshold(_threshold0)
I believe the correct fix is to not access any Py* functions from global constructors, or to avoid global->global object references since global construction order is indeterminate in C++.