using System; using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Sta; /// /// SafeHandle-style lifetime wrapper for an LMXProxyServer COM connection. Per Task B.3 /// + decision #65: must call Marshal.ReleaseComObject until /// refcount = 0, then UnregisterProxy. The finalizer runs as a /// to honor AppDomain-unload ordering. /// /// /// This scaffold accepts any RCW (tagged as ) so we can unit-test the /// release logic with a mock. The concrete wiring to ArchestrA.MxAccess.LMXProxyServer /// lands when the actual Galaxy code moves over (the part deferred to the parity gate). /// public sealed class MxAccessHandle : SafeHandle { private object? _comObject; private readonly Action? _unregister; public MxAccessHandle(object comObject, Action? unregister = null) : base(IntPtr.Zero, ownsHandle: true) { _comObject = comObject ?? throw new ArgumentNullException(nameof(comObject)); _unregister = unregister; // The pointer value itself doesn't matter — we're wrapping an RCW, not a native handle. SetHandle(new IntPtr(1)); } public override bool IsInvalid => handle == IntPtr.Zero; public object? RawComObject => _comObject; [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] protected override bool ReleaseHandle() { if (_comObject is null) return true; try { _unregister?.Invoke(_comObject); } catch { /* swallow — we're in finalizer/cleanup; log elsewhere */ } try { if (Marshal.IsComObject(_comObject)) { while (Marshal.ReleaseComObject(_comObject) > 0) { /* loop until fully released */ } } } catch { /* swallow */ } _comObject = null; SetHandle(IntPtr.Zero); return true; } }