118 lines
4.9 KiB
C++
118 lines
4.9 KiB
C++
// dllmain.cpp : Defines the entry point for the DLL application.
|
|
#include "pch.h"
|
|
#include "ANSCVVendorGate.h" // anscv_vendor_gate::IsNvidiaGpuAvailable()
|
|
#include "ANSLicense.h" // ANSCENTER::EngineType, CheckHardwareInformation
|
|
#include <mutex>
|
|
#include <unordered_map>
|
|
#include <iostream>
|
|
#include <functional>
|
|
|
|
// ── 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<void*, std::function<void()>>& shutdownRegistry() {
|
|
static std::unordered_map<void*, std::function<void()>> 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<std::mutex> 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<std::mutex> lk(shutdownMutex());
|
|
shutdownRegistry().erase(handle);
|
|
}
|
|
|
|
// Stop all leaked handles
|
|
static void stopAllHandles() {
|
|
std::unordered_map<void*, std::function<void()>> copy;
|
|
{
|
|
std::lock_guard<std::mutex> 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 <Unloaded_ANSCV.dll>.
|
|
// 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.
|
|
//
|
|
// CRITICAL: do NOT call CheckHardwareInformation() or
|
|
// anscv_vendor_gate::IsNvidiaGpuAvailable() here. DllMain holds the
|
|
// OS loader lock (LdrpLoaderLock). CheckHardwareInformation touches
|
|
// hwinfo → DXGI / WMI / COM which internally call LoadLibrary; doing
|
|
// that while holding the loader lock causes a classic loader-lock
|
|
// deadlock (confirmed by stress-test hang). The vendor gate will
|
|
// lazy-initialise on the first real call from worker code, which
|
|
// runs with the loader lock released.
|
|
{
|
|
HMODULE hSelf = nullptr;
|
|
GetModuleHandleExW(
|
|
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
|
|
GET_MODULE_HANDLE_EX_FLAG_PIN,
|
|
reinterpret_cast<LPCWSTR>(&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<std::mutex> 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;
|
|
}
|