Refactor project structure

This commit is contained in:
2026-03-28 19:56:39 +11:00
parent 1d267378b2
commit 8a2e721058
511 changed files with 59 additions and 48 deletions

106
modules/ANSCV/dllmain.cpp Normal file
View File

@@ -0,0 +1,106 @@
// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
#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.
{
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;
}