diff --git a/MUNGA_L4/L4KEYLIGHT.cpp b/MUNGA_L4/L4KEYLIGHT.cpp index 197d6cc..3a5c519 100644 --- a/MUNGA_L4/L4KEYLIGHT.cpp +++ b/MUNGA_L4/L4KEYLIGHT.cpp @@ -142,15 +142,48 @@ namespace //--------------------------------------------------------------- void Worker() { + bool ownsApartment = false; try { init_apartment(); + ownsApartment = true; } catch (...) { // apartment already set on this thread; carry on } + // + // C++/WinRT caches an activation factory the first time a type + // is used, and that cache is PROCESS-wide - it outlives this + // thread. The apartment does not: COM tears it down when the + // worker exits and unloads the Lights server with it, because + // by then nothing holds a reference. + // + // So the cached factory is left pointing into an address range + // that no longer has a module in it, and the NEXT race's worker + // calls straight through it - the crash was a call through the + // stale vtable, on the second race, every time. A machine with + // no Dynamic Lighting keyboard is not spared: asking for the + // device selector is enough to populate the cache. + // + // Clear it before the apartment goes, and on every way out of + // here rather than only the tidy one - the watcher setup below + // returns early when Dynamic Lighting is unavailable. + // + struct WinRTExit + { + bool owns; + ~WinRTExit() + { + clear_factory_cache(); + if (owns) + { + uninit_apartment(); + } + } + } winrtExit{ ownsApartment }; + std::mutex claimedLock; std::vector claimed; bool anySeen = false;