我想通过 C#(.NET 3.5,x86)应用程序从我们的 DHCP 服务器(Windows 2008 R2)枚举所有 DHCP 租约。
由于 .NET 中没有 DHCP 服务器类,因此我使用 P/Invoke (dhcpsapi.dll) 从 DHCP 服务器获取数据。
如果我将 DhcpEnumSubnetClients 函数与 DHCP_CLIENT_INFO 和 DHCP_CLIENT_INFO_ARRAY 结构一起使用,一切都很好。
但是,如果我使用相应的 V5 函数和结构(DhcpEnumSubnetClientsV5、DHCP_CLIENT_INFO_V5 和 DHCP_CLIENT_INFO_ARRAY_V5),我会遇到严重的内存泄漏(每个枚举租约大约 280-290 字节)。
我需要使用 V5 版本,因为我需要来自 DHCP_CLIENT_INFO_V5 结构的信息,这些信息在 DHCP_CLIENT_INFO 结构(bClientType 和 AddressState)中不可用。
在调用 DhcpEnumSubnetClientsV5 之后,我根据需要调用 DhcpRpcFreeMemory,但 DhcpRpcFreeMemory 在与 DhcpEnumSubnetClientsV5 一起使用时似乎没有做任何事情。
这是 .NET 3.5 或 dhcpsapi.dll 中的错误,还是我的代码中缺少某些内容?
非常感谢任何帮助!
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Runtime.InteropServices;
using System.Net.NetworkInformation;
using System.Text.RegularExpressions;
class Program
{
[DllImport("dhcpsapi.dll", CharSet = CharSet.Unicode)]
static extern UInt32 DhcpEnumSubnetClients(
string ServerIpAddress,
UInt32 SubnetAddress,
ref UInt32 ResumeHandle,
UInt32 PreferredMaximum,
out IntPtr ClientInfo,
out UInt32 ElementsRead,
out UInt32 ElementsTotal);
[DllImport("dhcpsapi.dll", CharSet = CharSet.Unicode)]
static extern UInt32 DhcpEnumSubnetClientsV5(
string ServerIpAddress,
UInt32 SubnetAddress,
ref UInt32 ResumeHandle,
UInt32 PreferredMaximum,
out IntPtr ClientInfo,
out UInt32 ElementsRead,
out UInt32 ElementsTotal);
[DllImport("dhcpsapi.dll")]
static extern void DhcpRpcFreeMemory(IntPtr BuffPtr);
[StructLayout(LayoutKind.Sequential)]
private struct DHCP_CLIENT_INFO_ARRAY_V5
{
public UInt32 NumElements;
public IntPtr Elements;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct DHCP_CLIENT_INFO_V5
{
public UInt32 ClientIpAddress;
public UInt32 SubnetMask;
public DHCP_CLIENT_UID ClientHardwareAddress;
public string ClientName;
public string ClientComment;
public DATE_TIME ClientLeaseExpires;
public DHCP_HOST_INFO OwnerHost;
public byte bClientType;
public byte AddressState;
}
[StructLayout(LayoutKind.Sequential)]
private struct DHCP_CLIENT_INFO_ARRAY
{
public UInt32 NumElements;
public IntPtr Elements;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct DHCP_CLIENT_INFO
{
public UInt32 ClientIpAddress;
public UInt32 SubnetMask;
public DHCP_CLIENT_UID ClientHardwareAddress;
public string ClientName;
public string ClientComment;
public DATE_TIME ClientLeaseExpires;
public DHCP_HOST_INFO OwnerHost;
}
[StructLayout(LayoutKind.Sequential)]
private struct DHCP_CLIENT_UID
{
public UInt32 DataLength;
public IntPtr Data;
}
[StructLayout(LayoutKind.Sequential)]
private struct DATE_TIME
{
public UInt32 dwLowDateTime;
public UInt32 dwHighDateTime;
public DATE_TIME(DateTime date)
{
if (date == DateTime.MaxValue)
{
this.dwLowDateTime = this.dwHighDateTime = UInt32.MaxValue;
return;
}
if (date == DateTime.MinValue)
{
this.dwLowDateTime = this.dwHighDateTime = UInt32.MinValue;
return;
}
this.dwLowDateTime = (UInt32)(date.ToFileTime() & 0xFFFFFFFF);
this.dwHighDateTime = (UInt32)((date.ToFileTime() & 0x7FFFFFFF00000000) >> 32);
}
public DateTime Convert()
{
if (this.dwHighDateTime == 0 && this.dwLowDateTime == 0)
return DateTime.MinValue;
if (this.dwHighDateTime == int.MaxValue && this.dwLowDateTime == UInt32.MaxValue)
return DateTime.MaxValue;
return DateTime.FromFileTime((((long)this.dwHighDateTime) << 32) | (UInt32)this.dwLowDateTime);
}
public override string ToString()
{
return this.Convert().ToString();
}
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct DHCP_HOST_INFO
{
public UInt32 IpAddress;
public string NetBiosName;
public string HostName;
}
/////////////////////////////////////////////////////////////////////////////////
//
// Main
//
/////////////////////////////////////////////////////////////////////////////////
static void Main(string[] args)
{
Console.Write("Server: ");
string server = Console.ReadLine();
Console.Write("Subnet: ");
string subnet = Console.ReadLine();
// 10 iterations for subnet with 150 leases: 5 MB RAM (DhcpEnumSubnetClients), 5 MB RAM (DhcpEnumSubnetClientsV5)
// 100 iterations for subnet with 150 leases: 7 MB RAM (DhcpEnumSubnetClients), 11 MB RAM (DhcpEnumSubnetClientsV5)
// 200 iterations for subnet with 150 leases: 7 MB RAM (DhcpEnumSubnetClients), 16 MB RAM (DhcpEnumSubnetClientsV5)
// 500 iterations for subnet with 150 leases: 7 MB RAM (DhcpEnumSubnetClients), 28 MB RAM (DhcpEnumSubnetClientsV5)
// -> 40-45 KB leaked per iteration for DhcpEnumSubnetClientsV5; 273-307 bytes per lease
// 10 iterations for subnet with 1100 leases: 8 MB RAM (DhcpEnumSubnetClients), 11 MB RAM (DhcpEnumSubnetClientsV5)
// 100 iterations for subnet with 1100 leases: 8 MB RAM (DhcpEnumSubnetClients), 38 MB RAM (DhcpEnumSubnetClientsV5)
// 200 iterations for subnet with 1100 leases: 8 MB RAM (DhcpEnumSubnetClients), 68 MB RAM (DhcpEnumSubnetClientsV5)
// 500 iterations for subnet with 1100 leases: 8 MB RAM (DhcpEnumSubnetClients), 157 MB RAM (DhcpEnumSubnetClientsV5)
// -> 305-307 KB leaked per iteration for DhcpEnumSubnetClientsV5; 283-285 bytes per lease
// -> around 280-290 bytes leaked per lease enumerated by DhcpEnumSubnetClientsV5
for (int k = 0; k < 10; k++)
{
DataTable dhcpLeases = new DataTable();
dhcpLeases = GetSubnetLeases(server, subnet);
if (dhcpLeases == null || dhcpLeases.Rows.Count < 1)
{
Console.WriteLine("No Leases found");
Environment.Exit(0);
}
for (int i = 0; i < dhcpLeases.Rows.Count; i++)
{
Console.WriteLine("MAC Address: " + dhcpLeases.Rows[i]["macAddress"] + ", IP Address: " + dhcpLeases.Rows[i]["ipAddress"]);
}
Console.WriteLine(dhcpLeases.Rows.Count + " Leases found");
}
Console.Write("Any key to exit");
Console.ReadLine();
}
/////////////////////////////////////////////////////////////////////////////////
//
// Get all Leases for "subnet" on "server"
// Returns DataTable containing Leases or null if there was a problem
//
/////////////////////////////////////////////////////////////////////////////////
public static DataTable GetSubnetLeases(string server, string subnet)
{
UInt32 response = 0, resumeHandle = 0;
UInt32 nRead = 0, nTotal = 0;
DataTable leases = new DataTable();
leases.Columns.Add("ipAddress");
leases.Columns.Add("macAddress");
leases.Columns.Add("subnet");
leases.Columns.Add("clientName");
for (; ; )
{
IntPtr retPtr = IntPtr.Zero;
response = DhcpEnumSubnetClientsV5(server, ConvertIP(subnet), ref resumeHandle, 65536, out retPtr, out nRead, out nTotal); // ---- Memory Leak ----
// response = DhcpEnumSubnetClients(server, ConvertIP(subnet), ref resumeHandle, 65536, out retPtr, out nRead, out nTotal); // No Memory Leak
if (response == 259) //ERROR_NO_MORE_ITEMS = 259
{
if (retPtr != IntPtr.Zero) DhcpRpcFreeMemory(retPtr);
break;
}
if (response != 0 && response != 234 || retPtr == IntPtr.Zero) //ERROR_MORE_DATA = 234
{
if (retPtr != IntPtr.Zero) DhcpRpcFreeMemory(retPtr);
return null;
}
DHCP_CLIENT_INFO_ARRAY_V5 nativeArray = (DHCP_CLIENT_INFO_ARRAY_V5)Marshal.PtrToStructure(retPtr, typeof(DHCP_CLIENT_INFO_ARRAY_V5)); // ---- Memory Leak ----
// DHCP_CLIENT_INFO_ARRAY nativeArray = (DHCP_CLIENT_INFO_ARRAY)Marshal.PtrToStructure(retPtr, typeof(DHCP_CLIENT_INFO_ARRAY)); // No Memory Leak
for (int i = 0; i < nativeArray.NumElements; i++)
{
IntPtr iPtr = Marshal.ReadIntPtr(nativeArray.Elements, (i * Marshal.SizeOf(typeof(IntPtr))));
DHCP_CLIENT_INFO_V5 nativeClient = (DHCP_CLIENT_INFO_V5)Marshal.PtrToStructure(iPtr, typeof(DHCP_CLIENT_INFO_V5)); // ---- Memory Leak ----
// DHCP_CLIENT_INFO nativeClient = (DHCP_CLIENT_INFO)Marshal.PtrToStructure(iPtr, typeof(DHCP_CLIENT_INFO)); // No Memory Leak
string ipAddress = ConvertIP(nativeClient.ClientIpAddress);
string clientName = nativeClient.ClientName;
byte[] macBytes = new byte[nativeClient.ClientHardwareAddress.DataLength];
Marshal.Copy(nativeClient.ClientHardwareAddress.Data, macBytes, 0, macBytes.Length);
PhysicalAddress mac = new PhysicalAddress(macBytes);
string macAddress = mac.ToString().ToUpper();
macAddress = Regex.Replace(macAddress, "(.{2})(.{2})(.{2})(.{2})(.{2})(.{2})", "$1:$2:$3:$4:$5:$6");
leases.Rows.Add(ipAddress, macAddress, subnet, clientName);
}
if (retPtr != IntPtr.Zero) DhcpRpcFreeMemory(retPtr);
if (response == 0) break;
}
return leases;
}
/////////////////////////////////////////////////////////////////////////////////
//
// Convert IP Address in w.x.y.z format to UInt32
//
/////////////////////////////////////////////////////////////////////////////////
public static UInt32 ConvertIP(string ipString)
{
string[] ipArray = ipString.Split('.');
UInt32 ipUint32 = (Convert.ToUInt32(ipArray[0]) * 16777216) + (Convert.ToUInt32(ipArray[1]) * 65536) + (Convert.ToUInt32(ipArray[2]) * 256) + (Convert.ToUInt32(ipArray[3]));
return ipUint32;
}
/////////////////////////////////////////////////////////////////////////////////
//
// Convert IP Address from UInt32 to string in w.x.y.z format
//
/////////////////////////////////////////////////////////////////////////////////
public static string ConvertIP(UInt32 ipUInt32)
{
string ipString = "";
UInt32 octet = 0;
octet = ipUInt32 / 16777216;
ipString = ipString + octet.ToString() + ".";
ipUInt32 = ipUInt32 - (octet * 16777216);
octet = ipUInt32 / 65536;
ipString = ipString + octet.ToString() + ".";
ipUInt32 = ipUInt32 - (octet * 65536);
octet = ipUInt32 / 256;
ipString = ipString + octet.ToString() + ".";
ipUInt32 = ipUInt32 - (octet * 256);
ipString = ipString + ipUInt32.ToString();
return ipString;
}
}
编辑: 即使我删除了所有使用从 DhcpEnumSubnetClientsV5 返回的 DHCP_CLIENT_INFO_ARRAY_V5 的代码(即我只调用 DhcpEnumSubnetClientsV5 并且不关心返回的内容),内存泄漏仍然是相同的。
/////////////////////////////////////////////////////////////////////////////////
//
// Get all Leases for "subnet" on "server"
// Returns DataTable containing Leases or null if there was a problem
//
/////////////////////////////////////////////////////////////////////////////////
public static DataTable GetSubnetLeases(string server, string subnet)
{
UInt32 response = 0, resumeHandle = 0;
UInt32 nRead = 0, nTotal = 0;
DataTable leases = new DataTable();
leases.Columns.Add("ipAddress");
leases.Columns.Add("macAddress");
leases.Columns.Add("subnet");
leases.Columns.Add("clientName");
for (; ; )
{
IntPtr retPtr = IntPtr.Zero;
response = DhcpEnumSubnetClientsV5(server, ConvertIP(subnet), ref resumeHandle, 65536, out retPtr, out nRead, out nTotal); // ---- Memory Leak ----
// response = DhcpEnumSubnetClients(server, ConvertIP(subnet), ref resumeHandle, 65536, out retPtr, out nRead, out nTotal); // No Memory Leak
if (response == 259) //ERROR_NO_MORE_ITEMS = 259
{
if (retPtr != IntPtr.Zero) DhcpRpcFreeMemory(retPtr);
break;
}
if (response != 0 && response != 234 || retPtr == IntPtr.Zero) //ERROR_MORE_DATA = 234
{
if (retPtr != IntPtr.Zero) DhcpRpcFreeMemory(retPtr);
return null;
}
if (retPtr != IntPtr.Zero) DhcpRpcFreeMemory(retPtr);
if (response == 0) break;
}
leases.Rows.Add("1.1.1.1", "11:11:11:11:11:11", subnet, "clientName");
return leases;
}
编辑:根据 David Heffernan 的输入,我还尝试在释放整个结构使用的内存之前释放 Clients 数组使用的内存,但这没有帮助。
/////////////////////////////////////////////////////////////////////////////////
//
// Get all Leases for "subnet" on "server"
// Returns DataTable containing Leases or null if there was a problem
//
/////////////////////////////////////////////////////////////////////////////////
public static DataTable GetSubnetLeases(string server, string subnet)
{
UInt32 response = 0, resumeHandle = 0;
UInt32 nRead = 0, nTotal = 0;
DataTable leases = new DataTable();
leases.Columns.Add("ipAddress");
leases.Columns.Add("macAddress");
leases.Columns.Add("subnet");
leases.Columns.Add("clientName");
for (; ; )
{
IntPtr retPtr = IntPtr.Zero;
response = DhcpEnumSubnetClientsV5(server, ConvertIP(subnet), ref resumeHandle, 65536, out retPtr, out nRead, out nTotal); // ---- Memory Leak ----
// response = DhcpEnumSubnetClients(server, ConvertIP(subnet), ref resumeHandle, 65536, out retPtr, out nRead, out nTotal); // No Memory Leak
if (response == 259) //ERROR_NO_MORE_ITEMS = 259
{
if (retPtr != IntPtr.Zero) DhcpRpcFreeMemory(retPtr);
break;
}
if (response != 0 && response != 234 || retPtr == IntPtr.Zero) //ERROR_MORE_DATA = 234
{
if (retPtr != IntPtr.Zero) DhcpRpcFreeMemory(retPtr);
return null;
}
IntPtr clientsArrayPtr = Marshal.ReadIntPtr(retPtr, sizeof(UInt32)); // Get Pointer to the Clients Array
if (clientsArrayPtr != IntPtr.Zero) DhcpRpcFreeMemory(clientsArrayPtr); // Release Memory used by Cients Array
if (retPtr != IntPtr.Zero) DhcpRpcFreeMemory(retPtr); // Release Memory used by the DHCP_CLIENT_INFO_ARRAY_V5 struct
if (response == 0) break;
}
leases.Rows.Add("1.1.1.1", "11:11:11:11:11:11", subnet, "clientName");
return leases;
}