diff --git a/Core/Extensions/InteropExtensions.cs b/Core/Extensions/InteropExtensions.cs
index 3218aa1f..1bce8224 100644
--- a/Core/Extensions/InteropExtensions.cs
+++ b/Core/Extensions/InteropExtensions.cs
@@ -54,6 +54,24 @@ namespace Vanara.Extensions
}
#endif
+ /// Returns the pointer.
+ /// The type of items.
+ /// A pointer to the starting address of a specified number of elements in memory.
+ /// The number of elements to be included in the pointer.
+ /// Bytes to skip before starting the span.
+ /// If known, the total number of bytes allocated to the native memory in .
+ /// A pointer that represents the memory.
+ ///
+ public static unsafe T* AsUnmanagedArrayPointer(this IntPtr ptr, int length, int prefixBytes = 0, SizeT allocatedBytes = default) where T : unmanaged
+ {
+ if (ptr == IntPtr.Zero) return null;
+ if (length < 0) throw new ArgumentOutOfRangeException(nameof(length));
+ if (allocatedBytes > 0 && SizeOf() * length + prefixBytes > allocatedBytes)
+ throw new InsufficientMemoryException();
+
+ return (T*)ptr.Offset(prefixBytes).ToPointer();
+ }
+
/// Copies the number of specified bytes from one unmanaged memory block to another.
/// The allocated memory pointer.
/// The allocated memory pointer to copy to.
diff --git a/Core/RefEnumerator.cs b/Core/RefEnumerator.cs
new file mode 100644
index 00000000..3deba494
--- /dev/null
+++ b/Core/RefEnumerator.cs
@@ -0,0 +1,47 @@
+using System.Runtime.InteropServices;
+
+namespace Vanara.PInvoke
+{
+ ///
+ /// Enumerator with zero copy access using ref.
+ ///
+ public unsafe ref struct RefEnumerator where T : unmanaged
+ {
+ private T* _arrayPtr;
+ private readonly int _count;
+ private int _index;
+
+ ///
+ /// Create RefEnumerator.
+ ///
+ /// Pointer to unmanaged array
+ /// Number of elements in the
+ public RefEnumerator(T* arrayPtr, int count)
+ {
+ _arrayPtr = arrayPtr;
+ _count = count;
+ _index = -1;
+ }
+
+ ///
+ /// Move to next element.
+ ///
+ public bool MoveNext()
+ {
+ int index = _index + 1;
+ if (index < _count)
+ {
+ _index = index;
+ _arrayPtr++;
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Return current element.
+ ///
+ public ref T Current => ref *_arrayPtr;
+ }
+}
diff --git a/PInvoke/IpHlpApi/IpHlpApi.cs b/PInvoke/IpHlpApi/IpHlpApi.cs
index ee0236b5..1a3e6c80 100644
--- a/PInvoke/IpHlpApi/IpHlpApi.cs
+++ b/PInvoke/IpHlpApi/IpHlpApi.cs
@@ -7801,6 +7801,9 @@ namespace Vanara.PInvoke
/// Dashed hex value string representation of a Physical Address (MAC).
public static string PhysicalAddressToString(byte[] physAddr) => $"{physAddr[0]:X}-{physAddr[1]:X}-{physAddr[2]:X}-{physAddr[3]:X}-{physAddr[4]:X}-{physAddr[5]:X}";
+ ///
+ public static unsafe string PhysicalAddressToString(byte* physAddr) => $"{physAddr[0]:X}-{physAddr[1]:X}-{physAddr[2]:X}-{physAddr[3]:X}-{physAddr[4]:X}-{physAddr[5]:X}";
+
///
/// The RestoreMediaSense function restores the media sensing capability of the TCP/IP stack on a local computer on which the
/// DisableMediaSense function was previously called.
diff --git a/PInvoke/IpHlpApi/NetIOApi.cs b/PInvoke/IpHlpApi/NetIOApi.cs
index c21ee6ad..4f60958e 100644
--- a/PInvoke/IpHlpApi/NetIOApi.cs
+++ b/PInvoke/IpHlpApi/NetIOApi.cs
@@ -3365,6 +3365,14 @@ namespace Vanara.PInvoke
[PInvokeData("netioapi.h", MSDNShortId = "6c45d735-9a07-41ca-8d8a-919f32c98a3c")]
public static extern Win32Error GetIpNetTable2(ADDRESS_FAMILY Family, out MIB_IPNET_TABLE2 Table);
+ ///
+ /// Unlike this returns structure with zero copy.
+ // https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-getipnettable2 _NETIOAPI_SUCCESS_ NETIOAPI_API
+ // GetIpNetTable2( ADDRESS_FAMILY Family, PMIB_IPNET_TABLE2 *Table );
+ [DllImport(Lib.IpHlpApi, SetLastError = false, ExactSpelling = true, EntryPoint = "GetIpNetTable2")]
+ [PInvokeData("netioapi.h", MSDNShortId = "6c45d735-9a07-41ca-8d8a-919f32c98a3c")]
+ public static extern Win32Error GetIpNetTable2_Unmanaged(ADDRESS_FAMILY Family, out MIB_IPNET_TABLE2_Unmanaged Table);
+
///
///
/// The GetIpNetworkConnectionBandwidthEstimates function retrieves historical bandwidth estimates for a network connection on
@@ -6975,6 +6983,203 @@ namespace Vanara.PInvoke
public override string ToString() => $"{Address}; MAC:{PhysicalAddressToString(PhysicalAddress)}; If:{(InterfaceIndex != 0 ? InterfaceIndex.ToString() : InterfaceLuid.ToString())}";
}
+ ///
+ /// Unlike this structure is zero copy
+ [PInvokeData("netioapi.h", MSDNShortId = "164dbd93-4464-40f9-989a-17597102b1d8")]
+ [StructLayout(LayoutKind.Sequential, Pack = 2)]
+ public unsafe struct MIB_IPNET_ROW2_Unmanaged
+ {
+ ///
+ /// Type: SOCKADDR_INET
+ /// The neighbor IP address. This member can be an IPv6 address or an IPv4 address.
+ ///
+ public SOCKADDR_INET Address;
+
+ ///
+ /// Type: NET_IFINDEX
+ ///
+ /// The local index value for the network interface associated with this IP address. This index value may change when a network
+ /// adapter is disabled and then enabled, or under other circumstances, and should not be considered persistent.
+ ///
+ ///
+ public uint InterfaceIndex;
+
+ ///
+ /// Type: NET_LUID
+ /// The locally unique identifier (LUID) for the network interface associated with this IP address.
+ ///
+ public NET_LUID InterfaceLuid;
+
+ ///
+ /// Type: UCHAR[IF_MAX_PHYS_ADDRESS_LENGTH]
+ /// The physical hardware address of the adapter for the network interface associated with this IP address.
+ ///
+ public fixed byte PhysicalAddress[IF_MAX_PHYS_ADDRESS_LENGTH];
+
+ ///
+ /// Type: ULONG
+ ///
+ /// The length, in bytes, of the physical hardware address specified by the PhysicalAddress member. The maximum value
+ /// supported is 32 bytes.
+ ///
+ ///
+ public uint PhysicalAddressLength;
+
+ ///
+ /// Type: NL_NEIGHBOR_STATE
+ ///
+ /// The state of a network neighbor IP address as defined in RFC 2461, section 7.3.2. For more information, see
+ /// http://www.ietf.org/rfc/rfc2461.txt. This member can be one of the values from the NL_NEIGHBOR_STATE enumeration type
+ /// defined in the Nldef.h header file.
+ ///
+ ///
+ ///
+ /// Value
+ /// Meaning
+ ///
+ /// -
+ /// NlnsUnreachable
+ /// The IP address is unreachable.
+ ///
+ /// -
+ /// NlnsIncomplete
+ ///
+ /// Address resolution is in progress and the link-layer address of the neighbor has not yet been determined. Specifically for
+ /// IPv6, a Neighbor Solicitation has been sent to the solicited-node multicast IP address of the target, but the corresponding
+ /// neighbor advertisement has not yet been received.
+ ///
+ ///
+ /// -
+ /// NlnsProbe
+ ///
+ /// The neighbor is no longer known to be reachable, and probes are being sent to verify reachability. For IPv6, a reachability
+ /// confirmation is actively being sought by retransmitting unicast Neighbor Solicitation probes at regular intervals until a
+ /// reachability confirmation is received.
+ ///
+ ///
+ /// -
+ /// NlnsDelay
+ ///
+ /// The neighbor is no longer known to be reachable, and traffic has recently been sent to the neighbor. Rather than probe the
+ /// neighbor immediately, however, delay sending probes for a short while in order to give upper layer protocols a chance to
+ /// provide reachability confirmation. For IPv6, more time has elapsed than is specified in the ReachabilityTime.ReachableTime
+ /// member since the last positive confirmation was received that the forward path was functioning properly and a packet was
+ /// sent. If no reachability confirmation is received within a period of time (used to delay the first probe) of entering the
+ /// NlnsDelay state, then a neighbor solicitation is sent and the State member is changed to NlnsProbe.
+ ///
+ ///
+ /// -
+ /// NlnsStale
+ ///
+ /// The neighbor is no longer known to be reachable but until traffic is sent to the neighbor, no attempt should be made to
+ /// verify its reachability. For IPv6, more time has elapsed than is specified in the ReachabilityTime.ReachableTime member since
+ /// the last positive confirmation was received that the forward path was functioning properly. While the State is NlnsStale, no
+ /// action takes place until a packet is sent. The NlnsStale state is entered upon receiving an unsolicited neighbor discovery
+ /// message that updates the cached IP address. Receipt of such a message does not confirm reachability, and entering the
+ /// NlnsStale state insures reachability is verified quickly if the entry is actually being used. However, reachability is not
+ /// actually verified until the entry is actually used.
+ ///
+ ///
+ /// -
+ /// NlnsReachable
+ ///
+ /// The neighbor is known to have been reachable recently (within tens of seconds ago). For IPv6, a positive confirmation was
+ /// received within the time specified in the ReachabilityTime.ReachableTime member that the forward path to the neighbor was
+ /// functioning properly. While the State is NlnsReachable, no special action takes place as packets are sent.
+ ///
+ ///
+ /// -
+ /// NlnsPermanent
+ /// The IP address is a permanent address.
+ ///
+ /// -
+ /// NlnsMaximum
+ /// The maximum possible value for the NL_NEIGHBOR_STATE enumeration type. This is not a legal value for the State member.
+ ///
+ ///
+ ///
+ public NL_NEIGHBOR_STATE State;
+
+ /// Undocumented.
+ public MIB_IPNET_ROW2_FLAGS Flags;
+
+ ///
+ ///
+ /// Type: ULONG The time, in milliseconds, that a node assumes a neighbor is reachable after having received a
+ /// reachability confirmation or is unreachable after not having received a reachability confirmation.
+ ///
+ ///
+ public uint ReachabilityTime;
+
+
+ /// Initializes a new instance of the struct.
+ /// The neighbor IP address.
+ /// The locally unique identifier (LUID) for the network interface associated with this IP address.
+ /// The physical hardware address of the adapter for the network interface associated with this IP address.
+ public MIB_IPNET_ROW2_Unmanaged(SOCKADDR_IN ipV4, NET_LUID ifLuid, byte[] macAddr = null) : this(ipV4, macAddr) => InterfaceLuid = ifLuid;
+
+ /// Initializes a new instance of the struct.
+ /// The neighbor IP address.
+ ///
+ /// The local index value for the network interface associated with this IP address. This index value may change when a network
+ /// adapter is disabled and then enabled, or under other circumstances, and should not be considered persistent.
+ ///
+ /// The physical hardware address of the adapter for the network interface associated with this IP address.
+ public MIB_IPNET_ROW2_Unmanaged(SOCKADDR_IN ipV4, uint ifIdx, byte[] macAddr = null) : this(ipV4, macAddr) => InterfaceIndex = ifIdx;
+
+ /// Initializes a new instance of the struct.
+ /// The neighbor IP address.
+ /// The locally unique identifier (LUID) for the network interface associated with this IP address.
+ /// The physical hardware address of the adapter for the network interface associated with this IP address.
+ public MIB_IPNET_ROW2_Unmanaged(SOCKADDR_IN6 ipV6, NET_LUID ifLuid, byte[] macAddr = null) : this(ipV6, macAddr) => InterfaceLuid = ifLuid;
+
+ /// Initializes a new instance of the struct.
+ /// The neighbor IP address.
+ ///
+ /// The local index value for the network interface associated with this IP address. This index value may change when a network
+ /// adapter is disabled and then enabled, or under other circumstances, and should not be considered persistent.
+ ///
+ /// The physical hardware address of the adapter for the network interface associated with this IP address.
+ public MIB_IPNET_ROW2_Unmanaged(SOCKADDR_IN6 ipV6, uint ifIdx, byte[] macAddr = null) : this(ipV6, macAddr) => InterfaceIndex = ifIdx;
+
+ private MIB_IPNET_ROW2_Unmanaged(SOCKADDR_IN ipV4, byte[] macAddr) : this()
+ {
+ Address.Ipv4 = ipV4;
+ SetMac(macAddr);
+ }
+
+ private MIB_IPNET_ROW2_Unmanaged(SOCKADDR_IN6 ipV6, byte[] macAddr) : this()
+ {
+ Address.Ipv6 = ipV6;
+ SetMac(macAddr);
+ }
+
+ private void SetMac(byte[] macAddr)
+ {
+ if (macAddr == null)
+ {
+ return;
+ }
+
+ PhysicalAddressLength = IF_MAX_PHYS_ADDRESS_LENGTH;
+
+ fixed (byte* physicalAddress = PhysicalAddress)
+ {
+ Marshal.Copy(macAddr, 0, (IntPtr)physicalAddress, 6);
+ }
+ }
+
+ ///
+ public override string ToString()
+ {
+ fixed (byte* physicalAddress = PhysicalAddress)
+ {
+ return
+ $"{Address}; MAC:{PhysicalAddressToString(physicalAddress)}; If:{(InterfaceIndex != 0 ? InterfaceIndex.ToString() : InterfaceLuid.ToString())}";
+ }
+ }
+ }
+
///
/// The MIB_IPPATH_ROW structure stores information about an IP path entry.
///
@@ -7536,6 +7741,18 @@ namespace Vanara.PInvoke
{
}
+ ///
+ /// Unlike this provides is zero copy///
+ [PInvokeData("Netioapi.h", MSDNShortId = "ff559267")]
+#if ALLOWSPAN
+ [CorrespondingType(typeof(MIB_IPNET_ROW2_Unmanaged)), DefaultProperty(nameof(TableAsSpan))]
+#else
+ [CorrespondingType(typeof(MIB_IPNET_ROW2_Unmanaged))]
+#endif
+ public class MIB_IPNET_TABLE2_Unmanaged : SafeUnmanagedMibEntryBase
+ {
+ }
+
/// The MIB_IPPATH_TABLE structure contains a table of IP path entries.
// typedef struct _MIB_IPPATH_TABLE { ULONG NumEntries; MIB_IPPATH_ROW Table[ANY_SIZE];} MIB_IPPATH_TABLE, *PMIB_IPPATH_TABLE; https://msdn.microsoft.com/en-us/library/windows/hardware/ff559273(v=vs.85).aspx
[PInvokeData("Netioapi.h", MSDNShortId = "ff559273")]
@@ -7623,6 +7840,28 @@ namespace Vanara.PInvoke
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
+ /// Base class for all structures that support a variable length array of structures with a count in the first field.
+ /// Type of the structure array.
+ public abstract class SafeUnmanagedMibEntryBase : SafeMibTableHandle where T : unmanaged
+ {
+ /// Gets the number of interface entries in the array.
+ public virtual uint NumEntries => IsInvalid ? 0 : handle.ToStructure();
+
+ /// Exposes the pointer to the array of structures.
+ public unsafe T* AsUnmanagedArrayPointer() => IsInvalid ? null : handle.AsUnmanagedArrayPointer((int)NumEntries, Marshal.SizeOf(typeof(ulong)));
+
+#if !ALLOWSPAN
+ /// Gets the enumerator.
+ public unsafe RefEnumerator GetEnumerator() => new RefEnumerator(AsUnmanagedArrayPointer(), (int)NumEntries);
+#else
+ /// Gets the enumerator.
+ public Span.Enumerator GetEnumerator() => TableAsSpan.GetEnumerator();
+
+ /// Gets the containing interface entries.
+ public virtual Span TableAsSpan => AsSpan((int)NumEntries, Marshal.SizeOf(typeof(ulong)));
+#endif
+ }
+
/// SafeHandle for all objects that must be freed with FreeMibTable.
///
public class SafeMibTableHandle : GenericSafeHandle
diff --git a/UnitTests/Core/Core.csproj b/UnitTests/Core/Core.csproj
index acd38b3e..c93c4a8c 100644
--- a/UnitTests/Core/Core.csproj
+++ b/UnitTests/Core/Core.csproj
@@ -21,6 +21,7 @@
prompt
4
AnyCPU
+ true
pdbonly
@@ -30,6 +31,7 @@
prompt
4
AnyCPU
+ true
diff --git a/UnitTests/Core/Extensions/InteropExtensionsTests.cs b/UnitTests/Core/Extensions/InteropExtensionsTests.cs
index c6d665c7..083a8b07 100644
--- a/UnitTests/Core/Extensions/InteropExtensionsTests.cs
+++ b/UnitTests/Core/Extensions/InteropExtensionsTests.cs
@@ -376,5 +376,19 @@ namespace Vanara.Extensions.Tests
Assert.That(ss, Is.Null);
}
}
+
+ [Test]
+ public unsafe void AsUnmanagedArrayPointerTest()
+ {
+ var h = new SafeHGlobalHandle(Marshal.SizeOf(typeof(RECT)) * 2 + i);
+ var rs = new[] { new RECT(), new RECT(10, 11, 12, 13) };
+ ((IntPtr)h).Write(rs, i, h.Size);
+
+ RECT* r = h.DangerousGetHandle().AsUnmanagedArrayPointer(Marshal.SizeOf(), i);
+ Assert.That(r->left, Is.EqualTo(10));
+ Assert.That(r->top, Is.EqualTo(11));
+ Assert.That(r->right, Is.EqualTo(12));
+ Assert.That(r->bottom, Is.EqualTo(13));
+ }
}
}
\ No newline at end of file
diff --git a/UnitTests/PInvoke/IpHlpApi/NetIOApiTests.cs b/UnitTests/PInvoke/IpHlpApi/NetIOApiTests.cs
index 8d7ad610..156bc6e2 100644
--- a/UnitTests/PInvoke/IpHlpApi/NetIOApiTests.cs
+++ b/UnitTests/PInvoke/IpHlpApi/NetIOApiTests.cs
@@ -117,6 +117,90 @@ namespace Vanara.PInvoke.Tests
t.Any(tr => tr.Address.Ipv4.sin_addr == r.Address.Ipv4.sin_addr && tr.InterfaceIndex == r.InterfaceIndex && tr.PhysicalAddress.SequenceEqual(r.PhysicalAddress));
}
+ [Test]
+ public unsafe void CreateSetDeleteIpNetEntry2UnmanagedPointerTest()
+ {
+ var target = new IN_ADDR(192, 168, 0, 202);
+ Assert.That(GetBestRoute(target, 0, out var fwdRow), ResultIs.Successful);
+ var mibrow = new MIB_IPNET_ROW2(new SOCKADDR_IN(target), fwdRow.dwForwardIfIndex, SendARP(target));
+ Assert.That(GetIpNetTable2_Unmanaged(ADDRESS_FAMILY.AF_INET, out var t1), ResultIs.Successful);
+ if (HasVal(t1.AsUnmanagedArrayPointer(), mibrow, t1.NumEntries))
+ Assert.That(DeleteIpNetEntry2(ref mibrow), ResultIs.Successful);
+
+ Assert.That(CreateIpNetEntry2(ref mibrow), ResultIs.Successful);
+ GetIpNetTable2_Unmanaged(ADDRESS_FAMILY.AF_INET, out var t2);
+ Assert.That(HasVal(t2.AsUnmanagedArrayPointer(), mibrow, t1.NumEntries), Is.True);
+
+ Assert.That(DeleteIpNetEntry2(ref mibrow), ResultIs.Successful);
+ GetIpNetTable2_Unmanaged(ADDRESS_FAMILY.AF_INET, out var t3);
+ Assert.That(HasVal(t3.AsUnmanagedArrayPointer(), mibrow, t1.NumEntries), Is.False);
+
+ static bool HasVal(MIB_IPNET_ROW2_Unmanaged* tr, MIB_IPNET_ROW2 r, uint numEntries)
+ {
+ for (uint i = 0; i < numEntries; i++, tr++)
+ {
+ if (tr->Address.Ipv4.sin_addr == r.Address.Ipv4.sin_addr &&
+ tr->InterfaceIndex == r.InterfaceIndex &&
+ CompareArrays(tr->PhysicalAddress, r.PhysicalAddress))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ static bool CompareArrays(byte* left, byte[] right)
+ {
+ return !right.Where((t, i) => left[i] != t).Any();
+ }
+ }
+
+ [Test]
+ public void CreateSetDeleteIpNetEntry2UnmanagedSpanTest()
+ {
+ var target = new IN_ADDR(192, 168, 0, 202);
+ Assert.That(GetBestRoute(target, 0, out var fwdRow), ResultIs.Successful);
+ var mibrow = new MIB_IPNET_ROW2(new SOCKADDR_IN(target), fwdRow.dwForwardIfIndex, SendARP(target));
+ Assert.That(GetIpNetTable2_Unmanaged(ADDRESS_FAMILY.AF_INET, out var t1), ResultIs.Successful);
+ if (HasVal(ref t1, mibrow))
+ Assert.That(DeleteIpNetEntry2(ref mibrow), ResultIs.Successful);
+
+ Assert.That(CreateIpNetEntry2(ref mibrow), ResultIs.Successful);
+ GetIpNetTable2_Unmanaged(ADDRESS_FAMILY.AF_INET, out var t2);
+ Assert.That(HasVal(ref t2, mibrow), Is.True);
+
+ Assert.That(DeleteIpNetEntry2(ref mibrow), ResultIs.Successful);
+ GetIpNetTable2_Unmanaged(ADDRESS_FAMILY.AF_INET, out var t3);
+ Assert.That(HasVal(ref t3, mibrow), Is.False);
+
+ static bool HasVal(ref MIB_IPNET_TABLE2_Unmanaged t, MIB_IPNET_ROW2 r)
+ {
+ foreach (ref MIB_IPNET_ROW2_Unmanaged tr in t)
+ {
+ unsafe
+ {
+ fixed (byte* physicalAddress = tr.PhysicalAddress)
+ {
+ if (tr.Address.Ipv4.sin_addr == r.Address.Ipv4.sin_addr &&
+ tr.InterfaceIndex == r.InterfaceIndex &&
+ CompareArrays(physicalAddress, r.PhysicalAddress))
+ {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ static unsafe bool CompareArrays(byte* left, byte[] right)
+ {
+ return !right.Where((t, i) => left[i] != t).Any();
+ }
+ }
+
[Test]
public void CreateSetGetDeleteUnicastIpAddressEntryTest()
{