// dllmain.cpp : Defines the entry point for the DLL application. #include "pch.h" #include #include #include #include // ── Global handle registry for shutdown cleanup ────────────────────────── // Streaming handle creators register a stop-callback on creation. // DLL_PROCESS_DETACH invokes all callbacks so threads exit before DLL unmaps. static std::mutex& shutdownMutex() { static std::mutex m; return m; } static std::unordered_map>& shutdownRegistry() { static std::unordered_map> r; return r; } // Called by Create*Handle functions to register a cleanup callback extern "C" void anscv_register_handle(void* handle, void(*stopFn)(void*)) { if (!handle || !stopFn) return; std::lock_guard lk(shutdownMutex()); shutdownRegistry()[handle] = [=]() { stopFn(handle); }; } // Called by Release*Handle functions to unregister (handle properly cleaned up) extern "C" void anscv_unregister_handle(void* handle) { if (!handle) return; std::lock_guard lk(shutdownMutex()); shutdownRegistry().erase(handle); } // Stop all leaked handles static void stopAllHandles() { std::unordered_map> copy; { std::lock_guard lk(shutdownMutex()); copy.swap(shutdownRegistry()); } for (auto& [ptr, fn] : copy) { try { fn(); } catch (...) {} } } BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) noexcept { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: // Pin the DLL so it is never unmapped from the process address space. // ANSCV creates background threads (RTSP receive, timers) that may // still be running when LabVIEW's CLR/COM shutdown unloads the DLL. // If the DLL is unmapped while threads are executing its code, the // threads crash with an access violation at . // Pinning keeps the code pages mapped; the OS kills all threads when // the process exits, so this is safe and is Microsoft's recommended // pattern for DLLs that own threads. { HMODULE hSelf = nullptr; GetModuleHandleExW( GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_PIN, reinterpret_cast(&DllMain), &hSelf); } break; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: break; case DLL_PROCESS_DETACH: // CRITICAL: Do NOT call stopAllHandles() here. // // DllMain holds the OS loader lock (LdrpLoaderLock). Destroying // streaming handles (CRtspPlayer → CVideoPlayer → CWAudioPlay) // calls dsound!CThread::Terminate which waits for the DirectSound // admin thread to exit. That thread needs the loader lock to shut // down → classic deadlock. (Confirmed by crash dump analysis: // Thread 0 holds LdrpLoaderLock and waits for dsound thread 331, // which is blocked on LdrpLoaderLock.) // // If lpReserved is non-NULL the process is terminating — the OS // will tear down all threads and reclaim resources; cleanup is // unnecessary and dangerous. If lpReserved is NULL (dynamic // FreeLibrary), LabVIEW should have called Release*Handle first. // Log leaked handles for diagnostics but do NOT stop them here. if (lpReserved == nullptr) { // Dynamic unload — warn about leaked handles (non-blocking) try { std::lock_guard lk(shutdownMutex()); if (!shutdownRegistry().empty()) { std::cerr << "[ANSCV] WARNING: " << shutdownRegistry().size() << " streaming handle(s) leaked at DLL unload." << " Call Release*Handle before FreeLibrary." << std::endl; } } catch (...) {} } break; } return TRUE; }