using System.Runtime.InteropServices; namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip; /// /// wrapper around a libplctag native tag handle (an int32 /// returned from plc_tag_create_ex). Owns lifetime of the native allocation so a /// leaked / GC-collected still calls plc_tag_destroy /// during finalization — necessary because native libplctag allocations are opaque to /// the driver's . /// /// /// Risk documented in driver-specs.md §3 ("Operational Stability Notes"): the CLR /// allocation tracker doesn't see libplctag's native heap, only whole-process RSS can. /// Every handle leaked past its useful life is a direct contributor to the Tier-B recycle /// trigger, so owning lifetime via SafeHandle is non-negotiable. /// /// is true when the native ID is <= 0 — libplctag /// returns negative PLCTAG_ERR_* codes on plc_tag_create_ex failure, which /// we surface as an invalid handle rather than a disposable one (destroying a negative /// handle would be undefined behavior in the native library). /// /// The actual DllImport for plc_tag_destroy is deferred to PR 3 when /// the driver first makes wire calls — PR 2 ships the lifetime scaffold + tests only. /// Until the P/Invoke lands, is a no-op; the finalizer still /// runs so the integration is correct as soon as the import is added. /// public sealed class PlcTagHandle : SafeHandle { /// Construct an invalid handle placeholder (use once created). public PlcTagHandle() : base(invalidHandleValue: IntPtr.Zero, ownsHandle: true) { } private PlcTagHandle(int nativeId) : base(invalidHandleValue: IntPtr.Zero, ownsHandle: true) { SetHandle(new IntPtr(nativeId)); } /// Handle is invalid when the native ID is zero or negative (libplctag error). public override bool IsInvalid => handle.ToInt32() <= 0; /// Integer ID libplctag issued on plc_tag_create_ex. public int NativeId => handle.ToInt32(); /// Wrap a native tag ID returned from libplctag. public static PlcTagHandle FromNative(int nativeId) => new(nativeId); /// /// Destroy the native tag. No-op for PR 2 (the wire P/Invoke lands in PR 3). The base /// machinery still guarantees this runs exactly once per /// handle — either during or during finalization /// if the owner was GC'd without explicit Dispose. /// protected override bool ReleaseHandle() { if (IsInvalid) return true; // PR 3: wire up plc_tag_destroy(handle.ToInt32()) once the DllImport lands. return true; } }