60 lines
3.0 KiB
C#
60 lines
3.0 KiB
C#
using System.Runtime.InteropServices;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip;
|
|
|
|
/// <summary>
|
|
/// <see cref="SafeHandle"/> wrapper around a libplctag native tag handle (an <c>int32</c>
|
|
/// returned from <c>plc_tag_create_ex</c>). Owns lifetime of the native allocation so a
|
|
/// leaked / GC-collected <see cref="PlcTagHandle"/> still calls <c>plc_tag_destroy</c>
|
|
/// during finalization — necessary because native libplctag allocations are opaque to
|
|
/// the driver's <see cref="Core.Abstractions.IDriver.GetMemoryFootprint"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>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.</para>
|
|
///
|
|
/// <para><see cref="IsInvalid"/> is <c>true</c> when the native ID is <= 0 — libplctag
|
|
/// returns negative <c>PLCTAG_ERR_*</c> codes on <c>plc_tag_create_ex</c> 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).</para>
|
|
///
|
|
/// <para>The actual <c>DllImport</c> for <c>plc_tag_destroy</c> 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, <see cref="ReleaseHandle"/> is a no-op; the finalizer still
|
|
/// runs so the integration is correct as soon as the import is added.</para>
|
|
/// </remarks>
|
|
public sealed class PlcTagHandle : SafeHandle
|
|
{
|
|
/// <summary>Construct an invalid handle placeholder (use <see cref="FromNative"/> once created).</summary>
|
|
public PlcTagHandle() : base(invalidHandleValue: IntPtr.Zero, ownsHandle: true) { }
|
|
|
|
private PlcTagHandle(int nativeId) : base(invalidHandleValue: IntPtr.Zero, ownsHandle: true)
|
|
{
|
|
SetHandle(new IntPtr(nativeId));
|
|
}
|
|
|
|
/// <summary>Handle is invalid when the native ID is zero or negative (libplctag error).</summary>
|
|
public override bool IsInvalid => handle.ToInt32() <= 0;
|
|
|
|
/// <summary>Integer ID libplctag issued on <c>plc_tag_create_ex</c>.</summary>
|
|
public int NativeId => handle.ToInt32();
|
|
|
|
/// <summary>Wrap a native tag ID returned from libplctag.</summary>
|
|
public static PlcTagHandle FromNative(int nativeId) => new(nativeId);
|
|
|
|
/// <summary>
|
|
/// Destroy the native tag. No-op for PR 2 (the wire P/Invoke lands in PR 3). The base
|
|
/// <see cref="SafeHandle"/> machinery still guarantees this runs exactly once per
|
|
/// handle — either during <see cref="SafeHandle.Dispose()"/> or during finalization
|
|
/// if the owner was GC'd without explicit Dispose.
|
|
/// </summary>
|
|
protected override bool ReleaseHandle()
|
|
{
|
|
if (IsInvalid) return true;
|
|
// PR 3: wire up plc_tag_destroy(handle.ToInt32()) once the DllImport lands.
|
|
return true;
|
|
}
|
|
}
|