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;
}
}